Commit7b4f1c3aRecorded10 Jul 2026Repositorysigil-nrepl
Remove superseded item1 abort prototype notes
Message
The abort/interrupt op is now implemented via the sliced-eval task machinery (commit 2edb78d); the prototype patch/notes are no longer a resume point.
Changed
notes/item1-abort-prototype.patch | 192 ----------------------------------------------------------------------------------------------------------------------------------------------------------
notes/item1-test-server-prototype.sgl | 110 ----------------------------------------------------------------------------------------
2 files changed, 302 deletions(-)Diff
notes/item1-abort-prototype.patchdeleted
@@ -1,192 +0,0 @@
−1
diff --git a/src/sigil/nrepl.sgl b/src/sigil/nrepl.sgl−2
index 4e475ec..981844f 100644−3
--- a/src/sigil/nrepl.sgl−4
+++ b/src/sigil/nrepl.sgl−5
@@ -24,6 +24,7 @@−6
(sigil inspect)−7
(sigil error)−8
(sigil diagnostic)−9
+ (sigil async)−10
(sigil repl) ; Provides: make-prompt, make-debug-prompt, format-srcloc,−11
; format-proc-name, format-backtrace-str, format-frame-str,−12
; debug-help-text, parse-debug-command, string->int,−13
@@ -76,11 +77,11 @@−14
;; DATA STRUCTURES−15
;; ============================================================−16
−17
- ;; Server state: (vector 'nrepl-server socket port sessions)−18
+ ;; Server state: (vector 'nrepl-server socket port sessions clients tasks)−19
;; sessions is an alist of (session-id . session-state)−20
−21
(define (make-nrepl-server socket port)−22
- (vector 'nrepl-server socket port '() '()))−23
+ (vector 'nrepl-server socket port '() '() '()))−24
−25
(define (nrepl-server? obj)−26
(and (vector? obj)−27
@@ -105,6 +106,28 @@−28
(define (nrepl-clients-set! server clients)−29
(vector-set! server 4 clients))−30
−31
+ (define (nrepl-tasks server)−32
+ (vector-ref server 5))−33
+−34
+ (define (nrepl-tasks-set! server tasks)−35
+ (vector-set! server 5 tasks))−36
+−37
+ ;; Eval task: (vector 'nrepl-eval-task client request-id thunk k response done? aborted?)−38
+ (define (make-eval-task client id thunk)−39
+ (vector 'nrepl-eval-task client id thunk #f #f #f #f))−40
+−41
+ (define (eval-task-client task) (vector-ref task 1))−42
+ (define (eval-task-id task) (vector-ref task 2))−43
+ (define (eval-task-thunk task) (vector-ref task 3))−44
+ (define (eval-task-k task) (vector-ref task 4))−45
+ (define (eval-task-k-set! task k) (vector-set! task 4 k))−46
+ (define (eval-task-response task) (vector-ref task 5))−47
+ (define (eval-task-response-set! task response) (vector-set! task 5 response))−48
+ (define (eval-task-done? task) (vector-ref task 6))−49
+ (define (eval-task-done-set! task done?) (vector-set! task 6 done?))−50
+ (define (eval-task-aborted? task) (vector-ref task 7))−51
+ (define (eval-task-aborted-set! task aborted?) (vector-set! task 7 aborted?))−52
+−53
;; Client connection: (vector 'nrepl-client socket buffer session-id module debug-mode? debug-error debug-trace debug-frame)−54
;; debug-mode?: #t when client is in debug mode after an error−55
;; debug-error: the error message string when in debug mode−56
@@ -233,6 +256,8 @@−57
;; Dispatch on operation−58
(case op−59
((eval) (handle-eval client id args))−60
+ ((abort interrupt) (make-error-response* client id "server-required"−61
+ "abort requests must be handled by an nREPL server"))−62
((complete) (handle-complete client id args))−63
((doc) (handle-doc client id args))−64
((describe) (handle-describe client id args))−65
@@ -394,6 +419,101 @@−66
(format "~s" value))))))))))−67
result))))))−68
−69
+ (define (make-eval-thunk client id args)−70
+ (lambda () (handle-eval client id args)))−71
+−72
+ (define (enqueue-eval-task! server client id args)−73
+ (let ((task (make-eval-task client id (make-eval-thunk client id args))))−74
+ (nrepl-tasks-set! server (append (nrepl-tasks server) (list task)))−75
+ #f))−76
+−77
+ (define (find-task-by-id server target-id)−78
+ (let loop ((tasks (nrepl-tasks server)))−79
+ (cond−80
+ ((null? tasks) #f)−81
+ ((string=? (eval-task-id (car tasks)) target-id) (car tasks))−82
+ (else (loop (cdr tasks))))))−83
+−84
+ (define (handle-abort server client id args)−85
+ (let ((target-id (get-arg args ':target-id #f)))−86
+ (if (not target-id)−87
+ (make-error-response* client id "missing-target-id" "No :target-id provided")−88
+ (let ((task (find-task-by-id server target-id)))−89
+ (if task−90
+ (begin−91
+ (eval-task-aborted-set! task #t)−92
+ (eval-task-k-set! task #f)−93
+ (eval-task-response-set! task−94
+ (make-error-response* (eval-task-client task) (eval-task-id task)−95
+ "interrupted" "Evaluation interrupted"))−96
+ (eval-task-done-set! task #t)−97
+ (make-response* client id 'ok ':aborted target-id))−98
+ (make-error-response* client id "not-found"−99
+ (format "No running eval for request id ~a" target-id)))))))−100
+−101
+ (define (nrepl-handle-request* server client request)−102
+ (if (not (and (pair? request) (eq? (car request) 'request)))−103
+ (make-error-response "unknown" "invalid-request" "Expected (request ...)")−104
+ (let* ((args (cdr request))−105
+ (id (get-arg args ':id "unknown"))−106
+ (op (get-arg args ':op #f))−107
+ (module-name (get-arg args ':module #f)))−108
+ (when (and module-name (string? module-name) (not (string=? module-name "")))−109
+ (let ((mod (find-module (read-expr module-name))))−110
+ (when mod−111
+ (client-module-set! client mod))))−112
+ (case op−113
+ ((eval) (enqueue-eval-task! server client id args))−114
+ ((abort interrupt) (handle-abort server client id args))−115
+ (else (nrepl-handle-request client request))))))−116
+−117
+ (define (run-eval-task-slice! task)−118
+ (when (and (not (eval-task-done? task))−119
+ (not (eval-task-aborted? task)))−120
+ (let ((saved-module (current-module))−121
+ (result #f))−122
+ (set-current-module! (client-module (eval-task-client task)))−123
+ (set-vm-async-prompt-tag! async-prompt-tag)−124
+ (set-vm-yield-requested! #t)−125
+ (set! result−126
+ (call-with-prompt−127
+ async-prompt-tag−128
+ (lambda (k op-type arg1 arg2)−129
+ (eval-task-k-set! task k)−130
+ 'yielded)−131
+ (lambda ()−132
+ (let ((k (eval-task-k task)))−133
+ (if k−134
+ (begin−135
+ (eval-task-k-set! task #f)−136
+ (k #t))−137
+ ((eval-task-thunk task)))))))−138
+ (set-vm-yield-requested! #f)−139
+ (set-current-module! saved-module)−140
+ (when (not (eq? result 'yielded))−141
+ (eval-task-response-set! task result)−142
+ (eval-task-done-set! task #t)))))−143
+−144
+ (define (send-eval-task-response! task)−145
+ (let ((response (eval-task-response task))−146
+ (client (eval-task-client task)))−147
+ (when response−148
+ (socket-write (client-socket client)−149
+ (encode-message response)))))−150
+−151
+ (define (process-eval-tasks server)−152
+ (let loop ((tasks (nrepl-tasks server))−153
+ (remaining '()))−154
+ (if (null? tasks)−155
+ (nrepl-tasks-set! server (reverse remaining))−156
+ (let ((task (car tasks)))−157
+ (run-eval-task-slice! task)−158
+ (if (eval-task-done? task)−159
+ (begin−160
+ (send-eval-task-response! task)−161
+ (loop (cdr tasks) remaining))−162
+ (loop (cdr tasks) (cons task remaining)))))))−163
+−164
;; complete - return completions for a prefix−165
;; Includes both value bindings and syntax/macro bindings−166
(define (handle-complete client id args)−167
@@ -550,7 +670,9 @@−168
;; Accept new connections−169
(accept-pending-connections server)−170
;; Process data from existing clients−171
- (process-client-data server)))−172
+ (process-client-data server)−173
+ ;; Run one cooperative slice for each pending eval task.−174
+ (process-eval-tasks server)))−175
−176
;; Accept any pending connections−177
(define (accept-pending-connections server)−178
@@ -608,10 +730,11 @@−179
(remaining (cdr result)))−180
(client-buffer-set! client remaining)−181
;; Handle the request−182
- (let ((response (nrepl-handle-request client message)))−183
+ (let ((response (nrepl-handle-request* server client message)))−184
;; Send response−185
- (socket-write (client-socket client)−186
- (encode-message response)))−187
+ (when response−188
+ (socket-write (client-socket client)−189
+ (encode-message response))))−190
;; Check for more messages−191
(process-client-messages server client)))))−192
notes/item1-test-server-prototype.sgldeleted
@@ -1,110 +0,0 @@
−1
(import (sigil test)−2
(sigil socket)−3
(sigil io)−4
(sigil math)−5
(sigil string)−6
(sigil nrepl))−7
−8
(define *test-port* 57888)−9
−10
(define (encode-test-message sexp)−11
(let* ((str (format "~s" sexp))−12
(msg-bytes (string->utf8 str))−13
(len (bytevector-length msg-bytes))−14
(prefix (make-bytevector 4)))−15
(bytevector-u8-set! prefix 0 (quotient len 16777216))−16
(bytevector-u8-set! prefix 1 (quotient (remainder len 16777216) 65536))−17
(bytevector-u8-set! prefix 2 (quotient (remainder len 65536) 256))−18
(bytevector-u8-set! prefix 3 (remainder len 256))−19
(bytevector-append prefix msg-bytes)))−20
−21
(define (send-request sock request)−22
(socket-write sock (encode-test-message request)))−23
−24
(define (read-at-least sock n acc attempts)−25
(cond−26
((<= attempts 0) #f)−27
((>= (bytevector-length acc) n) acc)−28
((socket-ready? sock 20)−29
(let ((data (socket-read-bytevector sock)))−30
(if (and data (not (eof-object? data)) (> (bytevector-length data) 0))−31
(read-at-least sock n (bytevector-append acc data) attempts)−32
(read-at-least sock n acc (- attempts 1)))))−33
(else (read-at-least sock n acc (- attempts 1)))))−34
−35
(define (read-response sock)−36
(let ((header (read-at-least sock 4 (make-bytevector 0) 100)))−37
(if header−38
(let* ((b0 (bytevector-u8-ref header 0))−39
(b1 (bytevector-u8-ref header 1))−40
(b2 (bytevector-u8-ref header 2))−41
(b3 (bytevector-u8-ref header 3))−42
(len (+ (* b0 16777216) (* b1 65536) (* b2 256) b3))−43
(needed (+ 4 len))−44
(full (read-at-least sock needed header 100)))−45
(and full−46
(read-expr (utf8->string (bytevector-copy full 4 needed)))))−47
#f)))−48
−49
(define (response-ref resp key default)−50
(let loop ((rest (if (and (pair? resp) (eq? (car resp) 'response))−51
(cdr resp)−52
'())))−53
(cond−54
((null? rest) default)−55
((null? (cdr rest)) default)−56
((eq? (car rest) key) (cadr rest))−57
(else (loop (cddr rest))))))−58
−59
(define (pump server n)−60
(when (> n 0)−61
(nrepl-process-pending server)−62
(pump server (- n 1))))−63
−64
(define (with-server thunk)−65
(let ((server (nrepl-start *test-port*)))−66
(dynamic-wind−67
(lambda () #t)−68
(lambda () (thunk server))−69
(lambda () (nrepl-stop server)))))−70
−71
(test-group "nrepl server eval"−72
(test "eval round-trips through a loopback server"−73
(with-server−74
(lambda (server)−75
(let ((sock (tcp-connect "127.0.0.1" *test-port*)))−76
(send-request sock '(request :id "eval-ok" :op eval :code "(+ 1 2)"))−77
(pump server 20)−78
(let ((resp (read-response sock)))−79
(assert-eq (response-ref resp ':status #f) 'ok)−80
(assert-equal (response-ref resp ':value #f) "3"))−81
(socket-close sock))))))−82
−83
(test-group "nrepl abort"−84
(test "runaway eval can be aborted from a second connection and the session survives"−85
(with-server−86
(lambda (server)−87
(let ((eval-sock (tcp-connect "127.0.0.1" *test-port*))−88
(abort-sock #f))−89
(send-request eval-sock−90
'(request :id "runaway" :op eval :code "(let loop () (loop))"))−91
(pump server 5)−92
(set! abort-sock (tcp-connect "127.0.0.1" *test-port*))−93
(send-request abort-sock−94
'(request :id "abort-runaway" :op abort :target-id "runaway"))−95
(pump server 20)−96
(let ((abort-resp (read-response abort-sock))−97
(eval-resp (read-response eval-sock)))−98
(assert-eq (response-ref abort-resp ':status #f) 'ok)−99
(assert-eq (response-ref eval-resp ':status #f) 'error)−100
(assert-equal (response-ref eval-resp ':code #f) "interrupted"))−101
(send-request eval-sock−102
'(request :id "after-abort" :op eval :code "(+ 20 22)"))−103
(pump server 20)−104
(let ((after-resp (read-response eval-sock)))−105
(assert-eq (response-ref after-resp ':status #f) 'ok)−106
(assert-equal (response-ref after-resp ':value #f) "42"))−107
(socket-close eval-sock)−108
(socket-close abort-sock))))))−109
−110
(run-tests)