Hi Alex,
> The drawback of implementing such a fully non-blocking system will
> be that the present separation of event generation (select) and data
> processing (read) cannot be held up any longer, and a completely
> different application flow is required.
yes, indeed.
I attach a simple non-blocking echo server. There are some functions
in C just to get the C read & write functions and some blocking stuff
up to the picolisp level. Then the echo server waits for events on a
socket and writes back what it read.
Conceptually it should not block, I am not sure how to test it though.
Any ideas?
There are some things that could be done better, like:
- the buffer could be circular and support for this could be in the C
functions rdx and wrx.
- buffer could be local for the sockets, so 'callback' should be a
closure... I guess I would have to use 'job' but that's next lesson
I have to look at:-)
It might be nice to build some kind of abstraction above this low
level, non-blocking code. Maybe to implement continuations to
abstract away the event driven code. Not sure how difficult it would
be in picolisp.
There should really be -m32 switch in the 'gcc' function. It would
also be useful if it would be possible optionally switch on -g without
having to modify the gcc.l file.
Cheers,
Tomas
(load "lib/gcc.l")
(gcc "nb" NIL 'eagain 'block 'rdx 'wrx)
//(eagain) -> 'cnt
any eagain(any ex __attribute__((unused))) {
return boxCnt(EAGAIN);
}
//(block 'any 'flg) -> 'flg
any block(any ex) {
int sd = (int)evCnt(ex,cdr(ex));
any y = EVAL(caddr(ex));
bool flg = isNil(y) ? NO : YES;
blocking(flg, ex, sd);
return y;
}
//(rdx 'lst 'cnt ['cnt]) -> 'cnt|NIL
any rdx(any ex) {
any lst = EVAL(cadr(ex));
int cnt = (int)evCnt(ex,cddr(ex));
int off = isNil(cadddr(ex)) ? 0 : (int)evCnt(ex,cdddr(ex));
int i = 0;
int j = 0;
NeedLst(ex,lst);
byte buf[cnt];
int n = read(InFile->fd, buf, cnt);
if (0 < n) {
for (; j < off && isCell(lst); lst = cdr(lst), j++);
for (; i < n && i < cnt && isCell(lst); lst = cdr(lst), i++) {
lst->car = boxCnt(buf[i]);
}
}
return n == 0 ? Nil : boxCnt(n < 0 ? -errno : i);
}
//(wrx 'lst 'cnt ['cnt]) -> 'cnt|NIL
any wrx(any ex) {
any lst = EVAL(cadr(ex));
int cnt = (int)evCnt(ex,cddr(ex));
int off = isNil(cadddr(ex)) ? 0 : (int)evCnt(ex,cdddr(ex));
int i = 0;
int j = 0;
NeedLst(ex,lst);
byte buf[cnt];
for (; j < off && isCell(lst); lst = cdr(lst), j++);
for (; i < cnt && isCell(lst); lst = cdr(lst), i++) {
buf[i] = (byte)evCnt(ex,lst);
}
int n = write(OutFile->fd, buf, i);
return n == 0 ? Nil : boxCnt(n < 0 ? -errno : n);
}
/**/
(cd (pack (sys "HOME") "/picolisp"))
(load (pack (sys "HOME") "/src/picolisp/nb.l"))
# (out "/tmp/a" (wrx '(1 2 3 4) 4))
# (out "/tmp/a" (wrx '(1 2 3 4) 3 1))
# (setq *B (need 5))
# (in "/tmp/a" (rdx *B 3))
# (in "/tmp/a" (rdx *B 2 3))
# *B
# non-blocking echo server
(setq *N 5) # try bigger buffer;-)
(setq *B (need *N))
(setq *I 0)
(setq *J 0)
(set 'EAGAIN (eagain))
(de _rdx (Sock)
(in Sock
(let? N (rdx *B (- *N *I) *I)
(when (gt0 N)
(inc '*I N))
N)))
(de _wrx (Sock)
(out Sock
(let? N (wrx *B (- *I *J) *J)
(when (gt0 N)
(inc '*J N))
N)))
(de callback (Sock)
(let End NIL
(prinl "callback " Sock " J=" *J " I=" *I " N=" *N)
(block Sock NIL) # first time would be enough
(unless End
(let N (_rdx Sock)
(prinl " read " N)
(unless (or (gt0 N) (= N 'EAGAIN))
(setq End (cons rd N)))))
(unless End
(let N (_wrx Sock)
(prinl " written " N)
(unless (or (gt0 N) (= N 'EAGAIN))
(setq End (cons wr N)))))
(when End
(prinl " finish")
(task Sock)
(close Sock))
(when (<= *I *J)
(prinl " rotate J=" *J " I=" *I " N=" *N)
(setq *I 0)
(setq *J 0))
(prinl "end " Sock " J=" *J " I=" *I " N=" *N)))
(task (port 4444) # Listen on port 4444
(when (accept @) # A connect arrived
(task @ # Install another task on this socket
Sock @ # Keep the socket in the task's env
(callback Sock) ) ) )