Commitf578388cRecorded1 Jun 2026Repositorysigil-socket

Use async DNS when a scheduler is active

Message

Wrap resolve-hostname and tcp-connect so scheduler-backed callers offload DNS through the new resolver fd natives, wait with the existing fd readiness protocol, and connect by numeric address. Non-async callers continue to use the synchronous native path.

The async resolver path uses dynamic-wind to cancel outstanding native jobs if the continuation exits before taking the result.

Verification: guix shell -m manifest.scm -- make test in the Sigil monorepo with this checkout redirected as sigil-socket.

Changed
 src/sigil/socket.sgl | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 59 insertions(+), 4 deletions(-)
Diff
src/sigil/socket.sglmodified
@@ -35,7 +35,8 @@
35
;;; ```
36
37
(define-library (sigil socket)
38
(import (sigil string))
+38
(import (sigil async scheduler)
+39
(sigil string))
40
41
(export
42
;; Type predicate
@@ -91,6 +92,60 @@
92
93
(begin
94
+95
(define-native (%tcp-connect-sync host port)
+96
(: string? integer? -> (maybe socket?)))
+97
+98
(define-native (%tcp-connect-ip ip port)
+99
(: string? integer? -> (maybe socket?)))
+100
+101
(define-native (%resolve-hostname-sync host)
+102
(: string? -> (maybe list?)))
+103
+104
(define-native (%resolve-start host)
+105
(: string? -> (maybe integer?)))
+106
+107
(define-native (%resolve-take fd)
+108
(: integer? -> (maybe list?)))
+109
+110
(define-native (%resolve-cancel fd)
+111
(: integer? -> boolean?))
+112
+113
(define (%await-readable-fd fd)
+114
(: integer? -> any?)
+115
(if (current-scheduler)
+116
(abort-to-prompt async-prompt-tag 'io-read-fd fd #f)
+117
(fd-select (list fd) '() #f)))
+118
+119
(define (resolve-hostname host)
+120
(: string? -> (maybe list?))
+121
(if (current-scheduler)
+122
(let ((fd (%resolve-start host)))
+123
(if fd
+124
(dynamic-wind
+125
(lambda () #t)
+126
(lambda ()
+127
(%await-readable-fd fd)
+128
(%resolve-take fd))
+129
(lambda ()
+130
(%resolve-cancel fd)))
+131
#f))
+132
(%resolve-hostname-sync host)))
+133
+134
(define (tcp-connect host port)
+135
(: string? integer? -> (maybe socket?))
+136
(if (current-scheduler)
+137
(let ((ips (resolve-hostname host)))
+138
(let loop ((remaining ips))
+139
(cond
+140
((not remaining) #f)
+141
((null? remaining) #f)
+142
(else
+143
(let ((sock (%tcp-connect-ip (car remaining) port)))
+144
(if sock
+145
sock
+146
(loop (cdr remaining))))))))
+147
(%tcp-connect-sync host port)))
+148
149
;; ========== Native Procedure Documentation ==========
150
151
;;; Check if a value is a socket object.
@@ -252,13 +307,13 @@
307
(%set-docstring! gethostname)
308
(%set-spec! gethostname '(-> string?))
309
255
;;; Resolve a hostname to an IP address.
+310
;;; Resolve a hostname to IP addresses.
311
;;;
312
;;; ```scheme
258
;;; (resolve-hostname "localhost") ; => "127.0.0.1"
+313
;;; (resolve-hostname "localhost") ; => ("127.0.0.1")
314
;;; ```
315
(%set-docstring! resolve-hostname)
261
(%set-spec! resolve-hostname '(string? -> (maybe string?)))
+316
(%set-spec! resolve-hostname '(string? -> (maybe list?)))
317
318
;;; Get the local address and port of a socket.
319
;;;