guix-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[no subject]


From: Ludovic Courtès
Date: Wed, 18 Sep 2024 16:50:07 -0400 (EDT)

branch: main
commit 5e5912717957d294de83e5dcea894214ad97ac75
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Wed Sep 18 13:08:37 2024 +0200

    database: Synchronously update the status of dependent builds.
    
    * src/cuirass/database.scm (db-reschedule-dependent-builds)
    (db-mark-dependent-builds-as-failed, db-get-build-dependents)
    (list->sql-array): New procedures.
    (db-update-build-status!): Use them to synchronously update the status
    of dependent builds.
    * tests/database.scm ("status of dependent builds"): New test.
    ("dependencies trigger"): Rename to…
    ("dependents marked as 'failed-dependency'"): … this.  Change to
    ‘test-equal’.  Remove call to ‘db-update-failed-builds!’, which is no
    longer needed.
---
 src/cuirass/database.scm | 83 ++++++++++++++++++++++++++++++++++++++++++++++--
 tests/database.scm       | 49 ++++++++++++++++++++++------
 2 files changed, 120 insertions(+), 12 deletions(-)

diff --git a/src/cuirass/database.scm b/src/cuirass/database.scm
index 70e965a..7e929ac 100644
--- a/src/cuirass/database.scm
+++ b/src/cuirass/database.scm
@@ -333,6 +333,16 @@ parameters matches the number of arguments to bind."
                       (delete-duplicates args))))
     (exec-query db query (map normalize params))))
 
