[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

looping in unwind-protect cleanups



I tend to agree with you that looping caused by UWP cleanup forms throwing 
back through the same cleanup forms is a serious error;  and that this 
situation not only ought to be "signalled" as an error, but also ought to
be recuperable (from the debugger, I suppose).   However, one or two loose 
ends need to be cleared up.  You mention the example from the original
proposal:

    (CATCH 'FOO
      (CATCH 'BAR
	(UNWIND-PROTECT (THROW 'FOO 3)
	  (THROW 'BAR 4)
	  (PRINT 'XXX))))

and you use the phrase "... this is not a valid Common Lisp program."  
I have trouble with that characterization, since it seems to imply 
some mechanically checkable property of programs.  The "serious error"
mentioned above is a dynamic condition, which could be caused by other 
programs that are not so clearly analyzable.  For example, consider just 
removing the (THROW 'BAR 4) out of the lexical context:

    (CATCH 'FOO
      (CATCH 'BAR
	(UNWIND-PROTECT (THROW 'FOO 3)
	  (DO-A-BIT-OF-WORK)
	  (PRINT 'XXX))))

    (DEFUN DO-A-BIT-OF-WORK () 
      (UNLESS (WINNINGP)
	(THROW 'BAR 4)))

I think you see how this could be extended to produce an example that could
not be mechanically proven invalid by the UWP-non-local-exit criterion,
even though it would get into the disastrous loop.

How about this characterization of the problem: for a given process, the
unwind-protect cleanup forms should be viewed as non-reentrant code.  An
attempt to execute such code reentrantly will signal an error, which will
probably enter the debugger (and entering the debugger won't, by itself,
cause another throw, so there is no fear of infinite looping from this
step).  In such a case, the user should have the choice of continuing with 
the re-entrant invocation, or of doing an "abort" which skips doing the
signalled UWP frame's cleanups.  In the first case, he may want to "try
again" at the cleanups, because it just might be that they were entered
"re-entrantly" only because he interrupted in the middle of some cleanups,
and then asked the debugger to "abort to toplevel"; or maybe entering the
"embrace" depends on some global data which he has just "fixed".  In the 
second case, he may recognize the "deadly embrace" as hopeless, and simply
wish to punt out of it.

There is some concern about "hairing up" the UWP/THROW mechanisms.  I
don't believe that this approach, or *** ones like it *** (which were
alluded to in previous messages) is really all that complex.  
Implementationally, it would seem to require at most:
  (1) For each UWP frame, the "cleanup forms" have an associated lock 
      (possibly a per-process lock) that is acquired upon entry [and 
      released upon exit?].  Failure to acquire the lock signals the 
      re-entrancy error.  The acquisition/release time could hardly
      be more than a few memory cycles time, and hence won't be a serious
      slowdown for THROWing.
  (2) The low-level part of THROW would admit a "skip me" argument, so
      that it could be told which UWP/CATCH frame to omit the cleanups
      on [but not the lock releasings?].  Only one "skip me" is needed, 
      since nested "deadly embraces"  could be undone one step at a time.
      [In fact there really only could be one frame to "skip" -- the one
       whose lock is already tied up.]

I don't mean to imply that this is the only, or the best, or even the
clearest implementation of such an idea; but I claim that non-re-entrancy 
isn't all that deep a concept here.  It's more to the point than saying
"non-interruptible" (which is wrong); and more succinct than some other
characterizations of how one might detect the "deadly embrace".


-- JonL --