Skip to content

Commit

Permalink
Handling selector timeout properly
Browse files Browse the repository at this point in the history
Means what I think how it should work
Make (net socket) test robust, at least shouldn't hang
  • Loading branch information
ktakashi committed Jan 22, 2025
1 parent 016381f commit 2c1bea8
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 27 deletions.
27 changes: 13 additions & 14 deletions ext/socket/socket-selector.incl
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,11 @@ static SgObject earliest_timeout(SgObject sockets, SgTime *start,

static struct timespec * update_timeout(SgObject sockets,
SgTime *start,
struct timespec *global,
struct timespec *out,
SgObject *timedout)
{
SgObject to = earliest_timeout(sockets, start, timedout);
struct timespec *r = global;
struct timespec *r = NULL;

if (!SG_FALSEP(to)) {
long sec = SG_TIME(to)->sec - start->sec;
Expand All @@ -218,15 +217,7 @@ static struct timespec * update_timeout(SgObject sockets,
if (sec > 0 || (sec == 0 && nsec > 0)) {
out->tv_sec = (sec > 0) ? sec : 0;
out->tv_nsec = (nsec > 0) ? nsec : 0;

if (global) {
if (global->tv_sec > out->tv_sec ||
(global->tv_sec = out->tv_sec && global->tv_nsec > out->tv_nsec)) {
r = out;
}
} else {
r = out;
}
r = out;
}
}
return r;
Expand All @@ -238,7 +229,7 @@ SgObject Sg_SocketSelectorWait(SgSocketSelector *selector, SgObject timeout)
SgTime start;
int n;
unsigned long sec, usec;
struct timespec spec, *sp, sock_to;
struct timespec spec, *sp, sock_to, *sto;

if (Sg_SocketSelectorClosedP(selector)) {
Sg_Error(UC("Socket selector is closed: %A"), selector);
Expand All @@ -256,7 +247,12 @@ SgObject Sg_SocketSelectorWait(SgSocketSelector *selector, SgObject timeout)
start.nsec = usec * 1000;

Sg_LockMutex(&selector->lock);
sp = update_timeout(selector->sockets, &start, sp, &sock_to, &timedout);
/* compute socket timeout */
sto = update_timeout(selector->sockets, &start, &sock_to, &timedout);
/* replace if exists */
if (sto) {
sp = sto;
}
strip_sockets(selector, timedout);
n = selector_sockets(selector);

Expand Down Expand Up @@ -293,7 +289,10 @@ SgObject Sg_SocketSelectorWait(SgSocketSelector *selector, SgObject timeout)
start.sec = sec;
start.nsec = nsec;
}
sp = update_timeout(selector->sockets, &start, sp, &sock_to, &timedout);
sto = update_timeout(selector->sockets, &start, &sock_to, &timedout);
if (sto) {
sp = sto;
}
strip_sockets(selector, timedout);
n = selector_sockets(selector);

Expand Down
9 changes: 6 additions & 3 deletions src/os/win/win_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,17 @@ static SgString* utf16ToUtf32WithRegion(wchar_t *s, wchar_t *e)
static SgObject get_last_error(DWORD e)
{
#define MSG_SIZE 128
wchar_t msg[MSG_SIZE];
int size = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
wchar_t msg[MSG_SIZE] = {0,};
int size = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
0,
e,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
msg,
MSG_SIZE,
0,
NULL);
if (size == 0) return SG_STRING("*no error message available*");

if (size > 2 && msg[size - 2] == '\r') {
msg[size - 2] = 0;
size -= 2;
Expand Down
30 changes: 20 additions & 10 deletions test/tests/net/socket.scm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
(srfi :18)
(srfi :19)
(srfi :64)
(sagittarius atomic)
(sagittarius threads)
(util concurrent)
(rename (sagittarius crypto keys)
Expand Down Expand Up @@ -202,6 +203,7 @@
(let ()
(define count 100)
(define (run-socket-selector hard-timeout soft-timeout)
(define ready-sockets (make-atomic-fixnum 0))
(define server-sock (make-server-socket "0"))
(define mark #vu8(0))
(define (echo sock e reuse-invoker)
Expand All @@ -225,6 +227,7 @@
(let loop ()
(let ((sock (socket-accept server-sock)))
(when sock
(atomic-fixnum-inc! ready-sockets)
(socket-selector sock echo soft-timeout)
(loop))))
(terminater)))))))
Expand All @@ -241,7 +244,6 @@
(msg (string->utf8
(string-append "Hello world " (number->string i)))))
(guard (e (else #;(print e) s))
(thread-sleep! 0.1)
(socket-send s msg)
(let ((v (socket-recv s 255)))
(cond ((bytevector=? v mark) (shared-queue-put! result #f))
Expand All @@ -258,18 +260,24 @@
(define (safe-close s)
(cond ((socket? s) (socket-close s))
((condition? s) (report-error s))))

(for-each safe-close (map safe-join! (map caller (iota count))))

(let ((t* (map caller (iota count))))
(do () ((= count (atomic-fixnum-load ready-sockets))))
(for-each safe-close (map safe-join! t*)))

(socket-shutdown server-sock SHUT_RDWR)
(socket-close server-sock)

(guard (e (else #t)) (thread-join! server-thread))

(shared-queue->list result))
(test-equal count (length (filter string? (run-socket-selector 1000 #f))))
(test-assert (not (= count (length
(filter string? (run-socket-selector 10 #f))))))
(test-equal count (length (filter string? (run-socket-selector 10 1000))))
(let ((r (run-socket-selector 1000 #f)))
(test-equal "no soft, hard = 1000ms" count (length (filter string? r))))
(let ((r (run-socket-selector 10 #f)))
(test-assert "no soft, hard = 10ms"
(not (= count (length (filter string? r))))))
(let ((r (run-socket-selector 10 1000)))
(test-equal "soft = 1000ms, hard = 10ms" count (length (filter string? r))))
)

(let ()
Expand Down Expand Up @@ -313,18 +321,20 @@
(string-append "Hello world " (number->string i))))
(selector s push-result soft-timeout)))
(define (collect-thread)
(do ((i 0 (+ i 1)) (r '() (cons (shared-queue-get! result) r)))
;; should wait 1s but if we do, it'd be max 100s per test
;; in theory, 300ms should be enough per socket
(do ((i 0 (+ i 1)) (r '() (cons (shared-queue-get! result 0.5 #f) r)))
((= i count) r)))

(let-values (((selector terminator) (make-socket-selector hard-timeout)))
(for-each (caller selector) (iota count))
(let ((r (map thread-join! (collect-thread))))
(let ((r (map (lambda (t?) (and t? (thread-join! t?))) (collect-thread))))
(terminator)
r)))

(test-equal "hard 1000ms soft #f"
count (length (filter string? (run-socket-selector 1000 #f))))
(test-equal "hard 0ms soft #f"
(test-equal "hard 100ms soft #f"
0 (length (filter string? (run-socket-selector 100 #f))))
(test-equal "hard 10ms soft 1000ms"
count (length (filter string? (run-socket-selector 10 1000))))
Expand Down

0 comments on commit 2c1bea8

Please sign in to comment.