Re: Strange lock-ups with simple E program [patch]

Thomas Leonard <tal-v5nx5w6akNyLE8xUarVfuPLx9OUvmyODWmv/[email protected]>
Newsgroups gmane.comp.lang.e.general
Organization IT Innovation
Message-ID <[email protected]>
Here's an attempt at a patch.

Vat provides a new method that takes the Resolver as an argument instead
of creating a promise pair:

public Throwable qSendAll(Object rec,
                        boolean nowFlag,
                        String verb,
                        Object[] args,
                        Resolver resolver);

BootRefHandler.handleSendAll now creates the promise/resolver pair
itself, creates a BootRefHandler for the promise, and only then queues
the invocation.

This seems to fix the problem, although it happens rarely enough that
it's hard to be sure. I think there might be a similar issue when
transferring an existing BootRef between vats (commented in the code
with XXX), but this isn't causing any problems for me currently.

Thanks,


On Mon, 2009-11-23 at 21:11 +0000, Thomas Leonard wrote:
> Hi Mark,
> 
> OK, I've got it now! I think I can explain the two exceptions ("OldFarRef may
> only be smashed" and "ViciousCycleException") and the hangs.
> 
> First, this is my understanding of how it's supposed to work:
> 
> When passing an object between vats in a single JVM, we use BootRefHandler.packageArg().
> There are two places where this is used:
> 
> 1. To package the arguments to a method call. In this case, the arguments are all in the
> current vat (and therefore won't change under us).
> 
> 2. To package the promise of the result. Here, the argument is a SwitchableRef in another vat, which the other vat is possibly in the process of resolving.
> 
> Case 2 seems pretty dangerous in general, especially knowing the optimisations
> Java likes to make when things aren't synchronized.
> 
> I think something like this is happening:
> 
> 1. The calling vat does "obj<-run()".
> 2. This calls BootRefHandler.handleSendAll().
> 3. In the calling thread, we create a SwitchableRef for the result.
> 4. We give the SwitchableRef to the target vat (synchronized on the runner's queue).
> 5. We call packageArg on the SwitchableRef in the calling thread.
> 6. packageArg sees that this is EVENTUAL.
> 7. The called vat resolves the SwitchableRef and calls commit() on it.
> 8. commit() briefly sets its target to TheViciousRef to detect cycles.
> 9. In the calling thread, we create a new BootRefHandler for the SwitchableRef.
> 10. We create a DelayedRedirector and call ViciousCycleException<-__whenMoreResolved(redirector)
> 11. In the called thread, we set the target to the real answer (null in the test case).
> 12. When we get the reply from ViciousCycleException, the DelayedRedirector can't set the new target to ViciousCycleException because it's already set to null (BTW, why isn't a problem DeepFrozen, and why isn't the handler fresh when this happens?)
> 
> 
> If the SwitchableRef resolves before we create the BootRefHandler, we get "OldFarRef may
> only be smashed", but the program continues OK with the already-working OldFarRef.
> 
> Otherwise, we have an OldRemotePromise that never gets set to anything and the program hangs.
> 
> Perhaps it would make more sense to package the SwitchableRef before giving it to the target vat? You could then use a cut down version of packageArg for that, and make packageArg itself only package things in its own vat.
> 
> 
> --
> Dr Thomas Leonard
> IT Innovation Centre
> 2 Venture Road
> Southampton
> Hampshire SO16 7NP
> 
> Tel: +44 0 23 8076 0834
> Fax: +44 0 23 8076 0833
> mailto:tal-v5nx5w6akNyLE8xUarVfuPLx9OUvmyODWmv/[email protected]
> http://www.it-innovation.soton.ac.uk
> 
> 
> -----Original Message-----
> From: [email protected] on behalf of Mark Miller
> Sent: Sun 2009-11-22 8:49 PM
> To: Discussion of E and other capability languages
> Subject: Re: [e-lang] Strange lock-ups with simple E program
>  
> Hi Thomas, thanks for tracking this down! This has been a long
> standing irritation that I had not been able to diagnose. I hope to be
> able to get to this either over the thanksgiving weekend or soon
> thereafter.
> 
> 
> On Tue, Nov 17, 2009 at 6:43 AM, Thomas Leonard
> <tal-v5nx5w6akNyLE8xUarVfuPLx9OUvmyODWmv/[email protected]> wrote:
> > OK, I think I'm making progress here. In BootRefHandler.packageArg(), we
> > have:
> >
> >            //arg is EVENTUAL (or was at the time of the test), and is not
> >            //handled by a BootRefHandler, so we treat it as a promise in the
> >            //src vat.
> >
> >            BootRefHandler handler = new BootRefHandler(src, arg);
> >            DelayedRedirector rdr = new DelayedRedirector(handler.myResolver);
> >            //handler and rdr are in the dest vat
> >            Object[] args = {packageArg(rdr, dest, src, currentVat)};
> >            src.qSendAllOnly(arg, false, "__whenMoreResolved", args);
> >            //Is it ok to ignore the E.sendOnly return result here?
> >            return handler.myResolver.getProxy();
> >
> > DelayedRedirector will create either OldFarRef or OldRemotePromise
> > objects, depending on whether "arg" has identity when it checks in its
> > constructor.
> >
> > OldRemotePromise.setTarget will accept anything, but OldFarRef.setTarget
> > complains if you try to set it.
> >
> > Adding a sleep makes it print the error message in all cases:
> >
> > diff --git a/src/jsrc/org/erights/e/elib/vat/BootRefHandler.java b/src/jsrc/org/erights/e/elib/vat/BootRefHandler.java
> > index e6d0818..4ca874a 100644
> > --- a/src/jsrc/org/erights/e/elib/vat/BootRefHandler.java
> > +++ b/src/jsrc/org/erights/e/elib/vat/BootRefHandler.java
> > @@ -239,6 +239,15 @@ class BootRefHandler implements EProxyHandler {
> >             //handled by a BootRefHandler, so we treat it as a promise in the
> >             //src vat.
> >
> > +           // TAL: but if it has an identity by the time we create BootRefHandler,
> > +           // then it will be an OldFarRef, not an OldRemotePromise, and subscribing to
> > +           // __whenMoreResolved will only cause trouble...
> > +
> > +           try {
> > +                   Thread.sleep(200);
> > +           } catch (Exception ex) {
> > +           }
> > +
> >             BootRefHandler handler = new BootRefHandler(src, arg);
> >             DelayedRedirector rdr = new DelayedRedirector(handler.myResolver);
> >             //handler and rdr are in the dest vat
> >
> > I'm not sure what the correct fix is, though.
> >
> >
> > On Sun, 2009-07-26 at 19:56 -0700, Mark Miller wrote:
> >> Hi Thomas, I haven't found the bug yet, but I have narrowed it down to
> >> a race condition bug in the boot-comm system. Your same stress test,
> >> with your
> >>
> >>     def seedVat := seedVatAuthor(<unsafe>)
> >>
> >> replaced by
> >>
> >>     introducer.onTheAir()
> >>     def seedVat := seedVatAuthor(<unsafe>).virtualize(introducer)
> >>
> >> seems to never lock up. The only difference between these is that the
> >> latter uses captp instead of boot-comm. In the boot comm case, the
> >> form of lock up looks like a lost signal: all vats are quiescent
> >> waiting for a message. I have also discovered many things that the bug
> >> is not ;). Unfortunately, the clues so far seem to point to a
> >> multi-threading race condition bug.
> >>
> >> Unfortunately, I won't have time for further investigation for another
> >> week. If you're blocked on this, might the above change be a useful
> >> workaround for you?
> >>
> >
> >
> > --
> > Dr Thomas Leonard
> > IT Innovation Centre
> > 2 Venture Road
> > Southampton
> > Hampshire SO16 7NP
> >
> > Tel: +44 0 23 8076 0834
> > Fax: +44 0 23 8076 0833
> > mailto:tal-v5nx5w6akNyLE8xUarVfuPLx9OUvmyODWmv/[email protected]
> > http://www.it-innovation.soton.ac.uk
> >
> > _______________________________________________
> > e-lang mailing list
> > [email protected]
> > http://www.eros-os.org/mailman/listinfo/e-lang
> >
> 
> 
> 


-- 
Dr Thomas Leonard
IT Innovation Centre
2 Venture Road
Southampton
Hampshire SO16 7NP

Tel: +44 0 23 8076 0834
Fax: +44 0 23 8076 0833
mailto:tal-v5nx5w6akNyLE8xUarVfuPLx9OUvmyODWmv/[email protected]
http://www.it-innovation.soton.ac.uk

_______________________________________________
e-lang mailing list
[email protected]
http://www.eros-os.org/mailman/listinfo/e-lang
0001-Fixed-race-condition-in-the-boot-comm-system.patch (text/x-patch, 10.7 KB)
>From 0323f169ab62aee6f41e5167ea77ed4b4d84d548 Mon Sep 17 00:00:00 2001
From: Thomas Leonard <tal-v5nx5w6akNyLE8xUarVfuPLx9OUvmyODWmv/[email protected]>
Date: Tue, 24 Nov 2009 10:21:48 +0000
Subject: [PATCH] Fixed race condition in the boot-comm system

When queuing an event in another vat on the same machine, we tried to construct
a BootRefHandler for the promise as the promise was being resolved in another
thread. This could lead to various race conditions, including errors such as
"OldFarRef may only be smashed", "ViciousCycleException" and deadlock).

Now, we create the BootRefHandler for the promise of the result before adding
it to the queue.

This might also mean that a number of "this method must be thread-safe"
requirements can be dropped.

Patch provided by the University of Southampton IT Innovation Centre.
---
 .../org/erights/e/elib/vat/BootRefHandler.java     |   75 +++++++++++++-------
 src/jsrc/org/erights/e/elib/vat/Vat.java           |   32 +++++++--
 2 files changed, 77 insertions(+), 30 deletions(-)

diff --git a/src/jsrc/org/erights/e/elib/vat/BootRefHandler.java b/src/jsrc/org/erights/e/elib/vat/BootRefHandler.java
index e6d0818..4144910 100644
--- a/src/jsrc/org/erights/e/elib/vat/BootRefHandler.java
+++ b/src/jsrc/org/erights/e/elib/vat/BootRefHandler.java
@@ -9,6 +9,7 @@ import org.erights.e.elib.ref.DelayedRedirector;
 import org.erights.e.elib.ref.EProxyHandler;
 import org.erights.e.elib.ref.EProxyResolver;
 import org.erights.e.elib.ref.Ref;
+import org.erights.e.elib.ref.Resolver;
 import org.erights.e.elib.sealing.Brand;
 import org.erights.e.elib.sealing.SealedBox;
 import org.erights.e.elib.sealing.Sealer;
@@ -119,8 +120,8 @@ class BootRefHandler implements EProxyHandler {
      * except that it's thread-safe.
      * <p/>
      * <tt>getOptBootRefHandler/1</tt> must be thread safe, in order for {@link
-     * org.erights.e.elib.vat.BootRefHandler#packageArg(Object,Vat,Vat,Vat)
-     * BootRefHandler.packageArg/4} to be thread safe: Callers of this should
+     * org.erights.e.elib.vat.BootRefHandler#packageArg(Object,Vat,Vat)
+     * BootRefHandler.packageArg/3} to be thread safe: Callers of this should
      * keep in mind that ref may be shortened after the handler is gotten but
      * before these callers use it. If they access only final fields of the
      * handler in a thread safe way, then everything should be fine.
@@ -153,15 +154,16 @@ class BootRefHandler implements EProxyHandler {
      * to the eventual reference in the src vat, where the argument is a
      * boot-ref on a {@link DelayedRedirector} on the returned boot-ref. <li>In
      * all other cases, an exception is thrown. </ul>
+     * <p/>
+     * If <tt>arg</tt> is not in <tt>src</tt> then you must ensure that it
+     * won't change during this method call (this is only used in the
+     * recursive case).
      *
      * @param arg        The reference to be packaged.
-     * @param src        The vat that 'arg' is valid within.
+     * @param src        The vat that 'arg' is valid within
      * @param dest       The vat the return result needs to be valid within.
-     * @param currentVat The vat within which we're currently executing, which
-     *                   may be src, dest, or a third introducing vat (Alice).
-     *                   This is used to resolve race conditions.
      */
-    private Object packageArg(Object arg, Vat src, Vat dest, Vat currentVat) {
+    private Object packageArg(Object arg, Vat src, Vat dest) {
         arg = Ref.resolution(arg);
         String state = Ref.state(arg);
         arg = Ref.resolution(arg);
@@ -187,7 +189,7 @@ class BootRefHandler implements EProxyHandler {
                 Object[] result = new Object[argList.size()];
                 for (int i = 0, len = result.length; i < len; i++) {
                     result[i] =
-                      packageArg(argList.get(i), src, dest, currentVat);
+                      packageArg(argList.get(i), src, dest);
                 }
                 return ConstList.fromArray(result);
             }
@@ -197,9 +199,9 @@ class BootRefHandler implements EProxyHandler {
                 //XXX Perhaps we should abstract out arrays as a separate case?
                 ConstMap argMap = (ConstMap)arg;
                 ConstList keys = ConstList.fromArray(argMap.getKeys());
-                keys = (ConstList)packageArg(keys, src, dest, currentVat);
+                keys = (ConstList)packageArg(keys, src, dest);
                 ConstList vals = ConstList.fromArray(argMap.getValues());
-                vals = (ConstList)packageArg(vals, src, dest, currentVat);
+                vals = (ConstList)packageArg(vals, src, dest);
                 try {
                     return ConstMap.fromColumns(keys, vals);
                 } catch (ArityMismatchException e) {
@@ -229,23 +231,22 @@ class BootRefHandler implements EProxyHandler {
                 T.require(src != optHandler.myTargetsVat,
                           "Unshortened boot-ref: ",
                           arg);
+                //XXX This doesn't look safe; what if optHandler.myTarget changes
+                //while we're packaging it? We could just fall-through to the code
+                //below, but that seems to break one of the test cases.
                 return packageArg(optHandler.myTarget,
                                   optHandler.myTargetsVat,
-                                  dest,
-                                  currentVat);
+                                  dest);
             }
 
-            //arg is EVENTUAL (or was at the time of the test), and is not
-            //handled by a BootRefHandler, so we treat it as a promise in the
-            //src vat.
-
             BootRefHandler handler = new BootRefHandler(src, arg);
             DelayedRedirector rdr = new DelayedRedirector(handler.myResolver);
-            //handler and rdr are in the dest vat
-            Object[] args = {packageArg(rdr, dest, src, currentVat)};
+            //handler and rdr will be in the dest vat
+            Object[] args = {packageArg(rdr, dest, src)};
             src.qSendAllOnly(arg, false, "__whenMoreResolved", args);
             //Is it ok to ignore the E.sendOnly return result here?
             return handler.myResolver.getProxy();
+
         }
     }
 
@@ -258,7 +259,7 @@ class BootRefHandler implements EProxyHandler {
         Object[] result = new Object[args.length];
         for (int i = 0, len = args.length; i < len; i++) {
             result[i] =
-              packageArg(args[i], currentVat, myTargetsVat, currentVat);
+              packageArg(args[i], currentVat, myTargetsVat);
         }
         return result;
     }
@@ -281,13 +282,37 @@ class BootRefHandler implements EProxyHandler {
      */
     public Ref handleSendAll(String verb, Object[] args) {
         myFreshFlag = false;
-        Ref promise =
-          myTargetsVat.qSendAll(myTarget, false, verb, packageArgs(args));
+
+        Object[] promisePair = Ref.promise();
+        Ref promise = (Ref)promisePair[0];  // In myTargetsVat
+
         Vat currentVat = Vat.getCurrentVat();
-        return Ref.toRef(packageArg(promise,
-                                    myTargetsVat,
-                                    currentVat,
-                                    currentVat));
+
+        Ref remotePromise;                  // In currentVat
+
+        if (myTargetsVat == currentVat) {
+            remotePromise = promise;
+        } else {
+            BootRefHandler handler = new BootRefHandler(myTargetsVat, promise);
+            Ref proxy = handler.myResolver.getProxy();
+
+            DelayedRedirector rdr = new DelayedRedirector(handler.myResolver);
+            //handler and rdr are in the dest (current) vat
+            Object[] moreArgs = {packageArg(rdr, currentVat, myTargetsVat)};
+            myTargetsVat.qSendAllOnly(promise, false, "__whenMoreResolved", moreArgs);
+            //Is it ok to ignore the E.sendOnly return result here?
+
+            remotePromise = proxy;
+        }
+
+        Throwable optProblem =
+          myTargetsVat.qSendAll(myTarget, false, verb, packageArgs(args), (Resolver)promisePair[1]);
+
+        if (null == optProblem) {
+            return remotePromise;
+        } else {
+            return Ref.broken(optProblem);
+        }
     }
 
     /**
diff --git a/src/jsrc/org/erights/e/elib/vat/Vat.java b/src/jsrc/org/erights/e/elib/vat/Vat.java
index 57a2d1b..539ab49 100644
--- a/src/jsrc/org/erights/e/elib/vat/Vat.java
+++ b/src/jsrc/org/erights/e/elib/vat/Vat.java
@@ -369,20 +369,21 @@ public class Vat {
      * that this event might never be delivered, then the reference eventually
      * becomes broken with a complaint explaining why.
      * <p/>
-     * May be called from any thead.
+     * Must only be called from the vat's own thread, since otherwise the
+     * returned promise may change as you access it (and, in particular, it
+     * may briefly turn into a ViciousCycleException).
      * <p/>
      * XXX to be made non-public. Uses outside this package should use {@link
      * BootRefHandler boot-refs} instead.
      */
-    public Ref qSendAll(Object rec,
+    Ref qSendAll(Object rec,
                         boolean nowFlag,
                         String verb,
                         Object[] args) {
         Object[] promise = Ref.promise();
         Resolver resolver = (Resolver)promise[1];
-        PendingDelivery pe =
-          new PendingDelivery(this, rec, resolver, nowFlag, verb, args);
-        Throwable optProblem = getRunner().enqueue(pe);
+
+        Throwable optProblem = qSendAll(rec, nowFlag, verb, args, resolver);
         if (null == optProblem) {
             return (Ref)promise[0];
         } else {
@@ -391,6 +392,27 @@ public class Vat {
     }
 
     /**
+     * Enqueues a 'rec <- verb(args...)'.
+     * <p/>
+     * If this vat is shut down, returns a Throwable explaining the problem.
+     * Otherwise, if it becomes known that this event might never be delivered,
+     * then the reference eventually becomes broken with a complaint explaining
+     * why.
+     * <p/>
+     * The only outside user of this call is {@link BootRefHandler}; everyone else
+     * should go via that.
+     */
+    public Throwable qSendAll(Object rec,
+                        boolean nowFlag,
+                        String verb,
+                        Object[] args,
+                        Resolver resolver) {
+        PendingDelivery pe =
+          new PendingDelivery(this, rec, resolver, nowFlag, verb, args);
+        return getRunner().enqueue(pe);
+    }
+
+    /**
      * Schedules a Runnable to execute in a Runner (in the Runner's thread as a
      * separate turn), while also effectively executing as a synchronous call
      * within the requestors's {@link Runner#getOptCurrentRunner() external
-- 
1.6.3.3
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.