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)