2024-11-19 18:43:43 +00:00
|
|
|
(define-module (knots)
|
2025-02-03 15:44:51 +00:00
|
|
|
#:use-module (srfi srfi-1)
|
|
|
|
|
#:use-module (ice-9 match)
|
2026-01-24 15:21:15 +00:00
|
|
|
#:use-module (ice-9 threads)
|
2026-01-12 10:00:35 +00:00
|
|
|
#:use-module (ice-9 binary-ports)
|
2024-11-19 18:43:43 +00:00
|
|
|
#:use-module (ice-9 suspendable-ports)
|
2026-01-12 10:00:35 +00:00
|
|
|
#:use-module (rnrs bytevectors)
|
2026-01-09 15:44:04 +00:00
|
|
|
#:use-module (fibers)
|
2026-01-24 15:21:15 +00:00
|
|
|
#:use-module (fibers channels)
|
2024-12-25 20:35:40 +00:00
|
|
|
#:use-module (fibers conditions)
|
2025-02-03 15:44:51 +00:00
|
|
|
#:use-module (system repl debug)
|
2024-11-19 18:43:43 +00:00
|
|
|
#:export (call-with-default-io-waiters
|
|
|
|
|
|
2024-12-25 20:35:40 +00:00
|
|
|
wait-when-system-clock-behind
|
|
|
|
|
|
2025-02-03 15:44:51 +00:00
|
|
|
call-with-sigint
|
|
|
|
|
|
2026-01-12 10:00:35 +00:00
|
|
|
display/knots
|
|
|
|
|
simple-format/knots
|
|
|
|
|
format/knots
|
|
|
|
|
|
2025-02-03 15:44:51 +00:00
|
|
|
&knots-exception
|
|
|
|
|
make-knots-exception
|
|
|
|
|
knots-exception?
|
|
|
|
|
knots-exception-stack
|
|
|
|
|
|
2026-01-09 15:44:04 +00:00
|
|
|
print-backtrace-and-exception/knots
|
|
|
|
|
|
2026-03-18 21:39:55 +00:00
|
|
|
call-with-temporary-thread
|
|
|
|
|
|
2026-01-09 15:44:04 +00:00
|
|
|
spawn-fiber/knots))
|
2024-11-19 18:43:43 +00:00
|
|
|
|
|
|
|
|
(define (call-with-default-io-waiters thunk)
|
2026-03-18 08:58:41 +00:00
|
|
|
"Run THUNK with Guile's default blocking I/O waiters active.
|
|
|
|
|
|
|
|
|
|
This is useful when restoring the default Guile I/O waiters from
|
|
|
|
|
within a context (like Fibers) where different I/O waiters are used,
|
|
|
|
|
for example when creating a new thread from a fiber."
|
2024-11-19 18:43:43 +00:00
|
|
|
(parameterize
|
|
|
|
|
((current-read-waiter (@@ (ice-9 suspendable-ports)
|
|
|
|
|
default-read-waiter))
|
|
|
|
|
(current-write-waiter (@@ (ice-9 suspendable-ports)
|
|
|
|
|
default-write-waiter)))
|
|
|
|
|
(thunk)))
|
|
|
|
|
|
|
|
|
|
(define (wait-when-system-clock-behind)
|
2026-03-18 08:58:41 +00:00
|
|
|
"Block until the system clock reads at least 2001-01-02.
|
|
|
|
|
|
|
|
|
|
Useful at startup in environments (virtual machines, embedded systems)
|
|
|
|
|
where the clock may start at or near the Unix epoch. Prints a warning
|
|
|
|
|
to the current error port every 20 seconds while waiting."
|
2026-03-17 21:13:30 +00:00
|
|
|
;; Jan 02 2001 02:00:00
|
|
|
|
|
(let ((start-of-the-year-2001 978400800))
|
2024-11-19 18:43:43 +00:00
|
|
|
(while (< (current-time)
|
2026-03-17 21:13:30 +00:00
|
|
|
start-of-the-year-2001)
|
2024-11-19 18:43:43 +00:00
|
|
|
(simple-format (current-error-port)
|
|
|
|
|
"warning: system clock potentially behind, waiting\n")
|
|
|
|
|
(sleep 20))))
|
2024-12-25 20:35:40 +00:00
|
|
|
|
|
|
|
|
;; Copied from (fibers web server)
|
|
|
|
|
(define (call-with-sigint thunk cvar)
|
2026-03-18 08:58:41 +00:00
|
|
|
"Run THUNK with a SIGINT handler that signals the Fibers condition
|
|
|
|
|
CVAR. Restores the previous handler when THUNK returns.
|
|
|
|
|
|
|
|
|
|
Typical usage is to pass a condition variable to this procedure and
|
|
|
|
|
wait on CVAR in a fiber to implement clean shutdown on Ctrl-C:
|
|
|
|
|
|
|
|
|
|
@example
|
|
|
|
|
(let ((quit-cvar (make-condition)))
|
|
|
|
|
(call-with-sigint
|
|
|
|
|
(lambda () (wait quit-cvar))
|
|
|
|
|
quit-cvar))
|
|
|
|
|
@end example"
|
2024-12-25 20:35:40 +00:00
|
|
|
(let ((handler #f))
|
|
|
|
|
(dynamic-wind
|
|
|
|
|
(lambda ()
|
|
|
|
|
(set! handler
|
|
|
|
|
(sigaction SIGINT (lambda (sig) (signal-condition! cvar)))))
|
|
|
|
|
thunk
|
|
|
|
|
(lambda ()
|
|
|
|
|
(if handler
|
|
|
|
|
;; restore Scheme handler, SIG_IGN or SIG_DFL.
|
|
|
|
|
(sigaction SIGINT (car handler) (cdr handler))
|
|
|
|
|
;; restore original C handler.
|
|
|
|
|
(sigaction SIGINT #f))))))
|
2025-02-03 15:44:51 +00:00
|
|
|
|
2026-01-24 15:21:15 +00:00
|
|
|
(define (call-with-temporary-thread thunk)
|
|
|
|
|
(let ((channel (make-channel)))
|
|
|
|
|
(call-with-new-thread
|
|
|
|
|
(lambda ()
|
|
|
|
|
(call-with-default-io-waiters
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
(put-message channel `(exception . ,exn)))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
(let ((stack
|
|
|
|
|
(match (fluid-ref %stacks)
|
|
|
|
|
((stack-tag . prompt-tag)
|
|
|
|
|
(make-stack #t
|
|
|
|
|
0 prompt-tag
|
|
|
|
|
0 (and prompt-tag 1)))
|
|
|
|
|
(_
|
|
|
|
|
(make-stack #t)))))
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-exception
|
|
|
|
|
exn
|
|
|
|
|
(make-knots-exception stack)))))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(call-with-values thunk
|
|
|
|
|
(lambda values
|
|
|
|
|
(put-message channel `(values ,@values)))))))
|
|
|
|
|
#:unwind? #t)))))
|
|
|
|
|
|
|
|
|
|
(match (get-message channel)
|
|
|
|
|
(('values . results)
|
|
|
|
|
(apply values results))
|
|
|
|
|
(('exception . exn)
|
|
|
|
|
(raise-exception exn)))))
|
|
|
|
|
|
2026-01-12 10:00:35 +00:00
|
|
|
(define* (display/knots obj #:optional (port (current-output-port)))
|
2026-03-18 08:58:41 +00:00
|
|
|
"Write OBJ to PORT (default: current output port) as a UTF-8 byte
|
|
|
|
|
sequence via @code{put-bytevector}.
|
|
|
|
|
|
|
|
|
|
When used with ports without buffering, this should be safer than
|
|
|
|
|
display."
|
2026-01-12 10:00:35 +00:00
|
|
|
(put-bytevector
|
|
|
|
|
port
|
|
|
|
|
(string->utf8
|
|
|
|
|
(call-with-output-string
|
|
|
|
|
(lambda (port)
|
|
|
|
|
(display obj port))))))
|
|
|
|
|
|
|
|
|
|
(define (simple-format/knots port s . args)
|
2026-03-18 08:58:41 +00:00
|
|
|
"Like @code{simple-format} but should be safer when used with a port
|
|
|
|
|
without buffering."
|
2026-01-12 10:00:35 +00:00
|
|
|
(let ((str (apply simple-format #f s args)))
|
|
|
|
|
(if (eq? #f port)
|
|
|
|
|
str
|
|
|
|
|
(display/knots
|
|
|
|
|
str
|
|
|
|
|
(if (eq? #t port)
|
|
|
|
|
(current-output-port)
|
|
|
|
|
port)))))
|
|
|
|
|
|
|
|
|
|
(define (format/knots port s . args)
|
2026-03-18 08:58:41 +00:00
|
|
|
"Like @code{format} but should be safer when used with a port
|
|
|
|
|
without buffering."
|
2026-01-12 10:00:35 +00:00
|
|
|
(let ((str (apply format #f s args)))
|
|
|
|
|
(if (eq? #f port)
|
|
|
|
|
str
|
|
|
|
|
(display/knots
|
|
|
|
|
str
|
|
|
|
|
(if (eq? #t port)
|
|
|
|
|
(current-output-port)
|
|
|
|
|
port)))))
|
|
|
|
|
|
2025-02-03 15:44:51 +00:00
|
|
|
(define &knots-exception
|
|
|
|
|
(make-exception-type '&knots-exception
|
|
|
|
|
&exception
|
|
|
|
|
'(stack)))
|
|
|
|
|
|
|
|
|
|
(define make-knots-exception
|
|
|
|
|
(record-constructor &knots-exception))
|
|
|
|
|
|
|
|
|
|
(define knots-exception?
|
2025-02-27 12:18:17 +00:00
|
|
|
(exception-predicate &knots-exception))
|
2025-02-03 15:44:51 +00:00
|
|
|
|
|
|
|
|
(define knots-exception-stack
|
|
|
|
|
(exception-accessor
|
|
|
|
|
&knots-exception
|
|
|
|
|
(record-accessor &knots-exception 'stack)))
|
|
|
|
|
|
|
|
|
|
(define* (print-backtrace-and-exception/knots
|
|
|
|
|
exn
|
|
|
|
|
#:key (port (current-error-port)))
|
2026-01-09 15:31:54 +00:00
|
|
|
(define (get-string port stack)
|
|
|
|
|
(define stack-len
|
|
|
|
|
(stack-length stack))
|
|
|
|
|
|
|
|
|
|
(let ((knots-stacks
|
|
|
|
|
(map knots-exception-stack
|
|
|
|
|
(filter knots-exception?
|
|
|
|
|
(simple-exceptions exn)))))
|
|
|
|
|
|
|
|
|
|
(let* ((stack-vec
|
|
|
|
|
(stack->vector stack))
|
|
|
|
|
(stack-vec-length
|
|
|
|
|
(vector-length stack-vec)))
|
|
|
|
|
(print-frames (list->vector
|
|
|
|
|
(drop
|
|
|
|
|
(vector->list stack-vec)
|
|
|
|
|
(if (< stack-vec-length 5)
|
|
|
|
|
0
|
|
|
|
|
4)))
|
|
|
|
|
port
|
|
|
|
|
#:count (stack-length stack)))
|
|
|
|
|
(for-each
|
|
|
|
|
(lambda (stack)
|
|
|
|
|
(let* ((stack-vec
|
|
|
|
|
(stack->vector stack))
|
|
|
|
|
(stack-vec-length
|
|
|
|
|
(vector-length stack-vec)))
|
|
|
|
|
(print-frames (list->vector
|
|
|
|
|
(drop
|
|
|
|
|
(vector->list stack-vec)
|
|
|
|
|
(if (< stack-vec-length 4)
|
|
|
|
|
0
|
|
|
|
|
3)))
|
|
|
|
|
port
|
|
|
|
|
#:count (stack-length stack))))
|
|
|
|
|
knots-stacks)
|
|
|
|
|
(print-exception
|
|
|
|
|
port
|
|
|
|
|
(if (null? knots-stacks)
|
|
|
|
|
(stack-ref stack
|
|
|
|
|
(if (< stack-len 4)
|
|
|
|
|
stack-len
|
|
|
|
|
4))
|
|
|
|
|
(let* ((stack (last knots-stacks))
|
|
|
|
|
(stack-len (stack-length stack)))
|
|
|
|
|
(stack-ref stack
|
|
|
|
|
(if (< stack-len 3)
|
|
|
|
|
stack-len
|
|
|
|
|
3))))
|
|
|
|
|
'%exception
|
|
|
|
|
(list exn))))
|
|
|
|
|
|
2025-05-15 09:25:30 +01:00
|
|
|
(let* ((stack
|
|
|
|
|
(match (fluid-ref %stacks)
|
|
|
|
|
((stack-tag . prompt-tag)
|
|
|
|
|
(make-stack #t
|
|
|
|
|
0 prompt-tag
|
|
|
|
|
0 (and prompt-tag 1)))
|
|
|
|
|
(_
|
|
|
|
|
(make-stack #t))))
|
2026-01-09 15:31:54 +00:00
|
|
|
(string-port
|
|
|
|
|
(open-output-string))
|
2025-02-03 15:44:51 +00:00
|
|
|
(error-string
|
2026-01-09 15:31:54 +00:00
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
2026-01-12 10:00:35 +00:00
|
|
|
(display/knots (get-output-string string-port)
|
|
|
|
|
port)
|
2026-01-09 15:31:54 +00:00
|
|
|
(close-output-port string-port)
|
2026-01-12 10:00:35 +00:00
|
|
|
(display/knots "\n\n" port)
|
|
|
|
|
|
|
|
|
|
(let* ((stack (make-stack #t))
|
|
|
|
|
(backtrace
|
|
|
|
|
(call-with-output-string
|
|
|
|
|
(lambda (port)
|
|
|
|
|
(display-backtrace stack port)
|
|
|
|
|
(newline port)))))
|
|
|
|
|
(display/knots backtrace))
|
|
|
|
|
(simple-format/knots
|
2025-02-03 15:44:51 +00:00
|
|
|
port
|
2026-01-09 15:31:54 +00:00
|
|
|
"\nexception in print-backtrace-and-exception/knots: ~A\n"
|
|
|
|
|
exn)
|
|
|
|
|
(raise-exception exn))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(get-string string-port stack)
|
|
|
|
|
(let ((str (get-output-string string-port)))
|
|
|
|
|
(close-output-port string-port)
|
|
|
|
|
str)))))
|
2026-01-12 10:00:35 +00:00
|
|
|
(display/knots error-string port)))
|
2026-01-09 15:44:04 +00:00
|
|
|
|
|
|
|
|
(define* (spawn-fiber/knots thunk #:optional scheduler #:key parallel?)
|
2026-03-18 08:58:41 +00:00
|
|
|
"Spawn a fiber to run THUNK, with knots exception handling.
|
|
|
|
|
|
|
|
|
|
Accepts the same optional SCHEDULER and @code{#:parallel?} arguments
|
|
|
|
|
as @code{spawn-fiber}."
|
2026-01-09 15:44:04 +00:00
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
2026-01-12 10:00:35 +00:00
|
|
|
(display/knots "Uncaught exception in task:\n"
|
|
|
|
|
(current-error-port))
|
2026-01-09 15:44:04 +00:00
|
|
|
(print-backtrace-and-exception/knots exn))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
(let ((stack
|
|
|
|
|
(match (fluid-ref %stacks)
|
|
|
|
|
((stack-tag . prompt-tag)
|
|
|
|
|
(make-stack #t
|
|
|
|
|
0 prompt-tag
|
|
|
|
|
0 (and prompt-tag 1)))
|
|
|
|
|
(_
|
|
|
|
|
(make-stack #t)))))
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-exception
|
|
|
|
|
exn
|
|
|
|
|
(make-knots-exception stack)))))
|
|
|
|
|
thunk))
|
|
|
|
|
#:unwind? #t))
|
|
|
|
|
scheduler
|
|
|
|
|
#:parallel? parallel?))
|