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