@smthrs/artifacts 0.0.0-stage → 1.0.0-rc.12
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 +77 -0
- package/LICENSE +21 -0
- package/README.md +175 -2
- package/dist/cjs/ArtifactBackupLease.d.ts +34 -0
- package/dist/cjs/ArtifactBackupLease.d.ts.map +1 -0
- package/dist/cjs/ArtifactBackupLease.js +183 -0
- package/dist/cjs/ArtifactBackupLease.js.map +7 -0
- package/dist/cjs/ArtifactStore.d.ts +129 -0
- package/dist/cjs/ArtifactStore.d.ts.map +1 -0
- package/dist/cjs/ArtifactStore.js +73 -0
- package/dist/cjs/ArtifactStore.js.map +7 -0
- package/dist/cjs/ArtifactStoreError.d.ts +82 -0
- package/dist/cjs/ArtifactStoreError.d.ts.map +1 -0
- package/dist/cjs/ArtifactStoreError.js +72 -0
- package/dist/cjs/ArtifactStoreError.js.map +7 -0
- package/dist/cjs/ArtifactStoreMetrics.d.ts +70 -0
- package/dist/cjs/ArtifactStoreMetrics.d.ts.map +1 -0
- package/dist/cjs/ArtifactStoreMetrics.js +51 -0
- package/dist/cjs/ArtifactStoreMetrics.js.map +7 -0
- package/dist/cjs/ArtifactSweep.d.ts +185 -0
- package/dist/cjs/ArtifactSweep.d.ts.map +1 -0
- package/dist/cjs/ArtifactSweep.js +171 -0
- package/dist/cjs/ArtifactSweep.js.map +7 -0
- package/dist/cjs/CombinedArtifacts.d.ts +103 -0
- package/dist/cjs/CombinedArtifacts.d.ts.map +1 -0
- package/dist/cjs/CombinedArtifacts.js +189 -0
- package/dist/cjs/CombinedArtifacts.js.map +7 -0
- package/dist/cjs/FileLease.d.ts +117 -0
- package/dist/cjs/FileLease.d.ts.map +1 -0
- package/dist/cjs/FileLease.js +208 -0
- package/dist/cjs/FileLease.js.map +7 -0
- package/dist/cjs/FileSystemArtifactStore.d.ts +93 -0
- package/dist/cjs/FileSystemArtifactStore.d.ts.map +1 -0
- package/dist/cjs/FileSystemArtifactStore.js +276 -0
- package/dist/cjs/FileSystemArtifactStore.js.map +7 -0
- package/dist/cjs/MemoryArtifactStore.d.ts +25 -0
- package/dist/cjs/MemoryArtifactStore.d.ts.map +1 -0
- package/dist/cjs/MemoryArtifactStore.js +84 -0
- package/dist/cjs/MemoryArtifactStore.js.map +7 -0
- package/dist/cjs/NoopArtifactStore.d.ts +17 -0
- package/dist/cjs/NoopArtifactStore.d.ts.map +1 -0
- package/dist/cjs/NoopArtifactStore.js +46 -0
- package/dist/cjs/NoopArtifactStore.js.map +7 -0
- package/dist/cjs/RemoteArtifacts.d.ts +237 -0
- package/dist/cjs/RemoteArtifacts.d.ts.map +1 -0
- package/dist/cjs/RemoteArtifacts.js +405 -0
- package/dist/cjs/RemoteArtifacts.js.map +7 -0
- package/dist/cjs/index.d.ts +65 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/ArtifactLocks.d.ts +30 -0
- package/dist/cjs/internal/ArtifactLocks.d.ts.map +1 -0
- package/dist/cjs/internal/ArtifactLocks.js +93 -0
- package/dist/cjs/internal/ArtifactLocks.js.map +7 -0
- package/dist/cjs/internal/ArtifactPath.d.ts +19 -0
- package/dist/cjs/internal/ArtifactPath.d.ts.map +1 -0
- package/dist/cjs/internal/ArtifactPath.js +74 -0
- package/dist/cjs/internal/ArtifactPath.js.map +7 -0
- package/dist/cjs/measureBytes.d.ts +18 -0
- package/dist/cjs/measureBytes.d.ts.map +1 -0
- package/dist/cjs/measureBytes.js +43 -0
- package/dist/cjs/measureBytes.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/snapshotBytes.d.ts +19 -0
- package/dist/cjs/snapshotBytes.d.ts.map +1 -0
- package/dist/cjs/snapshotBytes.js +41 -0
- package/dist/cjs/snapshotBytes.js.map +7 -0
- package/dist/cjs/validateDigest.d.ts +22 -0
- package/dist/cjs/validateDigest.d.ts.map +1 -0
- package/dist/cjs/validateDigest.js +42 -0
- package/dist/cjs/validateDigest.js.map +7 -0
- package/dist/esm/ArtifactBackupLease.d.ts +34 -0
- package/dist/esm/ArtifactBackupLease.d.ts.map +1 -0
- package/dist/esm/ArtifactBackupLease.js +126 -0
- package/dist/esm/ArtifactBackupLease.js.map +1 -0
- package/dist/esm/ArtifactStore.d.ts +129 -0
- package/dist/esm/ArtifactStore.d.ts.map +1 -0
- package/dist/esm/ArtifactStore.js +83 -0
- package/dist/esm/ArtifactStore.js.map +1 -0
- package/dist/esm/ArtifactStoreError.d.ts +82 -0
- package/dist/esm/ArtifactStoreError.d.ts.map +1 -0
- package/dist/esm/ArtifactStoreError.js +77 -0
- package/dist/esm/ArtifactStoreError.js.map +1 -0
- package/dist/esm/ArtifactStoreMetrics.d.ts +70 -0
- package/dist/esm/ArtifactStoreMetrics.d.ts.map +1 -0
- package/dist/esm/ArtifactStoreMetrics.js +77 -0
- package/dist/esm/ArtifactStoreMetrics.js.map +1 -0
- package/dist/esm/ArtifactSweep.d.ts +185 -0
- package/dist/esm/ArtifactSweep.d.ts.map +1 -0
- package/dist/esm/ArtifactSweep.js +210 -0
- package/dist/esm/ArtifactSweep.js.map +1 -0
- package/dist/esm/CombinedArtifacts.d.ts +103 -0
- package/dist/esm/CombinedArtifacts.d.ts.map +1 -0
- package/dist/esm/CombinedArtifacts.js +214 -0
- package/dist/esm/CombinedArtifacts.js.map +1 -0
- package/dist/esm/FileLease.d.ts +117 -0
- package/dist/esm/FileLease.d.ts.map +1 -0
- package/dist/esm/FileLease.js +249 -0
- package/dist/esm/FileLease.js.map +1 -0
- package/dist/esm/FileSystemArtifactStore.d.ts +93 -0
- package/dist/esm/FileSystemArtifactStore.d.ts.map +1 -0
- package/dist/esm/FileSystemArtifactStore.js +336 -0
- package/dist/esm/FileSystemArtifactStore.js.map +1 -0
- package/dist/esm/MemoryArtifactStore.d.ts +25 -0
- package/dist/esm/MemoryArtifactStore.d.ts.map +1 -0
- package/dist/esm/MemoryArtifactStore.js +67 -0
- package/dist/esm/MemoryArtifactStore.js.map +1 -0
- package/dist/esm/NoopArtifactStore.d.ts +17 -0
- package/dist/esm/NoopArtifactStore.d.ts.map +1 -0
- package/dist/esm/NoopArtifactStore.js +27 -0
- package/dist/esm/NoopArtifactStore.js.map +1 -0
- package/dist/esm/RemoteArtifacts.d.ts +237 -0
- package/dist/esm/RemoteArtifacts.d.ts.map +1 -0
- package/dist/esm/RemoteArtifacts.js +557 -0
- package/dist/esm/RemoteArtifacts.js.map +1 -0
- package/dist/esm/index.d.ts +65 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/ArtifactLocks.d.ts +30 -0
- package/dist/esm/internal/ArtifactLocks.d.ts.map +1 -0
- package/dist/esm/internal/ArtifactLocks.js +86 -0
- package/dist/esm/internal/ArtifactLocks.js.map +1 -0
- package/dist/esm/internal/ArtifactPath.d.ts +19 -0
- package/dist/esm/internal/ArtifactPath.d.ts.map +1 -0
- package/dist/esm/internal/ArtifactPath.js +59 -0
- package/dist/esm/internal/ArtifactPath.js.map +1 -0
- package/dist/esm/measureBytes.d.ts +18 -0
- package/dist/esm/measureBytes.d.ts.map +1 -0
- package/dist/esm/measureBytes.js +22 -0
- package/dist/esm/measureBytes.js.map +1 -0
- package/dist/esm/snapshotBytes.d.ts +19 -0
- package/dist/esm/snapshotBytes.d.ts.map +1 -0
- package/dist/esm/snapshotBytes.js +30 -0
- package/dist/esm/snapshotBytes.js.map +1 -0
- package/dist/esm/validateDigest.d.ts +22 -0
- package/dist/esm/validateDigest.d.ts.map +1 -0
- package/dist/esm/validateDigest.js +26 -0
- package/dist/esm/validateDigest.js.map +1 -0
- package/docs/README.md +202 -0
- package/docs/api.md +484 -0
- package/docs/concepts/content-addressing.md +174 -0
- package/docs/concepts/coordination.md +141 -0
- package/docs/concepts/tiers.md +147 -0
- package/docs/guides/fence-a-backup.md +123 -0
- package/docs/guides/reclaim-disk-space.md +143 -0
- package/docs/guides/serve-the-artifact-protocol.md +140 -0
- package/docs/guides/share-artifacts-across-machines.md +180 -0
- package/docs/guides/test-against-an-artifact-store.md +158 -0
- package/docs/installation.md +102 -0
- package/docs/quickstart.md +183 -0
- package/docs/troubleshooting.md +232 -0
- package/package.json +179 -3
- package/src/ArtifactBackupLease.ts +223 -0
- package/src/ArtifactStore.ts +152 -0
- package/src/ArtifactStoreError.ts +96 -0
- package/src/ArtifactStoreMetrics.ts +83 -0
- package/src/ArtifactSweep.ts +346 -0
- package/src/CombinedArtifacts.ts +369 -0
- package/src/FileLease.ts +397 -0
- package/src/FileSystemArtifactStore.ts +459 -0
- package/src/MemoryArtifactStore.ts +83 -0
- package/src/NoopArtifactStore.ts +30 -0
- package/src/RemoteArtifacts.ts +814 -0
- package/src/index.ts +71 -0
- package/src/internal/ArtifactLocks.ts +110 -0
- package/src/internal/ArtifactPath.ts +72 -0
- package/src/measureBytes.ts +28 -0
- package/src/snapshotBytes.ts +34 -0
- package/src/validateDigest.ts +31 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CombinedArtifacts.js","sourceRoot":"","sources":["../../src/CombinedArtifacts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Two artifact tiers composed into one: local first, remote second, with\n * write-back into the local tier.\n *\n * This is the shape of Bazel's `CombinedCache`\n * (`com.google.devtools.build.lib.remote.CombinedCache`): a read consults the\n * disk cache, falls back to the remote cache only on a miss, and *uploads what it\n * found back into the disk cache* so the next read is local\n * (`downloadActionResultFromRemote`, lines 230-303). A write goes to both.\n *\n * Deviation from Bazel: policy is declared once by the remote tier rather than\n * threaded through every call. `downloadPolicy` controls prefetch and local\n * materialization; composing only the local tier opts out of shared storage.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Cause from \"effect/Cause\"\nimport * as Clock from \"effect/Clock\"\nimport * as Deferred from \"effect/Deferred\"\nimport * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport * as Layer from \"effect/Layer\"\nimport * as Metric from \"effect/Metric\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as ArtifactStore from \"./ArtifactStore.ts\"\nimport * as ArtifactStoreMetrics from \"./ArtifactStoreMetrics.ts\"\nimport * as RemoteArtifacts from \"./RemoteArtifacts.ts\"\n\n/**\n * The two tiers to compose.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport interface Options {\n /** The fast, machine-local tier. Every read tries this one first. */\n readonly local: ArtifactStore.Service\n /** The shared tier. Consulted only on a local miss; written through on put. */\n readonly remote: ArtifactStore.Service\n /**\n * How long a `put` waits for its opportunistic upload to the shared tier\n * before abandoning it. The local digest is already in hand when the upload\n * starts, so the deadline bounds only how long a stalled remote can delay\n * the answer — an abandoned upload is dropped exactly like a refused one.\n * Defaults to 60 seconds, Bazel's `--remote_timeout` default\n * (its `RemoteOptions`).\n */\n readonly uploadTimeout?: Duration.Input | undefined\n /**\n * How long a `get` waits for opportunistic local write-back before\n * interrupting it and returning the verified remote bytes. Defaults to\n * 60 seconds, matching the default upload deadline.\n */\n readonly writeBackTimeout?: Duration.Input | undefined\n /**\n * How eagerly a read materializes a blob into the local tier. Defaults to\n * the policy the remote tier declares (`RemoteArtifacts.Options.downloadPolicy`),\n * and to `all` for a remote tier that declares none.\n *\n * `all` and `toplevel` both write a fetched blob back into the local tier, so\n * the second read is local; they differ only in whether\n * `@smthrs/engine-store`'s `ArtifactSync.hydrate` prefetches. `minimal`\n * serves the bytes without writing them back, so a host that must not\n * accumulate other machines' artifacts never does. `minimal` still repairs a\n * *corrupt* local address, because a rewrite of an address the local tier\n * already claims is repair rather than growth.\n *\n * No policy makes the write-back load-bearing: a local tier that refuses it\n * costs the next read a network round trip, never this read's answer.\n */\n readonly downloadPolicy?: RemoteArtifacts.DownloadPolicy | undefined\n}\n\n/**\n * The default deadline on the opportunistic upload. 60 seconds is Bazel's\n * `--remote_timeout` default for its remote cache calls.\n */\nconst defaultUploadTimeout = Duration.seconds(60)\n\n/** At most one dropped-transfer warning per operation in this window. */\nconst droppedWarningIntervalMs = 60_000\n\n/** The credential-free reason a dropped transfer failed: its code, never its bytes. */\nconst reasonOf = (error: ArtifactStore.ArtifactStoreError | Cause.TimeoutError): string =>\n Cause.isTimeoutError(error) ? error._tag : error.code\n\n/**\n * What the local tier answered a read with. A miss and a corrupt address both\n * fall through to the shared tier, but they are not the same fact: only\n * corruption earns a write-back the `minimal` policy would otherwise skip.\n */\ntype LocalRead =\n | { readonly _tag: \"hit\"; readonly bytes: Uint8Array }\n | { readonly _tag: \"miss\" }\n | { readonly _tag: \"corrupt\" }\n\n/**\n * Composes a local and a remote artifact store.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const make = (\n options: Options\n): Effect.Effect<RemoteArtifacts.Service, ArtifactStore.ArtifactStoreError> =>\n Effect.gen(function*() {\n const { local, remote } = options\n const parsedUploadTimeout = Duration.fromInput(options.uploadTimeout ?? defaultUploadTimeout)\n if (\n Option.isNone(parsedUploadTimeout) ||\n !Number.isFinite(Duration.toMillis(parsedUploadTimeout.value)) ||\n Duration.toMillis(parsedUploadTimeout.value) <= 0\n ) {\n return yield* Effect.fail(\n new ArtifactStore.ArtifactStoreError({\n code: \"invalid_configuration\",\n message: \"invalid combined artifact option: uploadTimeout\"\n })\n )\n }\n const uploadTimeout = parsedUploadTimeout.value\n const parsedWriteBackTimeout = Duration.fromInput(options.writeBackTimeout ?? defaultUploadTimeout)\n if (\n Option.isNone(parsedWriteBackTimeout) ||\n !Number.isFinite(Duration.toMillis(parsedWriteBackTimeout.value)) ||\n Duration.toMillis(parsedWriteBackTimeout.value) <= 0\n ) {\n return yield* Effect.fail(\n new ArtifactStore.ArtifactStoreError({\n code: \"invalid_configuration\",\n message: \"invalid combined artifact option: writeBackTimeout\"\n })\n )\n }\n const writeBackTimeout = parsedWriteBackTimeout.value\n const downloadPolicy = options.downloadPolicy ?? RemoteArtifacts.downloadPolicyOf(remote) ?? \"all\"\n if (!Schema.is(RemoteArtifacts.DownloadPolicy)(downloadPolicy)) {\n return yield* Effect.fail(\n new ArtifactStore.ArtifactStoreError({\n code: \"invalid_configuration\",\n message: \"invalid combined artifact option: downloadPolicy\"\n })\n )\n }\n /**\n * In-flight uploads, keyed by digest. Two settles in one process that spill\n * the same artifact would otherwise both push the same bytes over the\n * network; the second joins the first's `Deferred` instead. The map holds\n * only in-flight work — the entry is removed before the deferred is\n * completed, so a later `put` of the same digest starts a fresh upload\n * rather than replaying a stale outcome.\n */\n const uploads = new Map<string, Deferred.Deferred<ArtifactStore.Digest, ArtifactStore.ArtifactStoreError>>()\n /**\n * Counts a dropped opportunistic transfer and warns at most once per\n * operation per {@link droppedWarningIntervalMs}, so a shared tier that\n * refuses every request is visible without one log line per artifact.\n */\n const lastWarnedAt = new Map<\"put\" | \"write_back\", number>()\n const dropped =\n (operation: \"put\" | \"write_back\") => (error: ArtifactStore.ArtifactStoreError | Cause.TimeoutError) =>\n Effect.gen(function*() {\n yield* Metric.update(ArtifactStoreMetrics.remoteFailure[operation], 1)\n const now = yield* Clock.currentTimeMillis\n const last = lastWarnedAt.get(operation)\n if (last !== undefined && now - last < droppedWarningIntervalMs) return\n lastWarnedAt.set(operation, now)\n yield* Effect.logWarning(\"Combined artifact transfer dropped\").pipe(\n Effect.annotateLogs({ operation, reason: reasonOf(error) })\n )\n })\n const uploadInterrupted = (): ArtifactStore.ArtifactStoreError =>\n new ArtifactStore.ArtifactStoreError({\n code: \"unavailable\",\n message: \"the shared upload was interrupted before it settled\"\n })\n const uploadOnce = (digest: ArtifactStore.Digest, bytes: Uint8Array) =>\n Effect.suspend(() => {\n const joined = uploads.get(digest)\n if (joined !== undefined) return Deferred.await(joined)\n // Registration and settlement are atomic against interruption. The\n // upload itself stays interruptible — that is how the deadline in `put`\n // cuts it short — but everything around it runs masked: interruption\n // striking between registering the deferred and resolving it would\n // otherwise orphan the entry, and every later `put` of the digest would\n // join a deferred nobody will ever complete.\n return Effect.uninterruptibleMask((restore) =>\n Effect.gen(function*() {\n const deferred = yield* Deferred.make<ArtifactStore.Digest, ArtifactStore.ArtifactStoreError>()\n uploads.set(digest, deferred)\n return yield* restore(remote.put(bytes)).pipe(\n Effect.onExit((exit) => {\n uploads.delete(digest)\n // An interrupted upload resolves the deferred with a typed\n // failure, never with the interruption itself: interruption\n // would tear down every innocent waiter, while a typed refusal\n // is exactly the outcome `put` already drops. The map entry is\n // gone either way, so the next `put` of the digest retries\n // with a fresh upload.\n return Exit.hasInterrupts(exit)\n ? Deferred.fail(deferred, uploadInterrupted())\n : Deferred.done(deferred, exit)\n })\n )\n })\n )\n })\n\n const put: ArtifactStore.Service[\"put\"] = Effect.fn(\"CombinedArtifacts.put\")((bytes: Uint8Array) =>\n Effect.flatMap(ArtifactStore.snapshotBytes(bytes), (snapshot) =>\n Effect.gen(function*() {\n // Local first, and its digest is the answer: the local tier is the one\n // this machine's replays resolve against, so a remote tier that is down\n // must not stop an artifact from being recorded locally.\n const digest = yield* local.put(snapshot)\n yield* Effect.annotateCurrentSpan({ digest })\n // Which means the upload is opportunistic, and a refusal is dropped\n // rather than propagated. Failing here would fail whatever produced the\n // bytes — a step's `settle`, say — because a *cache* was unreachable,\n // which is the opposite of the line above. Nothing depends on this\n // upload: what actually guarantees a shared cache entry's blobs are\n // durable is the publication protocol's `findMissing` → upload →\n // confirm, run before the entry is published. A dropped upload here\n // costs that protocol one re-upload, never correctness. The deadline\n // keeps it opportunistic in time as well: a remote that stalls instead\n // of refusing must not hold the local answer hostage, so the upload is\n // interrupted after `uploadTimeout` and abandoned like any refusal.\n yield* uploadOnce(digest, snapshot).pipe(\n Effect.timeout(uploadTimeout),\n Effect.asVoid,\n Effect.catch(dropped(\"put\"))\n )\n return digest\n }))\n )\n\n const get: ArtifactStore.Service[\"get\"] = Effect.fn(\"CombinedArtifacts.get\")((digest: string) =>\n Effect.flatMap(ArtifactStore.validateDigest(digest), (validated) =>\n Effect.gen(function*() {\n yield* Effect.annotateCurrentSpan({ digest: validated })\n // A local miss AND local corruption fall through to the remote tier,\n // and they are kept apart because they earn different write-backs. A\n // local `ArtifactStoreError` deliberately does NOT fall through: a\n // host that refused the read has not answered the question, and\n // silently paying the network for it would hide a broken local tier\n // behind a working shared one.\n const cached = yield* local.get(validated).pipe(\n Effect.map((bytes): LocalRead => ({ _tag: \"hit\", bytes })),\n Effect.catchTags({\n \"@smthrs/artifacts/ArtifactMissing\": () => Effect.succeed<LocalRead>({ _tag: \"miss\" }),\n \"@smthrs/artifacts/ArtifactCorruption\": () => Effect.succeed<LocalRead>({ _tag: \"corrupt\" })\n })\n )\n if (cached._tag === \"hit\") return cached.bytes\n const fetched = yield* remote.get(validated)\n // `minimal` reads through without materializing: the caller gets the\n // bytes, the local tier stays exactly as small as it was, and the next\n // read pays the network again. Corruption is the exception every\n // policy makes. Handing the correct bytes to `local.put` lets its own\n // digest verification rewrite the mismatched blob, and an address the\n // local tier already claims — `has` and `findMissing` both report it\n // as present — must be one it can serve, or the publication protocol\n // is told a lie no later read can correct.\n if (downloadPolicy !== \"minimal\" || cached._tag === \"corrupt\") {\n // Opportunistic, exactly like `put`'s upload: the answer is already\n // in hand, so a full disk, a read-only mount, or a refused sync must\n // cost the next read a round trip rather than fail this one. A\n // stalled write is interrupted at the deadline so it cannot hold\n // the verified remote answer indefinitely.\n yield* local.put(fetched).pipe(\n Effect.timeout(writeBackTimeout),\n Effect.asVoid,\n Effect.catch(dropped(\"write_back\"))\n )\n }\n return fetched\n }))\n )\n\n const has: ArtifactStore.Service[\"has\"] = Effect.fn(\"CombinedArtifacts.has\")((digest: string) =>\n Effect.flatMap(ArtifactStore.validateDigest(digest), (validated) =>\n Effect.annotateCurrentSpan({ digest: validated }).pipe(\n Effect.andThen(\n Effect.flatMap(\n local.has(validated),\n (present) => present ? Effect.succeed(true) : remote.has(validated)\n )\n )\n ))\n )\n\n const findMissing: ArtifactStore.Service[\"findMissing\"] = Effect.fn(\"CombinedArtifacts.findMissing\")(\n (digests: Iterable<string>) =>\n Effect.gen(function*() {\n // The iterable is materialized once: it may be single-pass, and both the\n // annotation and the local probe need it.\n const requested = [...new Set(digests)]\n const validated: Array<ArtifactStore.Digest> = []\n for (const digest of requested) {\n validated.push(yield* ArtifactStore.validateDigest(digest))\n }\n // Missing means missing from BOTH tiers, and the remote probe is asked\n // only about what the local tier could not answer — one network round\n // trip, over the smallest possible set. The result stays a subset of the\n // input because each stage filters the previous stage's output.\n return yield* Effect.annotateCurrentSpan({ count: validated.length }).pipe(\n Effect.andThen(\n Effect.flatMap(\n local.findMissing(validated),\n (missingLocally) =>\n missingLocally.length === 0 ? Effect.succeed(missingLocally) : remote.findMissing(missingLocally)\n )\n )\n )\n })\n )\n\n return { put, get, has, findMissing, downloadPolicy }\n })\n\n/**\n * The two tiers as effects, plus the options {@link make} takes.\n *\n * Both tiers are supplied as *effects* rather than layers because they inhabit\n * the same tag: composing two `Layer<ArtifactStore>` would just shadow one\n * with the other. Pair `ArtifactStore.makeFileSystem` (wrapped in\n * `Effect.sync`) or `Effect.map(FileSystem.FileSystem, ...)` with\n * `RemoteArtifacts.make`.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport interface LayerOptions<EL, RL, ER, RR> {\n readonly local: Effect.Effect<ArtifactStore.Service, EL, RL>\n readonly remote: Effect.Effect<ArtifactStore.Service, ER, RR>\n readonly uploadTimeout?: Duration.Input | undefined\n readonly writeBackTimeout?: Duration.Input | undefined\n readonly downloadPolicy?: RemoteArtifacts.DownloadPolicy | undefined\n}\n\n/**\n * Provides a local-first artifact store, backed by local and remote effects, as\n * the `ArtifactStore` tag.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layer = <EL, RL, ER, RR>(\n options: LayerOptions<EL, RL, ER, RR>\n): Layer.Layer<ArtifactStore.ArtifactStore, EL | ER | ArtifactStore.ArtifactStoreError, RL | RR> =>\n Layer.effect(ArtifactStore.ArtifactStore)(\n Effect.flatMap(\n Effect.all({ local: options.local, remote: options.remote }),\n ({ local, remote }) =>\n make({\n local,\n remote,\n uploadTimeout: options.uploadTimeout,\n writeBackTimeout: options.writeBackTimeout,\n downloadPolicy: options.downloadPolicy\n })\n )\n )\n"]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cross-process lease on one lock file.
|
|
3
|
+
*
|
|
4
|
+
* The lock is a file created with `wx` (exclusive create) that holds a unique
|
|
5
|
+
* owner token. The holder refreshes its mtime every `heartbeatEvery`; a lock
|
|
6
|
+
* whose mtime is older than `staleAfterMs` belongs to a crashed or hard-killed
|
|
7
|
+
* owner and is reclaimed. Reclaimers of one lock generation race for a `wx`
|
|
8
|
+
* claim file first, so a stale generation is moved away at most once and a
|
|
9
|
+
* fresh lock is never displaced. Acquisition is bounded by `acquireWithin`,
|
|
10
|
+
* so a waiter fails with a typed error instead of waiting forever on a lock
|
|
11
|
+
* nothing will release.
|
|
12
|
+
*
|
|
13
|
+
* | Setting | Default |
|
|
14
|
+
* | ---------------- | ----------- |
|
|
15
|
+
* | `heartbeatEvery` | 10 seconds |
|
|
16
|
+
* | `staleAfterMs` | 60 000 ms |
|
|
17
|
+
* | `acquireWithin` | 2 minutes |
|
|
18
|
+
* | `retryEvery` | 25 millis |
|
|
19
|
+
*
|
|
20
|
+
* The fence is bounded, not absolute: a holder stalled past `staleAfterMs`
|
|
21
|
+
* (a suspended laptop, a stopped debugger) is reaped while it still runs. Its
|
|
22
|
+
* heartbeat then logs `<label> was reclaimed while its holder was still
|
|
23
|
+
* running`, and the protected effect continues unfenced.
|
|
24
|
+
*
|
|
25
|
+
* A directory at the lock path, left by a release that used an exclusive
|
|
26
|
+
* `makeDirectory` as its lock, is treated as a lock with no readable owner:
|
|
27
|
+
* it is reclaimed once its mtime is past `staleAfterMs`, and waited on
|
|
28
|
+
* before that.
|
|
29
|
+
*
|
|
30
|
+
* The host needs exclusive `wx` file creation, `readFileString`, `stat` with
|
|
31
|
+
* an mtime, `utimes`, `rename`, and `remove`. The claim file and the tombstone
|
|
32
|
+
* a reclaim renames the stale lock to are siblings of the lock:
|
|
33
|
+
* `<lockPath>.reclaim-<token>` and `<lockPath>.stale-<token>`.
|
|
34
|
+
*
|
|
35
|
+
* @since 1.0.0-rc.1
|
|
36
|
+
*/
|
|
37
|
+
import * as Duration from "effect/Duration";
|
|
38
|
+
import * as Effect from "effect/Effect";
|
|
39
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
40
|
+
/**
|
|
41
|
+
* Tuning for one lease.
|
|
42
|
+
*
|
|
43
|
+
* @category models
|
|
44
|
+
* @since 1.0.0-rc.1
|
|
45
|
+
*/
|
|
46
|
+
export interface Options {
|
|
47
|
+
/** Names the lock in the reclaimed-holder warning. Defaults to `"File lease"`. */
|
|
48
|
+
readonly label?: string | undefined;
|
|
49
|
+
/** Extra fields logged with the reclaimed-holder warning. */
|
|
50
|
+
readonly annotations?: Readonly<Record<string, unknown>> | undefined;
|
|
51
|
+
/** How often the holder refreshes the lock's mtime. Defaults to 10 seconds. */
|
|
52
|
+
readonly heartbeatEvery?: Duration.Input | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Age past which a lock or claim is stale. Defaults to 60 000 ms. Must be a
|
|
55
|
+
* finite positive number longer than `heartbeatEvery`; anything else fails
|
|
56
|
+
* the lease through `failure` before the lock is touched.
|
|
57
|
+
*/
|
|
58
|
+
readonly staleAfterMs?: number | undefined;
|
|
59
|
+
/** How long acquisition may wait. Defaults to 2 minutes. */
|
|
60
|
+
readonly acquireWithin?: Duration.Input | undefined;
|
|
61
|
+
/** How long a waiter sleeps between attempts. Defaults to 25 millis. */
|
|
62
|
+
readonly retryEvery?: Duration.Input | undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* {@link Options} for {@link hold}, which leaves the deadline to its caller.
|
|
66
|
+
*
|
|
67
|
+
* @category models
|
|
68
|
+
* @since 1.0.0-rc.1
|
|
69
|
+
*/
|
|
70
|
+
export interface HoldOptions extends Omit<Options, "acquireWithin"> {
|
|
71
|
+
/** Runs once, the moment the lease is held and before `effect` starts. */
|
|
72
|
+
readonly onAcquired?: Effect.Effect<void> | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Runs before every create, heartbeat, rename, and remove of the lock, its
|
|
75
|
+
* claim, or its tombstone. A failure refuses that step through `failure`,
|
|
76
|
+
* so a caller can re-check that the lock directory was not replaced (for
|
|
77
|
+
* example by a symlink) after it was created.
|
|
78
|
+
*/
|
|
79
|
+
readonly guard?: Effect.Effect<void, unknown> | undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The default acquisition deadline.
|
|
83
|
+
*
|
|
84
|
+
* @category constants
|
|
85
|
+
* @since 1.0.0-rc.1
|
|
86
|
+
*/
|
|
87
|
+
export declare const defaultAcquireWithin: Duration.Input;
|
|
88
|
+
/**
|
|
89
|
+
* The default stale bound, in milliseconds.
|
|
90
|
+
*
|
|
91
|
+
* @category constants
|
|
92
|
+
* @since 1.0.0-rc.1
|
|
93
|
+
*/
|
|
94
|
+
export declare const defaultStaleAfterMs = 60000;
|
|
95
|
+
/**
|
|
96
|
+
* Runs `effect` while holding the lease at `lockPath`, with no acquisition
|
|
97
|
+
* deadline. Use it when the caller already bounds a wider wait (an
|
|
98
|
+
* in-process semaphore plus this lease) with its own deadline; otherwise use
|
|
99
|
+
* {@link withLease}.
|
|
100
|
+
*
|
|
101
|
+
* @category combinators
|
|
102
|
+
* @since 1.0.0-rc.1
|
|
103
|
+
*/
|
|
104
|
+
export declare const hold: <A, E, R, E2>(fs: FileSystem.FileSystem, lockPath: string, effect: Effect.Effect<A, E, R>, failure: (cause: unknown) => E2, options?: HoldOptions) => Effect.Effect<A, E | E2, R>;
|
|
105
|
+
/**
|
|
106
|
+
* Runs `effect` while holding the lease at `lockPath`.
|
|
107
|
+
*
|
|
108
|
+
* Acquisition fails with `failure(TimeoutError)` once `acquireWithin` passes
|
|
109
|
+
* without the lease; every other host refusal reaches `failure` with the
|
|
110
|
+
* refusing `PlatformError`. The deadline ends when the lease is held: the
|
|
111
|
+
* protected effect and its release are never timed out.
|
|
112
|
+
*
|
|
113
|
+
* @category combinators
|
|
114
|
+
* @since 1.0.0-rc.1
|
|
115
|
+
*/
|
|
116
|
+
export declare const withLease: <A, E, R, E2>(fs: FileSystem.FileSystem, lockPath: string, effect: Effect.Effect<A, E, R>, failure: (cause: unknown) => E2, options?: Options) => Effect.Effect<A, E | E2, R>;
|
|
117
|
+
//# sourceMappingURL=FileLease.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileLease.d.ts","sourceRoot":"","sources":["../../src/FileLease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAKpD;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAA;IACpE,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;IACpD;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1C,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;IACnD,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAA;CACjD;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC;IACjE,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,SAAS,CAAA;IACrD;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CAC1D;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,KAAmB,CAAA;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAS,CAAA;AAczC;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAC9B,IAAI,UAAU,CAAC,UAAU,EACzB,UAAU,MAAM,EAChB,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC9B,SAAS,CAAC,KAAK,EAAE,OAAO,KAAK,EAAE,EAC/B,UAAS,WAAgB,KACxB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAyOzB,CAAA;AAEJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EACnC,IAAI,UAAU,CAAC,UAAU,EACzB,UAAU,MAAM,EAChB,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC9B,SAAS,CAAC,KAAK,EAAE,OAAO,KAAK,EAAE,EAC/B,UAAS,OAAY,KACpB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAYzB,CAAA"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cross-process lease on one lock file.
|
|
3
|
+
*
|
|
4
|
+
* The lock is a file created with `wx` (exclusive create) that holds a unique
|
|
5
|
+
* owner token. The holder refreshes its mtime every `heartbeatEvery`; a lock
|
|
6
|
+
* whose mtime is older than `staleAfterMs` belongs to a crashed or hard-killed
|
|
7
|
+
* owner and is reclaimed. Reclaimers of one lock generation race for a `wx`
|
|
8
|
+
* claim file first, so a stale generation is moved away at most once and a
|
|
9
|
+
* fresh lock is never displaced. Acquisition is bounded by `acquireWithin`,
|
|
10
|
+
* so a waiter fails with a typed error instead of waiting forever on a lock
|
|
11
|
+
* nothing will release.
|
|
12
|
+
*
|
|
13
|
+
* | Setting | Default |
|
|
14
|
+
* | ---------------- | ----------- |
|
|
15
|
+
* | `heartbeatEvery` | 10 seconds |
|
|
16
|
+
* | `staleAfterMs` | 60 000 ms |
|
|
17
|
+
* | `acquireWithin` | 2 minutes |
|
|
18
|
+
* | `retryEvery` | 25 millis |
|
|
19
|
+
*
|
|
20
|
+
* The fence is bounded, not absolute: a holder stalled past `staleAfterMs`
|
|
21
|
+
* (a suspended laptop, a stopped debugger) is reaped while it still runs. Its
|
|
22
|
+
* heartbeat then logs `<label> was reclaimed while its holder was still
|
|
23
|
+
* running`, and the protected effect continues unfenced.
|
|
24
|
+
*
|
|
25
|
+
* A directory at the lock path, left by a release that used an exclusive
|
|
26
|
+
* `makeDirectory` as its lock, is treated as a lock with no readable owner:
|
|
27
|
+
* it is reclaimed once its mtime is past `staleAfterMs`, and waited on
|
|
28
|
+
* before that.
|
|
29
|
+
*
|
|
30
|
+
* The host needs exclusive `wx` file creation, `readFileString`, `stat` with
|
|
31
|
+
* an mtime, `utimes`, `rename`, and `remove`. The claim file and the tombstone
|
|
32
|
+
* a reclaim renames the stale lock to are siblings of the lock:
|
|
33
|
+
* `<lockPath>.reclaim-<token>` and `<lockPath>.stale-<token>`.
|
|
34
|
+
*
|
|
35
|
+
* @since 1.0.0-rc.1
|
|
36
|
+
*/
|
|
37
|
+
import * as Clock from "effect/Clock";
|
|
38
|
+
import * as Deferred from "effect/Deferred";
|
|
39
|
+
import * as Duration from "effect/Duration";
|
|
40
|
+
import * as Effect from "effect/Effect";
|
|
41
|
+
import * as Option from "effect/Option";
|
|
42
|
+
import * as PlatformError from "effect/PlatformError";
|
|
43
|
+
import * as Random from "effect/Random";
|
|
44
|
+
/**
|
|
45
|
+
* The default acquisition deadline.
|
|
46
|
+
*
|
|
47
|
+
* @category constants
|
|
48
|
+
* @since 1.0.0-rc.1
|
|
49
|
+
*/
|
|
50
|
+
export const defaultAcquireWithin = "2 minutes";
|
|
51
|
+
/**
|
|
52
|
+
* The default stale bound, in milliseconds.
|
|
53
|
+
*
|
|
54
|
+
* @category constants
|
|
55
|
+
* @since 1.0.0-rc.1
|
|
56
|
+
*/
|
|
57
|
+
export const defaultStaleAfterMs = 60_000;
|
|
58
|
+
const isReason = (cause, tag) => cause instanceof PlatformError.PlatformError && cause.reason._tag === tag;
|
|
59
|
+
/** The generation name of a lock with no readable owner: a directory. */
|
|
60
|
+
const directoryGeneration = "legacy-directory";
|
|
61
|
+
const token = Effect.gen(function* () {
|
|
62
|
+
const first = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true });
|
|
63
|
+
const second = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true });
|
|
64
|
+
return `${first.toString(36)}-${second.toString(36)}-${yield* Clock.currentTimeMillis}`;
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Runs `effect` while holding the lease at `lockPath`, with no acquisition
|
|
68
|
+
* deadline. Use it when the caller already bounds a wider wait (an
|
|
69
|
+
* in-process semaphore plus this lease) with its own deadline; otherwise use
|
|
70
|
+
* {@link withLease}.
|
|
71
|
+
*
|
|
72
|
+
* @category combinators
|
|
73
|
+
* @since 1.0.0-rc.1
|
|
74
|
+
*/
|
|
75
|
+
export const hold = (fs, lockPath, effect, failure, options = {}) => Effect.gen(function* () {
|
|
76
|
+
const label = options.label ?? "File lease";
|
|
77
|
+
const heartbeatEvery = options.heartbeatEvery ?? "10 seconds";
|
|
78
|
+
const staleAfterMs = options.staleAfterMs ?? defaultStaleAfterMs;
|
|
79
|
+
const retryEvery = options.retryEvery ?? "25 millis";
|
|
80
|
+
// A holder heartbeats its lock to stay fresh, so a stale bound at or below
|
|
81
|
+
// the heartbeat, or one that is not a positive finite number, would let a
|
|
82
|
+
// contender read a live holder's lock as abandoned and move it, or would
|
|
83
|
+
// never reclaim a dead holder's lock at all.
|
|
84
|
+
const heartbeatMs = Option.map(Duration.fromInput(heartbeatEvery), Duration.toMillis);
|
|
85
|
+
if (typeof staleAfterMs !== "number" || !Number.isFinite(staleAfterMs) || staleAfterMs <= 0 ||
|
|
86
|
+
Option.isNone(heartbeatMs) || !(heartbeatMs.value > 0 && heartbeatMs.value < staleAfterMs)) {
|
|
87
|
+
return yield* Effect.fail(failure(new RangeError(`${label} needs 0 < heartbeatEvery < staleAfterMs, with a finite staleAfterMs`)));
|
|
88
|
+
}
|
|
89
|
+
const owner = yield* token;
|
|
90
|
+
/**
|
|
91
|
+
* Whether this call created the lock file, and therefore owes a release.
|
|
92
|
+
* It is set in the same uninterruptible step that creates the file, so
|
|
93
|
+
* interruption striking between the two cannot leak a lock nothing
|
|
94
|
+
* releases — the only party that would ever reclaim it is a later
|
|
95
|
+
* acquirer of the same path, which may never come.
|
|
96
|
+
*/
|
|
97
|
+
let acquired = false;
|
|
98
|
+
const guard = options.guard === undefined ? Effect.void : options.guard.pipe(Effect.mapError(failure));
|
|
99
|
+
const guarded = (mutation) => Effect.andThen(guard, mutation);
|
|
100
|
+
const statIfPresent = (path) => fs.stat(path).pipe(Effect.map(Option.some), Effect.catch((cause) => isReason(cause, "NotFound") ? Effect.succeed(Option.none()) : Effect.fail(failure(cause))));
|
|
101
|
+
const refuse = (cause) => Effect.fail(failure(cause));
|
|
102
|
+
const readOwner = (path, onRefused = refuse) => fs.readFileString(path).pipe(Effect.map(Option.some), Effect.catch((cause) => isReason(cause, "NotFound") ? Effect.succeed(Option.none()) : onRefused(cause)));
|
|
103
|
+
/**
|
|
104
|
+
* Names the lock generation at `path`: its owner token, or
|
|
105
|
+
* `directoryGeneration` for a directory, which has no readable owner.
|
|
106
|
+
* A read refused for any other reason is propagated, not guessed at.
|
|
107
|
+
*/
|
|
108
|
+
const observe = (path) => readOwner(path, (cause) => fs.stat(path).pipe(Effect.matchEffect({
|
|
109
|
+
onFailure: () => refuse(cause),
|
|
110
|
+
onSuccess: (info) => info.type === "Directory" ? Effect.succeed(Option.some(directoryGeneration)) : refuse(cause)
|
|
111
|
+
})));
|
|
112
|
+
const isStale = (path) => Effect.gen(function* () {
|
|
113
|
+
const info = yield* statIfPresent(path);
|
|
114
|
+
const modified = Option.isSome(info) ? Option.getOrUndefined(info.value.mtime) : undefined;
|
|
115
|
+
return modified !== undefined && (yield* Clock.currentTimeMillis) - modified.getTime() > staleAfterMs;
|
|
116
|
+
});
|
|
117
|
+
const removeIfOwnedBy = (path, expected) => fs.readFileString(path).pipe(Effect.flatMap((found) => found === expected ? guarded(fs.remove(path)) : Effect.void), Effect.ignore);
|
|
118
|
+
const acquire = Effect.gen(function* () {
|
|
119
|
+
while (true) {
|
|
120
|
+
const created = yield* Effect.uninterruptible(guarded(fs.writeFileString(lockPath, owner, { flag: "wx", mode: 0o600 })).pipe(Effect.andThen(Effect.sync(() => {
|
|
121
|
+
acquired = true;
|
|
122
|
+
return true;
|
|
123
|
+
})), Effect.catch((cause) => isReason(cause, "AlreadyExists") ? Effect.succeed(false) : Effect.fail(failure(cause)))));
|
|
124
|
+
if (created)
|
|
125
|
+
return;
|
|
126
|
+
// The owner is read before the age, so a stale verdict always
|
|
127
|
+
// belongs to the lock generation this read named.
|
|
128
|
+
const observed = yield* observe(lockPath);
|
|
129
|
+
if (Option.isNone(observed))
|
|
130
|
+
continue;
|
|
131
|
+
if (yield* isStale(lockPath)) {
|
|
132
|
+
const reclaimed = yield* reclaim(observed.value);
|
|
133
|
+
if (reclaimed)
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
yield* Effect.sleep(retryEvery);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* Moves away the stale lock generation `observed` names, at most once.
|
|
141
|
+
*
|
|
142
|
+
* Measuring a lock and renaming it are two steps, so two contenders
|
|
143
|
+
* that both measure the same generation as stale would otherwise both
|
|
144
|
+
* rename, and the second would move away the fresh lock the first just
|
|
145
|
+
* took. Reclaimers of one generation therefore race for a `wx` claim
|
|
146
|
+
* named after its owner token first. Only the winner renames, and only
|
|
147
|
+
* after re-reading that the path still holds that stale generation. A
|
|
148
|
+
* loser, or a winner whose generation is already gone, moves nothing.
|
|
149
|
+
* Owner tokens are unique per acquisition, so a claim never outlives
|
|
150
|
+
* the generation it names in any way that matters.
|
|
151
|
+
*/
|
|
152
|
+
const reclaim = (observed) => {
|
|
153
|
+
const claimPath = `${lockPath}.reclaim-${observed.replace(/[^0-9A-Za-z-]/g, "_").slice(0, 96)}`;
|
|
154
|
+
return Effect.acquireUseRelease(guarded(fs.writeFileString(claimPath, owner, { flag: "wx", mode: 0o600 })).pipe(Effect.as(true), Effect.catch((cause) => isReason(cause, "AlreadyExists") ? Effect.succeed(false) : Effect.fail(failure(cause)))), (claimed) => Effect.gen(function* () {
|
|
155
|
+
if (!claimed) {
|
|
156
|
+
// A claimant that died mid-reclaim leaves its claim behind.
|
|
157
|
+
// It ages out on the same bound as a lock.
|
|
158
|
+
const holder = yield* readOwner(claimPath);
|
|
159
|
+
if (Option.isSome(holder) && (yield* isStale(claimPath))) {
|
|
160
|
+
yield* removeIfOwnedBy(claimPath, holder.value);
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const current = yield* observe(lockPath);
|
|
165
|
+
if (Option.isNone(current) || current.value !== observed || !(yield* isStale(lockPath)))
|
|
166
|
+
return true;
|
|
167
|
+
const tombstone = `${lockPath}.stale-${owner}`;
|
|
168
|
+
const moved = yield* guarded(fs.rename(lockPath, tombstone)).pipe(Effect.as(true), Effect.catch((cause) => isReason(cause, "NotFound") ? Effect.succeed(false) : Effect.fail(failure(cause))));
|
|
169
|
+
if (!moved)
|
|
170
|
+
return true;
|
|
171
|
+
// Only a stalled owner releasing between the re-read and the
|
|
172
|
+
// rename, and a new owner acquiring in that gap, puts a live
|
|
173
|
+
// lock here. Hand it back with an atomic create, which cannot
|
|
174
|
+
// displace anyone who took the path since.
|
|
175
|
+
const displaced = yield* observe(tombstone);
|
|
176
|
+
if (Option.isSome(displaced) && displaced.value !== observed && displaced.value !== directoryGeneration) {
|
|
177
|
+
yield* guarded(fs.writeFileString(lockPath, displaced.value, { flag: "wx", mode: 0o600 })).pipe(Effect.ignore);
|
|
178
|
+
}
|
|
179
|
+
yield* guarded(fs.remove(tombstone, { recursive: true })).pipe(Effect.ignore);
|
|
180
|
+
return true;
|
|
181
|
+
}), (claimed) => claimed ? removeIfOwnedBy(claimPath, owner) : Effect.void);
|
|
182
|
+
};
|
|
183
|
+
const release = fs.readFileString(lockPath).pipe(Effect.flatMap((found) => found === owner ? guarded(fs.remove(lockPath)) : Effect.void),
|
|
184
|
+
// A concurrent stale-lock reaper can win release. `NotFound`
|
|
185
|
+
// means no lock remains for this owner to release or leak.
|
|
186
|
+
Effect.catch((cause) => isReason(cause, "NotFound") ? Effect.void : Effect.fail(failure(cause))));
|
|
187
|
+
// The finalizer is attached around acquisition itself, not just around
|
|
188
|
+
// the protected effect, so a lock created moments before an interruption
|
|
189
|
+
// is still released. A call that never created one owes nothing and must
|
|
190
|
+
// not touch a file another owner holds.
|
|
191
|
+
return yield* acquire.pipe(Effect.andThen(options.onAcquired ?? Effect.void), Effect.andThen(Effect.scoped(Effect.gen(function* () {
|
|
192
|
+
yield* Effect.forkScoped(Effect.gen(function* () {
|
|
193
|
+
while (true) {
|
|
194
|
+
yield* Effect.sleep(heartbeatEvery);
|
|
195
|
+
// Freshen only a lock this call still owns, the same comparison
|
|
196
|
+
// the release below makes. A holder stalled past the stale
|
|
197
|
+
// bound has already been reaped and replaced, and touching that
|
|
198
|
+
// pathname anyway would hold a stranger's lock fresh forever: a
|
|
199
|
+
// replacement that is then hard-killed could never be judged
|
|
200
|
+
// stale while this zombie ran, so every later acquirer would
|
|
201
|
+
// burn the full acquisition deadline and fail.
|
|
202
|
+
const beat = yield* fs.readFileString(lockPath).pipe(Effect.map((found) => found === owner ? "own" : "foreign"), Effect.catch((cause) => Effect.succeed(isReason(cause, "NotFound") ? "gone" : "unreadable")));
|
|
203
|
+
if (beat === "own") {
|
|
204
|
+
const timestamp = new Date(yield* Clock.currentTimeMillis);
|
|
205
|
+
yield* Effect.ignore(guarded(fs.utimes(lockPath, timestamp, timestamp)));
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
// A read the host refused for any other reason is no evidence
|
|
209
|
+
// either way, so this beat is skipped and the next one retries.
|
|
210
|
+
// Ending the heartbeat on it would retire a lock this call
|
|
211
|
+
// still holds: the file goes stale within the minute, another
|
|
212
|
+
// process reaps it, and this holder keeps working unfenced.
|
|
213
|
+
if (beat === "unreadable")
|
|
214
|
+
continue;
|
|
215
|
+
// `gone` and `foreign` are the two states worth an operator
|
|
216
|
+
// signal: this call's lock was reaped, and possibly already
|
|
217
|
+
// replaced, while the effect it fences is still running, so
|
|
218
|
+
// the rest of that effect proceeds unfenced against every other
|
|
219
|
+
// process. The heartbeat is the only party that ever observes
|
|
220
|
+
// it — the protected effect is not interrupted and the caller
|
|
221
|
+
// is not failed — so retiring the fiber silently would leave no
|
|
222
|
+
// trace at all.
|
|
223
|
+
yield* Effect.logWarning(`${label} was reclaimed while its holder was still running`, { ...options.annotations, state: beat });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}));
|
|
227
|
+
return yield* effect;
|
|
228
|
+
}))), Effect.onExit(() => acquired ? release : Effect.void));
|
|
229
|
+
});
|
|
230
|
+
/**
|
|
231
|
+
* Runs `effect` while holding the lease at `lockPath`.
|
|
232
|
+
*
|
|
233
|
+
* Acquisition fails with `failure(TimeoutError)` once `acquireWithin` passes
|
|
234
|
+
* without the lease; every other host refusal reaches `failure` with the
|
|
235
|
+
* refusing `PlatformError`. The deadline ends when the lease is held: the
|
|
236
|
+
* protected effect and its release are never timed out.
|
|
237
|
+
*
|
|
238
|
+
* @category combinators
|
|
239
|
+
* @since 1.0.0-rc.1
|
|
240
|
+
*/
|
|
241
|
+
export const withLease = (fs, lockPath, effect, failure, options = {}) => Effect.suspend(() => {
|
|
242
|
+
const ready = Deferred.makeUnsafe();
|
|
243
|
+
const deadline = Deferred.await(ready).pipe(Effect.timeout(options.acquireWithin ?? defaultAcquireWithin), Effect.catchTag("TimeoutError", (cause) => Effect.fail(failure(cause))), Effect.andThen(Effect.never));
|
|
244
|
+
return hold(fs, lockPath, effect, failure, {
|
|
245
|
+
...options,
|
|
246
|
+
onAcquired: Deferred.succeed(ready, undefined).pipe(Effect.asVoid)
|
|
247
|
+
}).pipe(Effect.raceFirst(deadline));
|
|
248
|
+
});
|
|
249
|
+
//# sourceMappingURL=FileLease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileLease.js","sourceRoot":"","sources":["../../src/FileLease.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A cross-process lease on one lock file.\n *\n * The lock is a file created with `wx` (exclusive create) that holds a unique\n * owner token. The holder refreshes its mtime every `heartbeatEvery`; a lock\n * whose mtime is older than `staleAfterMs` belongs to a crashed or hard-killed\n * owner and is reclaimed. Reclaimers of one lock generation race for a `wx`\n * claim file first, so a stale generation is moved away at most once and a\n * fresh lock is never displaced. Acquisition is bounded by `acquireWithin`,\n * so a waiter fails with a typed error instead of waiting forever on a lock\n * nothing will release.\n *\n * | Setting | Default |\n * | ---------------- | ----------- |\n * | `heartbeatEvery` | 10 seconds |\n * | `staleAfterMs` | 60 000 ms |\n * | `acquireWithin` | 2 minutes |\n * | `retryEvery` | 25 millis |\n *\n * The fence is bounded, not absolute: a holder stalled past `staleAfterMs`\n * (a suspended laptop, a stopped debugger) is reaped while it still runs. Its\n * heartbeat then logs `<label> was reclaimed while its holder was still\n * running`, and the protected effect continues unfenced.\n *\n * A directory at the lock path, left by a release that used an exclusive\n * `makeDirectory` as its lock, is treated as a lock with no readable owner:\n * it is reclaimed once its mtime is past `staleAfterMs`, and waited on\n * before that.\n *\n * The host needs exclusive `wx` file creation, `readFileString`, `stat` with\n * an mtime, `utimes`, `rename`, and `remove`. The claim file and the tombstone\n * a reclaim renames the stale lock to are siblings of the lock:\n * `<lockPath>.reclaim-<token>` and `<lockPath>.stale-<token>`.\n *\n * @since 1.0.0-rc.1\n */\n\nimport * as Clock from \"effect/Clock\"\nimport * as Deferred from \"effect/Deferred\"\nimport * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport type * as FileSystem from \"effect/FileSystem\"\nimport * as Option from \"effect/Option\"\nimport * as PlatformError from \"effect/PlatformError\"\nimport * as Random from \"effect/Random\"\n\n/**\n * Tuning for one lease.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Options {\n /** Names the lock in the reclaimed-holder warning. Defaults to `\"File lease\"`. */\n readonly label?: string | undefined\n /** Extra fields logged with the reclaimed-holder warning. */\n readonly annotations?: Readonly<Record<string, unknown>> | undefined\n /** How often the holder refreshes the lock's mtime. Defaults to 10 seconds. */\n readonly heartbeatEvery?: Duration.Input | undefined\n /**\n * Age past which a lock or claim is stale. Defaults to 60 000 ms. Must be a\n * finite positive number longer than `heartbeatEvery`; anything else fails\n * the lease through `failure` before the lock is touched.\n */\n readonly staleAfterMs?: number | undefined\n /** How long acquisition may wait. Defaults to 2 minutes. */\n readonly acquireWithin?: Duration.Input | undefined\n /** How long a waiter sleeps between attempts. Defaults to 25 millis. */\n readonly retryEvery?: Duration.Input | undefined\n}\n\n/**\n * {@link Options} for {@link hold}, which leaves the deadline to its caller.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface HoldOptions extends Omit<Options, \"acquireWithin\"> {\n /** Runs once, the moment the lease is held and before `effect` starts. */\n readonly onAcquired?: Effect.Effect<void> | undefined\n /**\n * Runs before every create, heartbeat, rename, and remove of the lock, its\n * claim, or its tombstone. A failure refuses that step through `failure`,\n * so a caller can re-check that the lock directory was not replaced (for\n * example by a symlink) after it was created.\n */\n readonly guard?: Effect.Effect<void, unknown> | undefined\n}\n\n/**\n * The default acquisition deadline.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const defaultAcquireWithin: Duration.Input = \"2 minutes\"\n\n/**\n * The default stale bound, in milliseconds.\n *\n * @category constants\n * @since 1.0.0-rc.1\n */\nexport const defaultStaleAfterMs = 60_000\n\nconst isReason = (cause: unknown, tag: PlatformError.SystemErrorTag): boolean =>\n cause instanceof PlatformError.PlatformError && cause.reason._tag === tag\n\n/** The generation name of a lock with no readable owner: a directory. */\nconst directoryGeneration = \"legacy-directory\"\n\nconst token = Effect.gen(function*() {\n const first = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true })\n const second = yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER, { halfOpen: true })\n return `${first.toString(36)}-${second.toString(36)}-${yield* Clock.currentTimeMillis}`\n})\n\n/**\n * Runs `effect` while holding the lease at `lockPath`, with no acquisition\n * deadline. Use it when the caller already bounds a wider wait (an\n * in-process semaphore plus this lease) with its own deadline; otherwise use\n * {@link withLease}.\n *\n * @category combinators\n * @since 1.0.0-rc.1\n */\nexport const hold = <A, E, R, E2>(\n fs: FileSystem.FileSystem,\n lockPath: string,\n effect: Effect.Effect<A, E, R>,\n failure: (cause: unknown) => E2,\n options: HoldOptions = {}\n): Effect.Effect<A, E | E2, R> =>\n Effect.gen(function*() {\n const label = options.label ?? \"File lease\"\n const heartbeatEvery = options.heartbeatEvery ?? \"10 seconds\"\n const staleAfterMs = options.staleAfterMs ?? defaultStaleAfterMs\n const retryEvery = options.retryEvery ?? \"25 millis\"\n // A holder heartbeats its lock to stay fresh, so a stale bound at or below\n // the heartbeat, or one that is not a positive finite number, would let a\n // contender read a live holder's lock as abandoned and move it, or would\n // never reclaim a dead holder's lock at all.\n const heartbeatMs = Option.map(Duration.fromInput(heartbeatEvery), Duration.toMillis)\n if (\n typeof staleAfterMs !== \"number\" || !Number.isFinite(staleAfterMs) || staleAfterMs <= 0 ||\n Option.isNone(heartbeatMs) || !(heartbeatMs.value > 0 && heartbeatMs.value < staleAfterMs)\n ) {\n return yield* Effect.fail(\n failure(new RangeError(`${label} needs 0 < heartbeatEvery < staleAfterMs, with a finite staleAfterMs`))\n )\n }\n const owner = yield* token\n /**\n * Whether this call created the lock file, and therefore owes a release.\n * It is set in the same uninterruptible step that creates the file, so\n * interruption striking between the two cannot leak a lock nothing\n * releases — the only party that would ever reclaim it is a later\n * acquirer of the same path, which may never come.\n */\n let acquired = false\n\n const guard = options.guard === undefined ? Effect.void : options.guard.pipe(Effect.mapError(failure))\n const guarded = <B, F, S>(mutation: Effect.Effect<B, F, S>): Effect.Effect<B, F | E2, S> =>\n Effect.andThen(guard, mutation)\n\n const statIfPresent = (path: string): Effect.Effect<Option.Option<FileSystem.File.Info>, E2> =>\n fs.stat(path).pipe(\n Effect.map(Option.some),\n Effect.catch((cause): Effect.Effect<Option.Option<FileSystem.File.Info>, E2> =>\n isReason(cause, \"NotFound\") ? Effect.succeed(Option.none()) : Effect.fail(failure(cause))\n )\n )\n\n const refuse = (cause: unknown): Effect.Effect<never, E2> => Effect.fail(failure(cause))\n\n const readOwner = (\n path: string,\n onRefused: (cause: unknown) => Effect.Effect<Option.Option<string>, E2> = refuse\n ): Effect.Effect<Option.Option<string>, E2> =>\n fs.readFileString(path).pipe(\n Effect.map(Option.some),\n Effect.catch((cause) => isReason(cause, \"NotFound\") ? Effect.succeed(Option.none()) : onRefused(cause))\n )\n\n /**\n * Names the lock generation at `path`: its owner token, or\n * `directoryGeneration` for a directory, which has no readable owner.\n * A read refused for any other reason is propagated, not guessed at.\n */\n const observe = (path: string): Effect.Effect<Option.Option<string>, E2> =>\n readOwner(path, (cause) =>\n fs.stat(path).pipe(\n Effect.matchEffect({\n onFailure: () => refuse(cause),\n onSuccess: (info) =>\n info.type === \"Directory\" ? Effect.succeed(Option.some(directoryGeneration)) : refuse(cause)\n })\n ))\n\n const isStale = (path: string): Effect.Effect<boolean, E2> =>\n Effect.gen(function*() {\n const info = yield* statIfPresent(path)\n const modified = Option.isSome(info) ? Option.getOrUndefined(info.value.mtime) : undefined\n return modified !== undefined && (yield* Clock.currentTimeMillis) - modified.getTime() > staleAfterMs\n })\n\n const removeIfOwnedBy = (path: string, expected: string): Effect.Effect<void> =>\n fs.readFileString(path).pipe(\n Effect.flatMap((found) => found === expected ? guarded(fs.remove(path)) : Effect.void),\n Effect.ignore\n )\n\n const acquire = Effect.gen(function*() {\n while (true) {\n const created = yield* Effect.uninterruptible(\n guarded(fs.writeFileString(lockPath, owner, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.andThen(Effect.sync(() => {\n acquired = true\n return true\n })),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"AlreadyExists\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n )\n )\n if (created) return\n\n // The owner is read before the age, so a stale verdict always\n // belongs to the lock generation this read named.\n const observed = yield* observe(lockPath)\n if (Option.isNone(observed)) continue\n if (yield* isStale(lockPath)) {\n const reclaimed = yield* reclaim(observed.value)\n if (reclaimed) continue\n }\n yield* Effect.sleep(retryEvery)\n }\n })\n\n /**\n * Moves away the stale lock generation `observed` names, at most once.\n *\n * Measuring a lock and renaming it are two steps, so two contenders\n * that both measure the same generation as stale would otherwise both\n * rename, and the second would move away the fresh lock the first just\n * took. Reclaimers of one generation therefore race for a `wx` claim\n * named after its owner token first. Only the winner renames, and only\n * after re-reading that the path still holds that stale generation. A\n * loser, or a winner whose generation is already gone, moves nothing.\n * Owner tokens are unique per acquisition, so a claim never outlives\n * the generation it names in any way that matters.\n */\n const reclaim = (observed: string): Effect.Effect<boolean, E2> => {\n const claimPath = `${lockPath}.reclaim-${observed.replace(/[^0-9A-Za-z-]/g, \"_\").slice(0, 96)}`\n return Effect.acquireUseRelease(\n guarded(fs.writeFileString(claimPath, owner, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.as(true),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"AlreadyExists\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n ),\n (claimed) =>\n Effect.gen(function*() {\n if (!claimed) {\n // A claimant that died mid-reclaim leaves its claim behind.\n // It ages out on the same bound as a lock.\n const holder = yield* readOwner(claimPath)\n if (Option.isSome(holder) && (yield* isStale(claimPath))) {\n yield* removeIfOwnedBy(claimPath, holder.value)\n }\n return false\n }\n const current = yield* observe(lockPath)\n if (Option.isNone(current) || current.value !== observed || !(yield* isStale(lockPath))) return true\n const tombstone = `${lockPath}.stale-${owner}`\n const moved = yield* guarded(fs.rename(lockPath, tombstone)).pipe(\n Effect.as(true),\n Effect.catch((cause): Effect.Effect<boolean, E2> =>\n isReason(cause, \"NotFound\") ? Effect.succeed(false) : Effect.fail(failure(cause))\n )\n )\n if (!moved) return true\n // Only a stalled owner releasing between the re-read and the\n // rename, and a new owner acquiring in that gap, puts a live\n // lock here. Hand it back with an atomic create, which cannot\n // displace anyone who took the path since.\n const displaced = yield* observe(tombstone)\n if (\n Option.isSome(displaced) && displaced.value !== observed && displaced.value !== directoryGeneration\n ) {\n yield* guarded(fs.writeFileString(lockPath, displaced.value, { flag: \"wx\", mode: 0o600 })).pipe(\n Effect.ignore\n )\n }\n yield* guarded(fs.remove(tombstone, { recursive: true })).pipe(Effect.ignore)\n return true\n }),\n (claimed) => claimed ? removeIfOwnedBy(claimPath, owner) : Effect.void\n )\n }\n\n const release = fs.readFileString(lockPath).pipe(\n Effect.flatMap((found) => found === owner ? guarded(fs.remove(lockPath)) : Effect.void),\n // A concurrent stale-lock reaper can win release. `NotFound`\n // means no lock remains for this owner to release or leak.\n Effect.catch((cause): Effect.Effect<void, E2> =>\n isReason(cause, \"NotFound\") ? Effect.void : Effect.fail(failure(cause))\n )\n )\n\n // The finalizer is attached around acquisition itself, not just around\n // the protected effect, so a lock created moments before an interruption\n // is still released. A call that never created one owes nothing and must\n // not touch a file another owner holds.\n return yield* acquire.pipe(\n Effect.andThen(options.onAcquired ?? Effect.void),\n Effect.andThen(Effect.scoped(Effect.gen(function*() {\n yield* Effect.forkScoped(\n Effect.gen(function*() {\n while (true) {\n yield* Effect.sleep(heartbeatEvery)\n // Freshen only a lock this call still owns, the same comparison\n // the release below makes. A holder stalled past the stale\n // bound has already been reaped and replaced, and touching that\n // pathname anyway would hold a stranger's lock fresh forever: a\n // replacement that is then hard-killed could never be judged\n // stale while this zombie ran, so every later acquirer would\n // burn the full acquisition deadline and fail.\n const beat = yield* fs.readFileString(lockPath).pipe(\n Effect.map((found) => found === owner ? \"own\" as const : \"foreign\" as const),\n Effect.catch((cause) =>\n Effect.succeed(isReason(cause, \"NotFound\") ? \"gone\" as const : \"unreadable\" as const)\n )\n )\n if (beat === \"own\") {\n const timestamp = new Date(yield* Clock.currentTimeMillis)\n yield* Effect.ignore(guarded(fs.utimes(lockPath, timestamp, timestamp)))\n continue\n }\n // A read the host refused for any other reason is no evidence\n // either way, so this beat is skipped and the next one retries.\n // Ending the heartbeat on it would retire a lock this call\n // still holds: the file goes stale within the minute, another\n // process reaps it, and this holder keeps working unfenced.\n if (beat === \"unreadable\") continue\n // `gone` and `foreign` are the two states worth an operator\n // signal: this call's lock was reaped, and possibly already\n // replaced, while the effect it fences is still running, so\n // the rest of that effect proceeds unfenced against every other\n // process. The heartbeat is the only party that ever observes\n // it — the protected effect is not interrupted and the caller\n // is not failed — so retiring the fiber silently would leave no\n // trace at all.\n yield* Effect.logWarning(\n `${label} was reclaimed while its holder was still running`,\n { ...options.annotations, state: beat }\n )\n return\n }\n })\n )\n return yield* effect\n }))),\n Effect.onExit(() => acquired ? release : Effect.void)\n )\n })\n\n/**\n * Runs `effect` while holding the lease at `lockPath`.\n *\n * Acquisition fails with `failure(TimeoutError)` once `acquireWithin` passes\n * without the lease; every other host refusal reaches `failure` with the\n * refusing `PlatformError`. The deadline ends when the lease is held: the\n * protected effect and its release are never timed out.\n *\n * @category combinators\n * @since 1.0.0-rc.1\n */\nexport const withLease = <A, E, R, E2>(\n fs: FileSystem.FileSystem,\n lockPath: string,\n effect: Effect.Effect<A, E, R>,\n failure: (cause: unknown) => E2,\n options: Options = {}\n): Effect.Effect<A, E | E2, R> =>\n Effect.suspend(() => {\n const ready = Deferred.makeUnsafe<void>()\n const deadline = Deferred.await(ready).pipe(\n Effect.timeout(options.acquireWithin ?? defaultAcquireWithin),\n Effect.catchTag(\"TimeoutError\", (cause) => Effect.fail(failure(cause))),\n Effect.andThen(Effect.never)\n )\n return hold(fs, lockPath, effect, failure, {\n ...options,\n onAcquired: Deferred.succeed(ready, undefined).pipe(Effect.asVoid)\n }).pipe(Effect.raceFirst(deadline))\n })\n"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The filesystem-backed artifact store: blobs in a workspace-relative objects
|
|
3
|
+
* directory under a two-hex-prefix fanout, published atomically, coordinated
|
|
4
|
+
* across processes, and digest-verified on every read.
|
|
5
|
+
*
|
|
6
|
+
* @since 1.0.0-rc.0
|
|
7
|
+
*/
|
|
8
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import type * as Path from "effect/Path";
|
|
10
|
+
import type { Service } from "./ArtifactStore.ts";
|
|
11
|
+
/**
|
|
12
|
+
* Where the filesystem-backed store keeps its blobs.
|
|
13
|
+
*
|
|
14
|
+
* The directory is workspace-relative rather than absolute so a workspace can
|
|
15
|
+
* be moved or copied whole and still resolve its own artifacts.
|
|
16
|
+
*
|
|
17
|
+
* @category models
|
|
18
|
+
* @since 1.0.0-rc.0
|
|
19
|
+
* @slop
|
|
20
|
+
*/
|
|
21
|
+
export interface FileSystemOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Where blobs are stored, content-addressed by digest. Workspace-relative;
|
|
24
|
+
* defaults to {@link defaultDirectory}. An `ArtifactSweep` over the same
|
|
25
|
+
* store must be built with this same directory, or it enumerates somewhere
|
|
26
|
+
* the store never publishes.
|
|
27
|
+
*/
|
|
28
|
+
readonly directory?: string | undefined;
|
|
29
|
+
/** New payload mode, default `0600`, restricted by the umask. Existing blobs are unchanged. */
|
|
30
|
+
readonly fileMode?: number | undefined;
|
|
31
|
+
/** New objects and fanout directory mode, default `0700`. Existing directories are unchanged. */
|
|
32
|
+
readonly directoryMode?: number | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* `required` reports success only after syncing the blob, its fanout,
|
|
35
|
+
* the objects directory, and every ancestor. `best-effort` is the explicit weaker
|
|
36
|
+
* capability for hosts that cannot sync file or directory handles.
|
|
37
|
+
* Both modes require exclusive writable handles and symlink inspection.
|
|
38
|
+
*/
|
|
39
|
+
readonly durability?: "required" | "best-effort" | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* `required` uses an atomic lock file to coordinate writers and sweepers
|
|
42
|
+
* across processes. `process` is the explicit weaker browser/test mode.
|
|
43
|
+
*
|
|
44
|
+
* The fence bounds the race rather than eliminating it: the lock is
|
|
45
|
+
* heartbeated every 10 seconds, another process reclaims it once it is 60
|
|
46
|
+
* seconds stale, and acquisition itself gives up after 2 minutes. A holder
|
|
47
|
+
* whose host stalls past the stale bound can therefore be reaped while it is
|
|
48
|
+
* still running.
|
|
49
|
+
*
|
|
50
|
+
* Contenders that measure the same lock as stale race for a claim file named
|
|
51
|
+
* after its owner token, so each lock generation is moved away at most once
|
|
52
|
+
* and a fresh replacement is never displaced by a late stale verdict.
|
|
53
|
+
* Neither the mtime check nor the backup lease independently protects
|
|
54
|
+
* against a reaped stalled holder: the check precedes a separate delete, and
|
|
55
|
+
* the gate uses this same protocol.
|
|
56
|
+
*
|
|
57
|
+
* It also only fences parties that agree. An `ArtifactSweep` over the same
|
|
58
|
+
* directory must be built with the same `coordination`: a store on `process`
|
|
59
|
+
* paired with a sweep on `required` takes lock files no writer observes, so
|
|
60
|
+
* the fence reads as armed and protects nothing.
|
|
61
|
+
*/
|
|
62
|
+
readonly coordination?: "required" | "process" | undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The default objects directory. Workspace-relative, so a workspace carries
|
|
66
|
+
* its own artifacts and a sandbox that mounts the workspace inherits them.
|
|
67
|
+
*
|
|
68
|
+
* Exported because the store and its sweep must name the same directory, and a
|
|
69
|
+
* second private copy of the literal is exactly how they would drift apart.
|
|
70
|
+
*
|
|
71
|
+
* @category constants
|
|
72
|
+
* @since 1.0.0-rc.0
|
|
73
|
+
*/
|
|
74
|
+
export declare const defaultDirectory = ".flows/objects";
|
|
75
|
+
/**
|
|
76
|
+
* Builds the filesystem-backed artifact store.
|
|
77
|
+
*
|
|
78
|
+
* Host access arrives through Effect's `FileSystem` tag. Persistence needs a
|
|
79
|
+
* trusted host with exclusive writable handles, symlink inspection and, for
|
|
80
|
+
* required durability, file and directory syncing. Build this service before
|
|
81
|
+
* decorating the workflow filesystem with the capability kernel, as native
|
|
82
|
+
* `layerHost` does. The guarded native filesystem deliberately refuses open
|
|
83
|
+
* handles and cannot back this store; best-effort durability does not lift
|
|
84
|
+
* that requirement. A browser host can instead use the memory store.
|
|
85
|
+
* `platformPath` is the host's `Path` service; the directory fsync walks ancestors with it,
|
|
86
|
+
* so a Windows drive directory syncs its real parents.
|
|
87
|
+
*
|
|
88
|
+
* @category constructors
|
|
89
|
+
* @since 1.0.0-rc.0
|
|
90
|
+
* @slop
|
|
91
|
+
*/
|
|
92
|
+
export declare const makeFileSystem: (fs: FileSystem.FileSystem, platformPath: Path.Path, options?: FileSystemOptions) => Service;
|
|
93
|
+
//# sourceMappingURL=FileSystemArtifactStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileSystemArtifactStore.d.ts","sourceRoot":"","sources":["../../src/FileSystemArtifactStore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAGpD,OAAO,KAAK,KAAK,IAAI,MAAM,aAAa,CAAA;AAGxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAgBjD;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,iGAAiG;IACjG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,aAAa,GAAG,SAAS,CAAA;IAC5D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAA;CAC3D;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,mBAAmB,CAAA;AAgChD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GACzB,IAAI,UAAU,CAAC,UAAU,EACzB,cAAc,IAAI,CAAC,IAAI,EACvB,UAAS,iBAAsB,KAC9B,OAqTF,CAAA"}
|