✨ Own the temp area as the tmpdir service

The legacy shell creates its tmpdir at namespace load, out of any
lifecycle. The :exporter/tmpdir service pulls that moment into the
boot: fail fast on a bad :tempdir, ordered before the renderer that
writes to it, carrying the path for the day it becomes injected.

AI-assisted-by: muse-spark-1.3-contributor
This commit is contained in:
Andrey Antukh 2026-10-08 21:54:59 +00:00
parent be3f7d39fd
commit 3225f1bdfd
4 changed files with 41 additions and 6 deletions

View File

@ -50,6 +50,7 @@ on this side: the row of the job, its cancel and its result are the backend's.
- `exporter.jobs` is the local cancel arm, smaller than the legacy `app.jobs`: one flag per claimed job (`register`/`mark-cancelled` returning true/nil/`cancelled?`/`release`) and one zero-arg check constructor per job (`check-cancelled`, raising `:job-cancelled`). No terminate callbacks and no shared-array signal travel through here — the abort belongs to whoever holds the render (the per-lease interval of `exporter.wasm.scope` terminates its own worker). The runner releases the registry entry on every settle; the legacy runner never did (one leaked entry per job).
- `exporter.consumer.plan` is the render plan (`prepare-exports`, `count-objects`, `sanitize-file-regex`), dispatching on `exporter.renderer/wasm?` instead of the legacy `headless?`.
- `exporter.consumer` is the runner: `run-export [cfg claim params]` keeps the legacy beats/watchdog/settle (first breath, render session, multipart complete or `fail-job`, temp release) but renders through the facade (`render [cfg & task]`) with one check from `exporter.jobs` shared between units of work and drivers. `cfg` is the render view; the claim names the job, `params` carries the frozen job params. The settle never rejects; only bugs of this process do.
- `exporter.tmpdir` owns the temp-file area (`:exporter/tmpdir`): the legacy shell creates its dir at namespace load, so referencing the var inside `init-key` pulls that moment into the lifecycle (fail fast on a bad `:tempdir`, ordered before the renderer that writes to it). Named for what it owns, not for the legacy namespace.
- `exporter.consumer.api` is the backend management client (claim/progress/session/settle, transit bodies, multipart artifact), a faithful `^:async` port with no promesa chains and the same `fetch` def seam for hermetic tests.
- `exporter.consumer.config` holds the consumer knobs (`concurrency` floored at 1, `queue-key`, `queue-name`): the runner, the worker and `exporter.main` read it, so no namespace under `exporter.*` depends on `app.consumer.*` anymore.
- `exporter.consumer.worker` is the queue poller (`read-payload`, `process`, `poll-once`, `start`/`stop` with a running flag, no promesa): K connections over the `:exporter` queue, claim → `run-export` through the injected `:renderer` → settle before the next pop. The worker config carries the queue knobs plus the `:renderer` by ref; no render statics nest inside it. `stop` quits every connection and ends every loop (the legacy loops kept failing turns after the stop); boot cleans orphaned temp files first.

View File

@ -24,6 +24,7 @@
[exporter.consumer :as-alias consumer]
[exporter.consumer.config :as ccfg]
[exporter.consumer.worker]
[exporter.tmpdir]
[exporter.utils.system :as system]
[exporter.wasm :as-alias wasm]
[exporter.wasm.pool]))
@ -41,12 +42,16 @@
::wasm/pool
{:max (ccfg/concurrency)}
:exporter/tmpdir
{}
:exporter/renderer
{::browser/pool (system/ref :exporter.browser/pool)
::wasm/pool (system/ref :exporter.wasm/pool)
:base-uri (cf/get-internal-uri)
:public-uri (cf/get :public-uri)
:svgo? (contains? cf/flags :exporter-svgo)}
{::browser/pool (system/ref :exporter.browser/pool)
::wasm/pool (system/ref :exporter.wasm/pool)
:exporter/tmpdir (system/ref :exporter/tmpdir)
:base-uri (cf/get-internal-uri)
:public-uri (cf/get :public-uri)
:svgo? (contains? cf/flags :exporter-svgo)}
::consumer/worker
{:renderer (system/ref :exporter/renderer)

View File

@ -0,0 +1,25 @@
;; 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 SUBSIDIARY SL
(ns exporter.tmpdir
"The temp-file area as a system component (`:exporter/tmpdir`).
The legacy `app.util.shell` creates its `tmpdir` at namespace load:
config read, log line and `mkdirSync` the first time anything
requires it, in every thread including render workers and tests.
This component pulls that moment into the lifecycle: referencing the
var forces the creation during boot (fail fast on a bad `:tempdir`),
orders it before anything that writes temp files, and carries the
path in the instance for the day the temp area becomes injected
instead of global. Named for what it owns, not for the legacy
namespace it shadows."
(:require
[app.util.shell :as sh]
[exporter.utils.system :as system]))
(defmethod system/init-key :exporter/tmpdir
[_ _]
{:tmpdir sh/tmpdir})

View File

@ -52,9 +52,11 @@
(js/Promise.resolve #js {}))}
:exporter/renderer {:exporter.browser/pool (system/ref :exporter.browser/pool)
:exporter.wasm/pool (system/ref :exporter.wasm/pool)
:exporter/tmpdir (system/ref :exporter/tmpdir)
:base-uri "http://internal/"
:public-uri "http://public/"
:svgo? false}})
:svgo? false}
:exporter/tmpdir {}})
(defn- pool-of
[]
@ -121,6 +123,8 @@
(t/is (some? (pool-of)))
(t/testing "the renderer boots as a render fn over the resolved pools"
(t/is (fn? (:exporter/renderer @main/system))))
(t/testing "the temp area boots before anything that writes to it"
(t/is (string? (:tmpdir (:exporter/tmpdir @main/system)))))
(t/testing "the wasm pool rides the same lifecycle"
(t/is (some? (:pool (wasm-pool-of))))
(t/is (= 300000 (:timeout-ms (wasm-pool-of)))))