racket/collects/setup/parallel-build.rkt
Matthew Flatt a41a607cbb tweak build message again
to what I meant in the first place
2011-04-19 21:28:14 -06:00

259 lines
9.9 KiB
Racket

#lang racket/base
(require compiler/cm
racket/list
racket/match
racket/path
racket/fasl
racket/serialize
setup/collects
setup/parallel-do
racket/class
racket/future
compiler/find-exe
racket/place
(for-syntax racket/base))
(provide parallel-compile
parallel-compile-files)
(define-syntax-rule (DEBUG_COMM a ...)
(void)
; (begin a ...)
)
(define Lock-Manager% (class object%
(field (locks (make-hash)))
(define/public (lock fn wrkr)
(let ([v (hash-ref locks fn #f)])
(hash-set! locks fn
(if v
(match v [(list w waitlst) (list w (append waitlst (list wrkr)))])
(begin
(wrkr/send wrkr (list 'locked))
(list wrkr null))))
(not v)))
(define/public (unlock fn)
(match (hash-ref locks fn)
[(list w waitlst)
(for ([x (second (hash-ref locks fn))])
(wrkr/send x (list 'compiled)))
(hash-remove! locks fn)]))
(super-new)))
(define/class/generics Lock-Manager%
(lm/lock lock fn wrkr)
(lm/unlock unlock fn))
(define (->bytes x)
(cond [(path? x) (path->bytes x)]
[(string? x) (string->bytes/locale x)]))
(define CollectsQueue% (class* object% (WorkQueue<%>)
(init-field cclst printer append-error)
(field (lock-mgr (new Lock-Manager%)))
(field (hash (make-hash)))
(inspect #f)
(define/public (work-done work wrkr msg)
(match (list work msg)
[(list (list cc file last) (list result-type out err))
(begin0
(match result-type
[(list 'ERROR msg)
(append-error cc "making" (exn msg (current-continuation-marks)) out err "error")
#t]
[(list 'LOCK fn) (lm/lock lock-mgr fn wrkr) #f]
[(list 'UNLOCK fn) (lm/unlock lock-mgr fn) #f]
['DONE
(define (string-!empty? s) (not (zero? (string-length s))))
(when (ormap string-!empty? (list out err))
(append-error cc "making" null out err "output"))
(when last (printer (current-output-port) "made" "~a" (cc-name cc)))
#t]
[else (eprintf "Failed trying to match:\n~v\n" result-type)]))]
[else
(match work
[(list-rest (list cc file last) message)
(append-error cc "making" null "" "" "error")
(eprintf "work-done match cc failed.\n")
(eprintf "trying to match:\n~a\n" (list work msg))
#t]
[else
(eprintf "work-done match cc failed.\n")
(eprintf "trying to match:\n~a\n" (list work msg))
(eprintf "FATAL\n")
(exit 1)])]))
;; assigns a collection to each worker to be compiled
;; when it runs out of collections, steals work from other workers collections
(define/public (get-job workerid)
(define (find-job-in-cc cc id)
(define (retry) (get-job workerid))
(define (build-job cc file last)
(values
(list cc file last)
(list (->bytes (cc-name cc))
(->bytes (cc-path cc))
(->bytes file))))
(match cc
[(list)
(hash-remove! hash id) (retry)]
[(list (list cc (list) (list))) ;empty collect
(hash-remove! hash id) (retry)]
[(cons (list cc (list) (list)) tail) ;empty parent collect
(hash-set! hash id tail) (retry)]
[(cons (list cc (list) subs) tail) ;empty srcs list
(hash-set! hash id (append subs tail)) (retry)]
[(cons (list cc (list file) subs) tail)
(hash-set! hash id (append subs tail))
(build-job cc file #t)]
[(cons (list cc (cons file ft) subs) tail)
(hash-set! hash id (cons (list cc ft subs) tail))
(build-job cc file #f)]
[else
(eprintf "get-job match cc failed.\n")
(eprintf "trying to match:\n~v\n" cc)]))
; find a cc
(cond
; lookup already assigned cc
[(hash-ref hash workerid #f) => (lambda (x)
(find-job-in-cc x workerid))]
; get next cc from cclst
[(pair? cclst)
(define workercc (list (car cclst)))
(set! cclst (cdr cclst))
(hash-set! hash workerid workercc)
(find-job-in-cc workercc workerid)]
; try to steal work from another workers cc
[(hash-iterate-first hash) => (lambda (x)
(find-job-in-cc (hash-iterate-value hash x)
(hash-iterate-key hash x)))]))
; no work left
; should never get here, get-job only called when the queue has work
(define/public (has-jobs?)
(define (hasjob? cct)
(let loop ([cct cct])
(ormap (lambda (x) (or ((length (second x)) . > . 0) (loop (third x)))) cct)))
(or (hasjob? cclst)
(for/or ([cct (in-hash-values hash)])
(hasjob? cct))))
(define/public (jobs-cnt)
(define (count-cct cct)
(let loop ([cct cct])
(apply + (map (lambda (x) (+ (length (second x)) (loop (third x)))) cct))))
(+ (count-cct cclst)
(for/fold ([cnt 0]) ([cct (in-hash-values hash)])
(+ cnt (count-cct cct)))))
(define/public (get-results) (void))
(super-new)))
(define FileListQueue% (class* object% (WorkQueue<%>)
(init-field filelist handler)
(field (lock-mgr (new Lock-Manager%)))
(inspect #f)
(define/public (work-done work wrkr msg)
(match msg
[(list result-type out err)
(match result-type
[(list 'LOCK fn) (lm/lock lock-mgr fn wrkr) #f]
[(list 'UNLOCK fn) (lm/unlock lock-mgr fn) #f]
[(list 'ERROR msg) (handler 'error work msg out err) #t]
['DONE
(define (string-!empty? s) (not (zero? (string-length s))))
(if (ormap string-!empty? (list out err))
(handler 'output work "" out err)
(handler 'done work "" "" ""))
#t])]
[else
(handler 'fatalerror (format "Error matching work: ~a queue ~a" work filelist) "" "") #t]))
(define/public (get-job workerid)
(match filelist
[(cons hd tail)
(define-values (dir file b) (split-path hd))
(set! filelist tail)
(values hd (list (->bytes hd) (->bytes dir) (->bytes file)))]
[(list) null]))
(define/public (has-jobs?) (not (null? filelist)))
(define/public (jobs-cnt) (length filelist))
(define/public (get-results) (void))
(super-new)))
(define (parallel-build work-queue worker-count)
(parallel-do
worker-count
(lambda (workerid) (list workerid))
work-queue
(define-worker (parallel-compile-worker worker-id)
(DEBUG_COMM (eprintf "WORKER ~a\n" worker-id))
(define prev-uncaught-exception-handler (uncaught-exception-handler))
(uncaught-exception-handler (lambda (x)
(when (exn:break? x) (exit 1))
(prev-uncaught-exception-handler x)))
(define cmc (make-caching-managed-compile-zo))
(match-message-loop
[(list name _dir _file)
(DEBUG_COMM (eprintf "COMPILING ~a ~a ~a ~a\n" worker-id name _file _dir))
(define dir (bytes->path _dir))
(define file (bytes->path _file))
(define out-str-port (open-output-string))
(define err-str-port (open-output-string))
(define cip (current-input-port))
(define cop (current-output-port))
(define cep (current-error-port))
(define (send/recv msg) (send/msg msg) (recv/req))
(define (send/resp type)
(send/msg (list type (get-output-string out-str-port) (get-output-string err-str-port))))
(define (pp x) (fprintf cep "COMPILING ~a ~a ~a ~a\n" worker-id name file x))
(define (lock-client cmd fn)
(match cmd
['lock
(DEBUG_COMM (eprintf "REQUESTING LOCK ~a ~a ~a ~a\n" worker-id name _file _dir))
(match (send/recv (list (list 'LOCK (path->bytes fn)) "" ""))
[(list 'locked) #t]
[(list 'compiled) #f]
[(list 'DIE) (worker/die 1)]
[x (send/error (format "DIDNT MATCH B ~v\n" x))]
[else (send/error (format "DIDNT MATCH B\n"))])]
['unlock
(DEBUG_COMM (eprintf "UNLOCKING ~a ~a ~a ~a\n" worker-id name _file _dir))
(send/msg (list (list 'UNLOCK (path->bytes fn)) "" ""))]
[x (send/error (format "DIDNT MATCH C ~v\n" x))]
[else (send/error (format "DIDNT MATCH C\n"))]))
(with-handlers ([exn:fail? (lambda (x)
(send/resp (list 'ERROR (exn-message x))))])
(parameterize ([parallel-lock-client lock-client]
[current-namespace (make-base-empty-namespace)]
[current-directory dir]
[current-load-relative-directory dir]
[current-input-port (open-input-string "")]
[current-output-port out-str-port]
[current-error-port err-str-port]
;[manager-compile-notify-handler pp]
)
(cmc (build-path dir file)))
(send/resp 'DONE))]
[x (send/error (format "DIDNT MATCH A ~v\n" x))]
[else (send/error (format "DIDNT MATCH A\n"))]))))
(define (parallel-compile-files list-of-files
#:worker-count [worker-count (processor-count)]
#:handler [handler void])
(parallel-build (make-object FileListQueue% list-of-files handler) worker-count))
(define (parallel-compile worker-count setup-fprintf append-error collects-tree)
(setup-fprintf (current-output-port) #f "--- parallel build using ~a processes ---" worker-count)
(define collects-queue (make-object CollectsQueue% collects-tree setup-fprintf append-error))
(parallel-build collects-queue worker-count))