Otherwise said, I want to rely on epoll
(or similar) to write asynchronous network code that looks like regular code that is without relying on callbacks.
The code must look like synchronous code but unlike synchronous code instead of blocking to wait for network io, it must suspend the current coroutine and restart it when the file descriptor is ready.
My initial thought to achieve that was relying on generators and
yield
. But this was a mistake that was partly mis-guided by the fact that python used to abuseyield from
.Anyway, guile fibers was a great insipiration and I adapted it to chez scheme.
Here is an example server code:
The
handler
returns its IP according the httpbin service. The code look synchronous with the help of call/cc actually call/1cc.untangle
will initiate the event loop with a lambda passed as argument!Here is the definition of
run-server
:As you can see there is no callback. The only thing that is somewhat different from simple synchronous webserver is the
spawn
procedure that will handle the request in its own coroutine. In particularaccept
is asynchronous.run-once
will just pass the scheme request tohandler
and take its 3 values to build the response. Not very interesting. The part that looks synchronous, but is actually asynchronous ishttp-get
above.I will only explain, how accept works, given http-get requires to introduce custom binary ports, but suffice to say it is the same behavior...
As you can see it calls a procedure
abort-to-prompt
that we could call simplypause
that will "stop" the coroutine and call the prompt handler.abort-to-prompt
works in cooperation withcall-with-prompt
.Since chez scheme doesn't have prompts I emulate it using two one shot continuations
call/1cc
call-with-prompt
will initiate a continuation aset!
global called%prompt
which means there is single prompt forTHUNK
. If the continuation argumentsOUT
, the second lambda ofcall-with-values
, starts with the unique object%abort
it means the continuation was reached viaabort-to-prompt
. It will call theHANDLER
with theabort-to-prompt
continuation and any argument passed tocall-with-prompt
continuation parameter that is the(apply handler (cons k (cdr out)))
.abort-to-promp
will initiate a new continuation to be able to come back, after the code executes the prompt's continuation stored in%prompt
.The
call-with-prompt
is at the heart of the event-loop. Here is it, in two pieces:I think that is all.
If you are using chez-scheme, there is chez-a-sync. It uses POSIX poll rather than epoll (epoll is linux specific). guile-a-sync2 is also available for guile-2.2/3.0.