#lang racket (require "status.rkt" "notify.rkt" "rewriting.rkt" "dirstruct.rkt" "cache.rkt") (define (command+args+env->command+args #:env env cmd args) (values "/usr/bin/env" (append (for/list ([(k v) (in-hash env)]) (format "~a=~a" k v)) (list* cmd args)))) (define (run/collect/wait #:env env #:timeout timeout command args) (define start-time (current-inexact-milliseconds)) ; Run the command (define-values (new-command new-args) (command+args+env->command+args #:env env command args)) (define command-line (list* command args)) (define-values (the-process stdout stdin stderr) (parameterize ([subprocess-group-enabled #t]) (apply subprocess #f #f #f new-command new-args))) (notify! "Running: ~a ~S" command args) ; Run it without input (close-output-port stdin) ; Wait for all the output and the process death or timeout (local [(define the-alarm (alarm-evt (+ start-time (* 1000 timeout)))) (define line-ch (make-channel)) (define (read-port-t make port) (thread (λ () (let loop () (define l (read-bytes-line port)) (if (eof-object? l) (channel-put line-ch l) (begin (channel-put line-ch (make l)) (loop))))))) (define stdout-t (read-port-t make-stdout stdout)) (define stderr-t (read-port-t make-stderr stderr)) (define final-status (let loop ([open-ports 2] [end-time #f] [status #f] [log empty]) (define process-done? (and end-time #t)) (define output-done? (zero? open-ports)) (if (and output-done? process-done?) (if status (make-exit start-time end-time command-line (reverse log) status) (make-timeout start-time end-time command-line (reverse log))) (sync (if process-done? never-evt (choice-evt (handle-evt the-alarm (λ (_) (define end-time (current-inexact-milliseconds)) (subprocess-kill the-process #f) (sleep) (subprocess-kill the-process #t) (loop open-ports end-time status log))) (handle-evt the-process (λ (_) (define end-time (current-inexact-milliseconds)) (loop open-ports end-time (subprocess-status the-process) log))))) (if output-done? never-evt (handle-evt line-ch (match-lambda [(? eof-object?) (loop (sub1 open-ports) end-time status log)] [l (loop open-ports end-time status (list* l log))])))))))] (close-input-port stdout) (close-input-port stderr) (notify! "Done: ~a ~S" command args) final-status)) (define-syntax regexp-replace** (syntax-rules () [(_ () s) s] [(_ ([pat0 subst0] [pat subst] ...) s) (regexp-replace* (regexp-quote pat0) (regexp-replace** ([pat subst] ...) s) subst0)])) (define (run/collect/wait/log log-path command #:timeout timeout #:env env args) (define ran? #f) (cache/file log-path (lambda () (define rev (number->string (current-rev))) (define home (hash-ref env "HOME")) (define tmp (hash-ref env "TMPDIR")) (define cwd (path->string (current-directory))) (define (rewrite s) (regexp-replace** ([rev ""] [tmp ""] [home ""] [cwd ""]) s)) (set! ran? #t) (rewrite-status #:rewrite rewrite (run/collect/wait #:timeout timeout #:env env command args)))) ran?) (provide/contract [command+args+env->command+args (string? (listof string?) #:env (hash/c string? string?) . -> . (values string? (listof string?)))] [run/collect/wait (string? #:env (hash/c string? string?) #:timeout exact-nonnegative-integer? (listof string?) . -> . status?)] [run/collect/wait/log (path-string? string? #:env (hash/c string? string?) #:timeout exact-nonnegative-integer? (listof string?) . -> . boolean?)])