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.
|