@namzu/sdk 59.0.0 → 60.1.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 +120 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +1 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +16 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/acp/update.d.ts.map +1 -1
- package/dist/bridge/acp/update.js +1 -0
- package/dist/bridge/acp/update.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +12 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/compaction/context-window.d.ts +53 -0
- package/dist/compaction/context-window.d.ts.map +1 -1
- package/dist/compaction/context-window.js +141 -2
- package/dist/compaction/context-window.js.map +1 -1
- package/dist/constants/continuation.d.ts +9 -0
- package/dist/constants/continuation.d.ts.map +1 -1
- package/dist/constants/continuation.js +9 -0
- package/dist/constants/continuation.js.map +1 -1
- package/dist/contracts/session/api.d.ts +7 -1
- package/dist/contracts/session/api.d.ts.map +1 -1
- package/dist/execution/workflow/index.d.ts +3 -1
- package/dist/execution/workflow/index.d.ts.map +1 -1
- package/dist/execution/workflow/index.js +2 -1
- package/dist/execution/workflow/index.js.map +1 -1
- package/dist/execution/workflow/journal.d.ts +24 -4
- package/dist/execution/workflow/journal.d.ts.map +1 -1
- package/dist/execution/workflow/journal.js +131 -9
- package/dist/execution/workflow/journal.js.map +1 -1
- package/dist/execution/workflow/prelude.d.ts +2 -0
- package/dist/execution/workflow/prelude.d.ts.map +1 -1
- package/dist/execution/workflow/prelude.js +49 -8
- package/dist/execution/workflow/prelude.js.map +1 -1
- package/dist/execution/workflow/resume.d.ts +59 -0
- package/dist/execution/workflow/resume.d.ts.map +1 -0
- package/dist/execution/workflow/resume.js +273 -0
- package/dist/execution/workflow/resume.js.map +1 -0
- package/dist/execution/workflow/runtime.d.ts.map +1 -1
- package/dist/execution/workflow/runtime.js +112 -8
- package/dist/execution/workflow/runtime.js.map +1 -1
- package/dist/execution/workflow/types.d.ts +105 -1
- package/dist/execution/workflow/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +1 -0
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/plugin/compat.d.ts +24 -0
- package/dist/plugin/compat.d.ts.map +1 -0
- package/dist/plugin/compat.js +149 -0
- package/dist/plugin/compat.js.map +1 -0
- package/dist/plugin/loader.d.ts +2 -1
- package/dist/plugin/loader.d.ts.map +1 -1
- package/dist/plugin/loader.js +6 -0
- package/dist/plugin/loader.js.map +1 -1
- package/dist/pricing/index.d.ts.map +1 -1
- package/dist/pricing/index.js +2 -1
- package/dist/pricing/index.js.map +1 -1
- package/dist/process/kill-tree.d.ts +44 -0
- package/dist/process/kill-tree.d.ts.map +1 -1
- package/dist/process/kill-tree.js +102 -4
- package/dist/process/kill-tree.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +9 -1
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/collect-chat-completion.d.ts.map +1 -1
- package/dist/provider/collect-chat-completion.js +4 -0
- package/dist/provider/collect-chat-completion.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +11 -0
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/mock.d.ts +2 -0
- package/dist/provider/mock.d.ts.map +1 -1
- package/dist/provider/mock.js +16 -8
- package/dist/provider/mock.js.map +1 -1
- package/dist/provider/model-window-suffix.d.ts +18 -0
- package/dist/provider/model-window-suffix.d.ts.map +1 -0
- package/dist/provider/model-window-suffix.js +78 -0
- package/dist/provider/model-window-suffix.js.map +1 -0
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +5 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +5 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +5 -4
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +1 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/registry/command/kernel-commands.d.ts +1 -4
- package/dist/registry/command/kernel-commands.d.ts.map +1 -1
- package/dist/registry/command/kernel-commands.js +0 -28
- package/dist/registry/command/kernel-commands.js.map +1 -1
- package/dist/runtime/jobs/awaited-jobs.d.ts +6 -0
- package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -1
- package/dist/runtime/jobs/awaited-jobs.js +11 -0
- package/dist/runtime/jobs/awaited-jobs.js.map +1 -1
- package/dist/runtime/jobs/host-exit-finalizer.d.ts +20 -0
- package/dist/runtime/jobs/host-exit-finalizer.d.ts.map +1 -0
- package/dist/runtime/jobs/host-exit-finalizer.js +57 -0
- package/dist/runtime/jobs/host-exit-finalizer.js.map +1 -0
- package/dist/runtime/jobs/liveness.d.ts +67 -0
- package/dist/runtime/jobs/liveness.d.ts.map +1 -0
- package/dist/runtime/jobs/liveness.js +94 -0
- package/dist/runtime/jobs/liveness.js.map +1 -0
- package/dist/runtime/jobs/registry.d.ts +21 -0
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +104 -2
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/jobs/wake-budget.d.ts +35 -0
- package/dist/runtime/jobs/wake-budget.d.ts.map +1 -0
- package/dist/runtime/jobs/wake-budget.js +48 -0
- package/dist/runtime/jobs/wake-budget.js.map +1 -0
- package/dist/runtime/query/detached-calls.d.ts +65 -0
- package/dist/runtime/query/detached-calls.d.ts.map +1 -0
- package/dist/runtime/query/detached-calls.js +116 -0
- package/dist/runtime/query/detached-calls.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +4 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +90 -6
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +34 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +32 -5
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/effort-clamp.d.ts +13 -0
- package/dist/runtime/query/iteration/effort-clamp.d.ts.map +1 -0
- package/dist/runtime/query/iteration/effort-clamp.js +42 -0
- package/dist/runtime/query/iteration/effort-clamp.js.map +1 -0
- package/dist/runtime/query/iteration/index.d.ts +34 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +199 -6
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/outstanding-work.d.ts.map +1 -1
- package/dist/runtime/query/iteration/outstanding-work.js +11 -3
- package/dist/runtime/query/iteration/outstanding-work.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +63 -1
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +6 -0
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/session-storage.d.ts.map +1 -1
- package/dist/runtime/query/session-storage.js +8 -3
- package/dist/runtime/query/session-storage.js.map +1 -1
- package/dist/runtime/query/steering.d.ts +29 -3
- package/dist/runtime/query/steering.d.ts.map +1 -1
- package/dist/runtime/query/steering.js +58 -3
- package/dist/runtime/query/steering.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +1 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/runtime/system-events.d.ts +5 -0
- package/dist/runtime/system-events.d.ts.map +1 -1
- package/dist/runtime/system-events.js +7 -1
- package/dist/runtime/system-events.js.map +1 -1
- package/dist/sandbox/denial.d.ts +11 -0
- package/dist/sandbox/denial.d.ts.map +1 -0
- package/dist/sandbox/denial.js +61 -0
- package/dist/sandbox/denial.js.map +1 -0
- package/dist/sandbox/file-walk-program.d.ts.map +1 -1
- package/dist/sandbox/file-walk-program.js +31 -13
- package/dist/sandbox/file-walk-program.js.map +1 -1
- package/dist/sandbox/file-walk.d.ts +2 -0
- package/dist/sandbox/file-walk.d.ts.map +1 -1
- package/dist/sandbox/file-walk.js +64 -13
- package/dist/sandbox/file-walk.js.map +1 -1
- package/dist/sandbox/ignore-matcher.d.ts +30 -0
- package/dist/sandbox/ignore-matcher.d.ts.map +1 -0
- package/dist/sandbox/ignore-matcher.js +11 -0
- package/dist/sandbox/ignore-matcher.js.map +1 -0
- package/dist/sandbox/ignore-source.d.ts +26 -0
- package/dist/sandbox/ignore-source.d.ts.map +1 -0
- package/dist/sandbox/ignore-source.js +221 -0
- package/dist/sandbox/ignore-source.js.map +1 -0
- package/dist/sandbox/provider/local.d.ts.map +1 -1
- package/dist/sandbox/provider/local.js +11 -1
- package/dist/sandbox/provider/local.js.map +1 -1
- package/dist/scheduler/completion-inbox.d.ts.map +1 -1
- package/dist/scheduler/completion-inbox.js +1 -0
- package/dist/scheduler/completion-inbox.js.map +1 -1
- package/dist/session/log-hash.d.ts.map +1 -1
- package/dist/session/log-hash.js +18 -1
- package/dist/session/log-hash.js.map +1 -1
- package/dist/store/memory/disk.d.ts.map +1 -1
- package/dist/store/memory/disk.js +1 -1
- package/dist/store/memory/disk.js.map +1 -1
- package/dist/store/memory/markdown.d.ts.map +1 -1
- package/dist/store/memory/markdown.js +1 -1
- package/dist/store/memory/markdown.js.map +1 -1
- package/dist/store/memory/operation-lock.d.ts +20 -4
- package/dist/store/memory/operation-lock.d.ts.map +1 -1
- package/dist/store/memory/operation-lock.js +235 -58
- package/dist/store/memory/operation-lock.js.map +1 -1
- package/dist/store/session-index/sqlite.d.ts.map +1 -1
- package/dist/store/session-index/sqlite.js +169 -16
- package/dist/store/session-index/sqlite.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +10 -4
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +35 -0
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +512 -67
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/tools/builtins/glob.d.ts +1 -0
- package/dist/tools/builtins/glob.d.ts.map +1 -1
- package/dist/tools/builtins/glob.js +65 -28
- package/dist/tools/builtins/glob.js.map +1 -1
- package/dist/tools/builtins/grep.d.ts +2 -0
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +94 -40
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/job.d.ts +1 -1
- package/dist/tools/builtins/job.d.ts.map +1 -1
- package/dist/tools/builtins/job.js +34 -7
- package/dist/tools/builtins/job.js.map +1 -1
- package/dist/tools/builtins/search-support.d.ts +44 -0
- package/dist/tools/builtins/search-support.d.ts.map +1 -0
- package/dist/tools/builtins/search-support.js +89 -0
- package/dist/tools/builtins/search-support.js.map +1 -0
- package/dist/tools/builtins/wait-for-job-bounds.d.ts +2 -0
- package/dist/tools/builtins/wait-for-job-bounds.d.ts.map +1 -1
- package/dist/tools/builtins/wait-for-job-bounds.js +3 -1
- package/dist/tools/builtins/wait-for-job-bounds.js.map +1 -1
- package/dist/tools/builtins/wait-for-job.d.ts +1 -1
- package/dist/tools/builtins/wait-for-job.d.ts.map +1 -1
- package/dist/tools/builtins/wait-for-job.js +13 -2
- package/dist/tools/builtins/wait-for-job.js.map +1 -1
- package/dist/tools/command-shell.d.ts.map +1 -1
- package/dist/tools/command-shell.js +14 -5
- package/dist/tools/command-shell.js.map +1 -1
- package/dist/turn/reporter.d.ts.map +1 -1
- package/dist/turn/reporter.js +11 -0
- package/dist/turn/reporter.js.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/computer-use/index.d.ts +129 -13
- package/dist/types/computer-use/index.d.ts.map +1 -1
- package/dist/types/computer-use/index.js.map +1 -1
- package/dist/types/message/index.d.ts +1 -1
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +1 -0
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/plugin/index.d.ts +24 -0
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +4 -0
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/provider/chat.d.ts +19 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/types/provider/config.d.ts +7 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/index.d.ts +1 -1
- package/dist/types/provider/index.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +15 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/provider/stream.d.ts +6 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/sandbox/index.d.ts +38 -1
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/session/events.d.ts +34 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/session/records.d.ts +298 -34
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +35 -1
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/session/stop-reason.d.ts +11 -0
- package/dist/types/session/stop-reason.d.ts.map +1 -1
- package/dist/types/tool/index.d.ts +30 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +8 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +1 -0
- package/src/bridge/a2a/mapper.ts +19 -0
- package/src/bridge/acp/update.ts +6 -1
- package/src/bridge/sse/mapper.ts +13 -0
- package/src/compaction/context-window.ts +163 -2
- package/src/constants/continuation.ts +13 -0
- package/src/contracts/session/api.ts +6 -0
- package/src/execution/workflow/index.ts +14 -1
- package/src/execution/workflow/journal.ts +161 -11
- package/src/execution/workflow/prelude.ts +51 -8
- package/src/execution/workflow/resume.ts +313 -0
- package/src/execution/workflow/runtime.ts +127 -9
- package/src/execution/workflow/types.ts +109 -1
- package/src/index.ts +17 -0
- package/src/manager/session/turn-recorder.ts +1 -0
- package/src/plugin/compat.ts +173 -0
- package/src/plugin/loader.ts +7 -1
- package/src/pricing/index.ts +2 -1
- package/src/process/kill-tree.ts +106 -4
- package/src/prompt/coding-agent-doctrine.ts +9 -1
- package/src/provider/collect-chat-completion.ts +3 -0
- package/src/provider/fallback.ts +17 -0
- package/src/provider/idle-timeout.ts +9 -0
- package/src/provider/mock.ts +19 -9
- package/src/provider/model-window-suffix.ts +85 -0
- package/src/provider/retry.ts +9 -0
- package/src/public-runtime.ts +21 -2
- package/src/public-types.ts +1 -0
- package/src/registry/command/kernel-commands.ts +1 -32
- package/src/runtime/jobs/awaited-jobs.ts +11 -0
- package/src/runtime/jobs/host-exit-finalizer.ts +67 -0
- package/src/runtime/jobs/liveness.ts +144 -0
- package/src/runtime/jobs/registry.ts +114 -2
- package/src/runtime/jobs/wake-budget.ts +55 -0
- package/src/runtime/query/detached-calls.ts +135 -0
- package/src/runtime/query/executor.ts +92 -10
- package/src/runtime/query/index.ts +65 -7
- package/src/runtime/query/iteration/effort-clamp.ts +46 -0
- package/src/runtime/query/iteration/index.ts +260 -6
- package/src/runtime/query/iteration/outstanding-work.ts +12 -3
- package/src/runtime/query/iteration/phases/context.ts +9 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +73 -8
- package/src/runtime/query/iteration/stream-turn.ts +5 -0
- package/src/runtime/query/session-storage.ts +8 -3
- package/src/runtime/query/steering.ts +77 -6
- package/src/runtime/query/tooling.ts +2 -0
- package/src/runtime/system-events.ts +12 -1
- package/src/sandbox/denial.ts +79 -0
- package/src/sandbox/file-walk-program.ts +32 -13
- package/src/sandbox/file-walk.ts +75 -12
- package/src/sandbox/ignore-matcher.ts +46 -0
- package/src/sandbox/ignore-source.ts +220 -0
- package/src/sandbox/provider/local.ts +11 -1
- package/src/scheduler/completion-inbox.ts +1 -0
- package/src/session/log-hash.ts +21 -0
- package/src/store/memory/disk.ts +1 -0
- package/src/store/memory/markdown.ts +1 -0
- package/src/store/memory/operation-lock.ts +288 -57
- package/src/store/session-index/sqlite.ts +171 -16
- package/src/tools/builtins/bash.ts +11 -4
- package/src/tools/builtins/computer-use.ts +593 -68
- package/src/tools/builtins/glob.ts +79 -32
- package/src/tools/builtins/grep.ts +107 -43
- package/src/tools/builtins/job.ts +42 -12
- package/src/tools/builtins/search-support.ts +107 -0
- package/src/tools/builtins/wait-for-job-bounds.ts +5 -1
- package/src/tools/builtins/wait-for-job.ts +14 -2
- package/src/tools/command-shell.ts +13 -5
- package/src/turn/reporter.ts +12 -0
- package/src/types/agent/query.ts +2 -0
- package/src/types/computer-use/index.ts +157 -15
- package/src/types/message/index.ts +5 -3
- package/src/types/plugin/index.ts +8 -0
- package/src/types/provider/chat.ts +20 -0
- package/src/types/provider/config.ts +7 -0
- package/src/types/provider/index.ts +1 -0
- package/src/types/provider/interface.ts +20 -0
- package/src/types/provider/stream.ts +6 -0
- package/src/types/sandbox/index.ts +40 -1
- package/src/types/session/events.ts +65 -5
- package/src/types/session/records.ts +39 -2
- package/src/types/session/stop-reason.ts +11 -0
- package/src/types/tool/index.ts +30 -0
- package/src/types/tool/presentation.ts +8 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { lstat, open, readFile, unlink } from 'node:fs/promises';
|
|
3
|
+
import { hostname as osHostname } from 'node:os';
|
|
3
4
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
5
|
import { NamzuError } from '../../types/errors/index.js';
|
|
5
|
-
/** Wait bound, not a lease: a slow
|
|
6
|
+
/** Wait bound, not a lease: a slow owner that is still running is never replaced. */
|
|
6
7
|
export const DEFAULT_MEMORY_LOCK_TIMEOUT_MS = 10_000;
|
|
7
8
|
export function validateMemoryLockTimeout(timeoutMs) {
|
|
8
9
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
|
|
@@ -13,82 +14,258 @@ export function validateMemoryLockTimeout(timeoutMs) {
|
|
|
13
14
|
});
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* An empty or unparsable lock file is only believed abandoned once it is older
|
|
19
|
+
* than this. A live acquirer writes its record immediately after creating the
|
|
20
|
+
* file, so a gap of seconds means the writer died in between (disk full, kill).
|
|
21
|
+
*/
|
|
22
|
+
export const MEMORY_LOCK_RECORD_GRACE_MS = 5_000;
|
|
23
|
+
/**
|
|
24
|
+
* Residual risk, deliberately accepted: a pid that is alive is treated as the
|
|
25
|
+
* owner even when it is an unrelated process that reused the number after the
|
|
26
|
+
* real owner died. That cannot be disproved from here, so such a lock stays and
|
|
27
|
+
* the caller gets the plain "another process is using it" error. It is not
|
|
28
|
+
* bounded by `acquiredAt` age: a lock's age is not a lease.
|
|
29
|
+
*/
|
|
30
|
+
function defaultIsPidAlive(pid) {
|
|
31
|
+
try {
|
|
32
|
+
process.kill(pid, 0);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return error.code !== 'ESRCH';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function lockError(path, message, timeoutMs) {
|
|
17
40
|
return new NamzuError({
|
|
18
41
|
code: 'storage_error',
|
|
19
|
-
message
|
|
42
|
+
message,
|
|
20
43
|
details: { lockPath: path, timeoutMs },
|
|
21
44
|
retryable: false,
|
|
22
45
|
});
|
|
23
46
|
}
|
|
47
|
+
function busyError(path, pid, timeoutMs) {
|
|
48
|
+
const who = pid === undefined ? 'Another Namzu process is' : `Another Namzu process (pid ${pid}) is`;
|
|
49
|
+
return lockError(path, `${who} using the stored memory. Close it, or wait for it to finish.`, timeoutMs);
|
|
50
|
+
}
|
|
51
|
+
function parseOwner(content) {
|
|
52
|
+
try {
|
|
53
|
+
const value = JSON.parse(content);
|
|
54
|
+
if (!value || typeof value !== 'object')
|
|
55
|
+
return undefined;
|
|
56
|
+
const pid = value.pid;
|
|
57
|
+
if (typeof pid !== 'number' || !Number.isSafeInteger(pid) || pid <= 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
const host = value.hostname;
|
|
60
|
+
return {
|
|
61
|
+
pid,
|
|
62
|
+
token: String(value.token ?? ''),
|
|
63
|
+
acquiredAt: Number(value.acquiredAt ?? 0),
|
|
64
|
+
...(typeof host === 'string' && host.length > 0 ? { hostname: host } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Reads the file at `path`; undefined when it is gone or was swapped while reading. */
|
|
72
|
+
async function inspect(path, env) {
|
|
73
|
+
const before = await lstat(path).catch((error) => {
|
|
74
|
+
if (error.code === 'ENOENT')
|
|
75
|
+
return undefined;
|
|
76
|
+
throw error;
|
|
77
|
+
});
|
|
78
|
+
if (!before)
|
|
79
|
+
return undefined;
|
|
80
|
+
if (!before.isFile() || before.isSymbolicLink())
|
|
81
|
+
return { stats: before, content: '', pid: undefined, staleReason: undefined };
|
|
82
|
+
const content = await readFile(path, 'utf8').catch((error) => {
|
|
83
|
+
if (error.code === 'ENOENT')
|
|
84
|
+
return undefined;
|
|
85
|
+
throw error;
|
|
86
|
+
});
|
|
87
|
+
if (content === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
const after = await lstat(path).catch(() => undefined);
|
|
90
|
+
if (!after || after.dev !== before.dev || after.ino !== before.ino)
|
|
91
|
+
return undefined;
|
|
92
|
+
const owner = parseOwner(content);
|
|
93
|
+
if (owner) {
|
|
94
|
+
// A record from another host on a shared filesystem cannot be checked from
|
|
95
|
+
// here; it is live. A record without a host predates the field: local.
|
|
96
|
+
if (owner.hostname !== undefined && owner.hostname !== env.hostname) {
|
|
97
|
+
return { stats: before, content, pid: owner.pid, staleReason: undefined };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
stats: before,
|
|
101
|
+
content,
|
|
102
|
+
pid: owner.pid,
|
|
103
|
+
staleReason: env.isPidAlive(owner.pid) ? undefined : `owner process ${owner.pid} is gone`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const age = env.now() - before.mtimeMs;
|
|
107
|
+
return {
|
|
108
|
+
stats: before,
|
|
109
|
+
content,
|
|
110
|
+
pid: undefined,
|
|
111
|
+
staleReason: age > MEMORY_LOCK_RECORD_GRACE_MS
|
|
112
|
+
? `lock holds no readable owner record and is ${Math.round(age / 1000)}s old`
|
|
113
|
+
: undefined,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Creates `path` exclusively and writes this process's owner record. Returns
|
|
118
|
+
* undefined when the path is occupied. A failed write (a full disk) leaves no
|
|
119
|
+
* file behind.
|
|
120
|
+
*/
|
|
121
|
+
async function createOwned(path, env) {
|
|
122
|
+
let handle;
|
|
123
|
+
try {
|
|
124
|
+
handle = await open(path, 'wx', 0o600);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error.code === 'EEXIST')
|
|
128
|
+
return undefined;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
const owner = `${JSON.stringify({
|
|
132
|
+
pid: process.pid,
|
|
133
|
+
token: randomUUID(),
|
|
134
|
+
acquiredAt: Date.now(),
|
|
135
|
+
hostname: env.hostname,
|
|
136
|
+
})}\n`;
|
|
137
|
+
let identity;
|
|
138
|
+
try {
|
|
139
|
+
identity = await handle.stat();
|
|
140
|
+
await handle.writeFile(owner, 'utf8');
|
|
141
|
+
await handle.sync();
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
await handle.close().catch(() => undefined);
|
|
145
|
+
// We created this path exclusively a moment ago; drop it so no empty or
|
|
146
|
+
// partial record is left to block the next start.
|
|
147
|
+
const current = await lstat(path).catch(() => undefined);
|
|
148
|
+
if (!identity || (current?.dev === identity.dev && current.ino === identity.ino)) {
|
|
149
|
+
await unlink(path).catch(() => undefined);
|
|
150
|
+
}
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
await handle.close();
|
|
154
|
+
return { identity, owner };
|
|
155
|
+
}
|
|
156
|
+
async function releaseOwned(path, identity, owner, timeoutMs) {
|
|
157
|
+
const current = await lstat(path);
|
|
158
|
+
if (!current.isFile() ||
|
|
159
|
+
current.isSymbolicLink() ||
|
|
160
|
+
current.dev !== identity.dev ||
|
|
161
|
+
current.ino !== identity.ino ||
|
|
162
|
+
(await readFile(path, 'utf8')) !== owner) {
|
|
163
|
+
throw lockError(path, `Memory operation lock ${path}: the lock owner changed before release.`, timeoutMs);
|
|
164
|
+
}
|
|
165
|
+
await unlink(path);
|
|
166
|
+
}
|
|
167
|
+
function sameFile(a, b) {
|
|
168
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
169
|
+
}
|
|
170
|
+
/** Unlinks `path` only if it is still exactly the stale file that was inspected. */
|
|
171
|
+
async function unlinkIfStillStale(path, seen, env) {
|
|
172
|
+
const again = await inspect(path, env);
|
|
173
|
+
if (!again || !sameFile(again.stats, seen.stats) || again.content !== seen.content)
|
|
174
|
+
return false;
|
|
175
|
+
if (again.staleReason === undefined)
|
|
176
|
+
return false;
|
|
177
|
+
// Narrow the window further: the inode must still be the inspected one now.
|
|
178
|
+
const last = await lstat(path).catch(() => undefined);
|
|
179
|
+
if (!last || !sameFile(last, seen.stats))
|
|
180
|
+
return false;
|
|
181
|
+
await unlink(path).catch((error) => {
|
|
182
|
+
if (error.code !== 'ENOENT')
|
|
183
|
+
throw error;
|
|
184
|
+
});
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Removes a lock whose owner is provably gone. Two processes that see the same
|
|
189
|
+
* stale lock are serialised through `<lock>.break`; the lock is re-inspected
|
|
190
|
+
* under it and only the inspected inode is unlinked. Returns whether the lock
|
|
191
|
+
* was removed.
|
|
192
|
+
*/
|
|
193
|
+
async function breakStaleLock(path, seen, timeoutMs, env) {
|
|
194
|
+
const breakPath = `${path}.break`;
|
|
195
|
+
let held;
|
|
196
|
+
for (let attempt = 0; attempt < 2 && !held; attempt++) {
|
|
197
|
+
held = await createOwned(breakPath, env);
|
|
198
|
+
if (held)
|
|
199
|
+
break;
|
|
200
|
+
const breaker = await inspect(breakPath, env);
|
|
201
|
+
if (!breaker)
|
|
202
|
+
continue;
|
|
203
|
+
// A dead breaker's file follows the same rule, once.
|
|
204
|
+
if (breaker.staleReason === undefined || !(await unlinkIfStillStale(breakPath, breaker, env))) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (!held)
|
|
209
|
+
return false;
|
|
210
|
+
try {
|
|
211
|
+
const removed = await unlinkIfStillStale(path, seen, env);
|
|
212
|
+
if (removed) {
|
|
213
|
+
env.logger?.info('Broke an abandoned memory operation lock', {
|
|
214
|
+
'namzu.store.lock_path': path,
|
|
215
|
+
'namzu.store.lock_reason': seen.staleReason,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return removed;
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
await releaseOwned(breakPath, held.identity, held.owner, timeoutMs).catch(() => undefined);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
24
224
|
/**
|
|
25
225
|
* Local-filesystem exclusion for cooperating processes. Atomic rename of the
|
|
26
226
|
* index alone cannot serialize the index/content read-modify-write transaction.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
227
|
+
* A lock whose owner is provably gone (dead pid on this host, or an owner-less
|
|
228
|
+
* file older than the grace period) is broken; a live or unverifiable owner is
|
|
229
|
+
* waited for until the timeout, never replaced on a guessed lease.
|
|
29
230
|
*/
|
|
30
|
-
export async function acquireMemoryOperationLock(path, timeoutMs) {
|
|
231
|
+
export async function acquireMemoryOperationLock(path, timeoutMs, options = {}) {
|
|
232
|
+
const env = {
|
|
233
|
+
logger: options.logger,
|
|
234
|
+
now: options.now ?? Date.now,
|
|
235
|
+
isPidAlive: options.isPidAlive ?? defaultIsPidAlive,
|
|
236
|
+
hostname: options.hostname ?? osHostname(),
|
|
237
|
+
};
|
|
31
238
|
const deadline = performance.now() + timeoutMs;
|
|
32
239
|
let attempted = false;
|
|
240
|
+
let lastPid;
|
|
241
|
+
// The wait bound limits waiting for a live owner, not the attempt that
|
|
242
|
+
// follows a successful break: the lock is free then, and breaking it can
|
|
243
|
+
// itself outlast a short bound on a slow disk.
|
|
244
|
+
let justBroke = false;
|
|
33
245
|
for (;;) {
|
|
34
|
-
if (attempted && performance.now() >= deadline) {
|
|
35
|
-
throw
|
|
246
|
+
if (attempted && !justBroke && performance.now() >= deadline) {
|
|
247
|
+
throw busyError(path, lastPid, timeoutMs);
|
|
36
248
|
}
|
|
249
|
+
justBroke = false;
|
|
37
250
|
attempted = true;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
251
|
+
const created = await createOwned(path, env);
|
|
252
|
+
if (created) {
|
|
253
|
+
return () => releaseOwned(path, created.identity, created.owner, timeoutMs);
|
|
41
254
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
throw error;
|
|
45
|
-
const existing = await lstat(path).catch((statError) => {
|
|
46
|
-
if (statError.code === 'ENOENT')
|
|
47
|
-
return undefined;
|
|
48
|
-
throw statError;
|
|
49
|
-
});
|
|
50
|
-
if (!existing)
|
|
51
|
-
continue;
|
|
52
|
-
if (!existing.isFile() || existing.isSymbolicLink()) {
|
|
53
|
-
throw lockError(path, 'the occupied lock is not a regular file', timeoutMs);
|
|
54
|
-
}
|
|
55
|
-
if (performance.now() >= deadline) {
|
|
56
|
-
throw lockError(path, `acquisition timed out after ${timeoutMs} ms`, timeoutMs);
|
|
57
|
-
}
|
|
58
|
-
await delay(Math.min(25, Math.max(1, deadline - performance.now())));
|
|
255
|
+
const seen = await inspect(path, env);
|
|
256
|
+
if (!seen)
|
|
59
257
|
continue;
|
|
258
|
+
if (!seen.stats.isFile() || seen.stats.isSymbolicLink()) {
|
|
259
|
+
throw lockError(path, `The stored memory lock ${path} is not a regular file, so Namzu will not use it. Move it aside to continue.`, timeoutMs);
|
|
60
260
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Leave the path for explicit recovery rather than unlink an unknown inode.
|
|
66
|
-
throw error;
|
|
67
|
-
});
|
|
68
|
-
try {
|
|
69
|
-
await handle.writeFile(owner, 'utf8');
|
|
70
|
-
await handle.sync();
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
await handle.close();
|
|
74
|
-
const current = await lstat(path).catch(() => undefined);
|
|
75
|
-
if (current?.dev === identity.dev && current.ino === identity.ino) {
|
|
76
|
-
await unlink(path).catch(() => undefined);
|
|
77
|
-
}
|
|
78
|
-
throw error;
|
|
261
|
+
lastPid = seen.pid;
|
|
262
|
+
if (seen.staleReason !== undefined && (await breakStaleLock(path, seen, timeoutMs, env))) {
|
|
263
|
+
justBroke = true;
|
|
264
|
+
continue;
|
|
79
265
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!current.isFile() ||
|
|
84
|
-
current.isSymbolicLink() ||
|
|
85
|
-
current.dev !== identity.dev ||
|
|
86
|
-
current.ino !== identity.ino ||
|
|
87
|
-
(await readFile(path, 'utf8')) !== owner) {
|
|
88
|
-
throw lockError(path, 'the lock owner changed before release', timeoutMs);
|
|
89
|
-
}
|
|
90
|
-
await unlink(path);
|
|
91
|
-
};
|
|
266
|
+
if (performance.now() >= deadline)
|
|
267
|
+
throw busyError(path, lastPid, timeoutMs);
|
|
268
|
+
await delay(Math.min(25, Math.max(1, deadline - performance.now())));
|
|
92
269
|
}
|
|
93
270
|
}
|
|
94
271
|
//# sourceMappingURL=operation-lock.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation-lock.js","sourceRoot":"","sources":["../../../src/store/memory/operation-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"operation-lock.js","sourceRoot":"","sources":["../../../src/store/memory/operation-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,MAAM,SAAS,CAAA;AAChD,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAGxD,qFAAqF;AACrF,MAAM,CAAC,MAAM,8BAA8B,GAAG,MAAM,CAAA;AAEpD,MAAM,UAAU,yBAAyB,CAAC,SAAiB;IAC1D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,UAAU,CAAC;YACpB,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,gEAAgE;YACzE,SAAS,EAAE,KAAK;SAChB,CAAC,CAAA;IACH,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,CAAA;AAiChD;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,GAAW;IACrC,IAAI,CAAC;QACJ,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACpB,OAAO,IAAI,CAAA;IACZ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAQ,KAA+B,CAAC,IAAI,KAAK,OAAO,CAAA;IACzD,CAAC;AACF,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,OAAe,EAAE,SAAiB;IAClE,OAAO,IAAI,UAAU,CAAC;QACrB,IAAI,EAAE,eAAe;QACrB,OAAO;QACP,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE;QACtC,SAAS,EAAE,KAAK;KAChB,CAAC,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,GAAuB,EAAE,SAAiB;IAC1E,MAAM,GAAG,GACR,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,8BAA8B,GAAG,MAAM,CAAA;IACzF,OAAO,SAAS,CACf,IAAI,EACJ,GAAG,GAAG,+DAA+D,EACrE,SAAS,CACT,CAAA;AACF,CAAC;AAED,SAAS,UAAU,CAAC,OAAe;IAClC,IAAI,CAAC;QACJ,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC1C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QACzD,MAAM,GAAG,GAAI,KAA2B,CAAC,GAAG,CAAA;QAC5C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,SAAS,CAAA;QACvF,MAAM,IAAI,GAAI,KAAgC,CAAC,QAAQ,CAAA;QACvD,OAAO;YACN,GAAG;YACH,KAAK,EAAE,MAAM,CAAE,KAA6B,CAAC,KAAK,IAAI,EAAE,CAAC;YACzD,UAAU,EAAE,MAAM,CAAE,KAAkC,CAAC,UAAU,IAAI,CAAC,CAAC;YACvE,GAAG,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1E,CAAA;IACF,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAA;IACjB,CAAC;AACF,CAAC;AAED,wFAAwF;AACxF,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,GAAa;IACjD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAA4B,EAAE,EAAE;QACvE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QAC7C,MAAM,KAAK,CAAA;IACZ,CAAC,CAAC,CAAA;IACF,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,cAAc,EAAE;QAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,CAAA;IAC9E,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAA4B,EAAE,EAAE;QACnF,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QAC7C,MAAM,KAAK,CAAA;IACZ,CAAC,CAAC,CAAA;IACF,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC3C,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IACtD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAEpF,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;IACjC,IAAI,KAAK,EAAE,CAAC;QACX,2EAA2E;QAC3E,uEAAuE;QACvE,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC;YACrE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,CAAA;QAC1E,CAAC;QACD,OAAO;YACN,KAAK,EAAE,MAAM;YACb,OAAO;YACP,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,WAAW,EAAE,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,GAAG,UAAU;SACzF,CAAA;IACF,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,OAAO,CAAA;IACtC,OAAO;QACN,KAAK,EAAE,MAAM;QACb,OAAO;QACP,GAAG,EAAE,SAAS;QACd,WAAW,EACV,GAAG,GAAG,2BAA2B;YAChC,CAAC,CAAC,8CAA8C,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO;YAC7E,CAAC,CAAC,SAAS;KACb,CAAA;AACF,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,WAAW,CACzB,IAAY,EACZ,GAAa;IAEb,IAAI,MAAwC,CAAA;IAC5C,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QACxE,MAAM,KAAK,CAAA;IACZ,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,KAAK,EAAE,UAAU,EAAE;QACnB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;QACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACtB,CAAC,IAAI,CAAA;IACN,IAAI,QAA2B,CAAA;IAC/B,IAAI,CAAC;QACJ,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;QAC9B,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QACrC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAC3C,wEAAwE;QACxE,kDAAkD;QAClD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QACxD,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,QAAQ,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAClF,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAC1C,CAAC;QACD,MAAM,KAAK,CAAA;IACZ,CAAC;IACD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;IACpB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;AAC3B,CAAC;AAED,KAAK,UAAU,YAAY,CAC1B,IAAY,EACZ,QAAe,EACf,KAAa,EACb,SAAiB;IAEjB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAA;IACjC,IACC,CAAC,OAAO,CAAC,MAAM,EAAE;QACjB,OAAO,CAAC,cAAc,EAAE;QACxB,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,GAAG;QAC5B,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,GAAG;QAC5B,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,KAAK,EACvC,CAAC;QACF,MAAM,SAAS,CACd,IAAI,EACJ,yBAAyB,IAAI,0CAA0C,EACvE,SAAS,CACT,CAAA;IACF,CAAC;IACD,MAAM,MAAM,CAAC,IAAI,CAAC,CAAA;AACnB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAQ,EAAE,CAAQ;IACnC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA;AAC1C,CAAC;AAED,oFAAoF;AACpF,KAAK,UAAU,kBAAkB,CAAC,IAAY,EAAE,IAAgB,EAAE,GAAa;IAC9E,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACtC,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAChG,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACjD,4EAA4E;IAC5E,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IACrD,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACtD,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAA4B,EAAE,EAAE;QACzD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAA;IACzC,CAAC,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACZ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,cAAc,CAC5B,IAAY,EACZ,IAAgB,EAChB,SAAiB,EACjB,GAAa;IAEb,MAAM,SAAS,GAAG,GAAG,IAAI,QAAQ,CAAA;IACjC,IAAI,IAAoD,CAAA;IACxD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,GAAG,MAAM,WAAW,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;QACxC,IAAI,IAAI;YAAE,MAAK;QACf,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;QAC7C,IAAI,CAAC,OAAO;YAAE,SAAQ;QACtB,qDAAqD;QACrD,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,kBAAkB,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YAC/F,OAAO,KAAK,CAAA;QACb,CAAC;IACF,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IACvB,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAA;QACzD,IAAI,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,0CAA0C,EAAE;gBAC5D,uBAAuB,EAAE,IAAI;gBAC7B,yBAAyB,EAAE,IAAI,CAAC,WAAW;aAC3C,CAAC,CAAA;QACH,CAAC;QACD,OAAO,OAAO,CAAA;IACf,CAAC;YAAS,CAAC;QACV,MAAM,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAC3F,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC/C,IAAY,EACZ,SAAiB,EACjB,UAAsC,EAAE;IAExC,MAAM,GAAG,GAAa;QACrB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG;QAC5B,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,iBAAiB;QACnD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,UAAU,EAAE;KAC1C,CAAA;IACD,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAA;IAC9C,IAAI,SAAS,GAAG,KAAK,CAAA;IACrB,IAAI,OAA2B,CAAA;IAC/B,uEAAuE;IACvE,yEAAyE;IACzE,+CAA+C;IAC/C,IAAI,SAAS,GAAG,KAAK,CAAA;IACrB,SAAS,CAAC;QACT,IAAI,SAAS,IAAI,CAAC,SAAS,IAAI,WAAW,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC9D,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAA;QAC1C,CAAC;QACD,SAAS,GAAG,KAAK,CAAA;QACjB,SAAS,GAAG,IAAI,CAAA;QAChB,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAC5C,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QAC5E,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QACrC,IAAI,CAAC,IAAI;YAAE,SAAQ;QACnB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;YACzD,MAAM,SAAS,CACd,IAAI,EACJ,0BAA0B,IAAI,8EAA8E,EAC5G,SAAS,CACT,CAAA;QACF,CAAC;QACD,OAAO,GAAG,IAAI,CAAC,GAAG,CAAA;QAClB,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YAC1F,SAAS,GAAG,IAAI,CAAA;YAChB,SAAQ;QACT,CAAC;QACD,IAAI,WAAW,CAAC,GAAG,EAAE,IAAI,QAAQ;YAAE,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAA;QAC5E,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAA;IACrE,CAAC;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../../src/store/session-index/sqlite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../../src/store/session-index/sqlite.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAa,SAAS,EAAU,MAAM,0BAA0B,CAAA;AAI5E,OAAO,KAAK,EACX,mBAAmB,EACnB,WAAW,EACX,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,cAAc,EAEd,WAAW,EAEX,mBAAmB,EACnB,MAAM,YAAY,CAAA;AACnB,OAAO,EAKN,KAAK,SAAS,EACd,gBAAgB,EAEhB,KAAK,UAAU,EAUf,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAoB,eAAe,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAErF,0GAA0G;AAC1G,eAAO,MAAM,qBAAqB,IAAI,CAAA;AAsBtC,0FAA0F;AAC1F,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAwLD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAiB,CAAA;AAoLxD,MAAM,WAAW,yBAAyB;IACzC,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,gCAAiC,SAAQ,yBAAyB;IAClF;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACxC,OAAO,EAAE,gCAAgC,GACvC,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,gBAAgB;;IACvD,QAAQ,CAAC,OAAO,EAAG,QAAQ,CAAS;IAUpC,2FAA2F;IAC3F,OAAO;IA6LP,6CAA6C;WAChC,OAAO,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBjF;;;;OAIG;WACU,IAAI,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0DlF,SAAS,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,CAAC,GAAG,CAAC;IAmCzD,SAAS,CAAC,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS;IAQnE,SAAS,CAAC,iBAAiB,IAAI,SAAS,EAAE;IAIpC,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAe1E,SAAS,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAOvD,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAUlE,oBAAoB,CACzB,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,SAAS,CAAA;KAAO,GACrC,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAW9B,eAAe,CACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAWnC,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAOpE,OAAO,CAAC,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,SAAS,CAAA;KAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAWzE,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA+B5E,KAAK,IAAI,IAAI;CAKb"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
1
2
|
import { mkdir, readdir, rename, rm, stat } from 'node:fs/promises';
|
|
2
3
|
import { createRequire } from 'node:module';
|
|
3
4
|
import { basename, dirname, join } from 'node:path';
|
|
@@ -253,33 +254,129 @@ async function sweepAbandonedRebuilds(path) {
|
|
|
253
254
|
await rm(journal, { force: true });
|
|
254
255
|
}
|
|
255
256
|
}
|
|
257
|
+
/** `device:inode` of the file at `path`, or undefined when there is none. */
|
|
258
|
+
function fileIdentity(path) {
|
|
259
|
+
try {
|
|
260
|
+
const { dev, ino } = statSync(path);
|
|
261
|
+
return `${dev}:${ino}`;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
256
267
|
function userVersion(db) {
|
|
257
268
|
return Number(db.prepare('PRAGMA user_version').get()?.user_version);
|
|
258
269
|
}
|
|
270
|
+
/** SQLite primary result codes for "the image is malformed" and "the file is not a database". */
|
|
271
|
+
const SQLITE_CORRUPT = 11;
|
|
272
|
+
const SQLITE_NOTADB = 26;
|
|
273
|
+
/** Whether `error` says the file's content is not a usable SQLite database. */
|
|
274
|
+
function isNotADatabase(error) {
|
|
275
|
+
const code = error?.errcode;
|
|
276
|
+
return typeof code === 'number' && [SQLITE_CORRUPT, SQLITE_NOTADB].includes(code & 0xff);
|
|
277
|
+
}
|
|
278
|
+
const SQLITE_BUSY = 5;
|
|
279
|
+
const SQLITE_READONLY = 8;
|
|
280
|
+
/** `SQLITE_READONLY_DBMOVED`: the file this connection opened was renamed or unlinked. */
|
|
281
|
+
const SQLITE_READONLY_DBMOVED = 1032;
|
|
282
|
+
function errcode(error) {
|
|
283
|
+
const code = error?.errcode;
|
|
284
|
+
return typeof code === 'number' ? code : undefined;
|
|
285
|
+
}
|
|
286
|
+
/** Whether `error` says another process holds the index locked past the busy timeout. */
|
|
287
|
+
function isBusy(error) {
|
|
288
|
+
const code = errcode(error);
|
|
289
|
+
return code !== undefined && (code & 0xff) === SQLITE_BUSY;
|
|
290
|
+
}
|
|
291
|
+
/** Whether `error` says the file under this connection was replaced by another process. */
|
|
292
|
+
function isMoved(error) {
|
|
293
|
+
const code = errcode(error);
|
|
294
|
+
return code === SQLITE_READONLY_DBMOVED && (code & 0xff) === SQLITE_READONLY;
|
|
295
|
+
}
|
|
296
|
+
/** A plain-language error for an index another process keeps locked. */
|
|
297
|
+
function busyError(path, cause) {
|
|
298
|
+
return new SessionIndexError(`The session index at ${path} is in use by another process. Try again in a moment.`, { cause });
|
|
299
|
+
}
|
|
259
300
|
/**
|
|
260
|
-
* The version of the index file at `path`: -1 when there is none, 0 when
|
|
261
|
-
*
|
|
301
|
+
* The version of the index file at `path`: -1 when there is none, 0 when its
|
|
302
|
+
* content is not a SQLite database, otherwise its `user_version`.
|
|
303
|
+
*
|
|
304
|
+
* The file is opened for writing, never read-only. A writer that died inside
|
|
305
|
+
* a transaction leaves a hot journal beside the file, and only a connection
|
|
306
|
+
* that may write can roll it back; a read-only one fails on it, which used to
|
|
307
|
+
* read as "not a database" and sent the caller to replace a healthy index,
|
|
308
|
+
* leaving the dead writer's journal beside the new file for the next
|
|
309
|
+
* connection to play back into it. Only a SQLite "corrupt" or "not a
|
|
310
|
+
* database" answer means 0; being busy, read-only or unable to read is thrown,
|
|
311
|
+
* never a reason to rebuild.
|
|
262
312
|
*/
|
|
263
313
|
function indexVersion(path) {
|
|
264
|
-
|
|
265
|
-
let db;
|
|
266
|
-
try {
|
|
267
|
-
db = new DatabaseSync(path, { readOnly: true });
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
314
|
+
if (!existsSync(path))
|
|
270
315
|
return -1;
|
|
271
|
-
}
|
|
316
|
+
const { DatabaseSync } = requireSqlite();
|
|
317
|
+
const db = new DatabaseSync(path);
|
|
272
318
|
try {
|
|
273
319
|
db.exec('PRAGMA busy_timeout = 10000');
|
|
274
320
|
return userVersion(db);
|
|
275
321
|
}
|
|
276
|
-
catch {
|
|
277
|
-
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (isNotADatabase(error))
|
|
324
|
+
return 0;
|
|
325
|
+
throw isBusy(error) ? busyError(path, error) : error;
|
|
278
326
|
}
|
|
279
327
|
finally {
|
|
280
328
|
db.close();
|
|
281
329
|
}
|
|
282
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Put the finished `temporary` file in place of the index at `path`.
|
|
333
|
+
*
|
|
334
|
+
* Takes the old index's exclusive lock first. That waits out a peer that is
|
|
335
|
+
* mid-write and rolls back a hot journal left by one that died, so no journal
|
|
336
|
+
* for the old file survives to be played into the new one. A leftover journal
|
|
337
|
+
* of a file that is not a database is removed too: it can describe nothing
|
|
338
|
+
* but that file.
|
|
339
|
+
*/
|
|
340
|
+
async function replaceIndexFile(temporary, path, force) {
|
|
341
|
+
let old;
|
|
342
|
+
if (existsSync(path)) {
|
|
343
|
+
const { DatabaseSync } = requireSqlite();
|
|
344
|
+
old = new DatabaseSync(path);
|
|
345
|
+
try {
|
|
346
|
+
old.exec('PRAGMA busy_timeout = 10000');
|
|
347
|
+
old.exec('BEGIN EXCLUSIVE');
|
|
348
|
+
// A peer may have finished its own rebuild while this one ran. With the
|
|
349
|
+
// lock held, nobody can change the file between this check and the
|
|
350
|
+
// rename, so a current index is left alone rather than replaced under
|
|
351
|
+
// the connections that already adopted it.
|
|
352
|
+
if (!force && userVersion(old) === SESSION_INDEX_VERSION) {
|
|
353
|
+
old.close();
|
|
354
|
+
old = undefined;
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
old?.close();
|
|
360
|
+
old = undefined;
|
|
361
|
+
if (!isNotADatabase(error))
|
|
362
|
+
throw isBusy(error) ? busyError(path, error) : error;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
// Windows will not rename over a file that is open, so the lock is let go
|
|
367
|
+
// first there; elsewhere it is held until the new file is in place.
|
|
368
|
+
if (process.platform === 'win32') {
|
|
369
|
+
old?.close();
|
|
370
|
+
old = undefined;
|
|
371
|
+
}
|
|
372
|
+
await rm(`${path}-journal`, { force: true });
|
|
373
|
+
await rename(temporary, path);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
old?.close();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
283
380
|
/**
|
|
284
381
|
* Build the index from every log under `home` into a temporary file beside
|
|
285
382
|
* it (`index.sqlite.tmp-<pid>-<uuidv7>`), then rename that file over the
|
|
@@ -303,13 +400,19 @@ export function rebuildSqliteSessionIndex(options) {
|
|
|
303
400
|
export class SqliteSessionIndex extends SessionIndexBase {
|
|
304
401
|
backend = 'sqlite';
|
|
305
402
|
#db;
|
|
403
|
+
/** Where `#db` was opened, or undefined for a temporary build file that is never watched. */
|
|
404
|
+
#path;
|
|
405
|
+
/** Identity of the file `#db` has open, to notice another process replacing it. */
|
|
406
|
+
#identity;
|
|
306
407
|
#sink;
|
|
307
408
|
#statements = new Map();
|
|
308
409
|
#open = true;
|
|
309
410
|
/** Wraps an open database whose schema is current. Use {@link SqliteSessionIndex.open}. */
|
|
310
|
-
constructor(db) {
|
|
411
|
+
constructor(db, path) {
|
|
311
412
|
super();
|
|
312
413
|
this.#db = db;
|
|
414
|
+
this.#path = path;
|
|
415
|
+
this.#identity = path === undefined ? undefined : fileIdentity(path);
|
|
313
416
|
const run = (sql, ...values) => {
|
|
314
417
|
this.#statement(sql).run(...values);
|
|
315
418
|
};
|
|
@@ -394,8 +497,7 @@ export class SqliteSessionIndex extends SessionIndexBase {
|
|
|
394
497
|
if (indexVersion(options.path) === SESSION_INDEX_VERSION)
|
|
395
498
|
return false;
|
|
396
499
|
}
|
|
397
|
-
await
|
|
398
|
-
return true;
|
|
500
|
+
return await replaceIndexFile(temporary, options.path, options.force === true);
|
|
399
501
|
}
|
|
400
502
|
finally {
|
|
401
503
|
await rm(temporary, { force: true });
|
|
@@ -428,12 +530,59 @@ export class SqliteSessionIndex extends SessionIndexBase {
|
|
|
428
530
|
db.close();
|
|
429
531
|
throw error;
|
|
430
532
|
}
|
|
431
|
-
const index = new SqliteSessionIndex(db);
|
|
533
|
+
const index = new SqliteSessionIndex(db, options.path);
|
|
432
534
|
await index.sync(options.home);
|
|
433
535
|
return index;
|
|
434
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Another process rebuilt the index and renamed a new file over it: this
|
|
539
|
+
* connection still has the old, unlinked file, whose reads go stale and
|
|
540
|
+
* whose writes fail with SQLITE_READONLY_DBMOVED. Reopen the file now at
|
|
541
|
+
* the path. A no-op for a temporary build file, and when the path holds
|
|
542
|
+
* nothing to adopt.
|
|
543
|
+
*/
|
|
544
|
+
#reopenIfReplaced(force = false) {
|
|
545
|
+
const path = this.#path;
|
|
546
|
+
if (path === undefined || !this.#open)
|
|
547
|
+
return;
|
|
548
|
+
const identity = fileIdentity(path);
|
|
549
|
+
if (identity === undefined || (!force && identity === this.#identity))
|
|
550
|
+
return;
|
|
551
|
+
const { DatabaseSync } = requireSqlite();
|
|
552
|
+
const db = new DatabaseSync(path);
|
|
553
|
+
try {
|
|
554
|
+
configure(db);
|
|
555
|
+
if (userVersion(db) !== SESSION_INDEX_VERSION) {
|
|
556
|
+
throw new SessionIndexError(`The index at ${path} changed version while in use.`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
catch (error) {
|
|
560
|
+
db.close();
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
const previous = this.#db;
|
|
564
|
+
this.#statements.clear();
|
|
565
|
+
this.#db = db;
|
|
566
|
+
this.#identity = fileIdentity(path);
|
|
567
|
+
try {
|
|
568
|
+
previous.close();
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
// The old file is already unlinked; nothing is lost if it will not close.
|
|
572
|
+
}
|
|
573
|
+
}
|
|
435
574
|
transaction(work) {
|
|
436
|
-
this.#
|
|
575
|
+
this.#reopenIfReplaced();
|
|
576
|
+
try {
|
|
577
|
+
this.#db.exec('BEGIN IMMEDIATE');
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
// Nothing has run yet, so the work can start again on the new file.
|
|
581
|
+
if (!isMoved(error))
|
|
582
|
+
throw error;
|
|
583
|
+
this.#reopenIfReplaced(true);
|
|
584
|
+
this.#db.exec('BEGIN IMMEDIATE');
|
|
585
|
+
}
|
|
437
586
|
try {
|
|
438
587
|
const result = work(this.#sink);
|
|
439
588
|
this.#db.exec('COMMIT');
|
|
@@ -454,9 +603,11 @@ export class SqliteSessionIndex extends SessionIndexBase {
|
|
|
454
603
|
return statement;
|
|
455
604
|
}
|
|
456
605
|
#all(sql, ...values) {
|
|
606
|
+
this.#reopenIfReplaced();
|
|
457
607
|
return this.#statement(sql).all(...values);
|
|
458
608
|
}
|
|
459
609
|
readSession(sessionId) {
|
|
610
|
+
this.#reopenIfReplaced();
|
|
460
611
|
const row = this.#statement('SELECT * FROM sessions WHERE id = ?').get(sessionId);
|
|
461
612
|
return row === undefined ? undefined : sessionRow(row);
|
|
462
613
|
}
|
|
@@ -493,6 +644,7 @@ export class SqliteSessionIndex extends SessionIndexBase {
|
|
|
493
644
|
return rows.map((row) => decisionView(decisionRow(row)));
|
|
494
645
|
}
|
|
495
646
|
async resolveExternal(protocol, kind, externalId) {
|
|
647
|
+
this.#reopenIfReplaced();
|
|
496
648
|
const row = this.#db
|
|
497
649
|
.prepare(`SELECT * FROM external_refs WHERE protocol = ? AND kind = ? AND external_id = ?
|
|
498
650
|
ORDER BY claimed_at, session_id, claimed_seq LIMIT 1`)
|
|
@@ -523,6 +675,7 @@ export class SqliteSessionIndex extends SessionIndexBase {
|
|
|
523
675
|
values.push(options.sessionId);
|
|
524
676
|
}
|
|
525
677
|
const clause = where.length === 0 ? '' : `WHERE ${where.join(' AND ')}`;
|
|
678
|
+
this.#reopenIfReplaced();
|
|
526
679
|
const statement = this.#db.prepare(`SELECT text, session_id, turn_id, seq, part, source, tool_name, is_error FROM evidence_fts
|
|
527
680
|
${clause} ORDER BY session_id, seq, part`);
|
|
528
681
|
const hits = [];
|