Dead Continuation cause resource leak?

Ron Pressler ron.pressler at oracle.com
Fri May 29 13:49:16 UTC 2020


There is no promise of any progress for any kind of expression. Java couldn't
make such a promise if it wanted to, as this is not even guaranteed for
*machine code* running in user-mode. Promises of such kind can only be made by
whatever component it is that schedules your code's execution. Normally
this is the OS kernel. When you use continuations (or write a custom virtual
thread scheduler), you assume this responsibility. Continuations put the
scheduling of execution into the userspace programmer's hands. But if you want
to make any sort of progress guarantees, you are now in charge of them.

Of course, the default virtual thread scheduler makes a progress guarantee
provided that the kernel scheduler does.

- Ron


On 29 May 2020 at 12:20:41, Alex Otenko (oleksandr.otenko at gmail.com(mailto:oleksandr.otenko at gmail.com)) wrote:

> Sure, waiting indefinitely is the absence of a promise of when the progress will be made. But there's also a promise that the progress will eventually be made for a broad range of expressions. With continuations this is no longer the case.  
>  
> Alex  
> On Fri, 29 May 2020, 12:00 Ron Pressler, wrote:
> > Blocking (as in I/O or synchronisation) is not the issue. In Java (or in C or
> > Assmebly for that matter), if you write *any* two statements,
> >  
> > x;  
> > y;
> >  
> > then no matter what x does -- whether or not it throws an exception or blocks --
> > there is no guarantee that if x executes then y will because there are no
> > liveness guarantees on a non-realtime operating system. The kernel is free to
> > preempt the current thread between x and y and suspend the thread indefinitely.
> >  
> > What continuations change is properties related to thread identity. Assuming x
> > doesn't throw any exceptions (mutatis mutandis if it does), what we know is the
> > following safety property:  
> >  
> > If x is executing on some thread t0 (i.e. Thread.currentThread() would
> > return a particular Thread object that we'll call t0), then no code that's
> > executing outside of any calls x makes will be running on t0 (i.e. will get
> > the t0 object from Thread.currentThread()) before y is entered.  
> >  
> > This is what continuations change, but virtual threads do not.
> >  
> > I don’t understand the definition of the property for which you seek a name :)
> >  
> > — Ron
> >  
> >  
> >  
> > On 29 May 2020 at 07:39:24, Alex Otenko (oleksandr.otenko at gmail.com(mailto:oleksandr.otenko at gmail.com)(mailto:oleksandr.otenko at gmail.com)) wrote:
> >  
> > > Thanks, that's a very good argument.  
> > >  
> > > So in vanilla java we can speak of A() being nonblocking, in which case we can speak of B() being eventually reachable. How would you describe the property of A() that preserves semantics of return value and exception delivery? That is, hands off control to continuations correctly?  
> > >  
> > >  
> > > Alex  
> > > On Fri, 29 May 2020, 01:05 Ron Pressler, wrote:
> > > >  
> > > > I’m not sure I follow. Suppose you have the following code:
> > > >  
> > > > try {  
> > > > A();  
> > > > } finally {  
> > > > B();  
> > > > }
> > > >  
> > > > Then Java makes no guarantees that, if you ever enter A, then B is eventually
> > > > executed. For example, A can sleep forever, or the kernel scheduler could starve  
> > > > the thread.
> > > >  
> > > > In fact, Continuation.run can be implemented in the mainline JDK by spawning a
> > > > new thread and synchronizing with it. What is semantically different would be
> > > > matters of thread identity. An example relevant to your situation is that two
> > > > methods could make concurrent progress while both running on the same thread.
> > > > The converse would be that the following code,
> > > >  
> > > > var t1 = Thread.currentThread();  
> > > > A();  
> > > > var t2 = Thread.currentThread();
> > > >  
> > > > would guarantee that t1 == t2 in the mainline JDK but not in Loom. This would
> > > > probably be even more troublesome than your example, which is one reason why
> > > > we're not exposing continuations at this time. If continuations are ever
> > > > exposed, their behaviour will clearly become a part of the relevant Java
> > > > specifications.
> > > >  
> > > > These issues do not arise with virtual threads.
> > > >  
> > > > - Ron
> > > >  
> > > >  
> > > >  
> > > > On 28 May 2020 at 23:38:59, Alex Otenko (oleksandr.otenko at gmail.com(mailto:oleksandr.otenko at gmail.com)(mailto:oleksandr.otenko at gmail.com)(mailto:oleksandr.otenko at gmail.com)) wrote:
> > > >  
> > > > > When you write an Iterator, you are conscious of writing an Iterator. When you are writing a consumer, you expect plain java semantics: combining pieces of computation that do terminate, produces a program that terminates.  
> > > > >  
> > > > > If Continuation is not going to be public, it's less of a concern. Although the question of verification of which parts of jls are not violated is interesting.  
> > > > >  
> > > > > Alex  
> > > > > On Thu, 28 May 2020, 17:32 Ron Pressler, wrote:
> > > > > > What happens if you write an iterator that only performs cleanup when it  
> > > > > > terminates, but then encounters an exception in the middle of iteration? How
> > > > > > does the consumer know if some cleanup has been missed?
> > > > > >  
> > > > > > Anyway, just to make clear, continuations will *not* be exposed as a public  
> > > > > > class, at least not at first. The current public class will be made internal.
> > > > > >  
> > > > > > — Ron  
> > > > > >  
> > > > > >  
> > > > > > On 28 May 2020 at 15:52:01, Alex Otenko (oleksandr.otenko at gmail.com(mailto:oleksandr.otenko at gmail.com)(mailto:oleksandr.otenko at gmail.com)(mailto:oleksandr.otenko at gmail.com)) wrote:
> > > > > >  
> > > > > > > I get that. What seems to be a problem, is that the language promise cannot be easily translated into Continuations.  
> > > > > > >  
> > > > > > > Case in point: https://github.com/forax/loom-fiber/blob/master/src/main/java/fr.umlv.loom/fr/umlv/loom/Generators.java#L74(https://urldefense.com/v3/__https://github.com/forax/loom-fiber/blob/master/src/main/java/fr.umlv.loom/fr/umlv/loom/Generators.java*L74__;Iw!!GqivPVa7Brio!NU6HHPPmB_c4ddOo6HK_BCS1olDVhg-f-WX1PBEIkCnQCxvU2Ixlw1rWNLIpQas1eA$)(https://urldefense.com/v3/__https://github.com/forax/loom-fiber/blob/master/src/main/java/fr.umlv.loom/fr/umlv/loom/Generators.java*L74(https:/*urldefense.com/v3/__https:/*github.com/forax/loom-fiber/blob/master/src/main/java/fr.umlv.loom/fr/umlv/loom/Generators.java*L74__;Iw!!GqivPVa7Brio!NU6HHPPmB_c4ddOo6HK_BCS1olDVhg-f-WX1PBEIkCnQCxvU2Ixlw1rWNLIpQas1eA$)__;Iy8vKg!!GqivPVa7Brio!KEQObH8AEiF0r5wuxDtHqJBZbGa8oPO7Z5YGkYSqHPxGIV3y79dmil1QmOTbu2PmjQ$)(https://urldefense.com/v3/__https://github.com/forax/loom-fiber/blob/master/src/main/java/fr.umlv.loom/fr/umlv/loom/Generators.java*L74(https:/*urldefense.com/v3/__https:/*github.com/forax/loom-fiber/blob/master/src/main/java/fr.umlv.loom/fr/umlv/loom/Generators.java*L74__;Iw!!GqivPVa7Brio!NU6HHPPmB_c4ddOo6HK_BCS1olDVhg-f-WX1PBEIkCnQCxvU2Ixlw1rWNLIpQas1eA$)(https:/*urldefense.com/v3/__https:/*github.com/forax/loom-fiber/blob/master/src/main/java/fr.umlv.loom/fr/umlv/loom/Generators.java*L74(https:/*urldefense.com/v3/__https:/*github.com/forax/loom-fiber/blob/master/src/main/java/fr.umlv.loom/fr/umlv/loom/Generators.java*L74__;Iw!!GqivPVa7Brio!NU6HHPPmB_c4ddOo6HK_BCS1olDVhg-f-WX1PBEIkCnQCxvU2Ixlw1rWNLIpQas1eA$)__;Iy8vKg!!GqivPVa7Brio!KEQObH8AEiF0r5wuxDtHqJBZbGa8oPO7Z5YGkYSqHPxGIV3y79dmil1QmOTbu2PmjQ$)__;Iy8vKi8vKioqKg!!GqivPVa7Brio!KS95eDnPQpbFjFmYvSw87EBmPiX4pZeKbpvUBsFMdnr-EZPZPgtpMp85AgJf-QzGPA$)  
> > > > > > >  
> > > > > > > This is an example, a technology demonstrator, yes, but...  
> > > > > > >  
> > > > > > > consumer potentially has a try-finally block encompassing the invocation of consumer on lines 54...57. (Consider the closing bracket is the actual last statement, so it's easier to see the problem).  
> > > > > > >  
> > > > > > > The yield returns us to line 73.  
> > > > > > >  
> > > > > > > What happens if this.action.accept throws? I presume we never get to line 74, because consumer is oblivious of the need to yield, nor does it have a way to communicate that yield has failed.  
> > > > > > >  
> > > > > > > Ok, suppose, we reach line 74 due to a Throwable. What reasoning can be used to determine that consumer's try-finally does not need attending? (That is, returning back to line 57, perhaps with a Throwable)  
> > > > > > >  
> > > > > > > Alex  
> > > > > > > On Thu, 28 May 2020, 12:54 Ron Pressler, wrote:
> > > > > > > > But the question was about a liveness property, not a safety property. *If* the  
> > > > > > > > continuation (or any thread, really) terminates *then* finally will run (actually,  
> > > > > > > > this, too, is not a guarantee we make, but that’s a different matter). If a  
> > > > > > > > continuation never terminates, or if a thread sleeps forever, then there is
> > > > > > > > no guarantee that finally blocks will ever run.
> > > > > > > >  
> > > > > > > > It is up to the scheduler and blocking constructs — just as in the case of  
> > > > > > > > the OS — to make any kind of liveness guarantees (or attempts).
> > > > > > > >  
> > > > > > > > — Ron  
> > > > > > > >  
> > > > > > > >  
> > > > > > > > On 28 May 2020 at 12:30:45, Alex Otenko (oleksandr.otenko at gmail.com(mailto:oleksandr.otenko at gmail.com)(mailto:oleksandr.otenko at gmail.com)(mailto:oleksandr.otenko at gmail.com)) wrote:
> > > > > > > >  
> > > > > > > > > Yes, but the JVM offers safety of try-finally. So finally is always executed, if the enclosed block terminates. Implementing something of the kind every time it is needed in CPS is not easy.  
> > > > > > > > >  
> > > > > > > > > Alex  
> > > > > > > > > On Thu, 28 May 2020, 11:09 Ron Pressler, wrote:
> > > > > > > > > > A continuation, or a virtual thread for that matter, that becomes unreachable before termination
> > > > > > > > > > corresponds to an ordinary platform thread that sleeps forever. Neither the JDK nor the OS makes
> > > > > > > > > > liveness guarantees about code.
> > > > > > > > > >  
> > > > > > > > > > Having said that, a virtual thread can become unreachable before termination only due to a serious
> > > > > > > > > > bug. When it is mounted, a reference to it is held by the scheduler or else it would be able to schedule
> > > > > > > > > > it, and when blocked (and unmounted) a reference to it is held by the blocking construct, or else
> > > > > > > > > > it would never be able to unblock it.
> > > > > > > > > >  
> > > > > > > > > > — Ron
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > > On 28 May 2020 at 07:43:09, 施慧 (kalinshi at qq.com(mailto:kalinshi at qq.com)(mailto:kalinshi at qq.com)(mailto:kalinshi at qq.com)) wrote:
> > > > > > > > > >  
> > > > > > > > > > Hi All,  
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > > Trying to understand Loom continuation implementation. In following LeakTest, Continuation Object is unreachable after first yield, but its runnable target is not finished yet.  
> > > > > > > > > > There might be some resources allcoated during continuation run (native memory in this test case and free in finally block --- not cleaner way), when continuation object is collected, these resources are not closed or freed.  
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > > Is it possible to "clean up" a dead continuation which is not finished yet but collecting by GC?  
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > > Tested with code cloned from github today.  
> > > > > > > > > > javac --add-exports java.base/jdk.internal.ref=ALL-UNNAMED --add-exports java.base/jdk.internal.misc=ALL-UNNAMED LeakTest.java  
> > > > > > > > > > java --add-exports java.base/jdk.internal.ref=ALL-UNNAMED --add-exports java.base/jdk.internal.misc=ALL-UNNAMED LeakTest  
> > > > > > > > > > clean continuation  
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > > import jdk.internal.ref.Cleaner;  
> > > > > > > > > > import jdk.internal.misc.Unsafe;  
> > > > > > > > > > public class LeakTest {  
> > > > > > > > > > private static final Unsafe unsafe = Unsafe.getUnsafe();  
> > > > > > > > > > static ContinuationScope scope = new ContinuationScope("scope");  
> > > > > > > > > > public static void main(String[] args) throws Exception {  
> > > > > > > > > > bar();  
> > > > > > > > > > System.gc();  
> > > > > > > > > > Thread.sleep(1000);  
> > > > > > > > > > System.gc();  
> > > > > > > > > > Thread.sleep(1000);  
> > > > > > > > > > }  
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > > public static void bar() {  
> > > > > > > > > > Continuation cont = new Continuation(scope, () -> {  
> > > > > > > > > > long mem = 0;  
> > > > > > > > > > try {  
> > > > > > > > > > // open file/socket  
> > > > > > > > > > mem = unsafe.allocateMemory(100);  
> > > > > > > > > > Continuation.yield(scope);  
> > > > > > > > > > } finally {  
> > > > > > > > > > unsafe.freeMemory(mem);  
> > > > > > > > > > System.out.println("release memory");  
> > > > > > > > > > }  
> > > > > > > > > > });  
> > > > > > > > > > Cleaner.create(cont, () -> { System.out.println("clean continuation"); });  
> > > > > > > > > > cont.run();  
> > > > > > > > > > //cont.run();  
> > > > > > > > > > }  
> > > > > > > > > > }  
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > >  
> > > > > > > > > > Regards
> > > >  
> >  



More information about the loom-dev mailing list