[Top][All Lists]
[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