Dead Continuation cause resource leak?
Ron Pressler
ron.pressler at oracle.com
Fri May 29 00:04:43 UTC 2020
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)) 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)) 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$)
> > >
> > > 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)) 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)) 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