Hi all, attached several patches I owe you.
Patches 0001-0004 should apply on chicken 4.13, i.e. 68eeaaef3fc. Patches 0005 and 0006 are against master (9f55823852). Patches 0007 and 0008 are against srfi-18 egg source. 0002 - Fix 1564: This establishes the invariant that slot #11, the blocking object and thread being in the relevant blocking queue are always in sync. This eases reasoning about the code and fixes the bug. Added advantage: Having a single spot to clear blocking objects might allow eggs to introduce additional objects to block on. 0003 - Test case regarding abandoned mutex handling: This warrents doube checking wrt. srfi-18. The test checks for my understanding of srfi-18 and fails without 0004 applied. 0004- Change abandoned mutexs state according to srfi-18: This passes the test cases added by 0003. Additionally it establishes a similar invariant as 0002 wrt. slot #4 and the timeout queue. Contains consistency checks to be phased out after testing. Best /Jörg
From 7288c18082a6334be0548e2e23ca13921f99076f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= <joerg.wittenber...@softeyes.net> Date: Tue, 18 Dec 2018 14:26:03 +0100 Subject: [PATCH 2/4] Add test case catching #1564 almost for sure. --- tests/mutex-test.scm | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/mutex-test.scm b/tests/mutex-test.scm index 873e812c..738e73d3 100644 --- a/tests/mutex-test.scm +++ b/tests/mutex-test.scm @@ -55,6 +55,29 @@ Slot Type Meaning (print "Got " mux1 " state " (mutex-state mux1) " expected " owner1 "\n") (test-exit 1))))) +(let ((m1 (make-mutex))) + ;; This fails if we manage to sort primorial before t1 and unleash + ;; both in one turn. + (define (sys-thread-sleep! limit) + ;; a copy from srfi-18 which expects pre-computed goal time. + (##sys#call-with-current-continuation + (lambda (return) + (let ((ct ##sys#current-thread)) + (##sys#setslot ct 1 (lambda () (return (##core#undefined)))) + (##sys#thread-block-for-timeout! ct limit) + (##sys#schedule) ) ) ) ) + #;(print "mutex state changes atomically wrt. blocking queues") + (mutex-lock! m1) + (let ((t1 (thread-start! (lambda () (mutex-lock! m1 0.1))))) + #;(print "have t1 it wait for m1") + (thread-yield!) + (let* ((to (##sys#slot t1 4)) + (hit (- to 0.0001))) + #;(print "waiting ever so slightly less than to " to " i.e. " hit "\n") + (sys-thread-sleep! hit)) + ;; catch inconsistent state + (mutex-unlock! m1))) + (set! mux1 (make-mutex 'unlock-leaves-no-memory-leak)) (mutex-lock! mux1) (mutex-unlock! mux1) -- 2.11.0
From b6837b2c94feb5f8348965f538b5a45bf01a7506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= <joerg.wittenber...@softeyes.net> Date: Mon, 3 Dec 2018 21:06:26 +0100 Subject: [PATCH 1/4] Fix 1564 internal scheduler error. --- scheduler.scm | 80 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/scheduler.scm b/scheduler.scm index 0b292f7f..a1a03293 100644 --- a/scheduler.scm +++ b/scheduler.scm @@ -34,7 +34,7 @@ ;; This isn't hidden ATM to allow set!ing it as a hook/workaround ; ##sys#force-primordial fdset-set fdset-test create-fdset stderr - ##sys#clear-i/o-state-for-thread! ##sys#abandon-mutexes) + ##sys#thread-clear-blocking-state! ##sys#abandon-mutexes) (not inline ##sys#interrupt-hook ##sys#force-primordial) (unsafe) (foreign-declare #<<EOF @@ -185,7 +185,7 @@ EOF (if (fp>= now tmo1) ; timeout reached? (begin (##sys#setislot tto 13 #t) ; mark as being unblocked by timeout - (##sys#clear-i/o-state-for-thread! tto) + (##sys#thread-clear-blocking-state! tto) (##sys#thread-basic-unblock! tto) (loop (cdr lst)) ) (begin @@ -335,17 +335,9 @@ EOF (define (##sys#thread-kill! t s) (dbg "killing: " t " -> " s ", recipients: " (##sys#slot t 12)) (##sys#abandon-mutexes t) - (let ((blocked (##sys#slot t 11))) - (cond - ((##sys#structure? blocked 'condition-variable) - (##sys#setslot blocked 2 (##sys#delq t (##sys#slot blocked 2)))) - ((##sys#structure? blocked 'thread) - (##sys#setslot blocked 12 (##sys#delq t (##sys#slot blocked 12))))) ) (##sys#remove-from-timeout-list t) - (##sys#clear-i/o-state-for-thread! t) + (##sys#thread-clear-blocking-state! t) (##sys#setslot t 3 s) - (##sys#setislot t 4 #f) - (##sys#setislot t 11 #f) (##sys#setislot t 8 '()) (let ((rs (##sys#slot t 12))) (unless (null? rs) @@ -353,13 +345,15 @@ EOF (lambda (t2) (dbg " checking: " t2 " (" (##sys#slot t2 3) ") -> " (##sys#slot t2 11)) (when (eq? (##sys#slot t2 11) t) - (##sys#thread-basic-unblock! t2) ) ) - rs) ) ) - (##sys#setislot t 12 '()) ) + (##sys#thread-unblock! t2) ) ) + rs) + (##sys#setislot t 12 '()) ) ) ) (define (##sys#thread-basic-unblock! t) (dbg "unblocking: " t) - (##sys#setislot t 11 #f) ; (FD . RWFLAGS) | #<MUTEX> | #<THREAD> + #;(if (##sys#slot t 11) ;; remove this case after testing + (##sys#error '##sys#thread-basic-unblock! "Internal scheduler error: unclean unblock" + (##sys#slot t 11))) (##sys#setislot t 4 #f) (##sys#add-to-ready-queue t) ) @@ -489,39 +483,20 @@ EOF ;; is incorrect but will be ignored, just let it run (when (##sys#slot t 4) ; also blocked for timeout? (##sys#remove-from-timeout-list t)) - (##sys#thread-basic-unblock! t) + (##sys#thread-clear-blocking-state! t) + (##sys#thread-basic-unblock! t) (loop2 (cdr threads) keep)) ((not (eq? fd (car p))) (panic (sprintf "thread is registered for I/O on unknown file-descriptor: ~S (expected ~S)" (car p) fd))) ((fdset-test inf outf (cdr p)) (when (##sys#slot t 4) ; also blocked for timeout? (##sys#remove-from-timeout-list t)) + (##sys#thread-clear-blocking-state! t) (##sys#thread-basic-unblock! t) (loop2 (cdr threads) keep)) (else (loop2 (cdr threads) (cons t keep))))))) (cons a (loop n (add1 pos) (cdr lst))) ) ) ) ) ) ] ))) ) - -;;; Clear I/O state for unblocked thread - -(define (##sys#clear-i/o-state-for-thread! t) - (when (pair? (##sys#slot t 11)) - (let ((fd (car (##sys#slot t 11)))) - (set! ##sys#fd-list - (let loop ((lst ##sys#fd-list)) - (if (null? lst) - '() - (let* ((a (car lst)) - (fd2 (car a)) ) - (if (eq? fd fd2) - (let ((ts (##sys#delq t (cdr a)))) ; remove from fd-list entry - (cond ((null? ts) (cdr lst)) - (else - (##sys#setslot a 1 ts) ; fd-list entry is list with t removed - lst) ) ) - (cons a (loop (cdr lst))))))))))) - - ;;; Get list of all threads that are ready or waiting for timeout or waiting for I/O: ; ; (contributed by Joerg Wittenberger) @@ -565,6 +540,34 @@ EOF (set! ##sys#fd-list (##sys#slot vec 2)) (set! ##sys#timeout-list (##sys#slot vec 3)) ) +;;; Clear blocking queues + +(define (##sys#thread-clear-blocking-state! t) + (let ((blocked (##sys#slot t 11))) ; (FD . RWFLAGS) | #<MUTEX> | #<THREAD> + (dbg "clear-blocking " t " from " blocked) + (cond + ((pair? blocked) + (let ((fd (car (##sys#slot t 11)))) + (set! ##sys#fd-list + (let loop ((lst ##sys#fd-list)) + (if (null? lst) + '() + (let* ((a (car lst)) + (fd2 (car a)) ) + (if (eq? fd fd2) + (let ((ts (##sys#delq t (cdr a)))) ; remove from fd-list entry + (cond ((null? ts) (cdr lst)) + (else + (##sys#setslot a 1 ts) ; fd-list entry is list with t removed + lst) ) ) + (cons a (loop (cdr lst)))))))))) + ((##sys#structure? blocked 'condition-variable) + (##sys#setslot blocked 2 (##sys#delq t (##sys#slot blocked 2)))) + ((##sys#structure? blocked 'mutex) + (##sys#setslot blocked 3 (##sys#delq t (##sys#slot blocked 3)))) + ((##sys#structure? blocked 'thread) + (##sys#setslot blocked 12 (##sys#delq t (##sys#slot blocked 12))))) + (##sys#setislot t 11 #f))) ;;; Unblock thread cleanly: @@ -572,10 +575,9 @@ EOF (when (or (eq? 'blocked (##sys#slot t 3)) (eq? 'sleeping (##sys#slot t 3))) (##sys#remove-from-timeout-list t) - (##sys#clear-i/o-state-for-thread! t) + (##sys#thread-clear-blocking-state! t) (##sys#thread-basic-unblock! t) ) ) - ;;; Kill all threads in fd-, io- and timeout-lists and assign one thread as the ; new primordial one. Overrides "##sys#kill-other-threads" in library.scm. -- 2.11.0
From aae2f95d0452ce7908eb281bbde90b22b8329429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= <joerg.wittenber...@softeyes.net> Date: Tue, 18 Dec 2018 15:46:46 +0100 Subject: [PATCH 3/4] Add test case for properly abondons of mutexs. --- tests/mutex-test.scm | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/mutex-test.scm b/tests/mutex-test.scm index 738e73d3..035d7092 100644 --- a/tests/mutex-test.scm +++ b/tests/mutex-test.scm @@ -86,6 +86,27 @@ Slot Type Meaning (test-error "thread still held in mutex after unlock: " mux1)) ;;============ +(let* ((cv (make-condition-variable)) + (m (begin + (condition-variable-specific-set! cv #f) + (make-mutex))) + (t (thread-start! + (lambda () + (do () + ((condition-variable-specific cv)) + (mutex-unlock! m cv)))))) + (thread-yield!) + (when + (not (eq? (##sys#slot t 3) 'sleeping)) + (test-error "thread not sleeping " t)) + (condition-variable-specific-set! cv #t) + (condition-variable-signal! cv) + (thread-yield!) + (when + (not (eq? (##sys#slot t 3) 'dead)) + (test-error "thread not completed " t))) + +;;============ ; Make a locked mutex (define mux (make-mutex 'foo)) (mutex-lock! mux #f #f) @@ -131,6 +152,23 @@ Slot Type Meaning (print "Abandoned Mutex not abandoned " mux "\n") (test-exit 1)) +(unless (eq? (mutex-state mux) (current-thread)) + (print "Mutex " mux " locked/not-owned but left in state " (mutex-state mux) "\n") + (test-exit 1)) + +;; repeat with owned mutex +(set! mux (make-mutex 'foobar)) +(thread-start! (lambda () (mutex-lock! mux))) +(thread-yield!) + +(when (not (handle-exceptions ex (abandoned-mutex-exception? ex) (and (mutex-lock! mux) #f))) + (print "Abandoned Mutex not abandoned " mux "\n") + (test-exit 1)) + +(unless (eq? (mutex-state mux) (current-thread)) + (print "Mutex " mux " not assigned to " (current-thread) " but left in state " (mutex-state mux) "\n") + (test-exit 1)) + (mutex-unlock! mux) (mutex-lock! mux) -- 2.11.0
From a062862de2acbaf4059f4898971a5285099d7211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= <joerg.wittenber...@softeyes.net> Date: Tue, 18 Dec 2018 22:40:34 +0100 Subject: [PATCH 4/4] Change abandoned mutexs state according to srfi-18. Also some cleanup prefering ##sys#thread-unblock! when appropriate. --- scheduler.scm | 53 +++++++++++++++------------ srfi-18.scm | 113 ++++++++++++++++++++-------------------------------------- 2 files changed, 69 insertions(+), 97 deletions(-) diff --git a/scheduler.scm b/scheduler.scm index c4b79f46..de008a1d 100644 --- a/scheduler.scm +++ b/scheduler.scm @@ -185,6 +185,7 @@ EOF (if (fp>= now tmo1) ; timeout reached? (begin (##sys#setislot tto 13 #t) ; mark as being unblocked by timeout + (##sys#setislot tto 4 #f) ; clear timeout (##sys#thread-clear-blocking-state! tto) (##sys#thread-basic-unblock! tto) (loop (cdr lst)) ) @@ -277,16 +278,20 @@ EOF (define ##sys#timeout-list '()) (define (##sys#remove-from-timeout-list t) - (let loop ((l ##sys#timeout-list) (prev #f)) - (if (null? l) - l - (let ((h (##sys#slot l 0)) - (r (##sys#slot l 1))) - (if (eq? (##sys#slot h 1) t) - (if prev - (set-cdr! prev r) - (set! ##sys#timeout-list r)) - (loop r l)))))) + (define (removeit t) + (let loop ((l ##sys#timeout-list) (prev #f)) + (if (null? l) + l + (let ((h (##sys#slot l 0)) + (r (##sys#slot l 1))) + (if (eq? (##sys#slot h 1) t) + (if prev + (set-cdr! prev r) + (set! ##sys#timeout-list r)) + (loop r l)))))) + (when (##sys#slot t 4) ;; no need to walk the queue without timeout + (removeit t) + (##sys#setislot t 4 #f))) ;; keep queue and thread state lexically in sync (define (##sys#thread-block-for-timeout! t tm) (dbg t " blocks for timeout " tm) @@ -351,11 +356,12 @@ EOF (define (##sys#thread-basic-unblock! t) (dbg "unblocking: " t) - (##sys#setslot t 11 #f) ;; still require from condition-variable-*! - #;(if (##sys#slot t 11) ;; remove this case after testing - (##sys#error '##sys#thread-basic-unblock! "Internal scheduler error: unclean unblock" + (if (##sys#slot t 11) ;; remove this case after testing + (##sys#error '##sys#thread-basic-unblock! "Internal scheduler error: unblock with block object" + (##sys#slot t 11))) + (if (##sys#slot t 4) ;; remove this case after testing + (##sys#error '##sys#thread-basic-unblock! "Internal scheduler error: unblock with timeout" (##sys#slot t 11))) - (##sys#setislot t 4 #f) (##sys#add-to-ready-queue t) ) (define (##sys#default-exception-handler arg) @@ -482,16 +488,14 @@ EOF ((not (pair? p)) ; not blocked for I/O? ;; thread on fd-list is not blocked for I/O - this ;; is incorrect but will be ignored, just let it run - (when (##sys#slot t 4) ; also blocked for timeout? - (##sys#remove-from-timeout-list t)) + (##sys#remove-from-timeout-list t) ; also blocked for timeout? (##sys#thread-clear-blocking-state! t) (##sys#thread-basic-unblock! t) (loop2 (cdr threads) keep)) ((not (eq? fd (car p))) (panic (sprintf "thread is registered for I/O on unknown file-descriptor: ~S (expected ~S)" (car p) fd))) ((fdset-test inf outf (cdr p)) - (when (##sys#slot t 4) ; also blocked for timeout? - (##sys#remove-from-timeout-list t)) + (##sys#remove-from-timeout-list t) (##sys#thread-clear-blocking-state! t) (##sys#thread-basic-unblock! t) (loop2 (cdr threads) keep)) @@ -572,12 +576,15 @@ EOF ;;; Unblock thread cleanly: +;;(: ##sys#thread-unblock! ((struct thread) -> boolean)) (define (##sys#thread-unblock! t) - (when (or (eq? 'blocked (##sys#slot t 3)) - (eq? 'sleeping (##sys#slot t 3))) - (##sys#remove-from-timeout-list t) - (##sys#thread-clear-blocking-state! t) - (##sys#thread-basic-unblock! t) ) ) + (and (let ((ts (##sys#slot t 3))) + (or (eq? 'blocked ts) (eq? 'sleeping ts))) + (begin + (##sys#remove-from-timeout-list t) + (##sys#thread-clear-blocking-state! t) + (##sys#thread-basic-unblock! t) + #t) ) ) ;;; Kill all threads in fd-, io- and timeout-lists and assign one thread as the ; new primordial one. Overrides "##sys#kill-other-threads" in library.scm. diff --git a/srfi-18.scm b/srfi-18.scm index 5d5c5305..dbb572bb 100644 --- a/srfi-18.scm +++ b/srfi-18.scm @@ -174,8 +174,6 @@ (lambda () (case (##sys#slot thread 3) ((dead) - (unless (##sys#slot ct 13) ; not unblocked by timeout - (##sys#remove-from-timeout-list ct)) (apply return (##sys#slot thread 2))) ((terminated) (return @@ -271,7 +269,7 @@ (when thread (##sys#check-structure thread 'thread 'mutex-lock!)) (##sys#call-with-current-continuation (lambda (return) - (let ([ct ##sys#current-thread]) + (let ((ct ##sys#current-thread)) (define (switch) (dbg ct " sleeping on mutex " (mutex-name mutex)) (##sys#setslot ct 11 mutex) @@ -281,25 +279,26 @@ (when (##sys#slot mutex 4) ; abandoned (return (##sys#signal (##sys#make-structure 'condition '(abandoned-mutex-exception) (list (##sys#slot mutex 1))))) ) ) (define (assign) - (##sys#setislot ct 11 #f) - (check) - (if (and threadsup (not thread)) - (begin - (##sys#setislot mutex 2 #f) - (##sys#setislot mutex 5 #t) ) - (let* ([t (or thread ct)] - [ts (##sys#slot t 3)] ) - (if (or (eq? 'terminated ts) (eq? 'dead ts)) - (begin - (##sys#setislot mutex 2 #f) - (##sys#setislot mutex 5 #f) - (##sys#setislot mutex 4 #t) - (check)) - (begin - (##sys#setslot mutex 2 t) - (##sys#setislot mutex 5 #t) - (##sys#setslot t 8 (cons mutex (##sys#slot t 8))) ) ) ) ) - (return #t)) + (let ((abd (##sys#slot mutex 4))) + (if (and threadsup (not thread)) + (begin + (##sys#setislot mutex 2 #f) + (##sys#setislot mutex 5 #t) ) + (let* ([t (or thread ct)] + [ts (##sys#slot t 3)] ) + (if (or (eq? 'terminated ts) (eq? 'dead ts)) + (begin + (##sys#setislot mutex 2 #f) + (##sys#setislot mutex 5 #f) + (##sys#setislot mutex 4 #t)) + (begin + (##sys#setslot mutex 2 t) + (##sys#setislot mutex 5 #t) + (##sys#setslot t 8 (cons mutex (##sys#slot t 8))) ) ) ) ) + (return + (if abd + (##sys#signal (##sys#make-structure 'condition '(abandoned-mutex-exception) (list (##sys#slot mutex 1)))) + #t)))) (dbg ct ": locking " mutex) (cond [(not (##sys#slot mutex 5)) (assign) ] @@ -309,13 +308,8 @@ ct 1 (lambda () (if (##sys#slot ct 13) ; unblocked by timeout - (begin - (##sys#setslot mutex 3 (##sys#delq ct (##sys#slot mutex 3))) - (##sys#setislot ct 11 #f) - (return #f)) - (begin - (##sys#remove-from-timeout-list ct) - (assign))) )) + (return #f) + (assign)) )) (##sys#thread-block-for-timeout! ct limit) (switch) ] [else @@ -326,16 +320,16 @@ (define mutex-unlock! (lambda (mutex . cvar-and-to) (##sys#check-structure mutex 'mutex 'mutex-unlock!) - (let ([ct ##sys#current-thread] - [cvar (and (pair? cvar-and-to) (car cvar-and-to))] - [timeout (and (fx> (length cvar-and-to) 1) (cadr cvar-and-to))] ) + (let ((ct ##sys#current-thread) + (cvar (and (pair? cvar-and-to) (car cvar-and-to))) + (timeout (and (fx> (length cvar-and-to) 1) (cadr cvar-and-to))) ) (dbg ct ": unlocking " (mutex-name mutex)) (when cvar (##sys#check-structure cvar 'condition-variable 'mutex-unlock!)) (##sys#call-with-current-continuation (lambda (return) - (let ([waiting (##sys#slot mutex 3)] - [limit (and timeout (compute-time-limit timeout 'mutex-unlock!))] ) + (let ((waiting (##sys#slot mutex 3)) + (limit (and timeout (compute-time-limit timeout 'mutex-unlock!))) ) (##sys#setislot mutex 4 #f) ; abandoned (##sys#setislot mutex 5 #f) ; blocked (let ((t (##sys#slot mutex 2))) @@ -348,31 +342,16 @@ (cond (limit (##sys#setslot ct 1 - (lambda () - (##sys#setislot ct 11 #f) - (if (##sys#slot ct 13) ; unblocked by timeout - (begin - (##sys#setslot cvar 2 (##sys#delq ct (##sys#slot cvar 2))) - (return #f)) - (begin - (##sys#remove-from-timeout-list ct) - (return #t))) ) ) + (lambda () (return (not (##sys#slot ct 13))) ) ) (##sys#thread-block-for-timeout! ct limit) ) (else (##sys#setslot ct 1 (lambda () (return #t))) (##sys#setslot ct 3 'sleeping)) ) ) (unless (null? waiting) - (let* ((wt (##sys#slot waiting 0)) - (wts (##sys#slot wt 3)) ) - (##sys#setslot mutex 3 (##sys#slot waiting 1)) - (##sys#setislot mutex 5 #t) - (case wts - ((blocked sleeping) - (##sys#setslot wt 11 #f) - (##sys#add-to-ready-queue wt)) - (else - (##sys#error 'mutex-unlock "Internal scheduler error: unknown thread state" - wt wts))) ) ) + (let ((wt (##sys#slot waiting 0))) + (or (##sys#thread-unblock! wt) + (##sys#error 'mutex-unlock "Internal scheduler error: unknown thread state" + wt (##sys#slot wt 3))))) (if (eq? (##sys#slot ct 3) 'running) (return #t) (##sys#schedule)) ) ) ) ) )) @@ -407,24 +386,18 @@ (define (condition-variable-signal! cvar) (##sys#check-structure cvar 'condition-variable 'condition-variable-signal!) (dbg "signalling " cvar) - (let ([ts (##sys#slot cvar 2)]) - (unless (null? ts) - (let* ([t0 (##sys#slot ts 0)] - [t0s (##sys#slot t0 3)] ) - (##sys#setslot cvar 2 (##sys#slot ts 1)) - (when (or (eq? t0s 'blocked) (eq? t0s 'sleeping)) - (##sys#thread-basic-unblock! t0) ) ) ) ) ) + (let ((ts (##sys#slot cvar 2))) + (unless (null? ts) (##sys#thread-unblock! (##sys#slot ts 0)) ) ) ) (define (condition-variable-broadcast! cvar) (##sys#check-structure cvar 'condition-variable 'condition-variable-broadcast!) (dbg "broadcasting " cvar) (##sys#for-each (lambda (ti) - (let ([tis (##sys#slot ti 3)]) + (let ((tis (##sys#slot ti 3))) (when (or (eq? tis 'blocked) (eq? tis 'sleeping)) - (##sys#thread-basic-unblock! ti) ) ) ) - (##sys#slot cvar 2) ) - (##sys#setislot cvar 2 '()) ) + (##sys#thread-unblock! ti) ) ) ) + (##sys#slot cvar 2) ) ) ;;; Change continuation of thread to signal an exception: @@ -434,15 +407,7 @@ (dbg "signal " thread exn) (if (eq? thread ##sys#current-thread) (##sys#signal exn) - (let ([old (##sys#slot thread 1)] - [blocked (##sys#slot thread 11)]) - (cond - ((##sys#structure? blocked 'condition-variable) - (##sys#setslot blocked 2 (##sys#delq thread (##sys#slot blocked 2)))) - ((##sys#structure? blocked 'mutex) - (##sys#setslot blocked 3 (##sys#delq thread (##sys#slot blocked 3)))) - ((##sys#structure? blocked 'thread) - (##sys#setslot blocked 12 (##sys#delq thread (##sys#slot blocked 12))))) + (let ((old (##sys#slot thread 1))) (##sys#setslot thread 1 (lambda () -- 2.11.0
From 307e9d806f421bd13e4b6f30a8cdb86378b8c1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= <joerg.wittenber...@softeyes.net> Date: Mon, 3 Dec 2018 22:22:05 +0100 Subject: [PATCH] Fix 1564 internal scheduler error. --- scheduler.scm | 79 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/scheduler.scm b/scheduler.scm index 238c348e..32c2743c 100644 --- a/scheduler.scm +++ b/scheduler.scm @@ -35,7 +35,7 @@ ;; This isn't hidden ATM to allow set!ing it as a hook/workaround ; ##sys#force-primordial remove-from-ready-queue fdset-test create-fdset stderr delq - ##sys#clear-i/o-state-for-thread! ##sys#abandon-mutexes) + ##sys#thread-clear-blocking-state! ##sys#abandon-mutexes) (not inline chicken.base#sleep-hook ##sys#interrupt-hook ##sys#force-primordial) (unsafe) (foreign-declare #<<EOF @@ -197,7 +197,7 @@ EOF (if (>= now tmo1) ; timeout reached? (begin (##sys#setislot tto 13 #t) ; mark as being unblocked by timeout - (##sys#clear-i/o-state-for-thread! tto) + (##sys#thread-clear-blocking-state! tto) (##sys#thread-basic-unblock! tto) (loop (cdr lst)) ) (begin @@ -343,17 +343,9 @@ EOF (define (##sys#thread-kill! t s) (dbg "killing: " t " -> " s ", recipients: " (##sys#slot t 12)) (##sys#abandon-mutexes t) - (let ((blocked (##sys#slot t 11))) - (cond - ((##sys#structure? blocked 'condition-variable) - (##sys#setslot blocked 2 (delq t (##sys#slot blocked 2)))) - ((##sys#structure? blocked 'thread) - (##sys#setslot blocked 12 (delq t (##sys#slot blocked 12))))) ) (##sys#remove-from-timeout-list t) - (##sys#clear-i/o-state-for-thread! t) + (##sys#thread-clear-blocking-state! t) (##sys#setslot t 3 s) - (##sys#setislot t 4 #f) - (##sys#setislot t 11 #f) (##sys#setislot t 8 '()) (let ((rs (##sys#slot t 12))) (unless (null? rs) @@ -361,13 +353,15 @@ EOF (lambda (t2) (dbg " checking: " t2 " (" (##sys#slot t2 3) ") -> " (##sys#slot t2 11)) (when (eq? (##sys#slot t2 11) t) - (##sys#thread-basic-unblock! t2) ) ) - rs) ) ) - (##sys#setislot t 12 '()) ) + (##sys#thread-unblock! t2) ) ) + rs) + (##sys#setislot t 12 '()) ) ) ) (define (##sys#thread-basic-unblock! t) (dbg "unblocking: " t) - (##sys#setislot t 11 #f) ; (FD . RWFLAGS) | #<MUTEX> | #<THREAD> + #;(if (##sys#slot t 11) ;; remove this case after testing + (##sys#error '##sys#thread-basic-unblock! "Internal scheduler error: unclean unblock" + (##sys#slot t 11))) (##sys#setislot t 4 #f) (##sys#add-to-ready-queue t) ) @@ -498,39 +492,20 @@ EOF ;; is incorrect but will be ignored, just let it run (when (##sys#slot t 4) ; also blocked for timeout? (##sys#remove-from-timeout-list t)) - (##sys#thread-basic-unblock! t) + (##sys#thread-clear-blocking-state! t) + (##sys#thread-basic-unblock! t) (loop2 (cdr threads) keep)) ((not (eq? fd (car p))) (panic (sprintf "thread is registered for I/O on unknown file-descriptor: ~S (expected ~S)" (car p) fd))) ((fdset-test inf outf (cdr p)) (when (##sys#slot t 4) ; also blocked for timeout? (##sys#remove-from-timeout-list t)) + (##sys#thread-clear-blocking-state! t) (##sys#thread-basic-unblock! t) (loop2 (cdr threads) keep)) (else (loop2 (cdr threads) (cons t keep))))))) (cons a (loop n (add1 pos) (cdr lst))) ) ) ) ) ) ] ))) ) - -;;; Clear I/O state for unblocked thread - -(define (##sys#clear-i/o-state-for-thread! t) - (when (pair? (##sys#slot t 11)) - (let ((fd (car (##sys#slot t 11)))) - (set! ##sys#fd-list - (let loop ((lst ##sys#fd-list)) - (if (null? lst) - '() - (let* ((a (car lst)) - (fd2 (car a)) ) - (if (eq? fd fd2) - (let ((ts (delq t (cdr a)))) ; remove from fd-list entry - (cond ((null? ts) (cdr lst)) - (else - (##sys#setslot a 1 ts) ; fd-list entry is list with t removed - lst) ) ) - (cons a (loop (cdr lst))))))))))) - - ;;; Get list of all threads that are ready or waiting for timeout or waiting for I/O: ; ; (contributed by Joerg Wittenberger) @@ -574,6 +549,34 @@ EOF (set! ##sys#fd-list (##sys#slot vec 2)) (set! ##sys#timeout-list (##sys#slot vec 3)) ) +;;; Clear blocking queues + +(define (##sys#thread-clear-blocking-state! t) + (let ((blocked (##sys#slot t 11))) ; (FD . RWFLAGS) | #<MUTEX> | #<THREAD> + (dbg "clear-blocking " t " from " blocked) + (cond + ((pair? blocked) + (let ((fd (car (##sys#slot t 11)))) + (set! ##sys#fd-list + (let loop ((lst ##sys#fd-list)) + (if (null? lst) + '() + (let* ((a (car lst)) + (fd2 (car a)) ) + (if (eq? fd fd2) + (let ((ts (delq t (cdr a)))) ; remove from fd-list entry + (cond ((null? ts) (cdr lst)) + (else + (##sys#setslot a 1 ts) ; fd-list entry is list with t removed + lst) ) ) + (cons a (loop (cdr lst)))))))))) + ((##sys#structure? blocked 'condition-variable) + (##sys#setslot blocked 2 (delq t (##sys#slot blocked 2)))) + ((##sys#structure? blocked 'mutex) + (##sys#setslot blocked 3 (delq t (##sys#slot blocked 3)))) + ((##sys#structure? blocked 'thread) + (##sys#setslot blocked 12 (delq t (##sys#slot blocked 12))))) + (##sys#setislot t 11 #f))) ;;; Unblock thread cleanly: @@ -581,7 +584,7 @@ EOF (when (or (eq? 'blocked (##sys#slot t 3)) (eq? 'sleeping (##sys#slot t 3))) (##sys#remove-from-timeout-list t) - (##sys#clear-i/o-state-for-thread! t) + (##sys#thread-clear-blocking-state! t) (##sys#thread-basic-unblock! t) ) ) -- 2.11.0
From 5da7a55bacfc6b1597eb4c5126005536aa3d0801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= <joerg.wittenber...@softeyes.net> Date: Wed, 19 Dec 2018 12:47:28 +0100 Subject: [PATCH 2/2] Modifiy internals to line up with fixes in srfi-18. --- scheduler.scm | 53 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/scheduler.scm b/scheduler.scm index 32c2743c..df4db928 100644 --- a/scheduler.scm +++ b/scheduler.scm @@ -197,6 +197,7 @@ EOF (if (>= now tmo1) ; timeout reached? (begin (##sys#setislot tto 13 #t) ; mark as being unblocked by timeout + (##sys#setislot tto 4 #f) ; clear timeout (##sys#thread-clear-blocking-state! tto) (##sys#thread-basic-unblock! tto) (loop (cdr lst)) ) @@ -288,16 +289,20 @@ EOF (define ##sys#timeout-list '()) (define (##sys#remove-from-timeout-list t) - (let loop ((l ##sys#timeout-list) (prev #f)) - (if (null? l) - l - (let ((h (##sys#slot l 0)) - (r (##sys#slot l 1))) - (if (eq? (##sys#slot h 1) t) - (if prev - (set-cdr! prev r) - (set! ##sys#timeout-list r)) - (loop r l)))))) + (define (removeit t) + (let loop ((l ##sys#timeout-list) (prev #f)) + (if (null? l) + l + (let ((h (##sys#slot l 0)) + (r (##sys#slot l 1))) + (if (eq? (##sys#slot h 1) t) + (if prev + (set-cdr! prev r) + (set! ##sys#timeout-list r)) + (loop r l)))))) + (when (##sys#slot t 4) ;; no need to walk the queue without timeout + (removeit t) + (##sys#setislot t 4 #f))) ;; keep queue and thread state lexically in sync (define (##sys#thread-block-for-timeout! t tm) (dbg t " blocks for timeout " tm) @@ -359,10 +364,12 @@ EOF (define (##sys#thread-basic-unblock! t) (dbg "unblocking: " t) - #;(if (##sys#slot t 11) ;; remove this case after testing - (##sys#error '##sys#thread-basic-unblock! "Internal scheduler error: unclean unblock" + (if (##sys#slot t 11) ;; remove this case after testing + (##sys#error '##sys#thread-basic-unblock! "Internal scheduler error: unblock with block object" + (##sys#slot t 11))) + (if (##sys#slot t 4) ;; remove this case after testing + (##sys#error '##sys#thread-basic-unblock! "Internal scheduler error: unblock with timeout" (##sys#slot t 11))) - (##sys#setislot t 4 #f) (##sys#add-to-ready-queue t) ) (define (##sys#default-exception-handler arg) @@ -490,16 +497,14 @@ EOF ((not (pair? p)) ; not blocked for I/O? ;; thread on fd-list is not blocked for I/O - this ;; is incorrect but will be ignored, just let it run - (when (##sys#slot t 4) ; also blocked for timeout? - (##sys#remove-from-timeout-list t)) + (##sys#remove-from-timeout-list t) ; also blocked for timeout? (##sys#thread-clear-blocking-state! t) (##sys#thread-basic-unblock! t) (loop2 (cdr threads) keep)) ((not (eq? fd (car p))) (panic (sprintf "thread is registered for I/O on unknown file-descriptor: ~S (expected ~S)" (car p) fd))) ((fdset-test inf outf (cdr p)) - (when (##sys#slot t 4) ; also blocked for timeout? - (##sys#remove-from-timeout-list t)) + (##sys#remove-from-timeout-list t) (##sys#thread-clear-blocking-state! t) (##sys#thread-basic-unblock! t) (loop2 (cdr threads) keep)) @@ -580,13 +585,15 @@ EOF ;;; Unblock thread cleanly: +;;(: ##sys#thread-unblock! ((struct thread) -> boolean)) (define (##sys#thread-unblock! t) - (when (or (eq? 'blocked (##sys#slot t 3)) - (eq? 'sleeping (##sys#slot t 3))) - (##sys#remove-from-timeout-list t) - (##sys#thread-clear-blocking-state! t) - (##sys#thread-basic-unblock! t) ) ) - + (and (let ((ts (##sys#slot t 3))) + (or (eq? 'blocked ts) (eq? 'sleeping ts))) + (begin + (##sys#remove-from-timeout-list t) + (##sys#thread-clear-blocking-state! t) + (##sys#thread-basic-unblock! t) + #t) ) ) ;;; Put a thread to sleep: -- 2.11.0
From 9e66180733588860a32db479d2283b4d73d598ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= <joerg.wittenber...@softeyes.net> Date: Wed, 19 Dec 2018 12:24:29 +0100 Subject: [PATCH 1/2] Add test cases and make test effective. --- tests/mutex-test.scm | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/mutex-test.scm b/tests/mutex-test.scm index 9c7f1e6..d052abb 100644 --- a/tests/mutex-test.scm +++ b/tests/mutex-test.scm @@ -57,6 +57,29 @@ Slot Type Meaning (print "Got " mux1 " state " (mutex-state mux1) " expected " owner1 "\n") (test-exit 1))))) +(let ((m1 (make-mutex))) + ;; This fails if we manage to sort primorial before t1 and unleash + ;; both in one turn. + (define (sys-thread-sleep! limit) + ;; a copy from srfi-18 which expects pre-computed goal time. + (##sys#call-with-current-continuation + (lambda (return) + (let ((ct ##sys#current-thread)) + (##sys#setslot ct 1 (lambda () (return (##core#undefined)))) + (##sys#thread-block-for-timeout! ct limit) + (##sys#schedule) ) ) ) ) + #;(print "mutex state changes atomically wrt. blocking queues") + (mutex-lock! m1) + (let ((t1 (thread-start! (lambda () (mutex-lock! m1 0.1))))) + #;(print "have t1 it wait for m1") + (thread-yield!) + (let* ((to (##sys#slot t1 4)) + (hit (- to 0.0001))) + #;(print "waiting ever so slightly less than to " to " i.e. " hit "\n") + (sys-thread-sleep! hit)) + ;; catch inconsistent state + (mutex-unlock! m1))) + (set! mux1 (make-mutex 'unlock-leaves-no-memory-leak)) (mutex-lock! mux1) (mutex-unlock! mux1) @@ -65,6 +88,27 @@ Slot Type Meaning (test-error "thread still held in mutex after unlock: " mux1)) ;;============ +(let* ((cv (make-condition-variable)) + (m (begin + (condition-variable-specific-set! cv #f) + (make-mutex))) + (t (thread-start! + (lambda () + (do () + ((condition-variable-specific cv)) + (mutex-unlock! m cv)))))) + (thread-yield!) + (when + (not (eq? (##sys#slot t 3) 'sleeping)) + (test-error "thread not sleeping " t)) + (condition-variable-specific-set! cv #t) + (condition-variable-signal! cv) + (thread-yield!) + (when + (not (eq? (##sys#slot t 3) 'dead)) + (test-error "thread not completed " t))) + +;;============ ; Make a locked mutex (define mux (make-mutex 'foo)) (mutex-lock! mux #f #f) @@ -110,6 +154,23 @@ Slot Type Meaning (print "Abandoned Mutex not abandoned " mux "\n") (test-exit 1)) +(unless (eq? (mutex-state mux) (current-thread)) + (print "Mutex " mux " locked/not-owned but left in state " (mutex-state mux) "\n") + (test-exit 1)) + +;; repeat with owned mutex +(set! mux (make-mutex 'foobar)) +(thread-start! (lambda () (mutex-lock! mux))) +(thread-yield!) + +(when (not (handle-exceptions ex (abandoned-mutex-exception? ex) (and (mutex-lock! mux) #f))) + (print "Abandoned Mutex not abandoned " mux "\n") + (test-exit 1)) + +(unless (eq? (mutex-state mux) (current-thread)) + (print "Mutex " mux " not assigned to " (current-thread) " but left in state " (mutex-state mux) "\n") + (test-exit 1)) + (mutex-unlock! mux) (mutex-lock! mux) @@ -189,3 +250,5 @@ Slot Type Meaning (thread-sleep! 3) ;(tprint 'exit) + +(if test-has-failed (exit 1) (exit 0)) -- 2.11.0
From 1ab69bf17fe620addf4fbbc4f3fae695df243b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= <joerg.wittenber...@softeyes.net> Date: Wed, 19 Dec 2018 12:51:44 +0100 Subject: [PATCH 2/2] Change abandoned mutexs state according to srfi-18. Also some cleanup prefering ##sys#thread-unblock! when appropriate. --- srfi-18.scm | 117 +++++++++++++++++++++--------------------------------------- 1 file changed, 41 insertions(+), 76 deletions(-) diff --git a/srfi-18.scm b/srfi-18.scm index f6253f1..28d3cd9 100644 --- a/srfi-18.scm +++ b/srfi-18.scm @@ -1,6 +1,6 @@ ;;;; srfi-18.scm - Simple thread unit - felix ; -; Copyright (c) 2008-2016, The Chicken Team +; Copyright (c) 2008-2018, The Chicken Team ; Copyright (c) 2000-2007, Felix L. Winkelmann ; All rights reserved. ; @@ -231,8 +231,6 @@ (lambda () (case (##sys#slot thread 3) ((dead) - (unless (##sys#slot ct 13) ; not unblocked by timeout - (##sys#remove-from-timeout-list ct)) (apply return (##sys#slot thread 2))) ((terminated) (return @@ -321,7 +319,7 @@ (when thread (##sys#check-structure thread 'thread 'mutex-lock!)) (##sys#call-with-current-continuation (lambda (return) - (let ([ct ##sys#current-thread]) + (let ((ct ##sys#current-thread)) (define (switch) (dbg ct " sleeping on mutex " (mutex-name mutex)) (##sys#setslot ct 11 mutex) @@ -331,25 +329,26 @@ (when (##sys#slot mutex 4) ; abandoned (return (signal (##sys#make-structure 'condition '(abandoned-mutex-exception) (list (##sys#slot mutex 1))))))) (define (assign) - (##sys#setislot ct 11 #f) - (check) - (if (and threadsup (not thread)) - (begin - (##sys#setislot mutex 2 #f) - (##sys#setislot mutex 5 #t) ) - (let* ([t (or thread ct)] - [ts (##sys#slot t 3)] ) - (if (or (eq? 'terminated ts) (eq? 'dead ts)) - (begin - (##sys#setislot mutex 2 #f) - (##sys#setislot mutex 5 #f) - (##sys#setislot mutex 4 #t) - (check)) - (begin - (##sys#setslot mutex 2 t) - (##sys#setislot mutex 5 #t) - (##sys#setslot t 8 (cons mutex (##sys#slot t 8))) ) ) ) ) - (return #t)) + (let ((abd (##sys#slot mutex 4))) + (if (and threadsup (not thread)) + (begin + (##sys#setislot mutex 2 #f) + (##sys#setislot mutex 5 #t) ) + (let* ([t (or thread ct)] + [ts (##sys#slot t 3)] ) + (if (or (eq? 'terminated ts) (eq? 'dead ts)) + (begin + (##sys#setislot mutex 2 #f) + (##sys#setislot mutex 5 #f) + (##sys#setislot mutex 4 #t)) + (begin + (##sys#setslot mutex 2 t) + (##sys#setislot mutex 5 #t) + (##sys#setslot t 8 (cons mutex (##sys#slot t 8))) ) ) ) ) + (return + (if abd + (signal (##sys#make-structure 'condition '(abandoned-mutex-exception) (list (##sys#slot mutex 1)))) + #t)))) (dbg ct ": locking " mutex) (cond [(not (##sys#slot mutex 5)) (assign) ] @@ -359,13 +358,8 @@ ct 1 (lambda () (if (##sys#slot ct 13) ; unblocked by timeout - (begin - (##sys#setslot mutex 3 (delq ct (##sys#slot mutex 3))) - (##sys#setislot ct 11 #f) - (return #f)) - (begin - (##sys#remove-from-timeout-list ct) - (assign))) )) + (return #f) + (assign)) )) (##sys#thread-block-for-timeout! ct limit) (switch) ] [else @@ -376,16 +370,16 @@ (define mutex-unlock! (lambda (mutex . cvar-and-to) (##sys#check-structure mutex 'mutex 'mutex-unlock!) - (let ([ct ##sys#current-thread] - [cvar (and (pair? cvar-and-to) (car cvar-and-to))] - [timeout (and (fx> (length cvar-and-to) 1) (cadr cvar-and-to))] ) + (let ((ct ##sys#current-thread) + (cvar (and (pair? cvar-and-to) (car cvar-and-to))) + (timeout (and (fx> (length cvar-and-to) 1) (cadr cvar-and-to))) ) (dbg ct ": unlocking " (mutex-name mutex)) (when cvar (##sys#check-structure cvar 'condition-variable 'mutex-unlock!)) (##sys#call-with-current-continuation (lambda (return) - (let ([waiting (##sys#slot mutex 3)] - [limit (and timeout (compute-time-limit timeout 'mutex-unlock!))] ) + (let ((waiting (##sys#slot mutex 3)) + (limit (and timeout (compute-time-limit timeout 'mutex-unlock!))) ) (##sys#setislot mutex 4 #f) ; abandoned (##sys#setislot mutex 5 #f) ; blocked (let ((t (##sys#slot mutex 2))) @@ -398,31 +392,16 @@ (cond (limit (##sys#setslot ct 1 - (lambda () - (##sys#setislot ct 11 #f) - (if (##sys#slot ct 13) ; unblocked by timeout - (begin - (##sys#setslot cvar 2 (delq ct (##sys#slot cvar 2))) - (return #f)) - (begin - (##sys#remove-from-timeout-list ct) - (return #t))) ) ) + (lambda () (return (not (##sys#slot ct 13))) ) ) (##sys#thread-block-for-timeout! ct limit) ) (else (##sys#setslot ct 1 (lambda () (return #t))) (##sys#setslot ct 3 'sleeping)) ) ) (unless (null? waiting) - (let* ((wt (##sys#slot waiting 0)) - (wts (##sys#slot wt 3)) ) - (##sys#setslot mutex 3 (##sys#slot waiting 1)) - (##sys#setislot mutex 5 #t) - (case wts - ((blocked sleeping) - (##sys#setslot wt 11 #f) - (##sys#add-to-ready-queue wt)) - (else - (##sys#error 'mutex-unlock "Internal scheduler error: unknown thread state" - wt wts))) ) ) + (let ((wt (##sys#slot waiting 0))) + (or (##sys#thread-unblock! wt) + (##sys#error 'mutex-unlock "Internal scheduler error: unknown thread state" + wt (##sys#slot wt 3))))) (if (eq? (##sys#slot ct 3) 'running) (return #t) (##sys#schedule)) ) ) ) ) )) @@ -457,24 +436,18 @@ (define (condition-variable-signal! cvar) (##sys#check-structure cvar 'condition-variable 'condition-variable-signal!) (dbg "signalling " cvar) - (let ([ts (##sys#slot cvar 2)]) - (unless (null? ts) - (let* ([t0 (##sys#slot ts 0)] - [t0s (##sys#slot t0 3)] ) - (##sys#setslot cvar 2 (##sys#slot ts 1)) - (when (or (eq? t0s 'blocked) (eq? t0s 'sleeping)) - (##sys#thread-basic-unblock! t0) ) ) ) ) ) + (let ((ts (##sys#slot cvar 2))) + (unless (null? ts) (##sys#thread-unblock! (##sys#slot ts 0)) ) ) ) (define (condition-variable-broadcast! cvar) (##sys#check-structure cvar 'condition-variable 'condition-variable-broadcast!) (dbg "broadcasting " cvar) (##sys#for-each (lambda (ti) - (let ([tis (##sys#slot ti 3)]) + (let ((tis (##sys#slot ti 3))) (when (or (eq? tis 'blocked) (eq? tis 'sleeping)) - (##sys#thread-basic-unblock! ti) ) ) ) - (##sys#slot cvar 2) ) - (##sys#setislot cvar 2 '()) ) + (##sys#thread-unblock! ti) ) ) ) + (##sys#slot cvar 2) ) ) ;;; Change continuation of thread to signal an exception: @@ -483,16 +456,8 @@ (##sys#check-structure thread 'thread 'thread-signal!) (dbg "signal " thread exn) (if (eq? thread ##sys#current-thread) - (signal exn) - (let ([old (##sys#slot thread 1)] - [blocked (##sys#slot thread 11)]) - (cond - ((##sys#structure? blocked 'condition-variable) - (##sys#setslot blocked 2 (delq thread (##sys#slot blocked 2)))) - ((##sys#structure? blocked 'mutex) - (##sys#setslot blocked 3 (delq thread (##sys#slot blocked 3)))) - ((##sys#structure? blocked 'thread) - (##sys#setslot blocked 12 (delq thread (##sys#slot blocked 12))))) + (##sys#signal exn) + (let ((old (##sys#slot thread 1))) (##sys#setslot thread 1 (lambda () -- 2.11.0
_______________________________________________ Chicken-hackers mailing list Chicken-hackers@nongnu.org https://lists.nongnu.org/mailman/listinfo/chicken-hackers