[plt-scheme] escape continuations and mred callback

From: Danny Yoo (dyoo at hkn.eecs.berkeley.edu)
Date: Sat Oct 7 00:24:10 EDT 2006

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


Posted on the users mailing list.