Prev Up Next

We will first set the clock interrupt handler. Note that the handler is invoked only if a non-quiescent clock runs out of ticks. This happens only during engine computations that are on the brink of failure, for only engines set the clock.

The handler captures the current continuation, which is the rest of the computation of the currently failing engine. This continuation is sent to another continuation stored in the global *engine-escape*. The *engine-escape* variable stores the exit continuation of the current engine. Thus the clock handler captures the rest of the failing engine and sends it to an exit point in the engine code, so the requisite failure action can be taken.

(define *engine-escape* #f)
(define *engine-entrance* #f)

(clock 'set-handler
  (lambda ()
    (call/cc *engine-escape*)))

Let us now look into the innards of the engine code itself. As said, make-engine takes a thunk and fashions an engine out of it:

(define make-engine
  (lambda (th)
    (lambda (ticks success failure)
      (let* ((ticks-left 0)
             (engine-succeeded? #f)
             (result
              (call/cc
               (lambda (k)
                 (set! *engine-escape* k)
                 (let ((result
                        (call/cc
                         (lambda (k)
                           (set! *engine-entrance* k)
                           (clock 'set ticks)
                           (let ((v (th)))
                             (*engine-entrance* v))))))
                   (set! ticks-left (clock 'set *infinity*))
                   (set! engine-succeeded? #t)
                   result)))))
        (if engine-succeeded?
            (success result ticks-left)
            (failure 
             (make-engine 
              (lambda () 
                (result 'resume)))))))))

First we introduce the variables ticks-left and engine-succeeded?. The first will hold the ticks left over should the engine thunk finish in time. The second is a flag that will be used in the engine code to signal if the engine suceeded.

We then run the engine thunk within two nested calls to call/cc. The first call/cc captures the continuation to be used by a failing engine to abort out of its engine computation. This continuation is stored in the global *engine-escape*. The second call/cc captures an inner continuation that will be used by the return value of the thunk th if it runs to completion. This continuation is stored in the global *engine-entrance*.

Running through the code, we find that after capturing the continuations *engine-escape* and *engine-entrance*, we set the clock's ticks to the time allotted this engine and run the thunk th. If th succeeds, its value v is sent to the continuation *engine-entrance*, after which the clock is stopped, the remaining ticks ascertained, and the flag engine-succeeded? is set to true. We now go past the *engine-escape* continuation, and run the final dispatcher in the code: Since we know the engine succeeded, we apply the success procedure to the result and the ticks left.

If the thunk th didn't finish in time though, it will suffer an interrupt. This invokes the clock interrupt handler, which captures the current continuation of the running and now failing thunk and sends it to the continuation *engine-escape*. This puts the failed-thunk continuation in the outer result variable, and we are now in the final dispatcher in the code: Since engine-succeeded? is still false, we apply the failure procedure to new engine fashioned out of result.

Notice that when a failed engine is removed, it will traverse the control path charted by the first run of the original engine. Nevertheless, because we have explicitly use the continuations stored in the global variables *engine-entrance* and *engine-escape*, and we always set them anew before executing an engine computation, we are assured that the jumps will always come back to the currently executing engine code.

Prev Up Next