kindergarten added by siiky on Fri May 26 13:14:33 2023
(import
(chicken file posix)
(chicken process)
(chicken process-context)
typed-records
)
; Kindergarten is a Process Pool abstraction. You spawn child processes into
; the Kindergarten and at the end wait for them.
;
; Usage example with chain from SRFI 197:
;
;(chain empty-kindergarten
; (kindergarten-spawn _ "ls" '() name: 'ls-cwd)
; (kindergarten-spawn _ "ls" '("/") name: 'ls-root)
; (kindergarten-spawn _ "echo" '("hello"))
; (kindergarten-spawn _ "cat" '("kinder.scm") name: 'a-kind-cat)
; (kindergarten-wait _))
(define (i-am-luke cmd args cwd env stdin stdout stderr)
(when cwd (change-directory cwd))
(when stdin (duplicate-fileno stdin fileno/stdin))
(when stdout (duplicate-fileno stdout fileno/stdout))
(when stderr (duplicate-fileno stderr fileno/stderr))
(process-execute cmd args env))
(define (i-am-your-father pid stdin stdout stderr)
(let ((stdin (and stdin (open-output-file* stdin)))
(stdout (and stdout (open-input-file* stdout)))
(stderr (and stderr (open-input-file* stderr))))
(values pid stdin stdout stderr)))
(define (maybe-setup-pipe std*)
(if std* (create-pipe) (values #f #f)))
(define (spawn cmd args
#!key
(stdin #t) (stdout #t) (stderr #t)
cwd env)
(let-values
(((stdin/r stdin/w) (maybe-setup-pipe stdin))
((stdout/r stdout/w) (maybe-setup-pipe stdout))
((stderr/r stderr/w) (maybe-setup-pipe stderr)))
(let ((pid (process-fork)))
(cond
((zero? pid) (i-am-luke cmd args cwd env stdin/r stdout/w stderr/w)) ; noret
((positive? pid) (i-am-your-father pid stdin/w stdout/r stderr/r))
(else (values #f #f #f #f)))))) ; error
(defstruct kind
name ; Anything, chosen by the user
(pid : fixnum)
(stdin : (or output-port false))
(stdout : (or input-port false))
(stderr : (or input-port false))
(exited? : boolean)
(exited-normally? : boolean)
((exit-status 0) : fixnum)
)
(define exit/ok? zero?)
(define (exited-ok? exited-normally? exit-status)
(and exited-normally? (exit/ok? exit-status)))
(define (kind-exited-ok? kind)
(and (kind-exited? kind)
(exited-ok? (kind-exited-normally? kind) (kind-exit-status kind))))
(define (kind-spawn cmd args
#!key
(stdin #t) (stdout #t) (stderr #t)
cwd env name)
(receive (pid stdin stdout stderr) (spawn cmd args
stdin: stdin stdout: stdout stderr: stderr
cwd: cwd env: env)
(make-kind pid: pid name: name stdin: stdin stdout: stdout stderr: stderr)))
; TODO: Update kind
(define (kind-wait kind #!optional nohang)
(process-wait (kind-pid kind) nohang))
(defstruct kindergarten
((running '()) : (list-of (struct kind)))
((successfully-exited '()) : (list-of (struct kind)))
((unsuccessfully-exited '()) : (list-of (struct kind)))
)
(define empty-kindergarten (make-kindergarten))
(: kindergarten-enroll ((struct kindergarten) (struct kind) --> (struct kindergarten)))
; Assume kind was just spawn and is still running
(define (kindergarten-enroll kg kind)
(kindergarten-update kg running: (cons kind (kindergarten-running kg))))
(define (kindergarten-spawn kg cmd args
#!key
(stdin #t) (stdout #t) (stderr #t)
cwd env name)
(let ((kind (kind-spawn cmd args
stdin: stdin stdout: stdout stderr: stderr
cwd: cwd env: env name: name)))
(kindergarten-enroll kg kind)))
; TODO: Update to be compatible with the kindergarten struct
(define (kindergarten-wait1 kg)
(let loop ((next-kg '())
(kg kg))
(if (null? kg)
(if (null? next-kg)
(values '() #f)
(loop '() next-kg))
(let ((kind (car kg))
(kg (cdr kg)))
(receive (pid exited-normally? exit-status) (kind-wait kind #t)
(if (zero? pid) ; process hasn't exited yet
(loop (cons kind next-kg) kg)
(let ((kind (kind-update kind exited?: #t exited-normally?: exited-normally? exit-status: exit-status))
(kg (delete pid kg (lambda (pid k) (= pid (kind-pid k))))))
(values kg kind))))))))
(define (kindergarten-wait1/any kg)
(if (null? kg)
(values '() #f)
(receive (pid exited-normally? exit-status) (process-wait)
(let ((kind (and-let* ((kind (find (lambda (k) (= (kind-pid k) pid)) kg)))
(kind-update kind exited?: #t exited-normally?: exited-normally? exit-status: exit-status))))
(if kind
(let ((kg (delete pid kg (lambda (pid k) (= pid (kind-pid k))))))
(values kg kind))
(kindergarten-wait1/any kg))))))
(define ((while func) kg)
(unless (null? kg)
(receive (kg . _) (func kg)
((while func) kg))))
(define kindergarten-wait! (while kindergarten-wait1))
(define kindergarten-wait!/any (while kindergarten-wait1/any))
(define ((kindergarten-wait-acc-loop func) kg)
(let loop ((ret '())
(kg kg))
(if (null? kg)
ret
(receive (kg kind) (func kg)
(let ((ret (if (or (not kind) (kind-exited-ok? kind))
ret
(cons kind ret))))
(loop ret kg))))))
;; @brief Wait for all children of a kindergarten. Busy poll.
(define kindergarten-wait (kindergarten-wait-acc-loop kindergarten-wait1))
;; @brief Wait for all children of a kindergarten. Blocking waitpid().
;; NOTE: May only be used if you're sure there are no child processes other
;; than those in @a kg!
(define kindergarten-wait/any (kindergarten-wait-acc-loop kindergarten-wait1/any))