database: Serialize all database accesses in a thread.
Fixes <https://bugs.gnu.org/32234>. * bin/cuirass.in (main): Keep only one WITH-DATABASE call around all fibers. Remove all DB arguments. * src/cuirass/base.scm (evaluate, update-build-statuses!, spawn-builds, handle-build-event, build-packages): Remove all DB arguments. (clear-build-queue, cancel-old-builds): Wrap in WITH-DB-CRITICAL-SECTION, remove all DB arguments. (restart-builds): Remove the NON-BLOCKING call, remove all DB arguments. (process-specs): Remove all DB arguments, remove the WITH-DATABASE call. * src/cuirass/database.scm (%db-channel): New parameter. (with-db-critical-section): New macro. (db-add-input, db-add-specification, db-get-inputs, db-get-specifications, db-add-evaluation, db-add-build, db-update-build-status!, db-get-outputs, db-get-builds, db-get-build, db-get-pending-derivations, db-get-stamp, db-add-stamp, db-get-evaluations, db-get-evaluations-build-summary, db-get-evaluations-id-min, db-get-evaluations-id-max, db-get-builds-min, db-get-builds-max): Wrap in WITH-DB-CRITICAL-SECTION, remove all DB arguments. (with-database): Wrap BODY in PARAMETERIZE form that sets %DB-CHANNEL to the channel returned by MAKE-CRITICAL-SECTION. * src/cuirass/http.scm (handle-build-request, handle-builds-request): Remove all DB arguments. (url-handler): Remove all DB arguments, remove the DB-CHANNEL state, remove the WITH-CRITICAL-SECTION calls. (run-cuirass-server): Remove the DB arguments, remove the MAKE-CRITICAL-SECTION call. * src/cuirass/utils.scm (make-critical-section): Replace SPAWN-FIBER with CALL-WITH-NEW-THREAD. Wrap body in PARAMETERIZE form that clears CURRENT-FIBER. * tests/database.scm (with-temporary-database, "db-add-specification", "db-add-build", "db-update-build-status!", "db-get-builds", "db-get-pending-derivations"): Remove the DB arguments. ("db-init"): Set the %DB-CHANNEL parameter to the channel returned by MAKE-CRITICAL-SECTION, and return #t. ("database"): Set %DB-CHANNEL to #f during cleanup. * tests/http.scm ("db-init"): Set the %DB-CHANNEL parameter to the channel returned by MAKE-CRITICAL-SECTION, and return #t. ("cuirass-run", "fill-db"): Remove the DB arguments. ("http"): Set %DB-CHANNEL to #f during cleanup.
This commit is contained in:
parent
e66e545b69
commit
4612a3a70f
|
@ -12,7 +12,8 @@
|
|||
(eval put 'call-with-time 'scheme-indent-function 1)
|
||||
(eval put 'test-error 'scheme-indent-function 1)
|
||||
(eval put 'make-parameter 'scheme-indent-function 1)
|
||||
(eval put 'with-database 'scheme-indent-function 1)
|
||||
(eval put 'with-database 'scheme-indent-function 0)
|
||||
(eval put 'with-db-critical-section 'scheme-indent-function 1)
|
||||
(eval . (put 'with-critical-section 'scheme-indent-function 2)))
|
||||
(texinfo-mode
|
||||
(indent-tabs-mode)
|
||||
|
|
|
@ -115,19 +115,19 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" "$@"
|
|||
(log-message "running Fibers on ~a kernel threads" threads)
|
||||
(run-fibers
|
||||
(lambda ()
|
||||
(with-database db
|
||||
(with-database
|
||||
(and specfile
|
||||
(let ((new-specs (save-module-excursion
|
||||
(lambda ()
|
||||
(set-current-module (make-user-module '()))
|
||||
(primitive-load specfile)))))
|
||||
(for-each (lambda (spec) (db-add-specification db spec))
|
||||
(for-each (lambda (spec) (db-add-specification spec))
|
||||
new-specs)))
|
||||
(if one-shot?
|
||||
(process-specs db (db-get-specifications db))
|
||||
(process-specs (db-get-specifications))
|
||||
(let ((exit-channel (make-channel)))
|
||||
|
||||
(clear-build-queue db)
|
||||
(clear-build-queue)
|
||||
|
||||
;; First off, restart builds that had not completed or
|
||||
;; were not even started on a previous run.
|
||||
|
@ -135,25 +135,22 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" "$@"
|
|||
(essential-task
|
||||
'restart-builds exit-channel
|
||||
(lambda ()
|
||||
(with-database db
|
||||
(restart-builds db)))))
|
||||
(restart-builds))))
|
||||
|
||||
(spawn-fiber
|
||||
(essential-task
|
||||
'build exit-channel
|
||||
(lambda ()
|
||||
(with-database db
|
||||
(while #t
|
||||
(process-specs db (db-get-specifications db))
|
||||
(process-specs (db-get-specifications))
|
||||
(log-message "next evaluation in ~a seconds" interval)
|
||||
(sleep interval))))))
|
||||
(sleep interval)))))
|
||||
|
||||
(spawn-fiber
|
||||
(essential-task
|
||||
'web-server exit-channel
|
||||
(lambda ()
|
||||
(with-database db
|
||||
(run-cuirass-server db #:host host #:port port))))
|
||||
(run-cuirass-server #:host host #:port port)))
|
||||
#:parallel? #t)
|
||||
|
||||
(spawn-fiber
|
||||
|
|
|
@ -248,7 +248,7 @@ fibers."
|
|||
(logior (@ (fibers epoll) EPOLLERR)
|
||||
(@ (fibers epoll) EPOLLHUP)))))
|
||||
|
||||
(define (evaluate store db spec checkouts commits)
|
||||
(define (evaluate store spec checkouts commits)
|
||||
"Evaluate and build package derivations defined in SPEC, using CHECKOUTS.
|
||||
Return a list of jobs."
|
||||
(define (augment-job job eval-id)
|
||||
|
@ -277,7 +277,7 @@ Return a list of jobs."
|
|||
(('evaluation jobs)
|
||||
(let* ((spec-name (assq-ref spec #:name))
|
||||
(eval-id (db-add-evaluation
|
||||
db `((#:specification . ,spec-name)
|
||||
`((#:specification . ,spec-name)
|
||||
(#:commits . ,commits)))))
|
||||
(log-message "created evaluation ~a for '~a'" eval-id spec-name)
|
||||
(map (lambda (job)
|
||||
|
@ -368,7 +368,7 @@ Essentially this procedure inverts the inversion-of-control that
|
|||
;; Our shuffling algorithm is simple: we sort by .drv file name. :-)
|
||||
(sort drv string<?))
|
||||
|
||||
(define (update-build-statuses! store db lst)
|
||||
(define (update-build-statuses! store lst)
|
||||
"Update the build status of the derivations listed in LST, which have just
|
||||
been passed to 'build-derivations' (meaning that we can assume that, if their
|
||||
outputs are invalid, that they failed to build.)"
|
||||
|
@ -376,8 +376,8 @@ outputs are invalid, that they failed to build.)"
|
|||
(match (derivation-path->output-paths drv)
|
||||
(((_ . outputs) ...)
|
||||
(if (any (cut valid-path? store <>) outputs)
|
||||
(db-update-build-status! db drv (build-status succeeded))
|
||||
(db-update-build-status! db drv (build-status failed))))))
|
||||
(db-update-build-status! drv (build-status succeeded))
|
||||
(db-update-build-status! drv (build-status failed))))))
|
||||
|
||||
(for-each update! lst))
|
||||
|
||||
|
@ -393,10 +393,11 @@ and returns the values RESULTS."
|
|||
(print-exception (current-error-port) frame key args)
|
||||
(apply values results)))))
|
||||
|
||||
(define* (spawn-builds store db drv
|
||||
(define* (spawn-builds store drv
|
||||
#:key (max-batch-size 200))
|
||||
"Build the derivations listed in DRV, updating DB as builds complete.
|
||||
Derivations are submitted in batches of at most MAX-BATCH-SIZE items."
|
||||
"Build the derivations listed in DRV, updating the database as builds
|
||||
complete. Derivations are submitted in batches of at most MAX-BATCH-SIZE
|
||||
items."
|
||||
;; XXX: We want to pass 'build-derivations' as many derivations at once so
|
||||
;; we benefit from as much parallelism as possible (we must be using
|
||||
;; #:keep-going? #t).
|
||||
|
@ -444,7 +445,7 @@ Derivations are submitted in batches of at most MAX-BATCH-SIZE items."
|
|||
;; from PORT and eventually close it.
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(handle-build-event db event))
|
||||
(handle-build-event event))
|
||||
(exception-reporter state)))
|
||||
#t)
|
||||
(close-port port)
|
||||
|
@ -455,14 +456,14 @@ Derivations are submitted in batches of at most MAX-BATCH-SIZE items."
|
|||
;; derivations were built "behind our back", in which case
|
||||
;; 'build-derivations' doesn't actually do anything and
|
||||
;; 'handle-build-event' doesn't see any event. Because of that,
|
||||
;; adjust DB here.
|
||||
(update-build-statuses! store db batch)
|
||||
;; adjust the database here.
|
||||
(update-build-statuses! store batch)
|
||||
|
||||
(loop rest (max (- count max-batch-size) 0))))))
|
||||
|
||||
(define* (handle-build-event db event)
|
||||
(define* (handle-build-event event)
|
||||
"Handle EVENT, a build event sexp as produced by 'build-event-output-port',
|
||||
updating DB accordingly."
|
||||
updating the database accordingly."
|
||||
(define (valid? file)
|
||||
;; FIXME: Sometimes we might get bogus events due to the interleaving of
|
||||
;; build messages. This procedure prevents us from propagating the bogus
|
||||
|
@ -475,7 +476,7 @@ updating DB accordingly."
|
|||
(if (valid? drv)
|
||||
(begin
|
||||
(log-message "build started: '~a'" drv)
|
||||
(db-update-build-status! db drv (build-status started)))
|
||||
(db-update-build-status! drv (build-status started)))
|
||||
(log-message "bogus build-started event for '~a'" drv)))
|
||||
(('build-remote drv host _ ...)
|
||||
(log-message "'~a' offloaded to '~a'" drv host))
|
||||
|
@ -483,13 +484,13 @@ updating DB accordingly."
|
|||
(if (valid? drv)
|
||||
(begin
|
||||
(log-message "build succeeded: '~a'" drv)
|
||||
(db-update-build-status! db drv (build-status succeeded)))
|
||||
(db-update-build-status! drv (build-status succeeded)))
|
||||
(log-message "bogus build-succeeded event for '~a'" drv)))
|
||||
(('build-failed drv _ ...)
|
||||
(if (valid? drv)
|
||||
(begin
|
||||
(log-message "build failed: '~a'" drv)
|
||||
(db-update-build-status! db drv (build-status failed)))
|
||||
(db-update-build-status! drv (build-status failed)))
|
||||
(log-message "bogus build-failed event for '~a'" drv)))
|
||||
(('substituter-started item _ ...)
|
||||
(log-message "substituter started: '~a'" item))
|
||||
|
@ -503,42 +504,42 @@ updating DB accordingly."
|
|||
(string=? (assq-ref build1 #:derivation)
|
||||
(assq-ref build2 #:derivation)))
|
||||
|
||||
(define (clear-build-queue db)
|
||||
"Reset the status of builds in DB that are marked as \"started\". This
|
||||
procedure is meant to be called at startup."
|
||||
(define (clear-build-queue)
|
||||
"Reset the status of builds in the database that are marked as \"started\".
|
||||
This procedure is meant to be called at startup."
|
||||
(log-message "marking stale builds as \"scheduled\"...")
|
||||
(sqlite-exec db "UPDATE Builds SET status = -2 WHERE status = -1;"))
|
||||
(with-db-critical-section db
|
||||
(sqlite-exec db "UPDATE Builds SET status = -2 WHERE status = -1;")))
|
||||
|
||||
(define (cancel-old-builds db age)
|
||||
(define (cancel-old-builds age)
|
||||
"Cancel builds older than AGE seconds."
|
||||
(log-message "canceling builds older than ~a seconds..." age)
|
||||
(sqlite-exec db
|
||||
"UPDATE Builds SET status = 4 WHERE status = -2 AND timestamp < "
|
||||
(- (time-second (current-time time-utc)) age)
|
||||
";"))
|
||||
(with-db-critical-section db
|
||||
(sqlite-exec
|
||||
db "UPDATE Builds SET status = 4 WHERE status = -2 AND timestamp < "
|
||||
(- (time-second (current-time time-utc)) age) ";")))
|
||||
|
||||
(define (restart-builds db)
|
||||
"Restart builds whose status in DB is \"pending\" (scheduled or started)."
|
||||
(define (restart-builds)
|
||||
"Restart builds whose status in the database is \"pending\" (scheduled or
|
||||
started)."
|
||||
(with-store store
|
||||
;; Note: On a big database, 'db-get-pending-derivations' can take a couple
|
||||
;; of minutes, hence 'non-blocking'.
|
||||
(log-message "retrieving list of pending builds...")
|
||||
(let*-values (((valid stale)
|
||||
(partition (cut valid-path? store <>)
|
||||
(non-blocking (db-get-pending-derivations db)))))
|
||||
(db-get-pending-derivations))))
|
||||
;; We cannot restart builds listed in STALE, so mark them as canceled.
|
||||
(log-message "canceling ~a stale builds" (length stale))
|
||||
(for-each (lambda (drv)
|
||||
(db-update-build-status! db drv (build-status canceled)))
|
||||
(db-update-build-status! drv (build-status canceled)))
|
||||
stale)
|
||||
|
||||
;; Those in VALID can be restarted. If some of them were built in the
|
||||
;; meantime behind our back, that's fine: 'spawn-builds' will DTRT.
|
||||
(log-message "restarting ~a pending builds" (length valid))
|
||||
(spawn-builds store db valid)
|
||||
(spawn-builds store valid)
|
||||
(log-message "done with restarted builds"))))
|
||||
|
||||
(define (build-packages store db jobs)
|
||||
(define (build-packages store jobs)
|
||||
"Build JOBS and return a list of Build results."
|
||||
(define (register job)
|
||||
(let* ((name (assq-ref job #:job-name))
|
||||
|
@ -570,14 +571,14 @@ procedure is meant to be called at startup."
|
|||
(#:timestamp . ,cur-time)
|
||||
(#:starttime . 0)
|
||||
(#:stoptime . 0))))
|
||||
(db-add-build db build))))
|
||||
(db-add-build build))))
|
||||
|
||||
(define derivations
|
||||
(filter-map register jobs))
|
||||
|
||||
(spawn-builds store db derivations)
|
||||
(spawn-builds store derivations)
|
||||
|
||||
(let* ((results (filter-map (cut db-get-build db <>) derivations))
|
||||
(let* ((results (filter-map (cut db-get-build <>) derivations))
|
||||
(status (map (cut assq-ref <> #:status) results))
|
||||
(success (count (lambda (status)
|
||||
(= status (build-status succeeded)))
|
||||
|
@ -651,11 +652,11 @@ procedure is meant to be called at startup."
|
|||
checkout)
|
||||
results)))
|
||||
|
||||
(define (process-specs db jobspecs)
|
||||
"Evaluate and build JOBSPECS and store results in DB."
|
||||
(define (process-specs jobspecs)
|
||||
"Evaluate and build JOBSPECS and store results in the database."
|
||||
(define (process spec)
|
||||
(with-store store
|
||||
(let* ((stamp (db-get-stamp db spec))
|
||||
(let* ((stamp (db-get-stamp spec))
|
||||
(name (assoc-ref spec #:name))
|
||||
(checkouts (fetch-inputs spec))
|
||||
(commits (map (cut assq-ref <> #:commit) checkouts))
|
||||
|
@ -663,7 +664,7 @@ procedure is meant to be called at startup."
|
|||
(unless (equal? commits-str stamp)
|
||||
;; Immediately mark SPEC's INPUTS as being processed so we don't
|
||||
;; spawn a concurrent evaluation of that same commit.
|
||||
(db-add-stamp db spec commits-str)
|
||||
(db-add-stamp spec commits-str)
|
||||
(compile-checkouts spec (filter compile? checkouts))
|
||||
(spawn-fiber
|
||||
(lambda ()
|
||||
|
@ -674,11 +675,10 @@ procedure is meant to be called at startup."
|
|||
(log-message "evaluating spec '~a': stamp ~s different from ~s"
|
||||
name commits-str stamp)
|
||||
(with-store store
|
||||
(with-database db
|
||||
(let ((jobs (evaluate store db spec checkouts commits)))
|
||||
(let ((jobs (evaluate store spec checkouts commits)))
|
||||
(log-message "building ~a jobs for '~a'"
|
||||
(length jobs) name)
|
||||
(build-packages store db jobs)))))))
|
||||
(build-packages store jobs))))))
|
||||
|
||||
;; 'spawn-fiber' returns zero values but we need one.
|
||||
*unspecified*))))
|
||||
|
|
|
@ -59,7 +59,9 @@
|
|||
;; Parameters.
|
||||
%package-database
|
||||
%package-schema-file
|
||||
%db-channel
|
||||
;; Macros.
|
||||
with-db-critical-section
|
||||
with-database))
|
||||
|
||||
(define (%sqlite-exec db sql . args)
|
||||
|
@ -139,6 +141,16 @@ question marks matches the number of arguments to bind."
|
|||
(string-append %datadir "/" %package))
|
||||
"/sql")))
|
||||
|
||||
(define %db-channel
|
||||
(make-parameter #f))
|
||||
|
||||
(define-syntax-rule (with-db-critical-section db exp ...)
|
||||
"Evaluate EXP... in the critical section corresponding to %DB-CHANNEL.
|
||||
DB is bound to the argument of that critical section: the database
|
||||
connection."
|
||||
(call-with-critical-section (%db-channel)
|
||||
(lambda (db) exp ...)))
|
||||
|
||||
(define (read-sql-file file-name)
|
||||
"Return a list of string containing SQL instructions from FILE-NAME."
|
||||
(call-with-input-file file-name
|
||||
|
@ -238,7 +250,8 @@ database object."
|
|||
(vector-ref (car (sqlite-exec db "SELECT last_insert_rowid();"))
|
||||
0))
|
||||
|
||||
(define (db-add-input db spec-name input)
|
||||
(define (db-add-input spec-name input)
|
||||
(with-db-critical-section db
|
||||
(sqlite-exec db "\
|
||||
INSERT OR IGNORE INTO Inputs (specification, name, url, load_path, branch, \
|
||||
tag, revision, no_compile_p) VALUES ("
|
||||
|
@ -250,29 +263,33 @@ tag, revision, no_compile_p) VALUES ("
|
|||
(assq-ref input #:tag) ", "
|
||||
(assq-ref input #:commit) ", "
|
||||
(if (assq-ref input #:no-compile?) 1 0) ");")
|
||||
(last-insert-rowid db))
|
||||
(last-insert-rowid db)))
|
||||
|
||||
(define (db-add-specification db spec)
|
||||
"Store SPEC in database DB. SPEC inputs are stored in the INPUTS table."
|
||||
(define (db-add-specification spec)
|
||||
"Store SPEC in database the database. SPEC inputs are stored in the INPUTS
|
||||
table."
|
||||
(with-db-critical-section db
|
||||
(sqlite-exec db "\
|
||||
INSERT OR IGNORE INTO Specifications (name, load_path_inputs, \
|
||||
package_path_inputs, proc_input, proc_file, proc, proc_args) \
|
||||
VALUES ("
|
||||
(assq-ref spec #:name) ", "
|
||||
(assq-ref spec #:load-path-inputs) ", "
|
||||
(assq-ref spec #:package-path-inputs)", "
|
||||
(assq-ref spec #:package-path-inputs) ", "
|
||||
(assq-ref spec #:proc-input) ", "
|
||||
(assq-ref spec #:proc-file) ", "
|
||||
(symbol->string (assq-ref spec #:proc)) ", "
|
||||
(assq-ref spec #:proc-args) ");")
|
||||
(let ((spec-id (last-insert-rowid db)))
|
||||
(for-each (lambda (input)
|
||||
(db-add-input db (assq-ref spec #:name) input))
|
||||
(db-add-input (assq-ref spec #:name) input))
|
||||
(assq-ref spec #:inputs))
|
||||
spec-id))
|
||||
spec-id)))
|
||||
|
||||
(define (db-get-inputs db spec-name)
|
||||
(let loop ((rows (sqlite-exec db "SELECT * FROM Inputs WHERE specification="
|
||||
(define (db-get-inputs spec-name)
|
||||
(with-db-critical-section db
|
||||
(let loop ((rows (sqlite-exec
|
||||
db "SELECT * FROM Inputs WHERE specification="
|
||||
spec-name ";"))
|
||||
(inputs '()))
|
||||
(match rows
|
||||
|
@ -287,9 +304,10 @@ package_path_inputs, proc_input, proc_file, proc, proc_args) \
|
|||
(#:tag . ,tag)
|
||||
(#:commit . ,revision)
|
||||
(#:no-compile? . ,(positive? no-compile-p)))
|
||||
inputs))))))
|
||||
inputs)))))))
|
||||
|
||||
(define (db-get-specifications db)
|
||||
(define (db-get-specifications)
|
||||
(with-db-critical-section db
|
||||
(let loop ((rows (sqlite-exec db "SELECT * FROM Specifications;"))
|
||||
(specs '()))
|
||||
(match rows
|
||||
|
@ -307,23 +325,36 @@ package_path_inputs, proc_input, proc_file, proc, proc_args) \
|
|||
(#:proc-file . ,proc-file)
|
||||
(#:proc . ,(with-input-from-string proc read))
|
||||
(#:proc-args . ,(with-input-from-string proc-args read))
|
||||
(#:inputs . ,(db-get-inputs db name)))
|
||||
specs))))))
|
||||
(#:inputs . ,(db-get-inputs name)))
|
||||
specs)))))))
|
||||
|
||||
(define (db-add-evaluation db eval)
|
||||
(define (db-add-evaluation eval)
|
||||
(with-db-critical-section db
|
||||
(sqlite-exec db "\
|
||||
INSERT INTO Evaluations (specification, commits) VALUES ("
|
||||
(assq-ref eval #:specification) ", "
|
||||
(string-join (assq-ref eval #:commits)) ");")
|
||||
(last-insert-rowid db))
|
||||
(last-insert-rowid db)))
|
||||
|
||||
(define-syntax-rule (with-database db body ...)
|
||||
"Run BODY with a connection to the database which is bound to DB in BODY."
|
||||
(define-syntax-rule (with-database body ...)
|
||||
"Run BODY with %DB-CHANNEL being dynamically bound to a channel implementing
|
||||
a critical section that allows database operations to be serialized."
|
||||
;; XXX: We don't install an unwind handler to play well with delimited
|
||||
;; continuations and fibers. But as a consequence, we leak DB when BODY
|
||||
;; raises an exception.
|
||||
(let ((db (db-open)))
|
||||
(unwind-protect body ... (db-close db))))
|
||||
(unwind-protect
|
||||
;; Process database queries sequentially in a thread. We need this
|
||||
;; because otherwise we would need to use the SQLite multithreading
|
||||
;; feature for which it is required to wait until the database is
|
||||
;; available, and the waiting would happen in non-cooperative and
|
||||
;; non-resumable code that blocks the fibers scheduler. Now the database
|
||||
;; access blocks on PUT-MESSAGE, which allows the scheduler to schedule
|
||||
;; another fiber. Also, creating one new handle for each request would
|
||||
;; be costly and may defeat statement caching.
|
||||
(parameterize ((%db-channel (make-critical-section db)))
|
||||
body ...)
|
||||
(db-close db))))
|
||||
|
||||
(define* (read-quoted-string #:optional (port (current-input-port)))
|
||||
"Read all of the characters out of PORT and return them as a SQL quoted
|
||||
|
@ -353,9 +384,10 @@ string."
|
|||
(failed-other 3)
|
||||
(canceled 4))
|
||||
|
||||
(define (db-add-build db build)
|
||||
"Store BUILD in database DB. BUILD eventual outputs are stored
|
||||
in the OUTPUTS table."
|
||||
(define (db-add-build build)
|
||||
"Store BUILD in database the database. BUILD eventual outputs are stored in
|
||||
the OUTPUTS table."
|
||||
(with-db-critical-section db
|
||||
(catch 'sqlite-error
|
||||
(lambda ()
|
||||
(sqlite-exec db "
|
||||
|
@ -388,33 +420,37 @@ INSERT INTO Outputs (derivation, name, path) VALUES ("
|
|||
;; produce the same derivation, and we can ignore it.
|
||||
(if (= code SQLITE_CONSTRAINT_PRIMARYKEY)
|
||||
#f
|
||||
(apply throw key who code rest)))))
|
||||
(apply throw key who code rest))))))
|
||||
|
||||
(define* (db-update-build-status! db drv status #:key log-file)
|
||||
"Update DB so that DRV's status is STATUS. This also updates the
|
||||
(define* (db-update-build-status! drv status #:key log-file)
|
||||
"Update the database so that DRV's status is STATUS. This also updates the
|
||||
'starttime' or 'stoptime' fields. If LOG-FILE is true, record it as the build
|
||||
log file for DRV."
|
||||
(define now
|
||||
(time-second (current-time time-utc)))
|
||||
|
||||
(with-db-critical-section db
|
||||
(if (= status (build-status started))
|
||||
(sqlite-exec db "UPDATE Builds SET starttime=" now ", status="
|
||||
status "WHERE derivation=" drv ";")
|
||||
|
||||
;; Update only if we're switching to a different status; otherwise leave
|
||||
;; things unchanged. This ensures that 'stoptime' remains valid and
|
||||
;; doesn't change every time we mark DRV as 'succeeded' several times in
|
||||
;; a row, for instance.
|
||||
;; Update only if we're switching to a different status; otherwise
|
||||
;; leave things unchanged. This ensures that 'stoptime' remains valid
|
||||
;; and doesn't change every time we mark DRV as 'succeeded' several
|
||||
;; times in a row, for instance.
|
||||
(if log-file
|
||||
(sqlite-exec db "UPDATE Builds SET stoptime=" now
|
||||
", status=" status ", log=" log-file
|
||||
"WHERE derivation=" drv "AND status != " status ";")
|
||||
(sqlite-exec db "UPDATE Builds SET stoptime=" now
|
||||
", status=" status
|
||||
"WHERE derivation=" drv " AND status != " status ";"))))
|
||||
"WHERE derivation=" drv " AND status != " status
|
||||
";")))))
|
||||
|
||||
(define (db-get-outputs db derivation)
|
||||
"Retrieve the OUTPUTS of the build identified by DERIVATION in DB database."
|
||||
(define (db-get-outputs derivation)
|
||||
"Retrieve the OUTPUTS of the build identified by DERIVATION in the
|
||||
database."
|
||||
(with-db-critical-section db
|
||||
(let loop ((rows
|
||||
(sqlite-exec db "SELECT name, path FROM Outputs
|
||||
WHERE derivation =" derivation ";"))
|
||||
|
@ -425,7 +461,7 @@ WHERE derivation =" derivation ";"))
|
|||
. rest)
|
||||
(loop rest
|
||||
(cons `(,name . ((#:path . ,path)))
|
||||
outputs))))))
|
||||
outputs)))))))
|
||||
|
||||
(define (filters->order filters)
|
||||
(match (assq 'order filters)
|
||||
|
@ -440,10 +476,11 @@ WHERE derivation =" derivation ";"))
|
|||
(('order . 'status+submission-time) "status DESC, timestamp DESC")
|
||||
(_ "rowid DESC")))
|
||||
|
||||
(define (db-get-builds db filters)
|
||||
"Retrieve all builds in database DB which are matched by given FILTERS.
|
||||
(define (db-get-builds filters)
|
||||
"Retrieve all builds in the database which are matched by given FILTERS.
|
||||
FILTERS is an assoc list whose possible keys are 'derivation | 'id | 'jobset |
|
||||
'job | 'system | 'nr | 'order | 'status | 'evaluation."
|
||||
(with-db-critical-section db
|
||||
(let* ((order (filters->order filters))
|
||||
(stmt-text (format #f "SELECT * FROM (
|
||||
SELECT Builds.derivation, Builds.rowid, Builds.timestamp, Builds.starttime,
|
||||
|
@ -511,42 +548,47 @@ ORDER BY ~a, rowid ASC;" order))
|
|||
(#:system . ,system)
|
||||
(#:nix-name . ,nix-name)
|
||||
(#:specification . ,specification)
|
||||
(#:outputs . ,(db-get-outputs db derivation)))
|
||||
builds)))))))
|
||||
(#:outputs . ,(db-get-outputs derivation)))
|
||||
builds))))))))
|
||||
|
||||
(define (db-get-build db derivation-or-id)
|
||||
"Retrieve a build in database DB which corresponds to DERIVATION-OR-ID."
|
||||
(define (db-get-build derivation-or-id)
|
||||
"Retrieve a build in the database which corresponds to DERIVATION-OR-ID."
|
||||
(with-db-critical-section db
|
||||
(let ((key (if (number? derivation-or-id) 'id 'derivation)))
|
||||
(match (db-get-builds db `((,key . ,derivation-or-id)))
|
||||
(match (db-get-builds `((,key . ,derivation-or-id)))
|
||||
((build)
|
||||
build)
|
||||
(() #f))))
|
||||
(() #f)))))
|
||||
|
||||
(define (db-get-pending-derivations db)
|
||||
(define (db-get-pending-derivations)
|
||||
"Return the list of derivation file names corresponding to pending builds in
|
||||
DB. The returned list is guaranteed to not have any duplicates."
|
||||
the database. The returned list is guaranteed to not have any duplicates."
|
||||
(with-db-critical-section db
|
||||
(map (match-lambda (#(drv) drv))
|
||||
(sqlite-exec db "
|
||||
SELECT derivation FROM Builds WHERE Builds.status < 0;")))
|
||||
SELECT derivation FROM Builds WHERE Builds.status < 0;"))))
|
||||
|
||||
(define (db-get-stamp db spec)
|
||||
"Return a stamp corresponding to specification SPEC in database DB."
|
||||
(define (db-get-stamp spec)
|
||||
"Return a stamp corresponding to specification SPEC in the database."
|
||||
(with-db-critical-section db
|
||||
(let ((res (sqlite-exec db "SELECT * FROM Stamps WHERE specification="
|
||||
(assq-ref spec #:name) ";")))
|
||||
(match res
|
||||
(() #f)
|
||||
((#(spec stamp)) stamp))))
|
||||
((#(spec stamp)) stamp)))))
|
||||
|
||||
(define (db-add-stamp db spec stamp)
|
||||
"Associate STAMP to specification SPEC in database DB."
|
||||
(if (db-get-stamp db spec)
|
||||
(define (db-add-stamp spec stamp)
|
||||
"Associate STAMP to specification SPEC in the database."
|
||||
(with-db-critical-section db
|
||||
(if (db-get-stamp spec)
|
||||
(sqlite-exec db "UPDATE Stamps SET stamp=" stamp
|
||||
"WHERE specification=" (assq-ref spec #:name) ";")
|
||||
(sqlite-exec db "\
|
||||
INSERT INTO Stamps (specification, stamp) VALUES ("
|
||||
(assq-ref spec #:name) ", " stamp ");")))
|
||||
(assq-ref spec #:name) ", " stamp ");"))))
|
||||
|
||||
(define (db-get-evaluations db limit)
|
||||
(define (db-get-evaluations limit)
|
||||
(with-db-critical-section db
|
||||
(let loop ((rows (sqlite-exec db "SELECT id, specification, commits
|
||||
FROM Evaluations ORDER BY id DESC LIMIT " limit ";"))
|
||||
(evaluations '()))
|
||||
|
@ -558,9 +600,10 @@ FROM Evaluations ORDER BY id DESC LIMIT " limit ";"))
|
|||
(cons `((#:id . ,id)
|
||||
(#:specification . ,specification)
|
||||
(#:commits . ,(string-tokenize commits)))
|
||||
evaluations))))))
|
||||
evaluations)))))))
|
||||
|
||||
(define (db-get-evaluations-build-summary db spec limit border-low border-high)
|
||||
(define (db-get-evaluations-build-summary spec limit border-low border-high)
|
||||
(with-db-critical-section db
|
||||
(let loop ((rows (sqlite-exec db "
|
||||
SELECT E.id, E.commits, B.succeeded, B.failed, B.scheduled
|
||||
FROM
|
||||
|
@ -588,40 +631,44 @@ ORDER BY E.id ASC;"))
|
|||
(#:succeeded . ,(or succeeded 0))
|
||||
(#:failed . ,(or failed 0))
|
||||
(#:scheduled . ,(or scheduled 0)))
|
||||
evaluations))))))
|
||||
evaluations)))))))
|
||||
|
||||
(define (db-get-evaluations-id-min db spec)
|
||||
(define (db-get-evaluations-id-min spec)
|
||||
"Return the min id of evaluations for the given specification SPEC."
|
||||
(with-db-critical-section db
|
||||
(let ((rows (sqlite-exec db "
|
||||
SELECT MIN(id) FROM Evaluations
|
||||
WHERE specification=" spec)))
|
||||
(vector-ref (car rows) 0)))
|
||||
(vector-ref (car rows) 0))))
|
||||
|
||||
(define (db-get-evaluations-id-max db spec)
|
||||
(define (db-get-evaluations-id-max spec)
|
||||
"Return the max id of evaluations for the given specification SPEC."
|
||||
(with-db-critical-section db
|
||||
(let ((rows (sqlite-exec db "
|
||||
SELECT MAX(id) FROM Evaluations
|
||||
WHERE specification=" spec)))
|
||||
(vector-ref (car rows) 0)))
|
||||
(vector-ref (car rows) 0))))
|
||||
|
||||
(define (db-get-builds-min db eval)
|
||||
(define (db-get-builds-min eval)
|
||||
"Return the min build (stoptime, id) pair for
|
||||
the given evaluation EVAL."
|
||||
(with-db-critical-section db
|
||||
(let ((rows (sqlite-exec db "
|
||||
SELECT stoptime, MIN(rowid) FROM
|
||||
(SELECT rowid, stoptime FROM Builds
|
||||
WHERE evaluation=" eval " AND
|
||||
stoptime = (SELECT MIN(stoptime)
|
||||
FROM Builds WHERE evaluation=" eval "))")))
|
||||
(vector->list (car rows))))
|
||||
(vector->list (car rows)))))
|
||||
|
||||
(define (db-get-builds-max db eval)
|
||||
(define (db-get-builds-max eval)
|
||||
"Return the max build (stoptime, id) pair for
|
||||
the given evaluation EVAL."
|
||||
(with-db-critical-section db
|
||||
(let ((rows (sqlite-exec db "
|
||||
SELECT stoptime, MAX(rowid) FROM
|
||||
(SELECT rowid, stoptime FROM Builds
|
||||
WHERE evaluation=" eval " AND
|
||||
stoptime = (SELECT MAX(stoptime)
|
||||
FROM Builds WHERE evaluation=" eval "))")))
|
||||
(vector->list (car rows))))
|
||||
(vector->list (car rows)))))
|
||||
|
|
|
@ -103,17 +103,17 @@
|
|||
(#:releasename . #nil)
|
||||
(#:buildinputs_builds . #nil)))
|
||||
|
||||
(define (handle-build-request db build-id)
|
||||
"Retrieve build identified by BUILD-ID over DB and convert it
|
||||
to hydra format. Return #f is not build was found."
|
||||
(let ((build (db-get-build db build-id)))
|
||||
(define (handle-build-request build-id)
|
||||
"Retrieve build identified by BUILD-ID over the database and convert it to
|
||||
hydra format. Return #f is not build was found."
|
||||
(let ((build (db-get-build build-id)))
|
||||
(and=> build build->hydra-build)))
|
||||
|
||||
(define (handle-builds-request db filters)
|
||||
"Retrieve all builds matched by FILTERS in DB and convert them
|
||||
to Hydra format."
|
||||
(define (handle-builds-request filters)
|
||||
"Retrieve all builds matched by FILTERS in the database and convert them to
|
||||
Hydra format."
|
||||
(let ((builds (with-time-logging "builds request"
|
||||
(db-get-builds db filters))))
|
||||
(db-get-builds filters))))
|
||||
(map build->hydra-build builds)))
|
||||
|
||||
(define (request-parameters request)
|
||||
|
@ -146,10 +146,10 @@
|
|||
(define (request-path-components request)
|
||||
(split-and-decode-uri-path (uri-path (request-uri request))))
|
||||
|
||||
(define (url-handler request body db-channel)
|
||||
(define (url-handler request body)
|
||||
|
||||
(define* (respond response #:key body (db-channel db-channel))
|
||||
(values response body db-channel))
|
||||
(define* (respond response #:key body)
|
||||
(values response body #f))
|
||||
|
||||
(define-syntax-rule (respond-json body ...)
|
||||
(respond '((content-type . (application/json)))
|
||||
|
@ -213,19 +213,14 @@
|
|||
(request-path-components request)
|
||||
'method-not-allowed)
|
||||
(((or "jobsets" "specifications") . rest)
|
||||
(respond-json (object->json-string
|
||||
(with-critical-section db-channel (db)
|
||||
(db-get-specifications db)))))
|
||||
(respond-json (object->json-string (db-get-specifications))))
|
||||
(("build" build-id)
|
||||
(let ((hydra-build
|
||||
(with-critical-section db-channel (db)
|
||||
(handle-build-request db (string->number build-id)))))
|
||||
(let ((hydra-build (handle-build-request (string->number build-id))))
|
||||
(if hydra-build
|
||||
(respond-json (object->json-string hydra-build))
|
||||
(respond-build-not-found build-id))))
|
||||
(("build" build-id "log" "raw")
|
||||
(let ((build (with-critical-section db-channel (db)
|
||||
(db-get-build db (string->number build-id)))))
|
||||
(let ((build (db-get-build (string->number build-id))))
|
||||
(if build
|
||||
(match (assq-ref build #:outputs)
|
||||
(((_ (#:path . (? string? output))) _ ...)
|
||||
|
@ -250,9 +245,7 @@
|
|||
;; 'nr parameter is mandatory to limit query size.
|
||||
(nr (assq-ref params 'nr)))
|
||||
(if nr
|
||||
(respond-json (object->json-string
|
||||
(with-critical-section db-channel (db)
|
||||
(db-get-evaluations db nr))))
|
||||
(respond-json (object->json-string (db-get-evaluations nr)))
|
||||
(respond-json-with-error 500 "Parameter not defined!"))))
|
||||
(("api" "latestbuilds")
|
||||
(let* ((params (request-parameters request))
|
||||
|
@ -262,10 +255,9 @@
|
|||
;; Limit results to builds that are "done".
|
||||
(respond-json
|
||||
(object->json-string
|
||||
(with-critical-section db-channel (db)
|
||||
(handle-builds-request db `((status . done)
|
||||
(handle-builds-request `((status . done)
|
||||
,@params
|
||||
(order . finish-time))))))
|
||||
(order . finish-time)))))
|
||||
(respond-json-with-error 500 "Parameter not defined!"))))
|
||||
(("api" "queue")
|
||||
(let* ((params (request-parameters request))
|
||||
|
@ -276,41 +268,35 @@
|
|||
(object->json-string
|
||||
;; Use the 'status+submission-time' order so that builds in
|
||||
;; 'running' state appear before builds in 'scheduled' state.
|
||||
(with-critical-section db-channel (db)
|
||||
(handle-builds-request db `((status . pending)
|
||||
(handle-builds-request `((status . pending)
|
||||
,@params
|
||||
(order . status+submission-time))))))
|
||||
(order . status+submission-time)))))
|
||||
(respond-json-with-error 500 "Parameter not defined!"))))
|
||||
('()
|
||||
(respond-html (html-page
|
||||
"Cuirass"
|
||||
(specifications-table
|
||||
(with-critical-section db-channel (db)
|
||||
(db-get-specifications db))))))
|
||||
(specifications-table (db-get-specifications)))))
|
||||
|
||||
(("jobset" name)
|
||||
(respond-html
|
||||
(with-critical-section db-channel (db)
|
||||
(let* ((evaluation-id-max (db-get-evaluations-id-max db name))
|
||||
(evaluation-id-min (db-get-evaluations-id-min db name))
|
||||
(let* ((evaluation-id-max (db-get-evaluations-id-max name))
|
||||
(evaluation-id-min (db-get-evaluations-id-min name))
|
||||
(params (request-parameters request))
|
||||
(border-high (assq-ref params 'border-high))
|
||||
(border-low (assq-ref params 'border-low))
|
||||
(evaluations (db-get-evaluations-build-summary db
|
||||
name
|
||||
(evaluations (db-get-evaluations-build-summary name
|
||||
%page-size
|
||||
border-low
|
||||
border-high)))
|
||||
(html-page name (evaluation-info-table name
|
||||
evaluations
|
||||
evaluation-id-min
|
||||
evaluation-id-max))))))
|
||||
evaluation-id-max)))))
|
||||
|
||||
(("eval" id)
|
||||
(respond-html
|
||||
(with-critical-section db-channel (db)
|
||||
(let* ((builds-id-max (db-get-builds-max db id))
|
||||
(builds-id-min (db-get-builds-min db id))
|
||||
(let* ((builds-id-max (db-get-builds-max id))
|
||||
(builds-id-min (db-get-builds-min id))
|
||||
(params (request-parameters request))
|
||||
(border-high-time (assq-ref params 'border-high-time))
|
||||
(border-low-time (assq-ref params 'border-low-time))
|
||||
|
@ -319,7 +305,7 @@
|
|||
(html-page
|
||||
"Evaluation"
|
||||
(build-eval-table
|
||||
(handle-builds-request db `((evaluation . ,id)
|
||||
(handle-builds-request `((evaluation . ,id)
|
||||
(nr . ,%page-size)
|
||||
(order . finish-time+build-id)
|
||||
(border-high-time . ,border-high-time)
|
||||
|
@ -327,26 +313,20 @@
|
|||
(border-high-id . ,border-high-id)
|
||||
(border-low-id . ,border-low-id)))
|
||||
builds-id-min
|
||||
builds-id-max))))))
|
||||
builds-id-max)))))
|
||||
|
||||
(("static" path ...)
|
||||
(respond-static-file path))
|
||||
('method-not-allowed
|
||||
;; 405 "Method Not Allowed"
|
||||
(values (build-response #:code 405) #f db-channel))
|
||||
(values (build-response #:code 405) #f #f))
|
||||
(_
|
||||
(respond-not-found (uri->string (request-uri request))))))
|
||||
|
||||
(define* (run-cuirass-server db #:key (host "localhost") (port 8080))
|
||||
(define* (run-cuirass-server #:key (host "localhost") (port 8080))
|
||||
(let* ((host-info (gethostbyname host))
|
||||
(address (inet-ntop (hostent:addrtype host-info)
|
||||
(car (hostent:addr-list host-info))))
|
||||
|
||||
;; Spawn a fiber to process database queries sequentially. We need
|
||||
;; this because guile-sqlite3 handles are not thread-safe (caching in
|
||||
;; particular), and creating one new handle for each request would be
|
||||
;; costly and may defeat statement caching.
|
||||
(db-channel (make-critical-section db)))
|
||||
(car (hostent:addr-list host-info)))))
|
||||
(log-message "listening on ~A:~A" address port)
|
||||
|
||||
;; Here we use our own web backend, call 'fiberized'. We cannot use the
|
||||
|
@ -371,7 +351,7 @@
|
|||
(spawn-fiber
|
||||
(lambda ()
|
||||
(let-values (((response body state)
|
||||
(handle-request (cut url-handler <> <> db-channel)
|
||||
(handle-request (cut url-handler <> <>)
|
||||
request body '())))
|
||||
(write-client impl server client response body)))))
|
||||
(loop)))))
|
||||
|
|
|
@ -103,9 +103,10 @@ then be passed to 'join-critical-section', which will ensure sequential
|
|||
ordering. ARGS are the arguments of the critical section.
|
||||
|
||||
Critical sections are implemented by passing the procedure to execute to a
|
||||
dedicated fiber."
|
||||
dedicated thread."
|
||||
(parameterize (((@@ (fibers internal) current-fiber) #f))
|
||||
(let ((channel (make-channel)))
|
||||
(spawn-fiber
|
||||
(call-with-new-thread
|
||||
(lambda ()
|
||||
(parameterize ((%critical-section-args args))
|
||||
(let loop ()
|
||||
|
@ -113,7 +114,7 @@ dedicated fiber."
|
|||
(((? channel? reply) . (? procedure? proc))
|
||||
(put-message reply (apply proc args))))
|
||||
(loop)))))
|
||||
channel))
|
||||
channel)))
|
||||
|
||||
(define (call-with-critical-section channel proc)
|
||||
"Send PROC to the critical section through CHANNEL. Return the result of
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
(use-modules (cuirass database)
|
||||
((guix utils) #:select (call-with-temporary-output-file))
|
||||
(cuirass utils)
|
||||
(srfi srfi-64))
|
||||
|
||||
(define example-spec
|
||||
|
@ -61,12 +62,12 @@
|
|||
(#:log . "log")
|
||||
(#:outputs . (("foo" . "/foo")))))
|
||||
|
||||
(define-syntax-rule (with-temporary-database db body ...)
|
||||
(define-syntax-rule (with-temporary-database body ...)
|
||||
(call-with-temporary-output-file
|
||||
(lambda (file port)
|
||||
(parameterize ((%package-database file))
|
||||
(db-init file)
|
||||
(with-database db
|
||||
(with-database
|
||||
body ...)))))
|
||||
|
||||
(define %db
|
||||
|
@ -79,7 +80,10 @@
|
|||
|
||||
(test-group-with-cleanup "database"
|
||||
(test-assert "db-init"
|
||||
(%db (db-init database-name)))
|
||||
(begin
|
||||
(%db (db-init database-name))
|
||||
(%db-channel (make-critical-section (%db)))
|
||||
#t))
|
||||
|
||||
(test-assert "sqlite-exec"
|
||||
(begin
|
||||
|
@ -94,41 +98,40 @@ INSERT INTO Evaluations (specification, commits) VALUES (3, 3);")
|
|||
(test-equal "db-add-specification"
|
||||
example-spec
|
||||
(begin
|
||||
(db-add-specification (%db) example-spec)
|
||||
(car (db-get-specifications (%db)))))
|
||||
(db-add-specification example-spec)
|
||||
(car (db-get-specifications))))
|
||||
|
||||
(test-equal "db-add-build"
|
||||
#f
|
||||
(let ((build (make-dummy-build "/foo.drv")))
|
||||
(db-add-build (%db) build)
|
||||
(db-add-build build)
|
||||
|
||||
;; Should return #f when adding a build whose derivation is already
|
||||
;; there, see <https://bugs.gnu.org/28094>.
|
||||
(db-add-build (%db) build)))
|
||||
(db-add-build build)))
|
||||
|
||||
(test-equal "db-update-build-status!"
|
||||
(list (build-status scheduled)
|
||||
(build-status started)
|
||||
(build-status succeeded)
|
||||
"/foo.drv.log")
|
||||
(with-temporary-database db
|
||||
(with-temporary-database
|
||||
(let* ((derivation (db-add-build
|
||||
db
|
||||
(make-dummy-build "/foo.drv" 1
|
||||
#:outputs '(("out" . "/foo")))))
|
||||
(get-status (lambda* (#:optional (key #:status))
|
||||
(assq-ref (db-get-build db derivation) key))))
|
||||
(db-add-evaluation db (make-dummy-eval))
|
||||
(db-add-specification db example-spec)
|
||||
(assq-ref (db-get-build derivation) key))))
|
||||
(db-add-evaluation (make-dummy-eval))
|
||||
(db-add-specification example-spec)
|
||||
|
||||
(let ((status0 (get-status)))
|
||||
(db-update-build-status! db "/foo.drv" (build-status started))
|
||||
(db-update-build-status! "/foo.drv" (build-status started))
|
||||
(let ((status1 (get-status)))
|
||||
(db-update-build-status! db "/foo.drv" (build-status succeeded)
|
||||
(db-update-build-status! "/foo.drv" (build-status succeeded)
|
||||
#:log-file "/foo.drv.log")
|
||||
|
||||
;; Second call shouldn't make any difference.
|
||||
(db-update-build-status! db "/foo.drv" (build-status succeeded)
|
||||
(db-update-build-status! "/foo.drv" (build-status succeeded)
|
||||
#:log-file "/foo.drv.log")
|
||||
|
||||
(let ((status2 (get-status))
|
||||
|
@ -144,61 +147,61 @@ INSERT INTO Evaluations (specification, commits) VALUES (3, 3);")
|
|||
((3 "/baz.drv") (2 "/bar.drv") (1 "/foo.drv")) ;ditto
|
||||
((3 "/baz.drv")) ;nr = 1
|
||||
((2 "/bar.drv") (1 "/foo.drv") (3 "/baz.drv"))) ;status+submission-time
|
||||
(with-temporary-database db
|
||||
(with-temporary-database
|
||||
;; Populate the 'Builds'', 'Evaluations', and
|
||||
;; 'Specifications' tables in a consistent way, as expected by the
|
||||
;; 'db-get-builds' query.
|
||||
(db-add-build db (make-dummy-build "/foo.drv" 1
|
||||
(db-add-build (make-dummy-build "/foo.drv" 1
|
||||
#:outputs `(("out" . "/foo"))))
|
||||
(db-add-build db (make-dummy-build "/bar.drv" 2
|
||||
(db-add-build (make-dummy-build "/bar.drv" 2
|
||||
#:outputs `(("out" . "/bar"))))
|
||||
(db-add-build db (make-dummy-build "/baz.drv" 3
|
||||
(db-add-build (make-dummy-build "/baz.drv" 3
|
||||
#:outputs `(("out" . "/baz"))))
|
||||
(db-add-evaluation db (make-dummy-eval))
|
||||
(db-add-evaluation db (make-dummy-eval))
|
||||
(db-add-evaluation db (make-dummy-eval))
|
||||
(db-add-specification db example-spec)
|
||||
(db-add-evaluation (make-dummy-eval))
|
||||
(db-add-evaluation (make-dummy-eval))
|
||||
(db-add-evaluation (make-dummy-eval))
|
||||
(db-add-specification example-spec)
|
||||
|
||||
(db-update-build-status! db "/bar.drv" (build-status started)
|
||||
(db-update-build-status! "/bar.drv" (build-status started)
|
||||
#:log-file "/bar.drv.log")
|
||||
|
||||
(let ((summarize (lambda (alist)
|
||||
(list (assq-ref alist #:id)
|
||||
(assq-ref alist #:derivation)))))
|
||||
(vector (map summarize (db-get-builds db '((nr . 3)
|
||||
(order . build-id))))
|
||||
(map summarize (db-get-builds db '()))
|
||||
(map summarize (db-get-builds db '((jobset . "guix"))))
|
||||
(map summarize (db-get-builds db '((nr . 1))))
|
||||
(vector (map summarize (db-get-builds '((nr . 3) (order . build-id))))
|
||||
(map summarize (db-get-builds '()))
|
||||
(map summarize (db-get-builds '((jobset . "guix"))))
|
||||
(map summarize (db-get-builds '((nr . 1))))
|
||||
(map summarize
|
||||
(db-get-builds
|
||||
db '((order . status+submission-time))))))))
|
||||
(db-get-builds '((order . status+submission-time))))))))
|
||||
|
||||
(test-equal "db-get-pending-derivations"
|
||||
'("/bar.drv" "/foo.drv")
|
||||
(with-temporary-database db
|
||||
(with-temporary-database
|
||||
;; Populate the 'Builds', 'Evaluations', and
|
||||
;; 'Specifications' tables. Here, two builds map to the same derivation
|
||||
;; but the result of 'db-get-pending-derivations' must not contain any
|
||||
;; duplicate.
|
||||
(db-add-build db (make-dummy-build "/foo.drv" 1
|
||||
(db-add-build (make-dummy-build "/foo.drv" 1
|
||||
#:outputs `(("out" . "/foo"))))
|
||||
(db-add-build db (make-dummy-build "/bar.drv" 2
|
||||
(db-add-build (make-dummy-build "/bar.drv" 2
|
||||
#:outputs `(("out" . "/bar"))))
|
||||
(db-add-build db (make-dummy-build "/foo.drv" 3
|
||||
(db-add-build (make-dummy-build "/foo.drv" 3
|
||||
#:outputs `(("out" . "/foo"))))
|
||||
(db-add-evaluation db (make-dummy-eval))
|
||||
(db-add-evaluation db (make-dummy-eval))
|
||||
(db-add-evaluation db (make-dummy-eval))
|
||||
(db-add-specification db example-spec)
|
||||
(db-add-evaluation (make-dummy-eval))
|
||||
(db-add-evaluation (make-dummy-eval))
|
||||
(db-add-evaluation (make-dummy-eval))
|
||||
(db-add-specification example-spec)
|
||||
|
||||
(sort (db-get-pending-derivations db) string<?)))
|
||||
(sort (db-get-pending-derivations) string<?)))
|
||||
|
||||
(test-assert "db-close"
|
||||
(db-close (%db)))
|
||||
|
||||
(delete-file database-name))
|
||||
(begin
|
||||
(%db-channel #f)
|
||||
(delete-file database-name)))
|
||||
|
||||
;;; Local Variables:
|
||||
;;; eval: (put 'with-temporary-database 'scheme-indent-function 1)
|
||||
;;; eval: (put 'with-temporary-database 'scheme-indent-function 0)
|
||||
;;; End:
|
||||
|
|
|
@ -125,14 +125,17 @@
|
|||
json->scm)))
|
||||
|
||||
(test-assert "db-init"
|
||||
(%db (db-init database-name)))
|
||||
(begin
|
||||
(%db (db-init database-name))
|
||||
(%db-channel (make-critical-section (%db)))
|
||||
#t))
|
||||
|
||||
(test-assert "cuirass-run"
|
||||
(call-with-new-thread
|
||||
(lambda ()
|
||||
(run-fibers
|
||||
(lambda ()
|
||||
(run-cuirass-server (%db) #:port 6688))
|
||||
(run-cuirass-server #:port 6688))
|
||||
#:drain? #t))))
|
||||
|
||||
(test-assert "wait-server"
|
||||
|
@ -184,11 +187,11 @@
|
|||
(evaluation2
|
||||
'((#:specification . "guix")
|
||||
(#:commits . ("fakesha2" "fakesha3")))))
|
||||
(db-add-build (%db) build1)
|
||||
(db-add-build (%db) build2)
|
||||
(db-add-specification (%db) specification)
|
||||
(db-add-evaluation (%db) evaluation1)
|
||||
(db-add-evaluation (%db) evaluation2)))
|
||||
(db-add-build build1)
|
||||
(db-add-build build2)
|
||||
(db-add-specification specification)
|
||||
(db-add-evaluation evaluation1)
|
||||
(db-add-evaluation evaluation2)))
|
||||
|
||||
(test-assert "/build/1"
|
||||
(hash-table=?
|
||||
|
@ -275,4 +278,6 @@
|
|||
(test-assert "db-close"
|
||||
(db-close (%db)))
|
||||
|
||||
(delete-file database-name))
|
||||
(begin
|
||||
(%db-channel #f)
|
||||
(delete-file database-name)))
|
||||
|
|
Loading…
Reference in New Issue