Request: export process-events and preserve I/O callbacks' order
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
Ikarus Scheme |
New
|
Undecided
|
Unassigned |
Bug Description
Currently, in do-select, after ikrt_select returns, the pending list is iterated over again to see which of them select has said are ready. However, the new pending list (of those that weren't ready) which is created is left in reverse order (and then potentially reversed back and forth again and again); and when in-queue is used in process-events, it is reversed but shouldn't be. I think the order of pending needs to be preserved and in-queue not reversed so that the older registered callbacks get dispatched first. Otherwise, for a server like:
(let loop ()
(let-values ([(to from)
(register-
(lambda ()
---)))
(loop))
which has many connections ready, the callbacks for the continuations of the accepts are dispatched before any of the lambda callbacks to handle a new client are, thereby starving these clients. Maybe this isn't the best example, but without preserving the order of callback registration in the order of dispatching, I worry unfair starvation situations will arise. Or am I missing something?
Attached is a tiny patch to preserve the order.
Also, a related question: what is the purpose of out-queue in process-events? Couldn't it be omitted and in-queue itself used?
Below are an example server and client I made to investigate this. For this server design, if there are connections always backlogged, starvation of clients really comes down to whether or not accept returns EAGAIN; if it does not, it won't capture its continuation and defer to process-events (I see long spurts of this happening with the below example); if none of the accepts return EAGAIN, it will just keep registering callbacks until it runs out of file-descriptors.
I think this is a good reason to both preserve callbacks order and to use a different design for servers: use register-callback to register server-sockets (already implemented in ikarus.io.ss) as well as client sockets and always defer to process-events, and re-register the server-socket after a new client's lambda is registered and then loop back to calling process-events, and with callbacks order preserved, clients' and the server's events will be serviced in the order they get registered, ensuring that, even if there are connections always backlogged, the events will be fairly serviced (I think). I think this will require exporting process-events. I think I'll make a test of this to see what happens...
;;;; Server ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;
tcp-server- socket- nonblocking
accept- connection- nonblocking
register- callback
#!/usr/bin/env scheme-script
(import
(rnrs)
(only (ikarus)
printf))
(define listen-port 12345) socket- nonblocking listen-port))
(define server-socket (tcp-server-
(let loop ([i 0])
(accept- connection- nonblocking server-socket)]) callback to
(close- output- port to)
(close- input-port from)))) ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;
(let-values ([(to from)
(printf "Got new connection ~a from ~a\n" i to)
(register-
(lambda ()
(printf "Servicing connection ~a\n" i)
(loop (+ 1 i)))
;;;;;;;
;;;; Client ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;
#!/usr/bin/env scheme-script
(import
(rnrs)
(only (ikarus) tcp-connect))
(define server-address "127.0.0.1")
(define server-port "12345")
(let loop () output- port to) input-port from)) ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;;;;;; ;;
(let-values ([(to from) (tcp-connect server-address server-port)])
(close-
(close-
(loop))
;;;;;;;