@openclaw/fs-safe 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/README.md +38 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +10 -11
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +11 -11
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-mode-owner.js +5 -5
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +5 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +13 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +5 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +27 -7
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +174 -29
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +26 -14
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +209 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +121 -72
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +5 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -9
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +56 -72
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +8 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +17 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +46 -17
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +24 -15
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +11 -35
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-publication.js +1 -1
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +3 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +4 -3
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +4 -2
- package/docs/archive.md +97 -46
- package/docs/atomic.md +85 -8
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +37 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/file-store.md +19 -0
- package/docs/install.md +31 -7
- package/docs/json-store.md +5 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +32 -12
- package/docs/native.md +38 -7
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +50 -14
- package/docs/public-api.md +3 -2
- package/docs/root.md +56 -3
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +114 -8
- package/docs/staged-file.md +12 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +76 -6
- package/package.json +19 -16
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -1,132 +1,109 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
|
|
5
3
|
import { FsSafeError } from "./errors.js";
|
|
6
4
|
import { fileObservation } from "./file-observation.js";
|
|
7
5
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
8
|
-
import { realpathSync } from "./realpath.js";
|
|
9
|
-
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
10
6
|
import { openSidecarRoot } from "./sidecar-lock-root.js";
|
|
11
7
|
import { createNativeExclusiveFile } from "./native-operations.js";
|
|
12
|
-
import { computeSidecarLockDelayMs,
|
|
13
|
-
import {
|
|
8
|
+
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockStaleMs, validateSidecarLockRetryOptions, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
9
|
+
import { relativeSidecarLockPath, removeSidecarLockIfUnchanged, serializeSidecarLockPayload, sidecarReclaimGuardExists, } from "./sidecar-lock-reclaim.js";
|
|
10
|
+
import { awaitSidecarAdmissionBoundary, conditionalSidecarLockParser, observeHeldSidecarParser, observeSidecarLockParser, runSidecarAdmissionBoundary, } from "./sidecar-lock-admission-parser.js";
|
|
11
|
+
import { handleStaleSidecarAdmission, } from "./sidecar-lock-stale-admission.js";
|
|
12
|
+
import { ancestryHasSidecarAdmission, captureSidecarAdmissionAncestry, createSidecarAdmissionController, } from "./sidecar-lock-admission-context.js";
|
|
13
|
+
import { sidecarLockTimeout } from "./sidecar-lock-admission.js";
|
|
14
|
+
import { resolveSidecarLockPaths } from "./sidecar-lock-target.js";
|
|
14
15
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
16
|
import { sleep } from "./timing.js";
|
|
16
|
-
import {
|
|
17
|
-
function isCwdIndependentAbsolutePath(filePath) {
|
|
18
|
-
if (!path.isAbsolute(filePath))
|
|
19
|
-
return false;
|
|
20
|
-
// A leading separator on Windows is rooted on the process's current drive.
|
|
21
|
-
// Drive-qualified, UNC, and namespace roots are longer and need no cwd state.
|
|
22
|
-
return process.platform !== "win32" || path.parse(filePath).root.length > 1;
|
|
23
|
-
}
|
|
24
|
-
async function resolveNormalizedTargetPath(resolved, lockRoot) {
|
|
25
|
-
assertNoWindowsPathAlias(resolved);
|
|
26
|
-
const dir = path.dirname(resolved);
|
|
27
|
-
if (lockRoot) {
|
|
28
|
-
// The target is an arbitration key, not necessarily inside the lock Root.
|
|
29
|
-
// Leave parent creation to the Root-backed sidecar write.
|
|
30
|
-
await lockRoot.resolve(".");
|
|
31
|
-
const parent = await canonicalPathFromExistingAncestor(dir);
|
|
32
|
-
await lockRoot.resolve(".");
|
|
33
|
-
assertNoWindowsPathAlias(parent);
|
|
34
|
-
const normalized = path.join(parent, path.basename(resolved));
|
|
35
|
-
assertNoWindowsPathAlias(normalized);
|
|
36
|
-
return normalized;
|
|
37
|
-
}
|
|
38
|
-
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
|
|
39
|
-
let parent;
|
|
40
|
-
try {
|
|
41
|
-
parent = realpathSync.native(dir);
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return resolved;
|
|
45
|
-
}
|
|
46
|
-
assertNoWindowsPathAlias(parent);
|
|
47
|
-
const normalized = path.join(parent, path.basename(resolved));
|
|
48
|
-
assertNoWindowsPathAlias(normalized);
|
|
49
|
-
return normalized;
|
|
50
|
-
}
|
|
17
|
+
import { sidecarExclusiveCreate } from "./root-create-input.js";
|
|
51
18
|
export async function acquireSidecarLock(options, context) {
|
|
19
|
+
const admissionAncestry = captureSidecarAdmissionAncestry();
|
|
20
|
+
const retainOnExit = options.retainOnExit;
|
|
21
|
+
context.assertRetainOnExitSupported(retainOnExit);
|
|
52
22
|
const retry = options.retry ?? {};
|
|
23
|
+
const timeoutMs = options.timeoutMs;
|
|
24
|
+
const staleMs = options.staleMs;
|
|
25
|
+
const compromiseCheckIntervalMs = options.compromiseCheckIntervalMs;
|
|
53
26
|
validateSidecarLockRetryOptions(retry);
|
|
54
|
-
validateSidecarLockTimeoutMs(
|
|
55
|
-
validateSidecarLockStaleMs(
|
|
56
|
-
validateSidecarLockCompromiseCheckIntervalMs(
|
|
27
|
+
validateSidecarLockTimeoutMs(timeoutMs);
|
|
28
|
+
validateSidecarLockStaleMs(staleMs);
|
|
29
|
+
validateSidecarLockCompromiseCheckIntervalMs(compromiseCheckIntervalMs);
|
|
57
30
|
const targetPath = options.targetPath;
|
|
58
31
|
const explicitLockPath = options.lockPath;
|
|
59
|
-
const anchoredTargetPath = anchorWindowsDriveRelativePath(targetPath);
|
|
60
|
-
assertNoWindowsPathAlias(anchoredTargetPath);
|
|
61
|
-
const resolvedTargetPath = path.resolve(anchoredTargetPath);
|
|
62
|
-
let resolvedLockPath;
|
|
63
|
-
if (explicitLockPath !== undefined) {
|
|
64
|
-
const anchoredLockPath = anchorWindowsDriveRelativePath(explicitLockPath);
|
|
65
|
-
assertNoWindowsPathAlias(anchoredLockPath);
|
|
66
|
-
resolvedLockPath = isCwdIndependentAbsolutePath(explicitLockPath)
|
|
67
|
-
? explicitLockPath
|
|
68
|
-
: path.resolve(anchoredLockPath);
|
|
69
|
-
}
|
|
70
|
-
context.ensureExitCleanupRegistered();
|
|
71
32
|
const lockRoot = options.lockRoot;
|
|
72
|
-
const
|
|
73
|
-
const lockPath =
|
|
74
|
-
|
|
33
|
+
const requestedReentrantOwner = options.reentrantOwner;
|
|
34
|
+
const { lockPath, normalizedTargetPath } = await resolveSidecarLockPaths(targetPath, explicitLockPath, lockRoot);
|
|
35
|
+
const activeDescendant = ancestryHasSidecarAdmission(admissionAncestry, context.admissions, normalizedTargetPath);
|
|
75
36
|
let held = context.held.get(normalizedTargetPath);
|
|
76
37
|
if (held &&
|
|
77
|
-
|
|
38
|
+
requestedReentrantOwner !== undefined &&
|
|
78
39
|
held.reentrantOwner !== undefined &&
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// cleanup settles: successful cleanup requires a fresh acquisition, while
|
|
83
|
-
// failed cleanup leaves the existing sidecar held for a retry.
|
|
40
|
+
requestedReentrantOwner === held.reentrantOwner) {
|
|
41
|
+
// Join a final release before deciding whether this completed owner remains.
|
|
42
|
+
// Successful cleanup requires a fresh acquisition; failed cleanup can retry.
|
|
84
43
|
if (held.releasePromise) {
|
|
44
|
+
if (activeDescendant)
|
|
45
|
+
throw sidecarLockTimeout(lockPath, normalizedTargetPath);
|
|
85
46
|
await held.releasePromise.catch(() => undefined);
|
|
86
47
|
held = context.held.get(normalizedTargetPath);
|
|
87
48
|
}
|
|
88
49
|
if (held &&
|
|
89
|
-
|
|
50
|
+
requestedReentrantOwner !== undefined &&
|
|
90
51
|
held.reentrantOwner !== undefined &&
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
52
|
+
requestedReentrantOwner === held.reentrantOwner) {
|
|
53
|
+
const lifecycle = context.ensureExitCleanupRegistered();
|
|
54
|
+
context.assertRetainOnExitSupported(retainOnExit);
|
|
55
|
+
if (context.held.get(normalizedTargetPath) === held && !held.releasePromise) {
|
|
56
|
+
held.refCount = (held.refCount ?? 1) + 1;
|
|
57
|
+
// Retention is monotonic: any same-owner request to keep the sidecar on
|
|
58
|
+
// exit upgrades the held lock; a later default acquisition never revokes it.
|
|
59
|
+
if (retainOnExit === true) {
|
|
60
|
+
held.retainOnExit = true;
|
|
61
|
+
}
|
|
62
|
+
const returnedHandle = context.handleForHeldLock(normalizedTargetPath, held);
|
|
63
|
+
context.armExitCleanup(lifecycle);
|
|
64
|
+
return returnedHandle;
|
|
97
65
|
}
|
|
98
|
-
return context.handleForHeldLock(normalizedTargetPath, held);
|
|
99
66
|
}
|
|
100
67
|
}
|
|
68
|
+
if (activeDescendant)
|
|
69
|
+
throw sidecarLockTimeout(lockPath, normalizedTargetPath);
|
|
70
|
+
const admission = createSidecarAdmissionController(admissionAncestry, context.admissions, normalizedTargetPath);
|
|
101
71
|
const startedAt = Date.now();
|
|
102
72
|
const reclaimGuardPath = `${lockPath}.reclaim`;
|
|
103
|
-
let
|
|
73
|
+
let reclaimGuard;
|
|
74
|
+
const releaseReclaimGuard = async () => {
|
|
75
|
+
if (!reclaimGuard)
|
|
76
|
+
return;
|
|
77
|
+
await admission.run(() => reclaimGuard.release());
|
|
78
|
+
reclaimGuard = undefined;
|
|
79
|
+
};
|
|
104
80
|
let attempt = 0;
|
|
105
81
|
// Bounded so a genuine denial still surfaces as EPERM, not a lock timeout.
|
|
106
82
|
let transientDenials = 0;
|
|
83
|
+
let payloadCallback;
|
|
84
|
+
let metadata = undefined;
|
|
85
|
+
let metadataObserved = false;
|
|
86
|
+
let onCompromised = undefined;
|
|
87
|
+
let onCompromisedObserved = false;
|
|
88
|
+
const staleOptionsState = {};
|
|
107
89
|
const withinDenialBudget = () => ++transientDenials <= maxTransientLockDenials;
|
|
108
90
|
const waitForRetry = async () => {
|
|
91
|
+
admission.release();
|
|
109
92
|
const elapsed = Date.now() - startedAt;
|
|
110
|
-
if ((
|
|
111
|
-
|
|
112
|
-
elapsed >=
|
|
93
|
+
if ((timeoutMs !== undefined &&
|
|
94
|
+
timeoutMs !== Number.POSITIVE_INFINITY &&
|
|
95
|
+
elapsed >= timeoutMs) ||
|
|
113
96
|
(retry.retries !== undefined && attempt >= retry.retries)) {
|
|
114
|
-
throw
|
|
115
|
-
code: "file_lock_timeout",
|
|
116
|
-
lockPath,
|
|
117
|
-
normalizedTargetPath,
|
|
118
|
-
});
|
|
97
|
+
throw sidecarLockTimeout(lockPath, normalizedTargetPath);
|
|
119
98
|
}
|
|
120
|
-
const remaining =
|
|
99
|
+
const remaining = timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY
|
|
121
100
|
? Number.POSITIVE_INFINITY
|
|
122
|
-
: Math.max(0,
|
|
101
|
+
: Math.max(0, timeoutMs - elapsed);
|
|
123
102
|
const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
|
|
124
103
|
attempt += 1;
|
|
125
104
|
await sleep(delay);
|
|
126
105
|
};
|
|
127
|
-
//
|
|
128
|
-
// a denial as contention must not cost them the original diagnosis, so hand
|
|
129
|
-
// the denial back when no further attempt can be scheduled.
|
|
106
|
+
// Preserve a denial diagnosis if contention backoff has no remaining budget.
|
|
130
107
|
const retryOrRethrowDenial = async (denial) => {
|
|
131
108
|
try {
|
|
132
109
|
await waitForRetry();
|
|
@@ -137,29 +114,166 @@ export async function acquireSidecarLock(options, context) {
|
|
|
137
114
|
throw waitError;
|
|
138
115
|
}
|
|
139
116
|
};
|
|
117
|
+
const assertAdmissionToken = () => {
|
|
118
|
+
if (!admission.hasToken())
|
|
119
|
+
throw sidecarLockTimeout(lockPath, normalizedTargetPath);
|
|
120
|
+
};
|
|
140
121
|
try {
|
|
141
122
|
while (true) {
|
|
142
|
-
if (!
|
|
123
|
+
if (!admission.owns) {
|
|
124
|
+
held = context.held.get(normalizedTargetPath);
|
|
125
|
+
if (held &&
|
|
126
|
+
requestedReentrantOwner !== undefined &&
|
|
127
|
+
held.reentrantOwner !== undefined &&
|
|
128
|
+
requestedReentrantOwner === held.reentrantOwner) {
|
|
129
|
+
if (held.releasePromise) {
|
|
130
|
+
await held.releasePromise.catch(() => undefined);
|
|
131
|
+
held = context.held.get(normalizedTargetPath);
|
|
132
|
+
}
|
|
133
|
+
if (held &&
|
|
134
|
+
requestedReentrantOwner !== undefined &&
|
|
135
|
+
held.reentrantOwner !== undefined &&
|
|
136
|
+
requestedReentrantOwner === held.reentrantOwner) {
|
|
137
|
+
const lifecycle = context.ensureExitCleanupRegistered();
|
|
138
|
+
context.assertRetainOnExitSupported(retainOnExit);
|
|
139
|
+
if (context.held.get(normalizedTargetPath) !== held || held.releasePromise) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
held.refCount = (held.refCount ?? 1) + 1;
|
|
143
|
+
if (retainOnExit === true)
|
|
144
|
+
held.retainOnExit = true;
|
|
145
|
+
const returnedHandle = context.handleForHeldLock(normalizedTargetPath, held);
|
|
146
|
+
context.armExitCleanup(lifecycle);
|
|
147
|
+
return returnedHandle;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (context.admissions.has(normalizedTargetPath)) {
|
|
151
|
+
await waitForRetry();
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
admission.reserve();
|
|
155
|
+
}
|
|
156
|
+
const attemptHeld = context.held.get(normalizedTargetPath);
|
|
157
|
+
const holderWasReplaced = () => {
|
|
158
|
+
const current = context.held.get(normalizedTargetPath);
|
|
159
|
+
return current !== undefined && current !== attemptHeld;
|
|
160
|
+
};
|
|
161
|
+
const lifecycle = admission.run(() => context.ensureExitCleanupRegistered());
|
|
162
|
+
assertAdmissionToken();
|
|
163
|
+
if (holderWasReplaced()) {
|
|
164
|
+
await waitForRetry();
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
admission.run(() => context.assertRetainOnExitSupported(retainOnExit));
|
|
168
|
+
assertAdmissionToken();
|
|
169
|
+
if (holderWasReplaced()) {
|
|
170
|
+
await waitForRetry();
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (reclaimGuard?.assertHeld)
|
|
174
|
+
await admission.run(() => reclaimGuard.assertHeld());
|
|
175
|
+
const reclaimGuardExists = !reclaimGuard && (await (lockRoot
|
|
176
|
+
? admission.run(() => sidecarReclaimGuardExists(reclaimGuardPath, lockRoot))
|
|
177
|
+
: sidecarReclaimGuardExists(reclaimGuardPath)));
|
|
178
|
+
assertAdmissionToken();
|
|
179
|
+
if (holderWasReplaced()) {
|
|
180
|
+
await waitForRetry();
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (reclaimGuardExists) {
|
|
143
184
|
await waitForRetry();
|
|
144
185
|
continue;
|
|
145
186
|
}
|
|
146
187
|
let handle = null;
|
|
147
|
-
let createdSnapshot = null;
|
|
188
|
+
let createdSnapshot = null, createdHeld;
|
|
189
|
+
let admissionConflict = false;
|
|
190
|
+
let exclusiveCreateConflict = false;
|
|
148
191
|
let lockFileCreateDenied = false;
|
|
149
|
-
const
|
|
150
|
-
|
|
192
|
+
const parserState = { observed: false };
|
|
193
|
+
if (!payloadCallback)
|
|
194
|
+
payloadCallback = runSidecarAdmissionBoundary(admission, assertAdmissionToken, () => options.payload);
|
|
195
|
+
if (holderWasReplaced()) {
|
|
196
|
+
await waitForRetry();
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const { value: payload } = await awaitSidecarAdmissionBoundary(admission, assertAdmissionToken, () => Reflect.apply(payloadCallback, options, []));
|
|
200
|
+
if (holderWasReplaced()) {
|
|
201
|
+
await waitForRetry();
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const { raw, ownershipToken } = runSidecarAdmissionBoundary(admission, assertAdmissionToken, () => serializeSidecarLockPayload(payload));
|
|
205
|
+
if (holderWasReplaced()) {
|
|
206
|
+
await waitForRetry();
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
held = context.held.get(normalizedTargetPath);
|
|
210
|
+
if (held?.releasePromise) {
|
|
211
|
+
// Release cleanup never acquires this admission; retaining it here lets
|
|
212
|
+
// the serialized attempt continue without another callback or retry.
|
|
213
|
+
await held.releasePromise.catch(() => undefined);
|
|
214
|
+
assertAdmissionToken();
|
|
215
|
+
held = context.held.get(normalizedTargetPath);
|
|
216
|
+
}
|
|
217
|
+
if (held?.lockPath === lockPath) {
|
|
218
|
+
const observedHeld = held;
|
|
219
|
+
const observation = await observeHeldSidecarParser({
|
|
220
|
+
admission, assertToken: assertAdmissionToken,
|
|
221
|
+
currentHeld: () => context.held.get(normalizedTargetPath), held: observedHeld,
|
|
222
|
+
lockPath, lockRoot, parserState,
|
|
223
|
+
parserAccessor: () => options.parsePayload,
|
|
224
|
+
isTransientDenial: (error) => isTransientLockFileDenial(error, lockPath),
|
|
225
|
+
});
|
|
226
|
+
assertAdmissionToken();
|
|
227
|
+
held = context.held.get(normalizedTargetPath);
|
|
228
|
+
if (held !== observedHeld || observedHeld.releasePromise) {
|
|
229
|
+
await waitForRetry();
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (observation.kind === "read-error") {
|
|
233
|
+
if (!observation.transientDenial || !withinDenialBudget())
|
|
234
|
+
throw observation.error;
|
|
235
|
+
await retryOrRethrowDenial(observation.error);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (held) {
|
|
240
|
+
await waitForRetry();
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (!metadataObserved) {
|
|
244
|
+
metadata = runSidecarAdmissionBoundary(admission, assertAdmissionToken, () => options.metadata);
|
|
245
|
+
metadataObserved = true;
|
|
246
|
+
if (context.held.has(normalizedTargetPath)) {
|
|
247
|
+
await waitForRetry();
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!onCompromisedObserved) {
|
|
252
|
+
onCompromised = runSidecarAdmissionBoundary(admission, assertAdmissionToken, () => options.onCompromised);
|
|
253
|
+
onCompromisedObserved = true;
|
|
254
|
+
if (context.held.has(normalizedTargetPath)) {
|
|
255
|
+
await waitForRetry();
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
assertAdmissionToken();
|
|
260
|
+
if (context.held.has(normalizedTargetPath)) {
|
|
261
|
+
await waitForRetry();
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
151
264
|
try {
|
|
152
265
|
if (lockRoot) {
|
|
153
266
|
const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
|
|
154
267
|
const observation = fileObservation();
|
|
155
268
|
try {
|
|
156
|
-
await observation.run(() => lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 }));
|
|
269
|
+
await observation.run(() => lockRoot.create(relativeLockPath, raw, { ...sidecarExclusiveCreate, mkdir: true, mode: 0o600 }));
|
|
157
270
|
}
|
|
158
271
|
catch (error) {
|
|
159
272
|
// Only this invocation's failed exclusive open grants denial retry authority.
|
|
160
273
|
lockFileCreateDenied = observation.has(error, `exclusive-create:${lockPath}`) &&
|
|
161
274
|
isTransientLockFileDenial(error, lockPath);
|
|
162
275
|
if (error instanceof FsSafeError && error.code === "already-exists") {
|
|
276
|
+
exclusiveCreateConflict = true;
|
|
163
277
|
throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
|
|
164
278
|
}
|
|
165
279
|
throw error;
|
|
@@ -191,45 +305,46 @@ export async function acquireSidecarLock(options, context) {
|
|
|
191
305
|
}
|
|
192
306
|
catch (createError) {
|
|
193
307
|
lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
|
|
308
|
+
exclusiveCreateConflict = createError.code === "EEXIST";
|
|
194
309
|
throw createError;
|
|
195
310
|
}
|
|
196
311
|
await handle.writeFile(raw, "utf8");
|
|
197
312
|
}
|
|
198
313
|
const snapshot = { raw, payload, stat: fsSync.fstatSync(handle.fd), ownershipToken };
|
|
314
|
+
createdSnapshot = snapshot;
|
|
199
315
|
if (snapshot.stat.nlink === 0) {
|
|
200
316
|
await handle.close();
|
|
201
317
|
handle = null;
|
|
202
318
|
await waitForRetry();
|
|
203
319
|
continue;
|
|
204
320
|
}
|
|
205
|
-
|
|
321
|
+
observeSidecarLockParser(parserState, admission, () => options.parsePayload, assertAdmissionToken);
|
|
322
|
+
if (context.held.has(normalizedTargetPath)) {
|
|
323
|
+
admissionConflict = true;
|
|
324
|
+
throw Object.assign(new Error("sidecar lock admission changed"), { code: "EEXIST" });
|
|
325
|
+
}
|
|
326
|
+
createdHeld = {
|
|
206
327
|
refCount: 1,
|
|
207
|
-
reentrantOwner:
|
|
328
|
+
reentrantOwner: requestedReentrantOwner,
|
|
208
329
|
handle,
|
|
209
330
|
lockPath,
|
|
210
331
|
snapshot,
|
|
211
332
|
acquiredAt: Date.now(),
|
|
212
|
-
metadata:
|
|
333
|
+
metadata: metadata ?? {},
|
|
213
334
|
lockRoot,
|
|
214
|
-
retainOnExit
|
|
215
|
-
parsePayload:
|
|
335
|
+
retainOnExit,
|
|
336
|
+
parsePayload: parserState.parser,
|
|
216
337
|
};
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
throw err;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
const returnedHandle = context.handleForHeldLock(normalizedTargetPath, createdHeld);
|
|
229
|
-
const interval = options.compromiseCheckIntervalMs;
|
|
230
|
-
if (options.onCompromised && interval !== undefined && interval > 0) {
|
|
338
|
+
const candidateHeld = createdHeld;
|
|
339
|
+
if (reclaimGuard)
|
|
340
|
+
await releaseReclaimGuard();
|
|
341
|
+
const returnedHandle = admission.run(() => context.handleForHeldLock(normalizedTargetPath, candidateHeld));
|
|
342
|
+
if (onCompromised &&
|
|
343
|
+
compromiseCheckIntervalMs !== undefined &&
|
|
344
|
+
compromiseCheckIntervalMs > 0) {
|
|
345
|
+
const compromisedCallback = onCompromised;
|
|
231
346
|
let compromiseCheckInFlight = false;
|
|
232
|
-
|
|
347
|
+
candidateHeld.compromiseTimer = admission.run(() => setInterval(() => {
|
|
233
348
|
if (compromiseCheckInFlight)
|
|
234
349
|
return;
|
|
235
350
|
compromiseCheckInFlight = true;
|
|
@@ -237,22 +352,34 @@ export async function acquireSidecarLock(options, context) {
|
|
|
237
352
|
.verifyStillHeld()
|
|
238
353
|
.catch(() => false)
|
|
239
354
|
.then((stillHeld) => {
|
|
240
|
-
if (!stillHeld &&
|
|
241
|
-
clearInterval(
|
|
242
|
-
|
|
243
|
-
|
|
355
|
+
if (!stillHeld && candidateHeld.compromiseTimer) {
|
|
356
|
+
clearInterval(candidateHeld.compromiseTimer);
|
|
357
|
+
candidateHeld.compromiseTimer = undefined;
|
|
358
|
+
Reflect.apply(compromisedCallback, options, [{ lockPath, normalizedTargetPath }]);
|
|
244
359
|
}
|
|
245
360
|
})
|
|
246
361
|
.finally(() => {
|
|
247
362
|
compromiseCheckInFlight = false;
|
|
248
363
|
});
|
|
249
|
-
},
|
|
250
|
-
|
|
364
|
+
}, compromiseCheckIntervalMs));
|
|
365
|
+
admission.run(() => candidateHeld.compromiseTimer?.unref());
|
|
366
|
+
}
|
|
367
|
+
if (!admission.hasToken() ||
|
|
368
|
+
context.held.has(normalizedTargetPath)) {
|
|
369
|
+
admissionConflict = true;
|
|
370
|
+
throw Object.assign(new Error("sidecar lock admission changed"), { code: "EEXIST" });
|
|
251
371
|
}
|
|
372
|
+
context.held.set(normalizedTargetPath, candidateHeld);
|
|
373
|
+
admission.release();
|
|
374
|
+
context.armExitCleanup(lifecycle);
|
|
252
375
|
return returnedHandle;
|
|
253
376
|
}
|
|
254
377
|
catch (err) {
|
|
255
378
|
try {
|
|
379
|
+
if (createdHeld?.compromiseTimer) {
|
|
380
|
+
clearInterval(createdHeld.compromiseTimer);
|
|
381
|
+
createdHeld.compromiseTimer = undefined;
|
|
382
|
+
}
|
|
256
383
|
if (handle) {
|
|
257
384
|
const failedSnapshot = createdSnapshot ?? { payload: null };
|
|
258
385
|
try {
|
|
@@ -262,118 +389,80 @@ export async function acquireSidecarLock(options, context) {
|
|
|
262
389
|
// Best-effort cleanup of a failed exclusive create.
|
|
263
390
|
}
|
|
264
391
|
const current = context.held.get(normalizedTargetPath);
|
|
265
|
-
if (current
|
|
392
|
+
if (createdHeld && current === createdHeld) {
|
|
266
393
|
context.held.delete(normalizedTargetPath);
|
|
267
394
|
}
|
|
268
395
|
await handle.close().catch(() => undefined);
|
|
269
|
-
// Root
|
|
270
|
-
// partial direct writes use the exclusive descriptor's identity.
|
|
396
|
+
// Root records use the creator receipt; partial raw writes use fd identity.
|
|
271
397
|
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
272
398
|
lockRoot,
|
|
273
|
-
parsePayload:
|
|
399
|
+
parsePayload: conditionalSidecarLockParser(parserState, admission, () => admission.hasToken() && !context.held.has(normalizedTargetPath)),
|
|
274
400
|
});
|
|
275
401
|
}
|
|
276
402
|
else if (createdSnapshot) {
|
|
277
403
|
await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
|
|
278
404
|
lockRoot,
|
|
279
|
-
parsePayload:
|
|
405
|
+
parsePayload: conditionalSidecarLockParser(parserState, admission, () => admission.hasToken() && !context.held.has(normalizedTargetPath)),
|
|
280
406
|
});
|
|
281
407
|
}
|
|
282
408
|
}
|
|
283
409
|
catch (cleanupError) {
|
|
284
410
|
throw createSuppressedError(cleanupError, err, "file lock acquisition and cleanup both failed");
|
|
285
411
|
}
|
|
286
|
-
if (
|
|
287
|
-
await retryOrRethrowDenial(err);
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
if (err.code !== "EEXIST") {
|
|
291
|
-
throw err;
|
|
292
|
-
}
|
|
293
|
-
if (ownsReclaimGuard) {
|
|
294
|
-
await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
|
|
295
|
-
ownsReclaimGuard = false;
|
|
412
|
+
if (admissionConflict) {
|
|
296
413
|
await waitForRetry();
|
|
297
414
|
continue;
|
|
298
415
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
catch (readErr) {
|
|
311
|
-
if (!lockFileOpenDenied || !withinDenialBudget())
|
|
312
|
-
throw readErr;
|
|
313
|
-
await retryOrRethrowDenial(readErr);
|
|
314
|
-
continue;
|
|
416
|
+
if (lockFileCreateDenied) {
|
|
417
|
+
assertAdmissionToken();
|
|
418
|
+
if (context.held.has(normalizedTargetPath)) {
|
|
419
|
+
await waitForRetry();
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (withinDenialBudget()) {
|
|
423
|
+
await retryOrRethrowDenial(err);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
throw err;
|
|
315
427
|
}
|
|
316
|
-
|
|
317
|
-
|
|
428
|
+
if (!exclusiveCreateConflict)
|
|
429
|
+
throw err;
|
|
430
|
+
assertAdmissionToken();
|
|
431
|
+
if (context.held.has(normalizedTargetPath)) {
|
|
318
432
|
await waitForRetry();
|
|
319
433
|
continue;
|
|
320
434
|
}
|
|
321
|
-
if (
|
|
435
|
+
if (reclaimGuard) {
|
|
436
|
+
await releaseReclaimGuard();
|
|
322
437
|
await waitForRetry();
|
|
323
438
|
continue;
|
|
324
439
|
}
|
|
325
|
-
|
|
326
|
-
|
|
440
|
+
await handleStaleSidecarAdmission({
|
|
441
|
+
admission,
|
|
442
|
+
assertToken: assertAdmissionToken,
|
|
443
|
+
currentHeld: () => context.held.get(normalizedTargetPath),
|
|
327
444
|
lockPath,
|
|
445
|
+
lockRoot,
|
|
328
446
|
normalizedTargetPath,
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (staleRecovery === "remove-if-unchanged") {
|
|
343
|
-
if (!(await tryAcquireSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath))) {
|
|
344
|
-
await waitForRetry();
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
ownsReclaimGuard = true;
|
|
348
|
-
const removal = await removeStaleSidecarLockIfAllowed({
|
|
349
|
-
lockPath,
|
|
350
|
-
normalizedTargetPath,
|
|
351
|
-
snapshot,
|
|
352
|
-
shouldRemoveStaleLock: options.shouldRemoveStaleLock,
|
|
353
|
-
lockRoot,
|
|
354
|
-
parsePayload: options.parsePayload,
|
|
355
|
-
});
|
|
356
|
-
if (removal === "removed" || removal === "changed") {
|
|
357
|
-
if (removal === "changed")
|
|
358
|
-
await waitForRetry();
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
await releaseSidecarReclaimGuard(context.reclaimGuards, reclaimGuardPath);
|
|
362
|
-
ownsReclaimGuard = false;
|
|
363
|
-
}
|
|
364
|
-
throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
|
|
365
|
-
code: "file_lock_stale",
|
|
366
|
-
lockPath,
|
|
367
|
-
normalizedTargetPath,
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
await waitForRetry();
|
|
447
|
+
options,
|
|
448
|
+
parserState,
|
|
449
|
+
reclaimGuardPath,
|
|
450
|
+
reclaimGuards: context.reclaimGuards,
|
|
451
|
+
setReclaimGuard: (guard) => { reclaimGuard = guard; },
|
|
452
|
+
releaseReclaimGuard,
|
|
453
|
+
staleOptionsState,
|
|
454
|
+
staleMs,
|
|
455
|
+
withinDenialBudget,
|
|
456
|
+
retryOrRethrowDenial,
|
|
457
|
+
waitForRetry,
|
|
458
|
+
});
|
|
459
|
+
continue;
|
|
371
460
|
}
|
|
372
461
|
}
|
|
373
462
|
}
|
|
374
463
|
finally {
|
|
375
|
-
if (
|
|
376
|
-
await
|
|
377
|
-
|
|
464
|
+
if (reclaimGuard)
|
|
465
|
+
await releaseReclaimGuard().catch(() => undefined);
|
|
466
|
+
admission.release();
|
|
378
467
|
}
|
|
379
468
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type AdmissionIdentity = {
|
|
2
|
+
active: boolean;
|
|
3
|
+
admissions: Map<string, object>;
|
|
4
|
+
normalizedTargetPath: string;
|
|
5
|
+
token: object;
|
|
6
|
+
};
|
|
7
|
+
type SidecarAdmissionAncestry = readonly AdmissionIdentity[];
|
|
8
|
+
/** Capture active ancestry now; later lifecycle changes cannot reclassify this call. */
|
|
9
|
+
export declare function captureSidecarAdmissionAncestry(): SidecarAdmissionAncestry;
|
|
10
|
+
export declare function ancestryHasSidecarAdmission(ancestry: SidecarAdmissionAncestry, admissions: Map<string, object>, normalizedTargetPath: string): boolean;
|
|
11
|
+
export declare function createSidecarAdmissionController(ancestry: SidecarAdmissionAncestry, admissions: Map<string, object>, normalizedTargetPath: string): {
|
|
12
|
+
readonly owns: boolean;
|
|
13
|
+
hasToken: () => boolean;
|
|
14
|
+
release(): void;
|
|
15
|
+
reserve(): void;
|
|
16
|
+
run<T>(callback: () => T): T;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=sidecar-lock-admission-context.d.ts.map
|