mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 09:30:38 +00:00
140 lines
6.5 KiB
Clojure
140 lines
6.5 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns benches.render-wasm.node.execution
|
|
"Checked command execution, final publication and exit status."
|
|
(:require
|
|
[app.common.schema :as sm]
|
|
[benches.render-wasm.errors :as errors :refer [attempt failure?]]
|
|
[benches.render-wasm.node.cli.registry :as registry]
|
|
[benches.render-wasm.node.log :as log]
|
|
[benches.render-wasm.node.outcome :as outcome]
|
|
[benches.render-wasm.node.request :as request]))
|
|
|
|
(defn on-signals
|
|
"Installs SIGINT/SIGTERM callbacks receiving the signal name. Returns an
|
|
idempotent remover. The caller can abort its work and let output drain."
|
|
[callback]
|
|
(let [interrupt (fn [] (callback "SIGINT"))
|
|
terminate (fn [] (callback "SIGTERM"))]
|
|
(.on js/process "SIGINT" interrupt)
|
|
(.on js/process "SIGTERM" terminate)
|
|
(fn []
|
|
(.removeListener js/process "SIGINT" interrupt)
|
|
(.removeListener js/process "SIGTERM" terminate))))
|
|
|
|
(defn ^:async with-interruption
|
|
"Awaits `(f signal)` and returns its outcome. SIGINT/SIGTERM abort the signal
|
|
and select status 130/143. Interrupted outcomes retain fatal errors and partial
|
|
evidence. Thrown or rejected failures become outcomes."
|
|
[f]
|
|
(let [controller (js/AbortController.)
|
|
signal (.-signal controller)
|
|
interrupted (atom nil)
|
|
remove-signals (on-signals
|
|
(fn [signal-name]
|
|
(when-not (.-aborted signal)
|
|
(reset! interrupted signal-name)
|
|
(.abort controller
|
|
(ex-info "interrupted"
|
|
{:type ::interrupted :signal signal-name})))))]
|
|
(try
|
|
(let [result (attempt :catch-fatal (await (f signal)))
|
|
finished (if (failure? result) (outcome/failed result {}) result)]
|
|
(cond-> finished
|
|
@interrupted (-> (update :errors #(filterv errors/fatal? %))
|
|
(assoc :exit-code (outcome/interruption-code @interrupted)))))
|
|
(finally
|
|
(remove-signals)))))
|
|
|
|
(defn- configure-console
|
|
[console local]
|
|
(log/configure (merge (select-keys local [:stdout :stderr]) console)))
|
|
|
|
(defn- check-gpu
|
|
[command options]
|
|
(when (and (:require-gpu options)
|
|
(or (= command "run")
|
|
(and (= command "ab") (not (:dry-run options))))
|
|
(not= "1" (.-PENPOT_GPU (.-env js/process))))
|
|
(throw (ex-info "--require-gpu needs PENPOT_GPU=1. Start the container with ./manage.sh run-devenv --gpu and use pnpm run render-bench."
|
|
{:type ::gpu-required}))))
|
|
|
|
(defn- log-failures
|
|
[finished]
|
|
(doseq [[index cause] (map-indexed vector (:errors finished))]
|
|
(let [data (ex-data cause)
|
|
validation (some-> (::sm/explain data) sm/simplify)]
|
|
(log/error {:message (errors/message-of cause)
|
|
:fields (into (filterv (comp some? second)
|
|
[[:type (:type data)] [:cause-type (:cause-type data)]
|
|
[:command (:command data)]
|
|
[:phase (:phase data)] [:build (:build-id data)]
|
|
[:reason (:reason data)] [:manifest-error (:manifest-error data)]
|
|
[:baseline (:baseline data)] [:candidate (:candidate data)]
|
|
[:features (:features data)]
|
|
[:count (:count data)] [:max-features (:max-features data)]
|
|
[:validation validation]])
|
|
(map #(vector :difference (:field %)) (:mismatches data)))
|
|
:tail-log (:log-path data)
|
|
:tail? (zero? index)}))))
|
|
|
|
(defn- report-fallback
|
|
[cause local]
|
|
(attempt :catch-fatal
|
|
(let [message (errors/message-of cause)
|
|
stream (or (:stderr local) (.-stderr js/process))]
|
|
(.write ^js stream
|
|
(str "Could not report benchmark failure: "
|
|
(subs message 0 (min 1024 (count message))) "\n")))))
|
|
|
|
(defn- finish
|
|
[result local]
|
|
(binding [log/*log-path* (:log-path result)]
|
|
(let [finished (if-some [report (:report result)]
|
|
(outcome/publish result #(log/result report))
|
|
result)
|
|
reported (attempt :catch-fatal (log-failures finished))]
|
|
(if (failure? reported)
|
|
(do
|
|
(report-fallback reported local)
|
|
(:exit-code (outcome/add-error finished reported)))
|
|
(:exit-code finished)))))
|
|
|
|
(defn fail
|
|
"Prints a failure and returns its numeric status. Optional resolved console
|
|
settings and local streams configure its output."
|
|
([cause]
|
|
(fail cause {} {}))
|
|
([cause console local]
|
|
(configure-console console local)
|
|
(finish (outcome/failed (errors/as-failure cause) {}) local)))
|
|
|
|
(defn ^:async execute
|
|
"Checks a complete received request, awaits its supported handler and returns
|
|
a numeric exit status after final publication. Handlers receive local signal,
|
|
root access and services plus resolved options. Local settings may supply
|
|
streams, a root thunk and services."
|
|
([received handlers]
|
|
(execute received handlers {}))
|
|
([received handlers local]
|
|
(configure-console {} local)
|
|
(let [result (attempt :catch-fatal
|
|
(let [{:keys [command console options]} (request/check received)]
|
|
(configure-console console local)
|
|
(let [handler (or (get handlers command)
|
|
(throw (ex-info "command is unavailable at this execution entry"
|
|
{:type ::unsupported-command :command command})))
|
|
options (registry/check-options command options)]
|
|
(check-gpu command options)
|
|
(await (with-interruption
|
|
(fn [signal]
|
|
(handler (assoc (select-keys local [:get-root :services])
|
|
:signal signal)
|
|
options)))))))
|
|
result (if (failure? result) (outcome/failed result {}) result)]
|
|
(finish result local))))
|