mirror of
https://github.com/penpot/penpot.git
synced 2026-11-01 07:20:32 +00:00
🐛 Settle a cancelled export without noise or a leaked watchdog
A cancelled export is a normal ending, but the run treated it like a failure: an error log with a full trace for something the user asked for. Worse, the watchdog interval outlived the run — its stopper was bound inside the scope the failure unwinds, so a cancelled or failed job kept beating on a dead row forever, one HTTP call a second that only ever answers skip. The stopper now travels in an atom the catch reaches, the run stops beating on every ending, and a cancel logs a single info line where a failure keeps its error with the cause. AI-assisted-by: glm-5.3-flash
This commit is contained in:
parent
b22cce46f1
commit
a63140d309
@ -322,6 +322,13 @@
|
||||
:code (or (:code data) :export-failed)
|
||||
:hint (or (:hint data) (ex-message cause) "export failed")}))
|
||||
|
||||
(defn cancelled?
|
||||
"Whether `cause` ends a run the user asked to end: a cancelled
|
||||
export is a normal ending, reported and logged as one, never as a
|
||||
failure with a trace."
|
||||
[cause]
|
||||
(= :job-cancelled (:code (ex-data cause))))
|
||||
|
||||
(defn- settle-failure!
|
||||
"Settles the job as failed, after releasing the temp files the run
|
||||
owns. The settle never rejects: if the failure cannot reach the
|
||||
@ -338,9 +345,14 @@
|
||||
|
||||
(defn run-export!
|
||||
[{:keys [job-id]} params]
|
||||
(let [session (atom nil)
|
||||
cancelled (atom false)
|
||||
_ (jobs/register! job-id)
|
||||
(let [session (atom nil)
|
||||
cancelled (atom false)
|
||||
;; the watchdog outlives the scope that starts it, so its
|
||||
;; stopper travels in this atom: a run that ends badly stops
|
||||
;; beating too, or its interval would knock on a dead job
|
||||
;; forever, one HTTP call a second that only ever answers skip
|
||||
stop-watchdog! (atom nil)
|
||||
_ (jobs/register! job-id)
|
||||
stop-cmd! (fn []
|
||||
(reset! cancelled true)
|
||||
;; the hard-cancel of the render the run holds: the
|
||||
@ -355,11 +367,11 @@
|
||||
:job-id (str job-id))
|
||||
beats (start-beats! job-id stop-cmd!)
|
||||
beats! (:beat! beats)
|
||||
stop! (with-watchdog! beats! (:last beats))
|
||||
_ (reset! stop-watchdog! (with-watchdog! beats! (:last beats)))
|
||||
_ (check-beat! (beats! :preparing {} :force? true))
|
||||
plan (make-plan job-id token params)
|
||||
resource (run-prepared! beats! cancelled plan)
|
||||
_ (stop!)
|
||||
_ (@stop-watchdog!)
|
||||
artifact {:path (str (:path resource))
|
||||
:filename (:filename resource)
|
||||
:mtype (:mtype resource)}
|
||||
@ -371,7 +383,10 @@
|
||||
:outcome "completed")]
|
||||
nil)
|
||||
(fn [cause]
|
||||
(l/error :hint "export job failed" :job-id (str job-id) :cause cause)
|
||||
(when-let [stop @stop-watchdog!] (stop))
|
||||
(if (cancelled? cause)
|
||||
(l/info :hint "export job cancelled" :job-id (str job-id))
|
||||
(l/error :hint "export job failed" :job-id (str job-id) :cause cause))
|
||||
(settle-failure! job-id session cause)))))
|
||||
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
["node:fs/promises" :as fsp]
|
||||
["node:path" :as path]
|
||||
["undici" :as http]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.transit :as transit]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.consumer.api :as api]
|
||||
@ -272,6 +273,34 @@
|
||||
(restore!)
|
||||
(done)))))
|
||||
|
||||
(t/deftest cancelled-is-a-normal-ending-not-a-failure
|
||||
(t/testing "the cooperative cancel classifies as cancelled"
|
||||
(t/is (true? (exports/cancelled?
|
||||
(ex/error :type :internal
|
||||
:code :job-cancelled
|
||||
:hint "export job was cancelled")))))
|
||||
(t/testing "anything else is a failure"
|
||||
(t/is (false? (exports/cancelled? (ex-info "render boom" {}))))))
|
||||
|
||||
(t/deftest a-failed-run-stops-beating-after-the-settle
|
||||
;; the watchdog interval belongs to the run: if the failure path
|
||||
;; does not clear it, it beats on a dead job forever — one HTTP call
|
||||
;; a second that only ever answers skip
|
||||
(t/async done
|
||||
(let [job-id (uuid/next)
|
||||
sid (uuid/next)
|
||||
params (export-params)
|
||||
stages (volatile! [])
|
||||
respond! (fake-fetch! (answer-with params sid stages))
|
||||
restore! (fake-render-fail!)]
|
||||
(p/let [_ (worker/process! (run-claim job-id))
|
||||
_ (p/delay 1400)]
|
||||
(t/testing "no beat traveled after the fail settle"
|
||||
(t/is (= "fail-job" (last (steps-of)))))
|
||||
(respond!)
|
||||
(restore!)
|
||||
(done)))))
|
||||
|
||||
(t/deftest process-settles-a-failed-export-with-fail-job
|
||||
(t/async done
|
||||
(let [job-id (uuid/next)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user