Skip to content

event loop

Chris Vine edited this page Jul 13, 2020 · 49 revisions

The (a-sync event-loop) library file provides an event loop for asynchronous operations. The event loop does not of itself start any new threads. Instead it runs in the thread which calls event-loop-run!: that procedure will block while there are events to be processed. In most programs the event loop will run in the main program thread, with which the program began. Many programs using event loops are entirely single threaded.

However, the event loop provided by this library file does support multiple threads. Most of the event loop procedures in this library file are thread safe and may be called in any thread. The thread safe procedures are event-loop-run! (as mentioned, this will cause the event loop to run in the thread which calls it), event-loop-block!, event-loop-quit!, event-post!, event-loop-tasks, event-loop-add-read-watch!, event-loop-add-write-watch!, event-loop-remove-read-watch!, event-loop-remove-write-watch!, timeout-post! and timeout-remove!. In particular, event-post! allows worker threads to post an event to an event loop, say with the result of a computation, so that the event callback will execute in the event loop thread.

(a-sync meeting) provides synchronization facilities for event loops constructed under this library file.

The file watches provided by this library file uses chez scheme's FFI to call a wrapper for the poll() system call. This has the consequence that in linux poll() applied to sockets can give rise to spurious wake-ups, which means that it may be best with sockets to set the socket as non-blocking to prevent a blocking read or write where it is not expected. Non-blocking ports are handled correctly by the await-getline!, await-geteveryline!, await-getsomelines!, await-getblock!, await-geteveryblock!, await-getsomeblocks, await-put-bytevector! and await-put-string! procedures.

Where a file watch on a file descriptor or port has been added with event-loop-add-read-watch! or event-loop-add-write-watch!, the event loop should be treated as owning the port concerned until it has subsequently been removed with event-loop-remove-read-watch! or event-loop-remove-write-watch!. This is because, if the port concerned is closed (say by calling close-port, close-input-port or close-output-port) before its watch has been removed from the event loop, and the underlying file descriptor value is then reused by the operating system, there may be a phantom watch on the new descriptor with an associated attempted read or write on a closed port. Also note that, where a watch on a port has just been removed, it is usually undesirable to close the port in a different thread than the event loop thread because there is no easy way of synchronizing the threads at that point. If it is necessary for a worker thread to close a port after removing the watch, one approach is for the thread to post the closing as an event using event-post!.

This library file provides the following procedures:


(set-default-event-loop! [loop])

