commander-s/scheme/job.scm

376 lines
10 KiB
Scheme

(define-record-type job :job
(really-make-job name console
proc status
start-time end-time run-status)
job?
(name job-name)
(console job-console)
(proc job-proc)
(status really-job-status set-job-status!)
(start-time job-start-time)
(end-time job-end-time set-job-end-time!)
(run-status job-run-status set-job-run-status!))
(define-record-discloser :job
(lambda (r)
`(job ,(job-name r) ,(job-run-status r))))
(define (make-job-with-console name proc pty-in pty-out terminal-buffer)
(let ((job (really-make-job
name
(make-console pty-in pty-out
(app-window-curses-win (result-window))
terminal-buffer)
proc (make-placeholder)
(date) #f 'running)))
(spawn-job-status-surveillant job)
(add-job! job)
job))
(define (make-job-sans-console name proc)
(let ((job (really-make-job
name #f proc (make-placeholder)
(date) #f 'running)))
(spawn-job-status-surveillant job)
(add-job! job)
job))
(define (job-with-console? v)
(and (job? v) (job-console v)))
(define (job-sans-console? v)
(not (job-with-console? v)))
(define (job-status-rv job)
(placeholder-value-rv (really-job-status job)))
(define (job-status job)
(sync (job-status-rv job)))
(define (spawn-job-status-surveillant job)
(spawn
(lambda ()
(let ((status (wait (job-proc job) wait/stopped-children)))
(cond
((status:exit-val status)
=> (lambda (i)
(debug-message "spawn-job-status-surveillant exit-val " i)
(set-job-run-status! job 'ready)
(set-job-end-time! job (date))))
((status:stop-sig status)
=> (lambda (signal)
(debug-message "spawn-job-status-surveillant stop-sig " signal)
(cond
((= signal signal/ttin)
(set-job-run-status! job 'waiting-for-input))
((= signal signal/ttou)
(set-job-run-status! job 'new-output))
(else
(set-job-run-status! job 'stopped)))))
((status:term-sig status)
=> (lambda (i)
(debug-message "spawn-job-status-surveillant term-sig " i)
(set-job-run-status! job 'stopped))))
(placeholder-set!
(really-job-status job) status)))))
(define (job-running? job)
(eq? (job-run-status job) 'running))
(define (job-ready? job)
(eq? (job-run-status job) 'ready))
(define (job-waiting-for-input? job)
(eq? (job-run-status job) 'waiting-for-input))
(define (job-has-new-output? job)
(eq? (job-run-status job) 'new-output))
(define (job-stopped? job)
(eq? (job-run-status job) 'stopped))
(define (signal-job signal job)
(signal-process-group (job-proc job) signal))
(define (stop-job job)
(signal-job signal/stop job))
(define (continue-job job)
(set-job-status! job (make-placeholder))
(set-job-run-status! job 'running)
(signal-process-group
(proc:pid (job-proc job)) signal/cont)
(spawn-job-status-surveillant job)
(send notify-continue-channel job))
(define (pause-job-output job)
(pause-console-output (job-console job)))
(define (resume-job-output job)
(resume-console-output (job-console job)))
(define (continue-job-in-foreground job)
(if (job-sans-console? job)
(begin
(drain-tty (current-output-port))
(def-prog-mode)
(clear)
(refresh)
(endwin)
; (newline)
; (drain-tty (current-output-port))
(obtain-lock paint-lock)
(set-tty-process-group
(current-output-port) (proc:pid (job-proc job)))
(continue-job job)
(job-status job)
(set-tty-process-group (current-output-port) (pid))
(display "Press any key to return to Commander S...")
(wait-for-key)
(set-redisplay-everything)
(release-lock paint-lock))))
(define (continue-job-in-background job)
(continue-job job))
;; channels for communicating with the joblist surveillant
(define add-job-channel
(make-channel))
(define get-job-list-channel
(make-channel))
(define clear-ready-jobs-channel
(make-channel))
(define notify-continue-channel
(make-channel))
(define (add-job! job)
(send add-job-channel job))
(define (running-jobs)
(let ((answer-channel (make-channel)))
(send get-job-list-channel (cons 'running answer-channel))
(receive answer-channel)))
(define (ready-jobs)
(let ((answer-channel (make-channel)))
(send get-job-list-channel (cons 'ready answer-channel))
(receive answer-channel)))
(define (stopped-jobs)
(let ((answer-channel (make-channel)))
(send get-job-list-channel (cons 'stopped answer-channel))
(receive answer-channel)))
(define (clear-ready-jobs!)
(send clear-ready-jobs-channel 'ignored))
(define (jobs-with-new-output)
(let ((answer-channel (make-channel)))
(send get-job-list-channel (cons 'new-output answer-channel))
(receive answer-channel)))
(define (jobs-waiting-for-input)
(let ((answer-channel (make-channel)))
(send get-job-list-channel (cons 'waiting-for-input answer-channel))
(receive answer-channel)))
(define (spawn-joblist-surveillant)
(let ((statistics-channel (make-channel)))
(spawn
(lambda ()
(let lp ((running '())
(ready '())
(stopped '())
(new-output '())
(waiting-for-input '())
(notify? #f))
(debug-message "spawn-joblist-surveillant "
running " " ready " " stopped " "
new-output " " waiting-for-input " " notify?)
(cond
(notify?
(send statistics-channel
(list (cons 'running (length running))
(cons 'ready (length ready))
(cons 'stopped (length stopped))
(cons 'new-output (length new-output))
(cons 'waiting-for-input (length waiting-for-input))))
(lp running ready stopped new-output waiting-for-input #f))
(else
(apply
select
(append
(list
(wrap (receive-rv add-job-channel)
(lambda (new-job)
(lp (cons new-job running)
ready stopped new-output waiting-for-input #t)))
(wrap (receive-rv notify-continue-channel)
(lambda (job)
(lp (cons job running)
ready
(delete job stopped)
(delete job new-output)
(delete job waiting-for-input) #t)))
(wrap (receive-rv clear-ready-jobs-channel)
(lambda (ignore)
(lp running '() stopped new-output waiting-for-input #t)))
(wrap (receive-rv get-job-list-channel)
(lambda (state.channel)
(send (cdr state.channel)
(case (car state.channel)
((running) running)
((ready) ready)
((stopped) stopped)
((new-output) new-output)
((waiting-for-input) waiting-for-input)
(else
(error "joblist-surveillant" state.channel))))
(lp running ready stopped new-output waiting-for-input #f))))
(map (lambda (job)
(wrap (job-status-rv job)
(lambda (status)
(cond
((status:exit-val status)
=> (lambda (ignore)
(lp (delete job running)
(cons job ready) stopped
new-output waiting-for-input #t)))
((status:stop-sig status)
=> (lambda (signal)
(cond
((= signal signal/ttin)
(lp (delete job running)
ready stopped new-output
(cons job waiting-for-input) #t))
((= signal signal/ttou)
(lp (delete job running)
ready stopped
(cons job new-output)
waiting-for-input #t))
((or (= signal signal/tstp)
(= signal signal/stop))
;; TODO catch any other signal here
(stop-job job)
(lp (delete job running)
ready (cons job stopped)
new-output waiting-for-input #t))
(else
(error "Unhandled signal" signal)))))
((status:term-sig status)
=> (lambda (signal)
(lp (delete job running)
ready (cons job stopped)
new-output waiting-for-input #t)))))))
running))))))))
statistics-channel))
(define (initial-job-statistics)
(list (cons 'running 0)
(cons 'ready 0)
(cons 'new-output 0)
(cons 'waiting-for-input 0)))
;; #### unfinished
(define (install-terminal/stop-handler)
(set-interrupt-handler
interrupt/tstp
(lambda args
(display args))))
(define (save-tty-excursion port thunk)
(let ((settings (tty-info port)))
(let ((val (thunk)))
(set-tty-info/now port settings)
val)))
;; run a job by running the program form
;; for use in command mode (used by command-line-compiler)
(define (run/console* s-expr)
(call-with-values
(lambda ()
(fork-pty-session
(lambda ()
(handle-signal-default signal/ttou)
(eval-s-expr s-expr))))
(lambda (proc pty-in pty-out tty-name)
(make-job-with-console
s-expr proc pty-in pty-out
(make-terminal-buffer
(result-buffer-num-cols (result-buffer))
(result-buffer-num-lines (result-buffer)))))))
;; for use in Scheme mode
(define-syntax run/console
(syntax-rules ()
((_ epf)
(run/console* '(exec-epf epf)))))
;; for use in command mode (used by command-line-compiler)
(define (run/fg* s-expr)
(debug-message "run/fg* " s-expr)
(save-tty-excursion
(current-input-port)
(lambda ()
(obtain-lock paint-lock)
(def-prog-mode)
(clear)
(refresh)
(endwin)
(restore-initial-tty-info! (current-input-port))
(drain-tty (current-output-port))
(let ((foreground-pgrp (tty-process-group (current-output-port)))
(proc
(fork
(lambda ()
(let ((child-pid (pid)))
(set-process-group child-pid)
(set-tty-process-group (current-output-port) child-pid)
(handle-signal-default signal/ttou)
(eval-s-expr s-expr))))))
(let* ((job (make-job-sans-console s-expr proc))
(status (job-status job)))
(set-tty-process-group (current-output-port) foreground-pgrp)
(newline)
(display "Press any key to return to Commander S...")
(wait-for-key)
(release-lock paint-lock)
job)))))
(define-syntax run/fg
(syntax-rules ()
((_ epf)
(run/fg* '(exec-epf epf)))))
;; for use in command mode (used by command-line-compiler)
(define (run/bg* s-expr)
(obtain-lock paint-lock)
(drain-tty (current-output-port))
(set-tty-process-group (current-output-port) (pid))
(let ((proc
(fork
(lambda ()
(set-process-group (pid) (pid))
(handle-signal-default signal/ttou)
(eval-s-expr s-expr)))))
(let ((job (make-job-sans-console s-expr proc)))
(release-lock paint-lock)
job)))
;; for use in Scheme mode
(define-syntax run/bg
(syntax-rules ()
((_ epf)
(run/bg* '(exec-epf epf)))))
;;; EOF