[plt-scheme] escape continuations and mred callback
> I am sorry, this continuation stuff makes my head spin. I understand the
> barrier idea but I don't understand the motivation. A callback is just
> another function, why is it barred from escaping?
Hi Pedro,
(I hope I get this right; I'm sure that someone else on the list will
correct me if I'm off...)
One might want to maintain some kind of invariant for the system. For
example, say we have the contrived code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(module test-continuation-barrier mzscheme
(require (lib "list.ss"))
(define listeners empty)
(define (add-listener! thunk)
(set! listeners (cons thunk listeners)))
(define (allocate-resource)
(printf "allocate~n"))
(define (deallocate-resource)
(printf "deallocate~n"))
(define (call-listeners)
(allocate-resource)
(for-each (lambda (t) (t)) listeners)
(deallocate-resource))
(define (test)
(let/ec return
(add-listener! (lambda ()
(printf "I'm evil and will get out~n")
(return)))
(call-listeners))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
The intent of CALL-LISTENERS here is to make sure that after we call each
of the listener functions, we finally call DEALLOCATE-RESOURCE. However,
in the presence of continuations, we can break through and escape out:
we've left the system in a state where a resource was allocated but not
reclaimed! If we run TEST, we'll see that we don't get to see the word
"deallocate".
So one way to prevent callbacks from seizing control away from the
underlying system is to put up a continuation barrier. Here's an updated
call-listeners that does so.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (call-listeners)
(allocate-resource)
(for-each (lambda (t)
(call-with-continuation-barrier t)) listeners)
(deallocate-resource))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
And now calling TEST will at least cause the system to shake its head.
Let's make one more revision: we can wrap an exception handler to at least
make sure we call DEALLOCATE-RESOURCE:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (call-listeners)
(allocate-resource)
(with-handlers ([exn:fail? void])
(for-each (lambda (t)
(call-with-continuation-barrier t)) listeners))
(deallocate-resource))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Now a call to CALL-LISTENERS, even in the presence of bad callbacks,
should work fine (... well, assuming that all callbacks terminate, but
that's another story.) That's where this sort of continuation-barrier
stuff can be useful.
Best of wishes!