The 'loop' (event loop) argument is optional. This procedure sets the default event loop for the procedures in this library file to the one passed in (which must have been constructed by the make-event-loop procedure), or if no argument is passed (or #f is passed), a new event loop will be constructed for you as the default, which can be accessed via the get-default-event-loop procedure. The default loop variable is not a parameter - it is intended that the default event loop is the same for every thread in the program, and that the default event loop would normally run in the thread with which the program started. This procedure is not thread safe - if it might be called by a different thread from others which might access the default event loop, then external synchronization may be required. However, that should not normally be an issue. The normal course would be to call this procedure once only on program start up, before other threads have started. It is usually a mistake to call this procedure twice: if there are asynchronous events pending (that is, if event-loop-run! has not returned) you will probably not get the results you expect.

Note that if a default event-loop is constructed for you because no argument is passed (or #f is passed), no throttling arguments are applied to it (see the documentation on make-event-loop for more about that). If throttling is wanted, the make-event-loop procedure should be called explicitly and the result passed to this procedure.


(get-default-event-loop)

This returns the default loop set by the set-default-event-loop! procedure, or #f if none has been set.


(make-event-loop [throttle-threshold throttle-delay])

This constructs a new event loop object. From version 0.4, this procedure optionally takes two throttling arguments for backpressure when applying the event-post! procedure to the event loop. The 'throttle-threshold' argument specifies the number of unexecuted tasks queued for execution, by virtue of calls to event-post!, at which throttling will first be applied. Where the threshold is exceeded, throttling proceeds by adding a wait to any thread which calls the event-post! procedure, equal to the cube of the number of times (if any) by which the number of queued tasks exceeds the threshold multiplied by the value of 'throttle-delay'. The value of 'throttle-delay' should be given in microseconds. Throttling is only applied where the call to event-post! is made in a thread other than the one in which the event loop runs.

So if the threshold given is 10000 tasks and the delay given is 1000 microseconds, upon 10000 unexecuted tasks accumulating a delay of 1000 microseconds will be applied to callers of event-post! which are not in the event loop thread, at 20000 unexecuted tasks a delay of 8000 microseconds will be applied, and at 30000 unexecuted tasks a delay of 27000 microseconds will be applied, and so on.

If throttle-threshold and throttle-delay arguments are not provided (or #f is passed for them), then no throttling takes place.


(event-loop? obj)

This procedure indicates whether 'obj' is an event-loop object constructed by make-event-loop.


(event-loop-run! [loop])

The 'loop' (event loop) argument is optional. This procedure starts the event loop passed in as an argument, or if none is passed (or #f is passed) it starts the default event loop. The event loop will run in the thread which calls this procedure. If this procedure has returned, including after a call to event-loop-quit!, this procedure may be called again to restart the event loop provided event-loop-close! has not been applied to the loop. If event-loop-close! has previously been invoked, this procedure will raise a &violation exception.

From version 0.18, this procedure will also raise a &violation exception if it is applied to an event loop which is currently running (prior to version 0.18, doing so would fail in a less helpful way).

If something else throws in the implementation or a callback throws, then this procedure will clean up the event loop as if event-loop-quit! had been called, and the exception will be rethrown out of this procedure. This means that if there are continuable exceptions, they will be converted into non-continuable ones (but continuable exceptions are usually incompatible with asynchronous event handlers and may break resource management using rethrows or dynamic winds).


(event-loop-add-read-watch! file proc [loop])

The 'loop' (event loop) argument is optional. This procedure will start a read watch in the event loop passed in as an argument, or if none is passed (or #f is passed), in the default event loop. The 'proc' callback should take a single argument, and when called this will be set to 'in or 'excpt. The same port or file descriptor can also be passed to event-loop-add-write-watch, and if so and the descriptor is also available for writing, the write callback will also be called with its argument set to 'out. If there is already a read watch for the file passed, the old one will be replaced by the new one. If 'proc' returns #f, the read watch will be removed from the event loop, otherwise the watch will continue. This is thread safe - any thread may add a watch, and the callback will execute in the event loop thread. The file argument can be either a port or a file descriptor. If 'file' is a file descriptor, any port for the descriptor is not referenced for garbage collection purposes - it must remain valid while operations are carried out on the descriptor. If 'file' is a buffered port, buffering will be taken into account in indicating whether a read can be made without blocking (but on a buffered port, for efficiency purposes each read operation in response to this watch should usually exhaust the buffer by looping on char-ready? or input-port-ready?, or by using chez scheme's various multi-byte/character reading procedures on non-blocking ports).

This procedure should not raise an exception unless memory is exhausted.


(event-loop-add-write-watch! file proc [loop])

The 'loop' (event loop) argument is optional. This procedure will start a write watch in the event loop passed in as an argument, or if none is passed (or #f is passed), in the default event loop. The 'proc' callback should take a single argument, and when called this will be set to 'out. The same port or file descriptor can also be passed to event-loop-add-read-watch, and if so and the descriptor is also available for reading or in exceptional condition, the read callback will also be called with its argument set to 'in or 'excpt (if both a read and a write watch have been set for the same file argument, and there is an exceptional condition, it is the read watch procedure which will be called with 'excpt rather than the write watch procedure, so if that procedure returns #f only the read watch will be removed). If there is already a write watch for the file passed, the old one will be replaced by the new one. If 'proc' returns #f, the write watch will be removed from the event loop, otherwise the watch will continue. This is thread safe - any thread may add a watch, and the callback will execute in the event loop thread. The file argument can be either a port or a file descriptor. If 'file' is a file descriptor, any port for the descriptor is not referenced for garbage collection purposes - it must remain valid while operations are carried out on the descriptor.

If 'file' is a buffered port, buffering will be not be taken into account in indicating whether a write can be made without blocking: that will only occur if the underlying file descriptor is ready. This is because the writer to the port must in any event cater for the fact that when the buffer is full but the underlying file descriptor is ready for a character, the next write will cause a buffer flush, and if the size of the buffer is greater than the number of characters that the file can receive without blocking, blocking might still occur. Therefore, this procedure will generally work best with unbuffered ports (say by using the open-file-output-port procedure with a buffer-mode of none or by applying the set-textual-port-output-size! procedure to the port with a value of 0), or with ports which have been set non-blocking so that a partial write is possible without blocking the writer.

This procedure should not raise an exception unless memory is exhausted.


(event-loop-remove-read-watch! file [loop])

The 'loop' (event loop) argument is optional. This procedure will remove a read watch from the event loop passed in as an argument, or if none is passed (or #f is passed), from the default event loop. The file argument may be a port or a file descriptor. This is thread safe - any thread may remove a watch. A file descriptor and a port with the same underlying file descriptor compare equal for the purposes of removal.

This procedure should not raise an exception unless memory is exhausted.


(event-loop-remove-write-watch! file [loop])

The 'loop' (event loop) argument is optional. This procedure will remove a write watch from the event loop passed in as an argument, or if none is passed (or #f is passed), from the default event loop. The file argument may be a port or a file descriptor. This is thread safe - any thread may remove a watch. A file descriptor and a port with the same underlying file descriptor compare equal for the purposes of removal.

This procedure should not raise an exception unless memory is exhausted.


(event-post! action [loop])

The 'loop' (event loop) argument is optional. This procedure will post a callback for execution in the event loop passed in as an argument, or if none is passed (or #f is passed), in the default event loop. The 'action' callback is a thunk. This is thread safe - any thread may post an event (that is its main purpose), and the action callback will execute in the event loop thread. Actions execute in the order in which they were posted. If an event is posted from a worker thread, it will normally be necessary to call event-loop-block! beforehand.

This procedure should not raise an exception unless memory is exhausted. If the 'action' callback throws, and the exception is not caught locally, it will propagate out of event-loop-run!.

Where this procedure is called by other than the event loop thread, throttling may take place if the number of posted callbacks waiting to execute exceeds the threshold set for the event loop - see the documentation on make-event-loop for further details.


(timeout-post! msecs action [loop])

The 'loop' (event loop) argument is optional. This procedure adds a timeout to the event loop passed in as an argument, or if none is passed (or #f is passed), to the default event loop. The timeout will repeat unless and until the passed-in callback returns #f or timeout-remove! is called. The passed-in callback must be a thunk. This procedure returns a tag symbol to which timeout-remove! can be applied. It may be called by any thread, and the timeout callback will execute in the event loop thread.

This procedure should not raise an exception unless memory is exhausted. If the 'action' callback throws, and the exception is not caught locally, it will propagate out of event-loop-run!.


(timeout-remove! tag [loop])

The 'loop' (event loop) argument is optional. This procedure stops the timeout with the given tag from executing in the event loop passed in as an argument, or if none is passed (or #f is passed), in the default event loop. It may be called by any thread.

This procedure should not raise an exception unless memory is exhausted.


(event-loop-tasks [loop])

This procedure returns the number of callbacks posted to an event loop with the event-post! procedure which at the time still remain queued for execution. Amongst other things, it can be used by a calling thread which is not the event loop thread to determine whether throttling is likely to be applied to it when calling event-post! - see the documentation on make-event-loop for further details.

The 'loop' (event loop) argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop. This procedure is thread safe - any thread may call it.

This procedure is first available in version 0.4 of this library.


(event-loop-block! val [loop])

By default, upon there being no more watches, timeouts and posted events for an event loop, event-loop-run! will return, which is normally what you want with a single threaded program. However, this is undesirable where a worker thread is intended to post an event to the main loop after it has reached a result, say via await-task-in-thread!, because the main loop may have ended before it posts. Passing #t to the val argument of this procedure will prevent that from happening, so that the event loop can only be ended by calling event-loop-quit!, or by calling event-loop-block! again with a #f argument (to switch the event loop back to non-blocking mode, pass #f). This is thread safe - any thread may call this procedure. The 'loop' (event loop) argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed as the 'loop' argument), on the default event loop.

This procedure should not raise an exception unless memory is exhausted.


(event-loop-quit! [loop])

This procedure causes an event loop to end and event-loop-run! to return. Any file watches, timeouts or posted events remaining in the event loop will be discarded. New file watches, timeouts and events may subsequently be added or posted after event-loop-run! has returned, and event-loop-run! then called for them. This is thread safe - any thread may call this procedure, including any callback or task running on the event loop. The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop. Applying this procedure to an event loop does not change the blocking status of the loop as may previously have been set by event-loop-block!, should event-loop-run! be applied to it again.

Applying this procedure to an event loop has no effect if the event loop is not actually running. A few await-* procedures (await-put-bytevector! and await-put-string! in this library file, and await-accept-ipv4-connection!, await-accept-ipv6-connection! and await-accept-unix-connection! in the chez-simple-sockets package) do not pass control to the event loop if they can operate immediately without waiting, so if (i) the event loop concerned has been set blocking by event-loop-block!, (ii) this procedure is invoked in an a-sync or compose-a-sync block in order to bring the event loop to an end, and (iii) before invoking this procedure the a-sync or compose-a-sync block has done nothing except make a call to one or more of those particular await-* procedures, then in order to make sure the loop is running consider calling await-yield! or some similar procedure before applying this procedure.

Note that the discarding of file watches, timeouts and unexecuted events remaining in the event loop means that if one of the helper await-* procedures provided by this library has been called but has not yet returned, it may fail to complete, as its continuation may disappear - it will be as if the a-sync or compose-a-sync block concerned had come to an end. It may therefore be best only to call this procedure on an event loop after all await-* procedures which are executing in it have returned.

This procedure should not raise an exception unless memory is exhausted.


(event-loop-close! [loop])

This procedure closes an event loop. Like event-loop-quit!, if the loop is still running it causes the event loop to unblock, and any file watches, timeouts or posted events remaining in the event loop will be discarded. However, unlike event-loop-quit!, it also closes the internal event pipe ports, and any subsequent application of event-loop-run! to the event loop will cause a &violation exception to be raised.

You might want to call this procedure to ensure that, after an event loop in a local scope has been finished with, the two internal event pipe file descriptors used by the loop are released to the operating system in advance of the garbage collector releasing them when the event loop object becomes inaccessible.

This is thread safe - any thread may call this procedure, including any callback or task running on the event loop. The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

Note that the discarding of file watches, timeouts and unexecuted events remaining in the event loop means that if one of the helper await-* procedures provided by this library has been called but has not yet returned, it may fail to complete, as its continuation may disappear - it will be as if the a-sync or compose-a-sync block concerned had come to an end. It may therefore be best only to call this procedure on an event loop after all such await-* procedures which are executing have returned.

This procedure should not raise an exception unless memory is exhausted.

This procedure is first available in version 0.17 of this library.


(await-task-in-thread! await resume [loop] thunk [handler])

The loop and handler arguments are optional. The procedure will run 'thunk' in its own thread, and then post an event to the event loop specified by the 'loop' argument when 'thunk' has finished, or to the default event loop if no 'loop' argument is provided or if #f is provided as the 'loop' argument (pattern matching is used to detect the type of the third argument). This procedure calls 'await' and will return the thunk's return value. It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). It will normally be necessary to call event-loop-block! before invoking this procedure. If the optional 'handler' argument is provided, then that handler will be run in the event loop thread if 'thunk' throws and the return value of the handler would become the return value of this procedure; otherwise the program will terminate if an unhandled exception propagates out of 'thunk'. 'handler' should take a single argument, which will be the thrown condition object.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs, where the result of calling 'thunk' will be received. As mentioned above, the thunk itself will run in its own thread.

As the worker thread calls event-post!, it might be subject to throttling by the event loop concerned. See the documentation on the make-event-loop procedure for further information about that.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the worker thread starts), which shouldn't happen unless memory is exhausted or pthread has run out of resources. Exceptions arising during execution of the task, if not caught by a handler procedure, will terminate the program. Exceptions raised by the handler procedure will propagate out of event-loop-run!.

Here is an example of the use of await-task-in-thread!:

(set-default-event-loop!) ;; if none has yet been set
(event-loop-block! #t) ;; because the task runs in another thread
(a-sync (lambda (await resume)
          (format #t "1 + 1 is ~A\n"
                  (await-task-in-thread! await resume
                                         (lambda ()
                                           (+ 1 1))))
          (event-loop-block! #f)))
(event-loop-run!)

(await-task-in-event-loop! await resume [waiter] worker thunk)

The 'waiter' argument is optional. The 'worker' argument is an event loop running in a different thread than the one in which this procedure is called, and is the one in which 'thunk' will be executed by posting an event to that loop. The result of executing 'thunk' will then be posted to the event loop specified by the 'waiter' argument, or to the default event loop if no 'waiter' argument is provided or if #f is provided as the 'waiter' argument, and will comprise this procedure's return value. This procedure is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). It will normally be necessary to call event-loop-block! on 'waiter' (or on the default event loop) before invoking this procedure.

This procedure calls 'await' and must (like the a-sync procedure) be called in the same thread as that in which the 'waiter' or default event loop runs (as the case may be).

This procedure acts as a form of channel through which two different event loops may communicate. It also offers a means by which a master event loop (the waiter or default event loop) may allocate work to worker event loops for execution.

Depending on the circumstances, it may be desirable to provide throttling arguments when constructing the 'worker' event loop, in order to enable backpressure to be supplied if the 'worker' event loop becomes overloaded: see the documentation on the make-event-loop procedure for further information about that. (This procedure calls event-post! in both the 'waiter' and 'worker' event loops by the respective threads of the other, so either could be subject to throttling.)

Exceptions may propagate out of this procedure if they arise while setting up, which shouldn't happen unless memory is exhausted or pthread has run out of resources. Exceptions arising during execution of the task, if not caught locally, will propagate out of the event-loop-run! procedure called for the 'worker' event loop.

This procedure is first available in version 0.4 of this library.

Here is an example of the use of await-task-in-event-loop!:

(set-default-event-loop!)     ;; if none has yet been set
(define worker (make-event-loop))
(event-loop-block! #t)        ;; because the task runs in another thread
(event-loop-block! #t worker)

(fork-thread
 (lambda ()
   (event-loop-run! worker)))

(a-sync (lambda (await resume)
	  (let ([res
		 (await-task-in-event-loop! await resume worker
					    (lambda ()
					      (+ 5 10)))])
	    (format #t "~a\n" res)
	    (event-loop-block! #f worker)
	    (event-loop-block! #f))))
(event-loop-run!)

(await-task! await resume [loop] thunk)

The 'loop' argument is optional. This is a convenience procedure for use with an event loop, which will run 'thunk' in the event loop specified by the 'loop' argument, or in the default event loop if no 'loop' argument is provided or #f is provided as the 'loop' argument. This procedure calls 'await' and will return the thunk's return value. It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). It is the single-threaded corollary of await-task-in-thread!. This means that (unlike with await-task-in-thread!) while 'thunk' is running other events in the event loop will not make progress, so blocking calls should not be made in 'thunk'.

This procedure can be used for the purpose of implementing co-operative multi-tasking. However, when 'thunk' is executed, this procedure is waiting on 'await', so 'await' and 'resume' cannot be used again in 'thunk' (although 'thunk' can call a-sync to start another series of asynchronous operations with a new await-resume pair). For that reason, await-yield! is usually more convenient for composing asynchronous tasks. In retrospect, this procedure offers little over await-yield!, apart from symmetry with await-task-in-thread!.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

This procedure calls event-post! in the event loop concerned. This is done in the same thread as that in which the event loop runs so it cannot of itself be throttled. However it may contribute to the number of accumulated unexecuted tasks in the event loop and therefore contribute to the throttling of other threads by the loop. See the documentation on the make-event-loop procedure for further information about that.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the task starts), which shouldn't happen unless memory is exhausted. Exceptions arising during execution of the task, if not caught locally, will propagate out of event-loop-run!.

Here is an example of the use of await-task!:

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
          (format #t "1 + 1 is ~A\n"
                  (await-task! await resume
                               (lambda ()
                                 (+ 1 1))))))
(event-loop-run!)

(await-yield! await resume [loop])

This is a convenience procedure which will surrender execution to the relevant event loop, so that code in other a-sync or compose-a-sync blocks can run. The remainder of the code after the call to await-yield! in the current a-sync or compose-a-sync block will execute on the next iteration through the loop. It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). It's effect is similar to calling await-task! with a task that does nothing.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the relevant event loop runs: for this purpose "the relevant event loop" is the event loop given by the 'loop' argument, or if no 'loop' argument is provided or #f is provided as the 'loop' argument, then the default event loop.

This procedure calls event-post! in the event loop concerned. This is done in the same thread as that in which the event loop runs so it cannot of itself be throttled. However it may contribute to the number of accumulated unexecuted tasks in the event loop and therefore contribute to the throttling of other threads by the loop. See the documentation on the make-event-loop procedure for further information about that.

This procedure should not raise any exceptions unless memory is exhausted.

This procedure is first available in version 0.9 of this library.

Here is an example of the use of await-yield!:

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
          (display "In first iteration through event loop\n")
          (await-yield! await resume)
          (display "In next iteration through event loop\n")))
(event-loop-run!)

(await-generator-in-thread! await resume [loop] generator proc [handler])

The 'loop' and 'handler' arguments are optional. The 'generator' argument is a procedure taking one argument, namely a yield argument (see the documentation on the make-iterator procedure for further details). This await-generator-in-thread! procedure will run 'generator' in its own worker thread, and whenever 'generator' yields a value will cause 'proc' to execute in the event loop specified by the 'loop' argument (or in the default event loop if no 'loop' argument is provided or if #f is provided as the 'loop' argument - pattern matching is used to detect the type of the third argument).

'proc' should be a procedure taking a single argument, namely the value yielded by the generator. If the optional 'handler' argument is provided, then that handler will be run in the event loop thread if 'generator' raises an exception; otherwise the program will terminate if an unhandled exception propagates out of 'generator'. 'handler' should take a single argument, which will be the raised condition object.

This procedure calls 'await' and will return when the generator has finished or, if 'handler' is provided, upon the generator raising an exception. This procedure will return #f if the generator completes normally, or 'chez-a-sync-thread-error if the generator raises an exception and 'handler' is run (the 'chez-a-sync-thread-error symbol is reserved to the implementation and should not be yielded by the generator).

This procedure is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). It will normally be necessary to call event-loop-block! before invoking this procedure.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs. As mentioned above, the generator itself will run in its own thread.

As the worker thread calls event-post!, it might be subject to throttling by the event loop concerned. See the documentation on the make-event-loop procedure for further information about that.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the worker thread starts), which shouldn't happen unless memory is exhausted or pthread has run out of resources. Exceptions arising during execution of the generator, if not caught by a handler procedure, will terminate the program. Exceptions raised by the handler procedure will propagate out of event-loop-run!. Exceptions raised by 'proc', if not caught locally, will also propagate out of event-loop-run!.

This procedure is first available in version 0.6 of this library.

Here is an example of the use of await-generator-in-thread!:

(set-default-event-loop!) ;; if none has yet been set
(event-loop-block! #t) ;; because the generator runs in another thread
(a-sync (lambda (await resume)
          (await-generator-in-thread! await resume
                                      (lambda (yield)
                                        (let loop ([count 0])
                                          (when (< count 5)
                                            (yield (* 2 count))
                                            (loop (+ count 1)))))
                                      (lambda (val)
                                        (display val)
                                        (newline)))
          (event-loop-block! #f)))
(event-loop-run!)

(await-generator-in-event-loop! await resume [waiter] worker generator proc)

The 'waiter' argument is optional. The 'worker' argument is an event loop running in a different thread than the one in which this procedure is called. The 'generator' argument is a procedure taking one argument, namely a yield argument (see the documentation on the make-iterator procedure for further details). This await-generator-in-event-loop! procedure will cause 'generator' to run in the 'worker' event loop, and whenever 'generator' yields a value this will cause 'proc' to execute in the event loop specified by the 'waiter' argument, or in the default event loop if no 'waiter' argument is provided or if #f is provided as the 'waiter' argument. 'proc' should be a procedure taking a single argument, namely the value yielded by the generator.

This procedure is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). It will normally be necessary to call event-loop-block! on 'waiter' (or on the default event loop) before invoking this procedure.

This procedure calls 'await' and will return when the generator has finished. It must (like the a-sync procedure) be called in the same thread as that in which the 'waiter' or default event loop runs (as the case may be).

This procedure acts, with await-task-in-event-loop!, as a form of channel through which two different event loops may communicate. It also offers a means by which a master event loop (the waiter or default event loop) may allocate work to worker event loops for execution.

Depending on the circumstances, it may be desirable to provide throttling arguments when constructing the 'worker' event loop, in order to enable backpressure to be supplied if the 'worker' event loop becomes overloaded: see the documentation on the make-event-loop procedure for further information about that. (This procedure calls event-post! in both the 'waiter' and 'worker' event loops by the respective threads of the other, so either could be subject to throttling.)

Exceptions may propagate out of this procedure if they arise while setting up, which shouldn't happen unless memory is exhausted or pthread has run out of resources. Exceptions arising during execution of the generator, if not caught locally, will propagate out of the event-loop-run! procedure called for the 'worker' event loop. Exceptions arising during the execution of 'proc', if not caught locally, will propagate out of the event-loop-run! procedure called for the 'waiter' or default event loop, as the case may be.

This procedure is first available in version 0.6 of this library.

Here is an example of the use of await-generator-in-event-loop!:

(set-default-event-loop!)     ;; if none has yet been set
(define worker (make-event-loop))
(event-loop-block! #t)        ;; because the generator runs in another thread
(event-loop-block! #t worker)

(fork-thread
 (lambda ()
   (event-loop-run! worker)))

(a-sync (lambda (await resume)
          (await-generator-in-event-loop! await resume worker
                                          (lambda (yield)
                                            (let loop ([count 0])
                                              (when (< count 5)
                                                (yield (* 2 count))
                                                (loop (+ count 1)))))
                                          (lambda (val)
                                            (display val)
                                            (newline)))
          (event-loop-block! #f worker)
          (event-loop-block! #f)))
(event-loop-run!)

(await-generator! await resume [loop] generator proc)

The 'loop' argument is optional. The 'generator' argument is a procedure taking one argument, namely a yield argument (see the documentation on the make-iterator procedure for further details). This await-generator! procedure will run 'generator', and whenever 'generator' yields a value will cause 'proc' to execute in the event loop specified by the 'loop' argument, or in the default event loop if no 'loop' argument is provided or #f is provided as the 'loop' argument. 'proc' should be a procedure taking a single argument, namely the value yielded by the generator. Each time 'proc' runs it will do so as a separate event in the event loop and so be multi-plexed with other events.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

This procedure is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). It is the single-threaded corollary of await-generator-in-thread!. This means that (unlike with await-generator-in-thread!) while 'generator' is running other events in the event loop will not make progress, so blocking calls (other than to the yield procedure) should not be made in 'generator'. This procedure can be useful for the purpose of implementing co-operative multi-tasking, say by composing tasks with compose-a-sync (see compose.scm).

When 'proc' executes, 'await' and 'resume' will still be in use by this procedure, so they may not be reused by 'proc' (even though 'proc' runs in the event loop thread).

This procedure calls event-post! in the event loop concerned. This is done in the same thread as that in which the event loop runs so it cannot of itself be throttled. However it may contribute to the number of accumulated unexecuted tasks in the event loop and therefore contribute to the throttling of other threads by the loop. See the documentation on the make-event-loop procedure for further information about that.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the task starts), which shouldn't happen unless memory is exhausted. Exceptions arising during execution of the generator, if not caught locally, will propagate out of event-loop-run!. Exceptions raised by 'proc', if not caught locally, will propagate out of event-loop-run!.

This procedure is first available in version 0.6 of this library.

Here is an example of the use of await-generator!:

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
          (await-generator! await resume
                            (lambda (yield)
                              (let loop ([count 0])
                                (when (< count 5)
                                  (yield (* 2 count))
                                  (loop (+ count 1)))))
                            (lambda (val)
                              (display val)
                              (newline)))))
(event-loop-run!)

(await-timeout! await resume [loop] msecs thunk)

This is a convenience procedure for use with an event loop, which will run 'thunk' in the event loop thread when the timeout expires. This procedure calls 'await' and will return the thunk's return value. It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). The timeout is single shot only - as soon as 'thunk' has run once and completed, the timeout will be removed from the event loop. The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

In practice, calling await-sleep! may often be more convenient for composing asynchronous code than using this procedure. That is because, when 'thunk' is executed, this procedure is waiting on 'await', so 'await' and 'resume' cannot be used again in 'thunk' (although 'thunk' can call a-sync to start another series of asynchronous operations with a new await-resume pair). In retrospect, this procedure offers little over await-sleep!.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), which shouldn't happen unless memory is exhausted. Exceptions raised by 'thunk', if not caught locally, will propagate out of event-loop-run!.

Here is an example of the use of event-timeout!:

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
          (format #t
                  "Timeout ~A\n"
                  (await-timeout! await resume
                                  100
                                  (lambda ()
                                    "expired")))))
(event-loop-run!)

(await-sleep! await resume [loop] msecs)

This is a convenience procedure which will suspend execution of code in the current a-sync or compose-a-sync block for the duration of 'msecs' milliseconds. The event loop will not be blocked by the sleep - instead any other events in the event loop (including any other a-sync or compose-a-sync blocks) will be serviced. It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

Calling this procedure is equivalent to calling await-timeout! with a 'proc' argument comprising a lambda expression that does nothing.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

This procedure should not raise any exceptions unless memory is exhausted.

This procedure is first available in version 0.9 of this library.

Here is an example of the use of await-sleep!:

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
	  (display "Entering sleep\n")
	  (await-sleep! await resume 500)
	  (display "Timeout expired\n")))
(event-loop-run!)

(a-sync-read-watch! resume file proc [loop])

This is a convenience procedure for use with an event loop, which will run 'proc' in the event loop thread whenever 'file' is ready for reading, and apply 'resume' (obtained from a call to a-sync) to the return value of 'proc'. 'file' can be a port or a file descriptor (and if it is a file descriptor, the revealed count is not incremented). 'proc' should take a single argument which will be set by the event loop to 'in or 'excpt (see the documentation on event-loop-add-read-watch! for further details). It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'resume' argument). The watch is multi-shot - it is for the user to bring it to an end at the right time by calling event-loop-remove-read-watch! in the waitable procedure. If 'file' is a buffered port, buffering will be taken into account in indicating whether a read can be made without blocking (but on a buffered port, for efficiency purposes each read operation in response to this watch should usually exhaust the buffer by looping on char-ready? or input-port-ready?, or by using chez scheme's various multi-byte/character reading procedures on non-blocking ports).

This procedure is mainly intended as something from which higher-level asynchronous file operations can be constructed, such as the await-readline! procedure. The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop

Because this procedure takes a 'resume' argument derived from the a-sync procedure, it must (like the a-sync procedure) in practice be called in the same thread as that in which the event loop runs.

This procedure should not raise an exception unless memory is exhausted. If 'proc' raises an exception, say because of port errors, and the exception is not caught locally, it will propagate out of event-loop-run!.

As an example of how to use a-sync-read-watch!, here is the implementation of await-getline!:

(define await-getline!
  (case-lambda
    [(await resume port) (await-getline! await resume #f port)]
    [(await resume loop port)
     (let ()
       (define chunk-size 128)
       (define text (make-string chunk-size))
       (define text-len 0)
       (define buf (make-string 1))
       (define (append-char! ch)
         (when (= text-len (string-length text))
           (let ([tmp text])
             (set! text (make-string (+ text-len chunk-size)))
             (string-copy! tmp 0 text 0 text-len)))
         (string-set! text text-len ch)
         (set! text-len (+ 1 text-len)))
       (a-sync-read-watch! resume
                           port
                           (lambda (status)
                             (if (eq? status 'excpt)
                                 #f
                                 (let next ([res (get-string-some! port buf 0 1)])
                                   (cond
                                    [(eqv? res 0)
                                     'more]
                                    [(eof-object? res)
                                     (if (= text-len 0)
                                         res
                                         (substring text 0 text-len))]
                                    [else
                                     (let ([ch (string-ref buf 0)])
                                       (if (char=? ch #\newline)
                                           (substring text 0 text-len)
                                           (begin
                                             (append-char! ch)
                                             (if (char-ready? port)
                                                 (next (get-string-some! port buf 0 1))
                                                 'more))))]))))
                           loop))
     (let next ((res (await)))
       (if (eq? res 'more)
           (next (await))
           (begin
             (event-loop-remove-read-watch! port loop)
             res)))]))

(await-getline! await resume [loop] port)

This is a convenience procedure for use with an event loop, which will start a read watch on 'port' for a line of input. It calls 'await' while waiting for input and will return the line of text received (without the terminating '\n' character). 'port' should be a textual port. The event loop will not be blocked by this procedure even if only individual characters are available at any one time (although if 'port' references a socket, it should be non-blocking for this to be guaranteed). It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments), and this procedure is implemented using a-sync-read-watch!. If an exceptional condition ('excpt) is encountered, #f will be returned. If an end-of-file object is encountered which terminates a line of text, a string containing the line of text will be returned (and if an end-of-file object is encountered without any text, the end-of-file object is returned rather than an empty string). The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

If this procedure is used with a port constructed by 'open-fd-input/output-port' for a non-seekable device such as a socket which has input buffering enabled, the 'clear-input-port' procedure should be applied to the port before 'close-port', 'close-output-port' or 'close-input-port' is applied to it, to avoid an illegal seek exception.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), which shouldn't happen unless memory is exhausted. With versions of this library before 0.11, any exceptions because of read errors would propagate out of event-loop-run! and could not be caught locally. Having read errors interfering with anything using the event loop in this way was not a good approach, so from version 0.11 of this library all read exceptions will propagate in the first instance out of this procedure so that they may be caught locally, say by putting a 'try' block around the call to this procedure, and only out of event-loop-run! if not caught in that way.

If a continuable exception propagates out of this procedure, it will be converted into a non-continuable one (continuable exceptions are incompatible with asynchronous event handling using this procedure and may break resource management which uses rethrows or dynamic winds).

Here is an example of the use of await-getline!:

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
          (display "Enter a line of text at the keyboard\n")
          (format #t
                  "The line was: ~A\n"
                  (await-getline! await resume
                                  (open-input-file "/dev/tty")))))
(event-loop-run!)

(await-geteveryline! await resume [loop] port proc)

This is a convenience procedure for use with an event loop, which will start a read watch on 'port' for lines of input. It calls 'await' while waiting for input and will apply 'proc' to every complete line of text received (without the terminating '\n' character). 'proc' should be a procedure taking a string as its only argument. 'port' should be a textual port.

The event loop will not be blocked by this procedure even if only individual characters are available at any one time (although if 'port' references a socket, it should be non-blocking for this to be guaranteed). It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments), and this procedure is implemented using a-sync-read-watch!. Unlike the await-getline! procedure, the watch will continue after a line of text has been received in order to receive further lines. The watch will not end until end-of-file or an exceptional condition ('excpt) is reached. In the event of that happening, this procedure will end and return an end-of-file object or #f respectively.

The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

If this procedure is used with a port constructed by 'open-fd-input/output-port' for a non-seekable device such as a socket which has input buffering enabled, the 'clear-input-port' procedure should be applied to the port before 'close-port', 'close-output-port' or 'close-input-port' is applied to it, to avoid an illegal seek exception.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

When 'proc' executes, 'await' and 'resume' will still be in use by this procedure, so they may not be reused by 'proc' (even though 'proc' runs in the event loop thread).

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), which shouldn't happen unless memory is exhausted. With versions of this library before 0.11, any exceptions because of read errors would propagate out of event-loop-run! and could not be caught locally. Having read errors interfering with anything using the event loop in this way was not a good approach, so from version 0.11 of this library all read exceptions will propagate in the first instance out of this procedure so that they may be caught locally, say by putting a 'try' block around the call to this procedure, and only out of event-loop-run! if not caught in that way. Exceptions raised by 'proc', if not caught locally, will also propagate out of event-loop-run!.

If a continuable exception propagates out of this procedure, it will be converted into a non-continuable one (continuable exceptions are incompatible with asynchronous event handling using this procedure and may break resource management which uses rethrows or dynamic winds).

Here is an example of the use of await-geteveryline! (because the keyboard has no end-of-file, use Ctrl-C to exit this code snippet):

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
          (display "Enter lines of text at the keyboard, ^C to finish\n")
          (let ([port (open-input-file "/dev/tty")])
            (await-geteveryline! await resume
                                 port
                                 (lambda (line)
                                   (format #t
                                           "The line was: ~A\n"
                                           line))))))
(event-loop-run!)

(await-getsomelines! await resume [loop] port proc)

This is a convenience procedure for use with an event loop, which does the same as await-geteveryline!, except that it provides a second argument to 'proc', namely an escape continuation which can be invoked by 'proc' to cause the procedure to return before end-of-file is reached. Behavior is identical to await-geteveryline! if the continuation is not invoked.

This procedure will start a read watch on 'port' for lines of input. It calls 'await' while waiting for input and will apply 'proc' to any complete line of text received (without the terminating '\n' character). 'proc' should be a procedure taking two arguments, a string as the first argument containing the line of text read, and an escape continuation as its second. 'port' should be a textual port.

The event loop will not be blocked by this procedure even if only individual characters are available at any one time (although if 'port' references a socket, it should be non-blocking for this to be guaranteed). It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). This procedure is implemented using a-sync-read-watch!. The watch will not end until end-of-file or an exceptional condition ('excpt) is reached, which would cause this procedure to end and return an end-of-file object or #f respectively, or until the escape continuation is invoked, in which case the value passed to the escape continuation will be returned.

The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

If this procedure is used with a port constructed by 'open-fd-input/output-port' for a non-seekable device such as a socket which has input buffering enabled, the 'clear-input-port' procedure should be applied to the port before 'close-port', 'close-output-port' or 'close-input-port' is applied to it, to avoid an illegal seek exception.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

When 'proc' executes, 'await' and 'resume' will still be in use by this procedure, so they may not be reused by 'proc' (even though 'proc' runs in the event loop thread).

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), which shouldn't happen unless memory is exhausted. With versions of this library before 0.11, any exceptions because of read errors would propagate out of event-loop-run! and could not be caught locally. Having read errors interfering with anything using the event loop in this way was not a good approach, so from version 0.11 of this library all read exceptions will propagate in the first instance out of this procedure so that they may be caught locally, say by putting a 'try' block around the call to this procedure, and only out of event-loop-run! if not caught in that way. Exceptions raised by 'proc', if not caught locally, will also propagate out of event-loop-run!.

If a continuable exception propagates out of this procedure, it will be converted into a non-continuable one (continuable exceptions are incompatible with asynchronous event handling using this procedure and may break resource management which uses rethrows or dynamic winds).

Here is an example of the use of await-getsomelines!:

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
          (display "Enter lines of text at the keyboard, enter an empty line to finish\n")
          (let ([port (open-input-file "/dev/tty")])
            (await-getsomelines! await resume
                                 port
                                 (lambda (line k)
                                   (when (string=? line "")
                                         (k #f))
                                   (format #t
                                           "The line was: ~A\n"
                                           line))))))
(event-loop-run!)

(await-getblock! await resume [loop] port size)

This is a convenience procedure for use with an event loop, which will start a read watch on 'port' for a block of data, such as a binary record, of size 'size'. It calls 'await' while waiting for input and will return a pair, normally comprising as its car a bytevector of length 'size' containing the data, and as its cdr the number of bytes received (which will be the same as 'size' unless an end-of-file object was encountered part way through receiving the data). 'port' should be a binary port.

The event loop will not be blocked by this procedure even if only individual bytes are available at any one time (although if 'port' references a socket, it should be non-blocking for this to be guaranteed). It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). This procedure is implemented using a-sync-read-watch!.

If an exceptional condition ('excpt) is encountered, a pair comprising (#f . #f) will be returned. As mentioned above, if an end-of-file object is encountered after receipt of some but not 'size' bytes, then a bytevector of length 'size' will be returned as car and the actual (lesser) number of bytes inserted in it as cdr. If an end-of-file object is encountered without any bytes of data, a pair with eof-object as car and #f as cdr will be returned.

The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

If this procedure is used with a port constructed by 'open-fd-input/output-port' for a non-seekable device such as a socket which has input buffering enabled, the 'clear-input-port' procedure should be applied to the port before 'close-port', 'close-output-port' or 'close-input-port' is applied to it, to avoid an illegal seek exception.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), which shouldn't happen unless memory is exhausted. With versions of this library before 0.11, any exceptions because of read errors would propagate out of event-loop-run! and could not be caught locally. Having read errors interfering with anything using the event loop in this way was not a good approach, so from version 0.11 of this library all read exceptions will propagate in the first instance out of this procedure so that they may be caught locally, say by putting a 'try' block around the call to this procedure, and only out of event-loop-run! if not caught in that way.

If a continuable exception propagates out of this procedure, it will be converted into a non-continuable one (continuable exceptions are incompatible with asynchronous event handling using this procedure and may break resource management which uses rethrows or dynamic winds).

This procedure is first available in version 0.8 of this library.


(await-geteveryblock! await resume [loop] port size proc)

This is a convenience procedure for use with an event loop, which will start a read watch on 'port' for blocks of data, such as binary records, of size 'size'. It calls 'await' while waiting for input and will apply 'proc' to any block of data received. 'proc' should be a procedure taking two arguments, first a bytevector of length 'size' containing the block of data read and second the size of the block of data placed in the bytevector. The value passed as the size of the block of data placed in the bytevector will always be the same as 'size' unless end-of-file has been encountered after receiving only a partial block of data. 'port' should be a binary port.

The event loop will not be blocked by this procedure even if only individual bytes are available at any one time (although if 'port' references a socket, it should be non-blocking for this to be guaranteed). It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). This procedure is implemented using a-sync-read-watch!. Unlike the await-getblock! procedure, the watch will continue after a complete block of data has been received in order to receive further blocks. The watch will not end until end-of-file or an exceptional condition ('excpt) is reached. In the event of that happening, this procedure will end and return an end-of-file object or #f respectively.

For efficiency reasons, this procedure passes its internal bytevector buffer to 'proc' as proc's first argument and, when 'proc' returns, re-uses it. Therefore, if 'proc' stores its first argument for use after 'proc' has returned, it should store it by copying it.

The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

If this procedure is used with a port constructed by 'open-fd-input/output-port' for a non-seekable device such as a socket which has input buffering enabled, the 'clear-input-port' procedure should be applied to the port before 'close-port', 'close-output-port' or 'close-input-port' is applied to it, to avoid an illegal seek exception.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

When 'proc' executes, 'await' and 'resume' will still be in use by this procedure, so they may not be reused by 'proc' (even though 'proc' runs in the event loop thread).

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), which shouldn't happen unless memory is exhausted. With versions of this library before 0.11, any exceptions because of read errors would propagate out of event-loop-run! and could not be caught locally. Having read errors interfering with anything using the event loop in this way was not a good approach, so from version 0.11 of this library all read exceptions will propagate in the first instance out of this procedure so that they may be caught locally, say by putting a 'try' block around the call to this procedure, and only out of event-loop-run! if not caught in that way. Exceptions raised by 'proc', if not caught locally, will also propagate out of event-loop-run!.

If a continuable exception propagates out of this procedure, it will be converted into a non-continuable one (continuable exceptions are incompatible with asynchronous event handling using this procedure and may break resource management which uses rethrows or dynamic winds).

This procedure is first available in version 0.8 of this library.


(await-getsomeblocks! await resume [loop] port size proc)

This is a convenience procedure for use with an event loop, which does the same as await-geteveryblock!, except that it provides a third argument to 'proc', namely an escape continuation which can be invoked by 'proc' to cause the procedure to return before end-of-file is reached. Behavior is identical to await-geteveryblock! if the continuation is not invoked.

This procedure will start a read watch on 'port' for blocks of data, such as binary records, of size 'size'. It calls 'await' while waiting for input and will apply 'proc' to any block of data received. 'proc' should be a procedure taking three arguments, first a bytevector of length 'size' containing the block of data read, second the size of the block of data placed in the bytevector and third an escape continuation. The value passed as the size of the block of data placed in the bytevector will always be the same as 'size' unless end-of-file has been encountered after receiving only a partial block of data. 'port' should be a binary port.

The event loop will not be blocked by this procedure even if only individual bytes are available at any one time (although if 'port' references a socket, it should be non-blocking for this to be guaranteed). It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments). This procedure is implemented using a-sync-read-watch!. The watch will not end until end-of-file or an exceptional condition ('excpt) is reached, which would cause this procedure to end and return an end-of-file object or #f respectively, or until the escape continuation is invoked, in which case the value passed to the escape continuation will be returned.

For efficiency reasons, this procedure passes its internal bytevector buffer to 'proc' as proc's first argument and, when 'proc' returns, re-uses it. Therefore, if 'proc' stores its first argument for use after 'proc' has returned, it should store it by copying it.

The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

If this procedure is used with a port constructed by 'open-fd-input/output-port' for a non-seekable device such as a socket which has input buffering enabled, the 'clear-input-port' procedure should be applied to the port before 'close-port', 'close-output-port' or 'close-input-port' is applied to it, to avoid an illegal seek exception.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

When 'proc' executes, 'await' and 'resume' will still be in use by this procedure, so they may not be reused by 'proc' (even though 'proc' runs in the event loop thread).

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), which shouldn't happen unless memory is exhausted. With versions of this library before 0.11, any exceptions because of read errors would propagate out of event-loop-run! and could not be caught locally. Having read errors interfering with anything using the event loop in this way was not a good approach, so from version 0.11 of this library all read exceptions will propagate in the first instance out of this procedure so that they may be caught locally, say by putting a 'try' block around the call to this procedure, and only out of event-loop-run! if not caught in that way. Exceptions raised by 'proc', if not caught locally, will also propagate out of event-loop-run!.

If a continuable exception propagates out of this procedure, it will be converted into a non-continuable one (continuable exceptions are incompatible with asynchronous event handling using this procedure and may break resource management which uses rethrows or dynamic winds).

This procedure is first available in version 0.8 of this library.


(a-sync-write-watch! resume file proc [loop])

This is a convenience procedure for use with an event loop, which will run 'proc' in the event loop thread whenever 'file' is ready for writing, and apply 'resume' (obtained from a call to a-sync) to the return value of 'proc'. 'file' can be a port or a file descriptor. 'proc' should take a single argument which will be set by the event loop to 'out (see the documentation on event-loop-add-write-watch! for further details). It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'resume' argument). The watch is multi-shot - it is for the user to bring it to an end at the right time by calling event-loop-remove-write-watch! in the waitable procedure. This procedure is mainly intended as something from which higher-level asynchronous file operations can be constructed. The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop

The documentation on the event-loop-add-write-watch! procedure explains why this procedure generally works best with an unbuffered or non-blocking port.

Because this procedure takes a 'resume' argument derived from the a-sync procedure, it must (like the a-sync procedure) in practice be called in the same thread as that in which the event loop runs.

This procedure should not raise an exception unless memory is exhausted. If 'proc' raises an exception, say because of port errors, and the exception is not caught locally, it will propagate out of event-loop-run!.

As an example of how to use a-sync-write-watch!, here is the implementation of await-put-bytevector!:

(define await-put-bytevector!
  (case-lambda
    [(await resume port bv) (await-put-bytevector! await resume #f port bv)]
    [(await resume loop port bv)
     (define length (bytevector-length bv))
     (define fd (port-file-descriptor port))
     (raise-exception-if-regular-file fd)

     (let ([index (c-write fd bv 0 length)])
       (when (< index length)
         (a-sync-write-watch! resume
                              port
                              (lambda (status)
                                (set! index (+ index (c-write fd
                                                              bv
                                                              index
                                                              (- length index))))
                                (if (< index length)
                                    'more
                                    #f))
                              loop)
         (let next ((res (await)))
           (if (eq? res 'more)
               (next (await))
               (event-loop-remove-write-watch! port loop)))))]))

(await-put-bytevector! await resume [loop] port bv)

This is a convenience procedure for use in an event loop, which will start a write watch on 'port' for writing the contents of a bytevector 'bv' to the port. It calls 'await' if it has to wait for output to become available. Provided 'port' is a non-blocking port, the event loop will not be blocked by this procedure even if only individual bytes can be written at any one time. It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments), and this procedure is implemented using a-sync-write-watch!. The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

'port' must be a non-blocking port, and may be a binary port or a textual port.

This procedure bypasses any output buffering in 'port' entirely, and sends the output to the underlying file descriptor directly. This is to enable this procedure to be used conveniently with sockets: in chez scheme ports can be constructed from file descriptors using the open-fd-input-port, open-fd-output-port and open-fd-input/output-port procedures, and the last of those would be useful for sockets except that chez scheme's port implementation has the infortunate feature that a port opened and used for both reading and writing via the port's buffers must be seekable (that is to say, its underlying file descriptor must have a file position pointer). For ports representing non-seekable read/write file descriptors such as sockets, this means that with any port other than a non-buffered binary port, an exception will arise if attempting to write to the port using R6RS procedures after it has previously been read from, unless an intervening call is made to clear-input-port between the last read and the first next write.

The fact that this procedure bypasses any output buffers enabled in 'port' means that any buffered output port which has previously been used for writing using chez scheme's R6RS write procedures must be flushed before this procedure is called; but the best thing is to carry out all writing to the port using this procedure or the await-put-string! procedure, in which case all is good. In the case of a socket port, this can be enforced by constructing the socket port with open-fd-input-port rather than open-fd-input/output-port.

One remaining point to watch out for is that clear-input-port must normally be called before an unseekable input/output port such as a socket port (that is, one which is unseekable and has been constructed with open-fd-input/output-port) is closed or otherwise flushed for output, otherwise the exception mentioned above might arise.

This procedure will raise a &i/o-write-error exception if passed a regular file with a file position pointer (prior to version 0.11 a &serious exception was raised): there should be no need to use this procedure with regular files, because they cannot normally block on write and are always signalled as ready.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), say because a regular file is passed to this procedure or memory is exhausted. In addition, any write error will cause a &i/o-write-error exception to be raised by this procedure. With versions of this library before 0.11, such write exceptions arising after the first write would propagate out of event-loop-run! and could not be caught locally. Having write exceptions (say, because of EPIPE) interfering with anything using the event loop in this way was not a good approach, so from version 0.11 of this library all write exceptions will propagate in the first instance out of this procedure so that they may be caught locally, say by putting a 'try' block around the call to this procedure.

If a continuable exception propagates out of this procedure, it will be converted into a non-continuable one (continuable exceptions are incompatible with asynchronous event handling using this procedure and may break resource management which uses rethrows or dynamic winds).

Here is how EPIPE might be tested for, using the 'try' form from the (a-sync try) library file (with linux and BSDs, the value of EPIPE is 32):

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
	  (try (await-put-bytevector! await resume port bv)
	       (except c
		       [(and (irritants-condition? c)
			     (= (cadr (condition-irritants c)) 32))
			... do something to cater for EPIPE ...]))))
(event-loop-run!)

Unlike the other await-* procedures in this library file, await-put-bytevector! and await-put-string! will not call 'await' if all the contents of the bytevector/string can be written immediately: instead, after writing this procedure would return straight away without invoking the event loop.

This procedure is first available in version 0.8 of this library.

An example of the use of await-put-string!, which is implemented using this procedure, can be found in the example-client.ss and example-server.ss files in the docs directory.


(await-put-string! await resume [loop] port text)

This is a convenience procedure for use in an event loop, which will start a write watch on 'port' for writing a string 'text' to the port. It calls 'await' if it has to wait for output to become available. Provided 'port' is a non-blocking port, the event loop will not be blocked by this procedure even if only individual characters or part characters can be written at any one time. It is intended to be called within a waitable procedure invoked by a-sync (which supplies the 'await' and 'resume' arguments), and this procedure is implemented using await-put-bytevector!. The 'loop' argument is optional: this procedure operates on the event loop passed in as an argument, or if none is passed (or #f is passed), on the default event loop.

'port' must be a non-blocking textual port.

This procedure bypasses any output buffering in 'port' entirely, and sends the output to the underlying file descriptor directly. This is to enable this procedure to be used conveniently with sockets: in chez scheme ports can be constructed from file descriptors using the open-fd-input-port, open-fd-output-port and open-fd-input/output-port procedures, and the last of those would be useful for sockets except that chez scheme's port implementation has the infortunate feature that a port opened and used for both reading and writing via the port's buffers must be seekable (that is to say, its underlying file descriptor must have a file position pointer). For ports representing non-seekable read/write file descriptors such as sockets, this means that with any port other than a non-buffered binary port, an exception will arise if attempting to write to the port using R6RS procedures after it has previously been read from, unless an intervening call is made to clear-input-port between the last read and the first next write.

The fact that this procedure bypasses any output buffers enabled in 'port' means that any textual output port which has previously been used for writing using chez scheme's R6RS write procedures must be flushed before this procedure is called; but the best thing is to carry out all writing to the port using this procedure or the await-put-bytevector! procedure, in which case all is good. In the case of a socket port, this can be enforced by constructing the socket port with open-fd-input-port rather than open-fd-input/output-port.

One remaining point to watch out for is that clear-input-port must normally be called before an unseekable input/output port such as a socket port (that is, one which is unseekable and has been constructed with open-fd-input/output-port) is closed or otherwise flushed for output, otherwise the exception mentioned above might arise.

This procedure will raise a &i/o-write-error exception if passed a regular file with a file position pointer (prior to version 0.11 a &serious exception was raised): there should be no need to use this procedure with regular files, because they cannot normally block on write and are always signalled as ready.

This procedure must (like the a-sync procedure) be called in the same thread as that in which the event loop runs.

Exceptions may propagate out of this procedure if they arise while setting up (that is, before the first call to 'await' is made), say because a regular file is passed to this procedure, memory is exhausted or a conversion error arises. In addition, any write error will cause a &i/o-write-error exception to be raised by this procedure. With versions of this library before 0.11, such write exceptions arising after the first write would propagate out of event-loop-run! and could not be caught locally. Having write exceptions (say, because of EPIPE) interfering with anything using the event loop in this way was not a good approach, so from version 0.11 of this library all write exceptions will propagate in the first instance out of this procedure so that they may be caught locally, say by putting a 'try' block around the call to this procedure.

If a continuable exception propagates out of this procedure, it will be converted into a non-continuable one (continuable exceptions are incompatible with asynchronous event handling using this procedure and may break resource management which uses rethrows or dynamic winds).

Here is how EPIPE might be tested for, using the 'try' form from the (a-sync try) library file (with linux and BSDs, the value of EPIPE is 32):

(set-default-event-loop!) ;; if none has yet been set
(a-sync (lambda (await resume)
	  (try (await-put-string! await resume port "test")
	       (except c
		       [(and (irritants-condition? c)
			     (= (cadr (condition-irritants c)) 32))
			... do something to cater for EPIPE ...]))))
(event-loop-run!)

Unlike the other await-* procedures in this library file, await-put-bytevector! and await-put-string! will not call 'await' if all the contents of the bytevector/string can be written immediately: instead, after writing this procedure would return straight away without invoking the event loop.

This procedure is first available in version 0.7 of this library.

An example of the use of this procedure can be found in the example-client.ss and example-server.ss files in the docs directory.


(c-write fd bv begin count)

This procedure is used by await-put-bytevector! (and so by await-put-string!) and is exported by event-loop.ss so that it can be used by other asynchronous procedures. It makes a block write directly to output, bypassing any output buffers, using unix write. Although it can be used with blocking file descriptors, it is mainly intended for use with asynchronous procedures which must not block and must write immediately without requiring a subsequent flush to do so (chez scheme's textual ports always implement some buffering and will not write without a flush, irrespective of their buffering status on construction).

This procedure provides a 'begin' parameter indicating the start of the sequence of bytes to be written, as an index. 'fd' is the file descriptor of the device to be written to, and it should normally be non-blocking (say, 'fd' is derived from a port to which set-port-nonblocking! has been applied with an argument of #t). 'bv' is a bytevector containing the bytes to be written. 'count' is the maximum number of bytes to be written. This procedure returns the number of bytes actually written, which can be less than 'count' bytes. The sum of 'begin' and 'count' must not be more than the length of the bytevector. The use of a separate 'begin' index enables the same bytevector to be written from repeatedly until all of it has been sent.

Provided 'fd' is non-blocking, this procedure returns straight away (so 0 may be returned if the file descriptor is not available for writing because of insufficient space). On a write error other than EAGAIN, EWOULDBLOCK or EINTR, a &i/o-write-error exception is raised which will give the errno number as an irritant (prior to version 0.11 a &serious exception was raised). EINTR is handled internally and is not an error.

This procedure is first available in version 0.8 of this library.


(make-pipe [read-buffer-mode [write-buffer-mode [transcoder]]])

This procedure makes a unix pipe using the system pipe() call. It is needed internally by the event-loop implementation, and is exported as part of the event-loop library in case it is useful to users.

This procedure returns two values, first a port for the read end of the pipe, and second a port for its write end. If creating the pipe gives rise to an error, a scheme exception of type &serious will be raised. The buffer mode arguments are optional (if not specified the read port will be block buffered and the write port will be unbuffered). The transcoder argument is also optional: if provided the ports will be textual ports, otherwise they will be binary ports. The ports are initially in blocking mode - use set-port-nonblocking! to change this if wanted. They also initially do not have FD_CLOEXEC set: use set-cloexec! below to change that.

Setting a buffer mode of 'none' is ineffective for textual ports.


(set-cloexec! port)

This procedure sets FD_CLOEXEC on the file descriptor underlying 'port'. It is mainly intended for use with pipes returned by make-pipe, but can be used with any port. It is used internally by the event-loop implementation, and is exported (along with make-pipe) as part of the event-loop library in case it is useful to users. It returns an unspecified value - it will always succeed unless 'port' has been closed.

This procedure is first available in version 0.20 of this library.


Additional await-connect-to-ipv4-host!, await-connect-to-ipv6-host!, await-connect-to-unix-host!, await-accept-ipv4-connection!, await-accept-ipv6-connection! and await-accept-unix-connection procedures are provided by the chez-simple-sockets package at https://github.com/ChrisVine/chez-simple-sockets . Examples of their use are in the example-client.ss and example-server.ss files in the docs directory. The (a-sync thread-pool) library file in this package also provides await-task-in-thread-pool and await-generator-in-thread-pool procedures.

Clone this wiki locally