+(define (list->sql-array lst)
+  "Return the SQL array representation of LST, a list of integers or strings."
+  (string-append "{ " (string-join
+                       (map (match-lambda
+                              ((? number? n) (number->string n))
+                              (str str))
+                            lst)
+                       ",")
+                 " }"))
+
 (define %create-database?
   (make-parameter #f))
 
@@ -1125,6 +1135,61 @@ WHERE dep.source = " build))
 
 (define build-dependencies/id (compose db-get-build-dependencies build-id))
 
+(define (db-reschedule-dependent-builds build)
+  "Reschedule builds that depend on BUILD that only have succeeding
+dependencies.
+
+Note: This is an expensive query but is usually rarely needed."
+  (let ((rescheduled (with-db-connection db
+                       (exec-query/bind db "
+UPDATE Builds SET status = " (build-status scheduled) "
+FROM
+  -- Select the dependents of this build with exactly one failing
+  -- dependency.
+  (SELECT dependents.id
+   FROM
+     -- Get all the dependents of this build.
+     (SELECT Builds.id, Builds.derivation FROM Builds
+      LEFT JOIN BuildDependencies as bd ON bd.source = Builds.id
+      WHERE bd.target = " (build-id build) " GROUP BY Builds.id)
+   AS dependents
+   LEFT JOIN BuildDependencies AS bd ON bd.source = dependents.id
+   LEFT JOIN Builds AS dependencies
+          ON dependencies.id = bd.target AND dependencies.status != 0
+   GROUP BY dependents.id HAVING count(dependencies.id) = 0)
+AS relevantdependents
+WHERE Builds.id = relevantdependents.id;"))))
+    (log-info "rescheduled ~a dependent builds of build ~a (~a)"
+              rescheduled (build-id build) (build-derivation build))
+    rescheduled))
+
+(define (db-get-build-dependents id)
+  "Return the list of IDs of builds that depend on ID."
+  (with-db-connection db
+    (match (exec-query/bind db "
+SELECT source FROM BuildDependencies
+WHERE target = " id ";")
+      (((id) ...)
+       (map string->number id)))))
+
+(define (db-mark-dependent-builds-as-failed build)
+  "Change the status of builds that depend on ID to \"failed-dependency\",
+recursively."
+  ;; Since this is recursive, this cannot be done as a single query.
+  (let loop ((dependents (db-get-build-dependents (build-id build))))
+    (let ((marked (with-db-connection db
+                    (exec-query/bind db "
+UPDATE Builds
+SET status = " (build-status failed-dependency) ",
+    stoptime = (extract(epoch from now()))::int
+WHERE id = ANY(" (list->sql-array dependents) ");"))))
+      (unless (zero? marked)
+        (log-info "marked ~a dependent builds of build ~a (~a) as failed"
+                  marked (build-id build) (build-derivation build))
+
+        ;; Recurse.
+        (loop (append-map db-get-build-dependents dependents))))))
+
 (define (db-update-resumable-builds!)
   "Update the build status of the failed-dependency builds which all
 dependencies are successful to scheduled."
@@ -1246,7 +1311,8 @@ log file for DRV."
         ;; times in a row, for instance.  The 'last_status' field is updated
         ;; with the status of the last completed build with the same
         ;; 'job_name' and 'specification'.
-        (let* ((last-status (db-get-last-status drv))
+        (let* ((build (db-get-build drv))
+               (last-status (db-get-last-status drv))
                (weather (build-status->weather status last-status))
                (rows
                 (exec-query/bind db "
@@ -1256,9 +1322,20 @@ UPDATE Builds SET stoptime =" now
 ", weather = " weather
 "WHERE derivation =" drv
 " AND status != " status ";")))
+
           (when (positive? rows)
-            (let* ((build (db-get-build drv))
-                   (spec (build-specification-name build))
+            (when (= status (build-status failed))
+              ;; Update the status of dependent builds.
+              (db-mark-dependent-builds-as-failed build))
+
+            (when (and (= status (build-status succeeded))
+                       (= (build-current-status build) (build-status failed)))
+              ;; Transitioning from "failed" to "succeeded", for instance
+              ;; because the build was restarted, so reschedule every build 
that
+              ;; depends on this one.
+              (db-reschedule-dependent-builds build))
+
+            (let* ((spec (build-specification-name build))
                    (specification (db-get-specification spec))
                    (notifications
                     (specification-notifications specification)))
diff --git a/tests/database.scm b/tests/database.scm
index 2f20cf5..d3bcc60 100644
--- a/tests/database.scm
+++ b/tests/database.scm
@@ -968,7 +968,8 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 
0);")
                      (db-update-build-status! drv (build-status submitted)))
                    (cons drv lst)))))))
 
-  (test-assert "dependencies trigger"
+  (test-equal "dependents marked as 'failed-dependency'"
+    (make-list 3 (build-status failed-dependency))
     (with-fibers
       (let ((drv-1 "/build-dep-1.drv")
             (drv-2 "/build-dep-2.drv")
@@ -977,8 +978,7 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 
0);")
             (drv-5 "/build-dep-5.drv")
             (drv-6 "/build-dep-6.drv")
             (drv-7 "/build-dep-7.drv")
-            (status (lambda (drv)
-                      (build-current-status (db-get-build drv)))))
+            (status (compose build-current-status db-get-build)))
         (for-each (compose db-add-build make-dummy-build)
                   (list drv-1 drv-2 drv-3 drv-4
                         drv-5 drv-6 drv-7))
@@ -995,12 +995,43 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
         (db-update-build-status! drv-1 (build-status failed))
         (db-update-build-status! drv-2 (build-status succeeded))
         (db-update-build-status! drv-5 (build-status canceled))
-        (let loop ()
-          (unless (eq? (db-update-failed-builds!) 0)
-            (loop)))
-        (and (eq? (status drv-4) (build-status failed-dependency))
-             (eq? (status drv-6) (build-status failed-dependency))
-             (eq? (status drv-7) (build-status failed-dependency))))))
+
+        (map status (list drv-4 drv-6 drv-7)))))
+
+  (test-equal "status of dependent builds"
+    `((initial ,(build-status failed-dependency)
+               ,(build-status failed-dependency)
+               ,(build-status failed-dependency))
+      (final ,(build-status scheduled)
+             ,(build-status failed-dependency)
+             ,(build-status scheduled)))
+    (with-fibers
+      (let ((drv '("/primary-drv-1.drv" "/dep-1.drv"
+                   "/primary-drv-2.drv" "/dep-2.drv"
+                   "/primary-drv-3.drv"))
+            (status (compose build-current-status db-get-build)))
+        (for-each (compose db-add-build make-dummy-build) drv)
+        (db-add-build-dependencies "/primary-drv-1.drv"
+                                   '("/dep-1.drv"))
+        (db-add-build-dependencies "/primary-drv-2.drv"
+                                   '("/dep-1.drv" "/dep-2.drv"))
+        (db-add-build-dependencies "/primary-drv-3.drv"
+                                   '("/dep-1.drv"))
+
+        ;; This should mark their dependents as "failed-dependency".
+        (db-update-build-status! "/dep-1.drv" (build-status failed))
+        (db-update-build-status! "/dep-2.drv" (build-status failed))
+
+        (let ((initial (map status '("/primary-drv-1.drv"
+                                     "/primary-drv-2.drv"
+                                     "/primary-drv-3.drv"))))
+          ;; This should reschedule its dependents, but only those that have
+          ;; no other failed dependency.
+          (db-update-build-status! "/dep-1.drv" (build-status succeeded))
+          `((initial ,@initial)
+            (final ,@(map status '("/primary-drv-1.drv"
+                                   "/primary-drv-2.drv"
+                                   "/primary-drv-3.drv"))))))))
 
   (test-equal "db-get-first-build-failure"
     '("/thing.drv2"                               ;last success



reply via email to

[Prev in Thread] Current Thread [Next in Thread]