chicken-users
[Top][All Lists]
Advanced

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

Re: [Chicken-users] Threads and dynamic-wind still problematic


From: John Cowan
Subject: Re: [Chicken-users] Threads and dynamic-wind still problematic
Date: Mon, 9 Mar 2009 18:21:29 -0400
User-agent: Mutt/1.5.13 (2006-08-11)

Tobia Conforto scripsit:

> I wonder how other compilers do it.  For example, I find Java's (and  
> Python's) try/finally syntax quite useful.  I've always thought  
> dynamic-wind was Scheme's equivalent construct, but it would appear I  
> was mistaken.

Here's the relevant writeup from Taylor Campbell's (Riastradh's) blag,
slightly edited.

  DYNAMIC-WIND has long been a source of contention in the Lisp world.
  It serves a very particular purpose, but this purpose is not very
  well understood, and many think that this purpose is the same as the
  purpose of other Lisps' UNWIND-PROTECT.  While the general idea of
  UNWIND-PROTECT is useful, it is not directly applicable in Scheme,
  because Scheme has more powerful control abstractions than other
  Lisps', and it therefore requires more powerful means of controlling
  them.

  (UNWIND-PROTECT form protection-form ...), a special form, evaluates
  the first form and returns its value, but before returning the value
  also executes the protection forms.  Furthermore, if a throw in the
  primary form transfers control to outside the whole UNWIND-PROTECT
  form, this, too, will execute the protection forms.  This allows
  programmers to *reliably* clean things up at certain points in the
  program -- for instance, to close open files, to shut down sockets,
  to release database handles, &c.; here is hypothetical example of
  its use:

    (define (call-with-input-file pathname procedure)
      (let ((input-port (open-input-file pathname)))
        (unwind-protect (procedure input-port)
          (close-input-port input-port))))

  (DYNAMIC-WIND before during after), a procedure of three procedural
  parameters, calls the during thunk, ensuring that any transfer of
  control into it calls the before thunk, and that any transfer of
  control out of it calls the after thunk.  Normal control transfers,
  i.e. the initial call to the during thunk and its final return,
  qualify as control transfers too.  This means that, any time control
  is inside the dynamic extent of the during thunk, any state
  established by the before thunk will be in effect, but any time
  control is outside its dynamic extent, that state will be torn down
  by the after thunk.  Here is an actual example of DYNAMIC-WIND's
  use, from the Edwin text editor:

    (define (with-current-local-bindings! thunk)
      (dynamic-wind 
        (lambda ()
          (install-buffer-local-bindings! (current-buffer)))
        thunk
        (lambda ()
          (uninstall-buffer-local-bindings! (current-buffer)))))

  This ensures that, within THUNK, all Edwin variables have any values
  specified by the buffer's local bindings.  This is the kind of use
  that DYNAMIC-WIND is appropriate for: a sort of control bracket that
  ensures the presence of certain dynamic state or context within a
  certain dynamic extent.

  Now, unfortunately, DYNAMIC-WIND can be seen as somewhat similar to
  UNWIND-PROTECT, because for basic cases (involving no sophisticated
  exploitation of Scheme's continuation reification) DYNAMIC-WIND with
  a null entrance thunk seems to be equivalent to UNWIND-PROTECT.
  This is misleading, however.  Either control may return to the
  dynamic extent of the during thunk in the DYNAMIC-WIND, in which
  case the after thunk will be called twice -- which is unacceptably
  wrong --, or the before thunk really signals an error if it is
  called twice.  This, though, violates the philosophy described in
  the first sentence of the R5RS:

    Programming languages should be designed not by piling feature
    on top of feature, but by removing the weaknesses and
    restrictions that make additional features appear necessary.

  Signalling an error in a DYNAMIC-WIND before thunk is usually a
  limitation and a weakness in a program.  We have an *extremely*
  powerful means of abstraction of control -- CALL/CC[1] -- and we ought
  not to prevent it from working merely because we want to prevent a
  certain fragment of code from running more than once.  This should,
  however, spur a deeper look at why the code is being run more than
  once in the first place.

  DYNAMIC-WIND is meant to establish local dynamic state for specific
  dynamic extents within programs, by setting it up in before thunks
  and tearing it down in after thunks.  These operations, to set up
  and to tear down dynamic state, are meant to be reversible, local
  changes.[2]  That is, permanent actions, such as closing a port,
  that one would put in a protection form of an UNWIND-PROTECT, are
  entirely inappropriate in a DYNAMIC-WIND after thunk.

  Yet the basic idea of UNWIND-PROTECT is still useful.  What,
  exactly, though, is useful from UNWIND-PROTECT, and what is
  incompatible with Scheme?  The concept of permanent finalization is
  useful, and the identification of a good point to perform it is
  useful.  *Requiring* that it be performed at this point, though, is
  incompatible with Scheme, because this point may be passed several
  times, yet we must perform the finalization once and only once.

  Many languages, and many Scheme systems, provide ways to ensure that
  certain code be run when an object is no longer reachable.  We can
  apply this same idea, in fact, to continuations!  The protection
  thunk in an UNWIND-PROTECT of a lesser Lisp is run only once the
  continuation is about to become unreachable.  The only difference
  between Scheme and CALL/CC-less Lisps is that the point at which
  continuations become unreachable is unfixed in Scheme.

  So we must use a general object finalization mechanism -- for the
  kind of object known as a continuation.  This is easy to concoct,
  though: we need only to make an object, to associate with that
  object the protection thunk so that it will be executed when the
  object become unreachable, and to make a link from the continuation
  to that object.  There is an implementation of this in MIT Scheme at
  the end of this entry.

  This, though, is non-optimal.  The protection thunk may run at any
  time in the system.  We should *like* it to be run as soon as
  control returns through the continuation in question, but this is
  valid only if that continuation has not been reified and will not
  ever be reused.  We can say that a continuation is /dirty/ if it has
  been reified and may be instantiated again, and that a continuation
  is /clean/ if and only if there will be exactly one instance of it,
  which will be destroyed as soon as control returns through it.

  If we store a bit in any continuation involved with UNWIND-PROTECT,
  i.e. any continuation with an associated protection thunk, then we
  can initially clear the bit, to indicate that it is clean, and
  instrument CALL/CC to mark all such continuations as dirty by setting
  their bits.  When control returns through a protected continuation,
  it checks the bit.  If the bit is clear, then the continuation is
  clean, and it runs the protection thunk, because control will never
  again return to this point; it also deregisters any finalization
  procedure.  If the bit is set, then the continuation is dirty, and
  general finalization system for objects of unlimited extent will
  handle the protection thunk.

  (We need to be careful in the case of a reified continuation
  instantiated on the stack whose heap storage has been reclaimed.
  The continuation will be marked dirty, but before control returns
  through it for the last time its protection thunk might be run,
  because the GC could finalize its heap storage.)

  Both DYNAMIC-WIND and UNWIND-PROTECT are useful for different
  applications in a language with powerful control abstractions such
  as Scheme.  DYNAMIC-WIND, though, does not subsume the operation of
  UNWIND-PROTECT, and UNWIND-PROTECT cannot be taken literally from a
  language with weaker control abstractions.  By examining the core of
  the intent of UNWIND-PROTECT, though, we can separate its actual
  purpose from an optimization.  We can implement the actual purpose,
  and then analyze cases where the optimization can still be useful,
  in order to provide UNWIND-PROTECT efficiently in Scheme.  The
  result is that we can retain the powerful control abstraction of
  CALL/CC and in the same language integrate the same idioms of resource
  release as other languages provide.

  Of course, there are still some limited cases where restriction is
  really necessary.  There are a number of procedures in MIT Scheme
  that work with sensitive strings, and into which reentry is not an
  option, in order to enforce the proper destruction of sensitive
  information.  In this case, DYNAMIC-WIND is appropriate to reject
  reentry and to nullify sensitive strings on exit.

  [1] Some would argue that composable continuations ought to be
      enough for everyone, but the issues here are exactly the same
      whether we use CALL/CC or composable continuations.

  [2] This is not *exactly* true, but symmetry is a very desirable
      property of DYNAMIC-WIND before & after thunks, and reversible
      set-up & tear-down operations are usual.

-- 
And through this revolting graveyard of the universe the muffled, maddening
beating of drums, and thin, monotonous whine of blasphemous flutes from
inconceivable, unlighted chambers beyond Time; the detestable pounding
and piping whereunto dance slowly, awkwardly, and absurdly the gigantic
tenebrous ultimate gods --the blind, voiceless, mindless gargoyles whose soul
is Nyarlathotep. (Lovecraft)   John Cowan  address@hidden




reply via email to

[Prev in Thread] Current Thread [Next in Thread]