guile-user
[Top][All Lists]
Advanced

[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

Attachment: async.scm
Description: Text document

Attachment: server.scm
Description: Text document

Attachment: client.scm
Description: Text document


reply via email to

[Prev in Thread] Current Thread [Next in Thread]