[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Yet Another Event Loop for Guile
From: |
Amirouche Boubekki |
Subject: |
Yet Another Event Loop for Guile |
Date: |
Wed, 02 Mar 2016 20:41:34 +0100 |
User-agent: |
Roundcube Webmail/1.1.2 |
Héllo,
A while back I've written an event loop trying to understand how both
asyncio and 8sync work. The result of this work is attached to this
email.
The result of this study is much shorter than any other event loop,
so it might be the easiest way to jump into the train to learn the
basics
of event loop mish mash.
It only implements receiving and sending bytes without blocking using
select.
It doesn't support the following:
- sleep a certain amount of time
- call-when-idle
- call later with a time
- any kind of multi-thread operation
It's only a study draft implementation of an event loop and as matter
of fact I'm not sure which procedure is blocking which is not. So,
I've only implemented asynchronous variants of recv and send. There is
also an implementation of write and read but I think those are
blocking anyway.
Now some code. The client is implemented like so:
```
(define (client)
(define socket (make-client-socket 12345))
(write/ (list 0 "héllo") socket)
(write/ (list 42 "world") socket)
(close socket)
(loop-stop!))
```
Look there is no callback, but it's asynchronous! How?
Here ``write/`` is asynchronous variant of ``write`` which doesn't
block. Instead of actually calling write. It register a procedure
(with a continuation) to be called when the socket is ready.
It's implemented with the following code:
```
(define-public (write/ message socket)
(abort-to-prompt 'loop ;; Abort to the event loop
(lambda (cc) ;; [*] And call this with the
continuation `cc`
(loop-add-writer socket ;; register this callback
to be called
;; when the socket is
ready.
;; [#] When the socket is ready
write message
;; and return using continuation
`cc`
;; given by `abort-to-prompt`
(lambda () (cc (write message
socket)))))))
```
Before looking at the core of the event loop, let's look
at the outer procedure, that makes the event loop run forever:
```
(define-public (loop-run-forever)
(let* ((loop (fluid-ref *loop*)))
(loop-running! loop #true)
(while (loop-running loop)
(call-with-prompt 'loop
loop-run-once
;; This is the prompt handler which calls the above lambda with
[*] mark
(lambda (cc callback) (callback cc))))))
```
When `write/` abort to the prompt `'loop`, it execute the handler which
only pass the continuation the the callback registred by `write/` which
register a procedure to be called when the socket is ready. And run
again
the same loop, which probably does nothing because the socket is not
ready
yet. Otherwise said, just after `write/` does register it's callback,
the
event loop takes back the responsability to do what the program is meant
to do.
The procedure `loop-run-once` has the responsibility to run the
callbacks
registered against select using `loop-add-writer`:
```
(define (loop-run-once)
(let ((loop (fluid-ref *loop*))
(readers (hash-table-keys (loop-readers loop)))
(writers (hash-table-keys (loop-writers loop))))
;; first select ready ports
(match (select readers writers '() 0)
((to-read to-write _)
(for-each call-read-callback to-read)
(for-each call-write-callback to-write)))
;; execute tasks
(while (not (empty? (loop-tasks loop)))
((pop! (loop-tasks loop))))))
```
Here `call-write-callback` will *only* call the lambda marked [#] at
some point and remove it from the waiting procedures. Once [#] is
finished it returns to where it was registred ie. where `write/` is
called.
And the event loop basically loose the control over the flow of the
program
until the next async call or the *calling* procedures returns.
The server looks very similar. It's not event a echo server. Anyway,
here is it's code for symmetry sake:
```
(define (server)
(let* ((sock (make-server-socket 12345))
(client (car (accept/ sock))))
(pk (read/ client))
(pk (read/ client))
(close client)
(loop-stop!)))
```
There is two obvious things for me to take away in this implementation:
- The underlying event loop as a clear visible interface (which is not
presented in the above)
- There is *no future*, prompt replace the use of futures done in
asyncio
- There is no need for coroutines of any kind, since
prompts/continuations
do the job as-is
HTH,
Amirouche ~ amz3 ~ http://www.hyperdev.fr
async.scm
Description: Text document
server.scm
Description: Text document
client.scm
Description: Text document
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- Yet Another Event Loop for Guile,
Amirouche Boubekki <=