Contact/support | Changelog

More simply addressing the confusion about UNWIND-PROTECT in Scheme

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
Source: http://mumble.net/~campbell/blag.txt

2009-03-28 More simply addressing the confusion about UNWIND-PROTECT
    in Scheme

  Suppose you want to release a resource after a part of a program
  that uses the resource.  For example, consider a connection to a
  database that should be disconnected, a file descriptor that should
  be closed, or a buffer for sensitive data such as a pass phrase that
  should be destroyed by zeros or random data.  Once the resource is
  released, it can be neither reused nor readily reconstituted.

  Is the resource represented in Scheme by a single object?  If so,
  ensure that with the object is registered a finalizer that releases
  the resource, so that even in the face of an interactive ^C at the
  REPL the resource will be released.  All of the above examples
  qualify.

    (define (open-database-connection database-descriptor)
      (let ((handle (allocate-database-handle)))
        (register-finalizer handle %close-database-connection)
        ;; After this point, if we are interrupted -- say with ^C at
        ;; the REPL --, somewhere the handle will be recorded so that
        ;; its resources can be released even if the usual logic to
        ;; close the connection is never reached.
        (%open-database-connection database-descriptor handle)
        handle))

  Is it /necessary/ for the resource to be released immediately after
  the part of the program that uses it?  If so, use REWIND-PROTECT at
  <http://mumble.net/~campbell/scheme/rewind-protect.scm>.  In this
  case, control can never re-enter the extent that uses the resource.
  Of the above examples, only a buffer for sensitive data qualifies.

    (define (call-with-pass-phrase prompt receiver)
      (let ((buffer (make-pass-phrase-buffer)))
        (rewind-protect
            (lambda ()
              (prompt-for-pass-phrase prompt buffer)
              (receiver buffer))
          (lambda ()
            (clear-pass-phrase-buffer buffer)))))

  Is it /desirable/ for the resource to be released immediately after
  the part of the program that uses it?  If so, use UNWIND-PROTECT at
  <http://mumble.net/~campbell/scheme/unwind-protect.scm>.  Of the
  above examples, both the database connection and file descriptor
  qualify.

    (define (call-with-database-connection database-descriptor
              procedure)
      (let ((handle (open-database-connection database-descriptor)))
        (unwind-protect (lambda () (procedure handle))
          (lambda ()
            ;; If a continuation is captured inside PROCEDURE so that
            ;; this cannot execute immediately when PROCEDURE returns,
            ;; then closing the database connection here is redundant,
            ;; since registered with HANDLE is a finalizer that will
            ;; close the connection.  However, provided that the
            ;; finalizer have no effect if the connection is already
            ;; closed, this redundancy is harmless and enables
            ;; UNWIND-PROTECT to close the connection immediately if
            ;; no such continuation has been captured.
            (close-database-connection handle)))))

  Rationale: Scheme's CWCC (or composable continuations) enables
  powerful control abstractions such as inverting control.  For
  example, (FOR-EACH <procedure> <list>) applies <procedure> to each
  element of <list> in sequence.  With CWCC, one can reify the
  iteration of the list by FOR-EACH into a first-class cursor object
  that can be advanced independently.  This way, for example, we can
  traverse multiple lists in parallel using only the one-list FOR-EACH
  procedure.

  If we rather consider a procedure (FOR-EACH-QUERY-RESULT <procedure>
  <database-query>), the situation is not much different, except that
  FOR-EACH-QUERY-RESULT may entail the opening of a connection to a
  database, and the connection is a resource which should be released
  after the query results are finished being received.  Using CWCC to
  invert control of FOR-EACH-QUERY-RESULT is just as valid as using
  CWCC to invert control of FOR-EACH, but if FOR-EACH-QUERY-RESULT
  uses REWIND-PROTECT (a common idiom in unfortunately much Scheme
  code), it inhibits control inversion.  UNWIND-PROTECT allows the
  control inversion.

  Why involve finalization in UNWIND-PROTECT, rather than just
  associating a finalizer with the object representing the connection
  to the database?  The difference is in the intent implied by the use
  of UNWIND-PROTECT that if control cannot re-enter the protected
  extent, the protector can be immediately applied when control exits
  the protected extent extent even if no garbage collection is about
  to run finalizers.

  Note that one cannot in general reconcile

  (1) needing to release the resource immediately after control exits
    the extent that uses it, and

  (2) enjoying the benefits of powerful control abstractions such as
    inversion of control.

  However, if (1) is relaxed to

  (1') needing to release the resource immediately after control
    /returns normally from/ the extent that uses it,

  then one can reconcile (1') and (2) by writing

    (CALL-WITH-VALUES (LAMBDA () <resource-usage>)
      (LAMBDA RESULTS
        <resource-release>
        (APPLY VALUES RESULTS))), or simply

    (BEGIN0 <resource-usage> <resource-release>)

  using the common name BEGIN0 for this idiom.  (In Common Lisp, this
  is called PROG1.)

  Also note that condition systems are not involved; they are a red
  herring.  Any condition handler that releases a resource should do
  so only when the condition implies that the resource is no longer
  usable; for example, if a database connection becomes desynchronized
  somehow and cannot proceed, then it is reasonable for the handler
  for this condition to close the connection and destroy any of its
  state.  However, when handling conditions that do not imply that the
  resource is no longer usable, the resource should not be released.
  An enclosing handler may continue within a protected extent, or even
  exit and re-enter the protected extent, for instance while iterating
  over a reified database cursor procured by control inversion.
  Handling any condition by releasing the resource defeats these
  useful operations.

  At <http://mumble.net/~campbell/scheme/mit-unwind-protect.scm> is an
  implementation of UNWIND-PROTECT for MIT Scheme.  This definition is
  lighter-weight than the portable one.  However, it does not attempt
  to run the protector immediately if it can.

  Appendix A: John Cowan has asked for elaboration on the inversion of
  control for iteration with FOR-EACH.

    (define (list->stream list)
      (iteration-procedure->stream
       (lambda (receiver)
         (for-each receiver list))))

    (define (iteration-procedure->stream iteration-procedure)
      (lazy
       (call-with-current-continuation
        (lambda (return-stream)
          (iteration-procedure
           (lambda (element)
             (call-with-current-continuation
              (lambda (return-to-iteration)
                (return-stream
                 (stream-cons element
                              (lazy
                               (call-with-current-continuation
                                (lambda (return-cdr)
                                  (set! return-stream return-cdr)
                                  (return-to-iteration))))))))))
          (return-stream stream-nil)))))

    ;;; Simpler definition with reset & shift.

    (define (iteration-procedure->stream iteration-procedure)
      (lazy
       (reset
        (begin
          (iteration-procedure
           (lambda (element)
             (shift continue-iteration
               (stream-cons element (lazy (continue-iteration))))))
          stream-nil))))

  Appendix B, discussion question:  Should UNWIND-PROTECT evaluate
  protection forms in the dynamic context of the call to
  UNWIND-PROTECT?  This rules out using UNWIND-PROTECT within
  REWIND-PROTECT, which may be an onerous requirement.