mirror of
https://github.com/penpot/penpot.git
synced 2026-11-08 19:16:56 +00:00
* 🐛 Give the branch concurrency limits a timeout Every global entry for branch and pull-request commands lacked a :timeout, so a saturated limiter parked its waiters forever and climit-test/example-config-bounds-every-wait failed on seven ids. Each entry now waits at most 60 s, the value of the other slow operations. The size gates in files_branch.clj refuse files whose operation would exceed 30 s, so the wait covers one in-flight operation with as much again in margin. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro> * 🐛 Make the quota racer a member of the file team File access requires membership of the file's team since the permission check started joining team_profile_rel. The racer in create-checks-the-branch-quota-inside-the-lock held only a file role, so its create failed with :object-not-found and the test never raced. The racer now joins the file's team as an editor, so both creates again compete for the last branch slot. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro> --------- Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
201 lines
7.7 KiB
Clojure
201 lines
7.7 KiB
Clojure
;; Example climit.edn file
|
|
;; Required: permits
|
|
;; Optional: queue, omitted means Integer/MAX_VALUE (unbounded)
|
|
;; Optional: timeout in milliseconds, omitted means the thread waits for a
|
|
;; permit forever, and a saturated limiter parks it for good. Values below:
|
|
;; 60s for operations that can be slow (file updates, images, fonts, thumbnails,
|
|
;; snapshots, imports, branches, pull requests) and 30s for the rest.
|
|
;; Note: a queue bounds how many waiters pile up, a timeout bounds how long one
|
|
;; waiter waits. Every entry sets a timeout; the by-profile ones also set a
|
|
;; queue, which is where a pile-up hurts.
|
|
{:update-file/global
|
|
{:permits 20 :timeout 60000}
|
|
:update-file/by-profile
|
|
{:permits 1 :queue 5 :timeout 60000}
|
|
|
|
:process-font/global
|
|
{:permits 4 :timeout 60000}
|
|
:process-font/by-profile
|
|
{:permits 1 :timeout 60000}
|
|
|
|
:process-image/global
|
|
{:permits 8 :timeout 60000}
|
|
:process-image/by-profile
|
|
{:permits 1 :timeout 60000}
|
|
|
|
:auth/global
|
|
{:permits 8 :timeout 30000}
|
|
|
|
:root/global
|
|
{:permits 40 :timeout 30000}
|
|
|
|
:root/by-profile
|
|
{:permits 10 :queue 30 :timeout 30000}
|
|
|
|
:file-thumbnail-ops/global
|
|
{:permits 20 :timeout 60000}
|
|
:file-thumbnail-ops/by-profile
|
|
{:permits 2 :timeout 60000}
|
|
|
|
:submit-audit-events/by-profile
|
|
{:permits 1 :queue 3 :timeout 30000}
|
|
|
|
:restore-file-snapshot/global
|
|
{:permits 3 :timeout 60000}
|
|
|
|
:restore-file-snapshot/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
:create-file-snapshot/global
|
|
{:permits 3 :timeout 60000}
|
|
|
|
:create-file-snapshot/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
:send-user-feedback/global
|
|
{:permits 4 :timeout 30000}
|
|
|
|
:send-user-feedback/by-profile
|
|
{:permits 1 :queue 3 :timeout 30000}
|
|
|
|
:import-binfile/global
|
|
{:permits 4 :timeout 60000}
|
|
|
|
:import-binfile/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
:create-import-binfile-job/global
|
|
{:permits 4 :timeout 60000}
|
|
|
|
:create-import-binfile-job/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
:profile-plugin-ops/global
|
|
{:permits 4 :timeout 30000}
|
|
|
|
:profile-plugin-ops/by-profile
|
|
{:permits 1 :queue 2 :timeout 30000}
|
|
|
|
;; Every limit below is provisional. The container that runs the preview
|
|
;; has no heap cap: `docker/devenv/files/entrypoint.sh` and
|
|
;; `backend/scripts/_env` both replace JAVA_OPTS without `-Xmx`, and the
|
|
;; live JVM command line carries none, so the budget is the JVM default
|
|
;; and it is unmeasured. The permit counts are derived from the
|
|
;; neighbours above instead of from a memory figure, and they stay
|
|
;; provisional until a peak-heap measurement of a cold compare on the
|
|
;; design-system file replaces them.
|
|
;;
|
|
;; The anchor is the snapshot pair above: `:create-file-snapshot/global`
|
|
;; admits three whole-file operations at once, each realizing one
|
|
;; document and writing one snapshot, and `:restore-file-snapshot/global`
|
|
;; admits the same three for an operation that also writes a rollback
|
|
;; snapshot and the older payload back into the file. A branch operation
|
|
;; realizes more than one document, so it has to admit fewer at once to
|
|
;; keep the same number of realized documents in flight.
|
|
;;
|
|
;; What each operation holds, read from the code:
|
|
;; * `create-file-branch` realizes main once and writes one snapshot
|
|
;; (`backend/src/app/rpc/commands/files_branch.clj::create-file-branch`),
|
|
;; which is the anchor's shape and the anchor's cost.
|
|
;; * `merge-file-branch` and `update-branch-from-main` hold three
|
|
;; realized documents at once: main, the derived branch, and the base
|
|
;; snapshot (`files_branch.clj::merge-file-branch`,
|
|
;; `files_branch.clj::update-branch-from-main`). Both also write a
|
|
;; snapshot and a whole file payload, and the update computes a
|
|
;; second diff for the squash, so they earn the tightest count.
|
|
;; * `materialize-file-branch` realizes the branch and the libraries it
|
|
;; validates against, then writes the whole payload
|
|
;; (`files_branch.clj::materialize-file-branch`), so it sits between
|
|
;; the anchor and the merge pair.
|
|
;; * `create-pull-request` and `update-pull-request-snapshot` realize the
|
|
;; branch once and write one review snapshot
|
|
;; (`files_pull_request.clj::create-pull-request`,
|
|
;; `files_pull_request.clj::update-pull-request-snapshot`), which is
|
|
;; the anchor's shape again.
|
|
;;
|
|
;; The write-side operations carry a queue so that a refusal follows a
|
|
;; bounded wait rather than arriving instantly. The queue sits on the
|
|
;; per-profile entry, following the anchor, and on the global entry
|
|
;; where the command declares no per-profile queue. The per-profile
|
|
;; entry of one permit keeps a single designer to one operation at a
|
|
;; time. The two read-side entries are generous by design and carry no
|
|
;; queue, so a panel open waits for its turn instead of being refused:
|
|
;; six three-document comparisons hold eighteen realized documents,
|
|
;; fewer than the twenty one-document saves `:update-file/global`
|
|
;; already admits.
|
|
;;
|
|
;; Every entry below, global and per-profile, read and write, takes the
|
|
;; 60 s timeout of the slow operations above. The size gates in
|
|
;; `backend/src/app/rpc/commands/files_branch.clj` refuse any file large
|
|
;; enough for an operation to exceed 30 s, with the measured limit
|
|
;; halved, so a 60 s wait covers one in-flight operation at that
|
|
;; ceiling with as much again in margin.
|
|
|
|
;; create-file-branch realizes main and writes one snapshot, the shape
|
|
;; of `:create-file-snapshot` above, so it takes the anchor's numbers
|
|
;; unchanged. Provisional.
|
|
:create-file-branch/global
|
|
{:permits 3 :timeout 60000}
|
|
|
|
:create-file-branch/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; A merge holds three realized documents, a snapshot write, and a
|
|
;; whole-file write into main, so one instance-wide operation holds the
|
|
;; anchor's whole three-permit budget on its own. Provisional.
|
|
:merge-file-branch/global
|
|
{:permits 1 :timeout 60000}
|
|
|
|
:merge-file-branch/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; An update from main holds the same three documents and pays for a
|
|
;; snapshot write and a squash diff on top, so it takes the merge's
|
|
;; count. Provisional.
|
|
:update-branch-from-main/global
|
|
{:permits 1 :timeout 60000}
|
|
|
|
:update-branch-from-main/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; Materialize realizes one document plus its libraries and writes the
|
|
;; payload, which is cheaper than a merge and dearer than the anchor,
|
|
;; so it admits two where the merge admits one and the anchor admits
|
|
;; three. Provisional.
|
|
:materialize-file-branch/global
|
|
{:permits 2 :timeout 60000}
|
|
|
|
:materialize-file-branch/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; A cold listing realizes main once and derives every uncached branch,
|
|
;; which makes it the slowest read of the preview. Six at once is above
|
|
;; what a panel open needs under normal use, and six three-document
|
|
;; operations hold eighteen realized documents, below the twenty
|
|
;; one-document saves `:update-file/global` above already admits.
|
|
;; Provisional.
|
|
:get-file-branches/global
|
|
{:permits 6 :timeout 60000}
|
|
|
|
;; The compare dialog's query holds main, the derived branch, and the
|
|
;; base at once, the same three documents the listing holds, so it takes
|
|
;; the same count. Provisional.
|
|
:get-branch-diff/global
|
|
{:permits 6 :timeout 60000}
|
|
|
|
;; Creating a pull request realizes the branch and pins one review
|
|
;; snapshot, the anchor's shape. Provisional.
|
|
:create-pull-request/global
|
|
{:permits 3 :timeout 60000}
|
|
|
|
:create-pull-request/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; Publishing the branch state also realizes the branch and pins one
|
|
;; snapshot. The command declares a single instance-wide queue
|
|
;; (`files_pull_request.clj::update-pull-request-snapshot`), so the
|
|
;; bounded wait sits on the global entry here rather than on a
|
|
;; per-profile entry. Provisional.
|
|
:update-pull-request-snapshot/global
|
|
{:permits 3 :queue 4 :timeout 60000}}
|