@elaraai/e3-core 0.0.2-beta.4 → 0.0.2-beta.40
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/README.md +25 -22
- package/dist/src/dataflow/api-compat.d.ts +90 -0
- package/dist/src/dataflow/api-compat.d.ts.map +1 -0
- package/dist/src/dataflow/api-compat.js +139 -0
- package/dist/src/dataflow/api-compat.js.map +1 -0
- package/dist/src/dataflow/index.d.ts +18 -0
- package/dist/src/dataflow/index.d.ts.map +1 -0
- package/dist/src/dataflow/index.js +23 -0
- package/dist/src/dataflow/index.js.map +1 -0
- package/dist/src/dataflow/orchestrator/LocalOrchestrator.d.ts +76 -0
- package/dist/src/dataflow/orchestrator/LocalOrchestrator.d.ts.map +1 -0
- package/dist/src/dataflow/orchestrator/LocalOrchestrator.js +695 -0
- package/dist/src/dataflow/orchestrator/LocalOrchestrator.js.map +1 -0
- package/dist/src/dataflow/orchestrator/index.d.ts +12 -0
- package/dist/src/dataflow/orchestrator/index.d.ts.map +1 -0
- package/dist/src/dataflow/orchestrator/index.js +12 -0
- package/dist/src/dataflow/orchestrator/index.js.map +1 -0
- package/dist/src/dataflow/orchestrator/interfaces.d.ts +163 -0
- package/dist/src/dataflow/orchestrator/interfaces.d.ts.map +1 -0
- package/dist/src/dataflow/orchestrator/interfaces.js +52 -0
- package/dist/src/dataflow/orchestrator/interfaces.js.map +1 -0
- package/dist/src/dataflow/state-store/FileStateStore.d.ts +67 -0
- package/dist/src/dataflow/state-store/FileStateStore.d.ts.map +1 -0
- package/dist/src/dataflow/state-store/FileStateStore.js +300 -0
- package/dist/src/dataflow/state-store/FileStateStore.js.map +1 -0
- package/dist/src/dataflow/state-store/InMemoryStateStore.d.ts +42 -0
- package/dist/src/dataflow/state-store/InMemoryStateStore.d.ts.map +1 -0
- package/dist/src/dataflow/state-store/InMemoryStateStore.js +229 -0
- package/dist/src/dataflow/state-store/InMemoryStateStore.js.map +1 -0
- package/dist/src/dataflow/state-store/index.d.ts +13 -0
- package/dist/src/dataflow/state-store/index.d.ts.map +1 -0
- package/dist/src/dataflow/state-store/index.js +13 -0
- package/dist/src/dataflow/state-store/index.js.map +1 -0
- package/dist/src/dataflow/state-store/interfaces.d.ts +159 -0
- package/dist/src/dataflow/state-store/interfaces.d.ts.map +1 -0
- package/dist/src/dataflow/state-store/interfaces.js +6 -0
- package/dist/src/dataflow/state-store/interfaces.js.map +1 -0
- package/dist/src/dataflow/steps.d.ts +222 -0
- package/dist/src/dataflow/steps.d.ts.map +1 -0
- package/dist/src/dataflow/steps.js +707 -0
- package/dist/src/dataflow/steps.js.map +1 -0
- package/dist/src/dataflow/types.d.ts +127 -0
- package/dist/src/dataflow/types.d.ts.map +1 -0
- package/dist/src/dataflow/types.js +7 -0
- package/dist/src/dataflow/types.js.map +1 -0
- package/dist/src/dataflow.d.ts +113 -38
- package/dist/src/dataflow.d.ts.map +1 -1
- package/dist/src/dataflow.js +269 -416
- package/dist/src/dataflow.js.map +1 -1
- package/dist/src/dataset-refs.d.ts +124 -0
- package/dist/src/dataset-refs.d.ts.map +1 -0
- package/dist/src/dataset-refs.js +319 -0
- package/dist/src/dataset-refs.js.map +1 -0
- package/dist/src/errors.d.ts +39 -9
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +51 -8
- package/dist/src/errors.js.map +1 -1
- package/dist/src/execution/LocalTaskRunner.d.ts +73 -0
- package/dist/src/execution/LocalTaskRunner.d.ts.map +1 -0
- package/dist/src/execution/LocalTaskRunner.js +399 -0
- package/dist/src/execution/LocalTaskRunner.js.map +1 -0
- package/dist/src/execution/MockTaskRunner.d.ts +49 -0
- package/dist/src/execution/MockTaskRunner.d.ts.map +1 -0
- package/dist/src/execution/MockTaskRunner.js +54 -0
- package/dist/src/execution/MockTaskRunner.js.map +1 -0
- package/dist/src/execution/index.d.ts +16 -0
- package/dist/src/execution/index.d.ts.map +1 -0
- package/dist/src/execution/index.js +8 -0
- package/dist/src/execution/index.js.map +1 -0
- package/dist/src/execution/interfaces.d.ts +246 -0
- package/dist/src/execution/interfaces.d.ts.map +1 -0
- package/dist/src/execution/interfaces.js +6 -0
- package/dist/src/execution/interfaces.js.map +1 -0
- package/dist/src/execution/processHelpers.d.ts +20 -0
- package/dist/src/execution/processHelpers.d.ts.map +1 -0
- package/dist/src/execution/processHelpers.js +62 -0
- package/dist/src/execution/processHelpers.js.map +1 -0
- package/dist/src/executions.d.ts +71 -104
- package/dist/src/executions.d.ts.map +1 -1
- package/dist/src/executions.js +110 -476
- package/dist/src/executions.js.map +1 -1
- package/dist/src/index.d.ts +19 -9
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +48 -18
- package/dist/src/index.js.map +1 -1
- package/dist/src/objects.d.ts +8 -51
- package/dist/src/objects.d.ts.map +1 -1
- package/dist/src/objects.js +13 -230
- package/dist/src/objects.js.map +1 -1
- package/dist/src/packages.d.ts +22 -14
- package/dist/src/packages.d.ts.map +1 -1
- package/dist/src/packages.js +134 -88
- package/dist/src/packages.js.map +1 -1
- package/dist/src/storage/in-memory/InMemoryRepoStore.d.ts +35 -0
- package/dist/src/storage/in-memory/InMemoryRepoStore.d.ts.map +1 -0
- package/dist/src/storage/in-memory/InMemoryRepoStore.js +107 -0
- package/dist/src/storage/in-memory/InMemoryRepoStore.js.map +1 -0
- package/dist/src/storage/in-memory/InMemoryStorage.d.ts +139 -0
- package/dist/src/storage/in-memory/InMemoryStorage.d.ts.map +1 -0
- package/dist/src/storage/in-memory/InMemoryStorage.js +439 -0
- package/dist/src/storage/in-memory/InMemoryStorage.js.map +1 -0
- package/dist/src/storage/in-memory/index.d.ts +12 -0
- package/dist/src/storage/in-memory/index.d.ts.map +1 -0
- package/dist/src/storage/in-memory/index.js +12 -0
- package/dist/src/storage/in-memory/index.js.map +1 -0
- package/dist/src/storage/index.d.ts +18 -0
- package/dist/src/storage/index.d.ts.map +1 -0
- package/dist/src/storage/index.js +10 -0
- package/dist/src/storage/index.js.map +1 -0
- package/dist/src/storage/interfaces.d.ts +581 -0
- package/dist/src/storage/interfaces.d.ts.map +1 -0
- package/dist/src/storage/interfaces.js +6 -0
- package/dist/src/storage/interfaces.js.map +1 -0
- package/dist/src/storage/local/LocalBackend.d.ts +56 -0
- package/dist/src/storage/local/LocalBackend.d.ts.map +1 -0
- package/dist/src/storage/local/LocalBackend.js +145 -0
- package/dist/src/storage/local/LocalBackend.js.map +1 -0
- package/dist/src/storage/local/LocalDatasetRefStore.d.ts +22 -0
- package/dist/src/storage/local/LocalDatasetRefStore.d.ts.map +1 -0
- package/dist/src/storage/local/LocalDatasetRefStore.js +118 -0
- package/dist/src/storage/local/LocalDatasetRefStore.js.map +1 -0
- package/dist/src/storage/local/LocalLockService.d.ts +111 -0
- package/dist/src/storage/local/LocalLockService.d.ts.map +1 -0
- package/dist/src/storage/local/LocalLockService.js +355 -0
- package/dist/src/storage/local/LocalLockService.js.map +1 -0
- package/dist/src/storage/local/LocalLogStore.d.ts +23 -0
- package/dist/src/storage/local/LocalLogStore.d.ts.map +1 -0
- package/dist/src/storage/local/LocalLogStore.js +66 -0
- package/dist/src/storage/local/LocalLogStore.js.map +1 -0
- package/dist/src/storage/local/LocalObjectStore.d.ts +55 -0
- package/dist/src/storage/local/LocalObjectStore.d.ts.map +1 -0
- package/dist/src/storage/local/LocalObjectStore.js +300 -0
- package/dist/src/storage/local/LocalObjectStore.js.map +1 -0
- package/dist/src/storage/local/LocalRefStore.d.ts +50 -0
- package/dist/src/storage/local/LocalRefStore.d.ts.map +1 -0
- package/dist/src/storage/local/LocalRefStore.js +337 -0
- package/dist/src/storage/local/LocalRefStore.js.map +1 -0
- package/dist/src/storage/local/LocalRepoStore.d.ts +55 -0
- package/dist/src/storage/local/LocalRepoStore.d.ts.map +1 -0
- package/dist/src/storage/local/LocalRepoStore.js +365 -0
- package/dist/src/storage/local/LocalRepoStore.js.map +1 -0
- package/dist/src/storage/local/gc.d.ts +92 -0
- package/dist/src/storage/local/gc.d.ts.map +1 -0
- package/dist/src/storage/local/gc.js +377 -0
- package/dist/src/storage/local/gc.js.map +1 -0
- package/dist/src/storage/local/index.d.ts +18 -0
- package/dist/src/storage/local/index.d.ts.map +1 -0
- package/dist/src/storage/local/index.js +18 -0
- package/dist/src/storage/local/index.js.map +1 -0
- package/dist/src/storage/local/localHelpers.d.ts +25 -0
- package/dist/src/storage/local/localHelpers.d.ts.map +1 -0
- package/dist/src/storage/local/localHelpers.js +69 -0
- package/dist/src/storage/local/localHelpers.js.map +1 -0
- package/dist/src/{repository.d.ts → storage/local/repository.d.ts} +8 -4
- package/dist/src/storage/local/repository.d.ts.map +1 -0
- package/dist/src/{repository.js → storage/local/repository.js} +31 -29
- package/dist/src/storage/local/repository.js.map +1 -0
- package/dist/src/tasks.d.ts +16 -10
- package/dist/src/tasks.d.ts.map +1 -1
- package/dist/src/tasks.js +35 -41
- package/dist/src/tasks.js.map +1 -1
- package/dist/src/test-helpers.d.ts +5 -4
- package/dist/src/test-helpers.d.ts.map +1 -1
- package/dist/src/test-helpers.js +9 -21
- package/dist/src/test-helpers.js.map +1 -1
- package/dist/src/transfer/InMemoryTransferBackend.d.ts +66 -0
- package/dist/src/transfer/InMemoryTransferBackend.d.ts.map +1 -0
- package/dist/src/transfer/InMemoryTransferBackend.js +166 -0
- package/dist/src/transfer/InMemoryTransferBackend.js.map +1 -0
- package/dist/src/transfer/index.d.ts +8 -0
- package/dist/src/transfer/index.d.ts.map +1 -0
- package/dist/src/transfer/index.js +9 -0
- package/dist/src/transfer/index.js.map +1 -0
- package/dist/src/transfer/interfaces.d.ts +103 -0
- package/dist/src/transfer/interfaces.d.ts.map +1 -0
- package/dist/src/transfer/interfaces.js +6 -0
- package/dist/src/transfer/interfaces.js.map +1 -0
- package/dist/src/transfer/types.d.ts +79 -0
- package/dist/src/transfer/types.d.ts.map +1 -0
- package/dist/src/transfer/types.js +58 -0
- package/dist/src/transfer/types.js.map +1 -0
- package/dist/src/trees.d.ts +147 -59
- package/dist/src/trees.d.ts.map +1 -1
- package/dist/src/trees.js +372 -419
- package/dist/src/trees.js.map +1 -1
- package/dist/src/uuid.d.ts +26 -0
- package/dist/src/uuid.d.ts.map +1 -0
- package/dist/src/uuid.js +80 -0
- package/dist/src/uuid.js.map +1 -0
- package/dist/src/workspaceStatus.d.ts +6 -4
- package/dist/src/workspaceStatus.d.ts.map +1 -1
- package/dist/src/workspaceStatus.js +43 -49
- package/dist/src/workspaceStatus.js.map +1 -1
- package/dist/src/workspaces.d.ts +35 -47
- package/dist/src/workspaces.d.ts.map +1 -1
- package/dist/src/workspaces.js +194 -156
- package/dist/src/workspaces.js.map +1 -1
- package/package.json +4 -4
- package/dist/src/gc.d.ts +0 -54
- package/dist/src/gc.d.ts.map +0 -1
- package/dist/src/gc.js +0 -233
- package/dist/src/gc.js.map +0 -1
- package/dist/src/repository.d.ts.map +0 -1
- package/dist/src/repository.js.map +0 -1
- package/dist/src/workspaceLock.d.ts +0 -67
- package/dist/src/workspaceLock.d.ts.map +0 -1
- package/dist/src/workspaceLock.js +0 -217
- package/dist/src/workspaceLock.js.map +0 -1
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Local filesystem implementation of workspace locking.
|
|
7
|
+
*
|
|
8
|
+
* Provides exclusive locks on workspaces to prevent concurrent dataflow
|
|
9
|
+
* executions or writes that could corrupt workspace state. Uses Linux
|
|
10
|
+
* flock() for automatic lock release on process death.
|
|
11
|
+
*
|
|
12
|
+
* Lock mechanism:
|
|
13
|
+
* - Uses flock(LOCK_EX | LOCK_NB) via the `flock` command for kernel-managed locking
|
|
14
|
+
* - Lock is automatically released when the process dies (kernel handles this)
|
|
15
|
+
* - Lock state stored in beast2 format using LockStateType from e3-types
|
|
16
|
+
* - Holder stored as East text string (e.g., `.process (pid=1234, ...)`)
|
|
17
|
+
* - Stale lock detection via bootId comparison (handles system restarts)
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'fs/promises';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import { spawn } from 'child_process';
|
|
22
|
+
import { encodeBeast2For, decodeBeast2For, printFor, parseInferred, variant, none, VariantType } from '@elaraai/east';
|
|
23
|
+
import { LockStateType, ProcessHolderType } from '@elaraai/e3-types';
|
|
24
|
+
import { WorkspaceLockError } from '../../errors.js';
|
|
25
|
+
import { getBootId, getPidStartTime, isProcessAlive } from '../../execution/processHelpers.js';
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Holder Encoding
|
|
28
|
+
// =============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Variant type for encoding holder as East text.
|
|
31
|
+
* The holder string stores `.process (...)` or other backend-specific variants.
|
|
32
|
+
*/
|
|
33
|
+
const HolderVariantType = VariantType({
|
|
34
|
+
process: ProcessHolderType,
|
|
35
|
+
});
|
|
36
|
+
/** Print a process holder to East text format */
|
|
37
|
+
const printProcessHolder = printFor(HolderVariantType);
|
|
38
|
+
/**
|
|
39
|
+
* Parse an East text holder string.
|
|
40
|
+
* Returns the parsed variant or null if parsing fails.
|
|
41
|
+
*/
|
|
42
|
+
function parseHolder(holderStr) {
|
|
43
|
+
try {
|
|
44
|
+
const [_type, value] = parseInferred(holderStr);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// Lock File Helpers
|
|
53
|
+
// =============================================================================
|
|
54
|
+
/**
|
|
55
|
+
* Get the lock file path for a workspace.
|
|
56
|
+
*/
|
|
57
|
+
export function workspaceLockPath(repoPath, workspace) {
|
|
58
|
+
return path.join(repoPath, 'workspaces', `${workspace}.lock`);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read lock state from a lock file.
|
|
62
|
+
* Returns null if file doesn't exist or is invalid.
|
|
63
|
+
*/
|
|
64
|
+
async function readLockState(lockPath) {
|
|
65
|
+
try {
|
|
66
|
+
const data = await fs.readFile(lockPath);
|
|
67
|
+
if (data.length === 0)
|
|
68
|
+
return null;
|
|
69
|
+
const decoder = decodeBeast2For(LockStateType);
|
|
70
|
+
return decoder(data);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Write lock state to a lock file in beast2 format.
|
|
78
|
+
*/
|
|
79
|
+
async function writeLockState(lockPath, state) {
|
|
80
|
+
const encoder = encodeBeast2For(LockStateType);
|
|
81
|
+
const data = encoder(state);
|
|
82
|
+
await fs.writeFile(lockPath, data);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Convert LockState to LockHolderInfo for error display.
|
|
86
|
+
*/
|
|
87
|
+
export function lockStateToHolderInfo(state) {
|
|
88
|
+
const info = {
|
|
89
|
+
acquiredAt: state.acquiredAt.toISOString(),
|
|
90
|
+
operation: state.operation.type,
|
|
91
|
+
};
|
|
92
|
+
// Parse the holder string to extract process-specific fields
|
|
93
|
+
const holder = parseHolder(state.holder);
|
|
94
|
+
if (holder?.type === 'process') {
|
|
95
|
+
info.pid = Number(holder.value.pid);
|
|
96
|
+
info.bootId = holder.value.bootId;
|
|
97
|
+
info.startTime = Number(holder.value.startTime);
|
|
98
|
+
info.command = holder.value.command;
|
|
99
|
+
}
|
|
100
|
+
return info;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if a lock holder is still alive.
|
|
104
|
+
* @param holderStr - East text-encoded holder string
|
|
105
|
+
*/
|
|
106
|
+
export async function isLockHolderAlive(holderStr) {
|
|
107
|
+
const holder = parseHolder(holderStr);
|
|
108
|
+
if (!holder)
|
|
109
|
+
return true; // Can't parse - assume alive (safer)
|
|
110
|
+
if (holder.type === 'process') {
|
|
111
|
+
return isProcessAlive(Number(holder.value.pid), Number(holder.value.startTime), holder.value.bootId);
|
|
112
|
+
}
|
|
113
|
+
// Unknown holder type - assume alive (safer default)
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
// =============================================================================
|
|
117
|
+
// Lock Acquisition
|
|
118
|
+
// =============================================================================
|
|
119
|
+
/**
|
|
120
|
+
* Acquire an exclusive lock on a workspace.
|
|
121
|
+
*
|
|
122
|
+
* Uses Linux flock() for kernel-managed locking. The lock is automatically
|
|
123
|
+
* released when the process exits (even on crash/kill).
|
|
124
|
+
*
|
|
125
|
+
* @param repoPath - Path to e3 repository
|
|
126
|
+
* @param workspace - Workspace name to lock
|
|
127
|
+
* @param operation - What operation is acquiring the lock
|
|
128
|
+
* @param options - Lock acquisition options
|
|
129
|
+
* @returns Lock handle - call release() when done
|
|
130
|
+
* @throws {WorkspaceLockError} If workspace is locked by another process
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const lock = await acquireWorkspaceLock(repoPath, 'production', { type: 'dataflow', value: null });
|
|
135
|
+
* try {
|
|
136
|
+
* await dataflowExecute(repoPath, 'production', { lock });
|
|
137
|
+
* } finally {
|
|
138
|
+
* await lock.release();
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export async function acquireWorkspaceLock(repoPath, workspace, operation, options = {}) {
|
|
143
|
+
const lockPath = workspaceLockPath(repoPath, workspace);
|
|
144
|
+
// Ensure workspaces directory exists
|
|
145
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
146
|
+
// Gather our process identification
|
|
147
|
+
const pid = process.pid;
|
|
148
|
+
const bootId = await getBootId();
|
|
149
|
+
const startTime = await getPidStartTime(pid);
|
|
150
|
+
const command = process.argv.join(' ');
|
|
151
|
+
const acquiredAt = new Date();
|
|
152
|
+
// Encode holder as East text: .process (pid=..., bootId="...", ...)
|
|
153
|
+
const holderVariant = variant('process', {
|
|
154
|
+
pid: BigInt(pid),
|
|
155
|
+
bootId,
|
|
156
|
+
startTime: BigInt(startTime),
|
|
157
|
+
command,
|
|
158
|
+
});
|
|
159
|
+
const holder = printProcessHolder(holderVariant);
|
|
160
|
+
const lockState = {
|
|
161
|
+
operation,
|
|
162
|
+
holder,
|
|
163
|
+
acquiredAt,
|
|
164
|
+
expiresAt: none,
|
|
165
|
+
};
|
|
166
|
+
// Try to acquire flock via subprocess
|
|
167
|
+
// The subprocess holds the lock and we communicate with it via stdin/signals
|
|
168
|
+
const flockProcess = await tryAcquireFlock(lockPath, lockState, options);
|
|
169
|
+
if (!flockProcess) {
|
|
170
|
+
// Failed to acquire - read lock state to report who has it
|
|
171
|
+
const existingState = await readLockState(lockPath);
|
|
172
|
+
const holderInfo = existingState ? lockStateToHolderInfo(existingState) : undefined;
|
|
173
|
+
throw new WorkspaceLockError(workspace, holderInfo);
|
|
174
|
+
}
|
|
175
|
+
// Lock acquired! Create handle
|
|
176
|
+
let released = false;
|
|
177
|
+
const handle = {
|
|
178
|
+
resource: workspace,
|
|
179
|
+
workspace,
|
|
180
|
+
lockPath,
|
|
181
|
+
async release() {
|
|
182
|
+
if (released)
|
|
183
|
+
return;
|
|
184
|
+
released = true;
|
|
185
|
+
// Kill the flock subprocess to release the lock
|
|
186
|
+
flockProcess.kill('SIGTERM');
|
|
187
|
+
// Clean up lock file (best effort)
|
|
188
|
+
try {
|
|
189
|
+
await fs.unlink(lockPath);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Ignore - file might already be gone
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
return handle;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Try to acquire flock using a subprocess.
|
|
200
|
+
*
|
|
201
|
+
* We spawn `flock --nonblock <lockfile> cat` which:
|
|
202
|
+
* 1. Tries to acquire exclusive lock (non-blocking)
|
|
203
|
+
* 2. If successful, runs `cat` which blocks reading stdin forever
|
|
204
|
+
* 3. We keep the subprocess alive to hold the lock
|
|
205
|
+
* 4. When we kill the subprocess, the lock is released
|
|
206
|
+
*
|
|
207
|
+
* Returns the subprocess if lock acquired, null if lock is held by another.
|
|
208
|
+
*/
|
|
209
|
+
async function tryAcquireFlock(lockPath, lockState, options) {
|
|
210
|
+
// First, check if there's a stale lock we can clean up
|
|
211
|
+
// (only for exclusive mode — shared locks don't need stale checking)
|
|
212
|
+
if (options.mode !== 'shared') {
|
|
213
|
+
await checkAndCleanStaleLock(lockPath);
|
|
214
|
+
}
|
|
215
|
+
const isShared = options.mode === 'shared';
|
|
216
|
+
const args = [];
|
|
217
|
+
if (isShared) {
|
|
218
|
+
args.push('--shared');
|
|
219
|
+
}
|
|
220
|
+
if (options.wait) {
|
|
221
|
+
args.push('--timeout', String((options.timeout ?? 30000) / 1000));
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
args.push('--nonblock');
|
|
225
|
+
}
|
|
226
|
+
args.push(lockPath, 'cat');
|
|
227
|
+
const child = spawn('flock', args, {
|
|
228
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
229
|
+
detached: false,
|
|
230
|
+
});
|
|
231
|
+
return new Promise((resolve) => {
|
|
232
|
+
let resolved = false;
|
|
233
|
+
// If flock fails to acquire, it exits with code 1
|
|
234
|
+
child.on('error', () => {
|
|
235
|
+
if (!resolved) {
|
|
236
|
+
resolved = true;
|
|
237
|
+
resolve(null);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
child.on('exit', () => {
|
|
241
|
+
if (!resolved) {
|
|
242
|
+
resolved = true;
|
|
243
|
+
// Exit code 1 means lock is held by another
|
|
244
|
+
resolve(null);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
// Give flock a moment to either acquire or fail
|
|
248
|
+
// If it's still running after 100ms, we have the lock
|
|
249
|
+
setTimeout(() => {
|
|
250
|
+
if (!resolved && !child.killed && child.exitCode === null) {
|
|
251
|
+
resolved = true;
|
|
252
|
+
// Write lock state to lock file now that we have the lock
|
|
253
|
+
void writeLockState(lockPath, lockState).catch((err) => {
|
|
254
|
+
console.warn(`Failed to write lock state: ${err instanceof Error ? err.message : String(err)}`);
|
|
255
|
+
});
|
|
256
|
+
resolve(child);
|
|
257
|
+
}
|
|
258
|
+
}, 100);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Check if a lock file exists with stale lock state and clean it up.
|
|
263
|
+
* A lock is stale if the holder process no longer exists.
|
|
264
|
+
*/
|
|
265
|
+
async function checkAndCleanStaleLock(lockPath) {
|
|
266
|
+
const state = await readLockState(lockPath);
|
|
267
|
+
if (!state)
|
|
268
|
+
return;
|
|
269
|
+
// Check if the holder is still alive
|
|
270
|
+
const alive = await isLockHolderAlive(state.holder);
|
|
271
|
+
if (!alive) {
|
|
272
|
+
// Stale lock - try to remove it
|
|
273
|
+
try {
|
|
274
|
+
await fs.unlink(lockPath);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Ignore - another process might have cleaned it up
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get the lock state for a workspace.
|
|
283
|
+
*
|
|
284
|
+
* @param repoPath - Path to e3 repository
|
|
285
|
+
* @param workspace - Workspace name to check
|
|
286
|
+
* @returns Lock state if locked, null if not locked
|
|
287
|
+
*/
|
|
288
|
+
export async function getWorkspaceLockState(repoPath, workspace) {
|
|
289
|
+
const lockPath = workspaceLockPath(repoPath, workspace);
|
|
290
|
+
const state = await readLockState(lockPath);
|
|
291
|
+
if (!state)
|
|
292
|
+
return null;
|
|
293
|
+
// Check if the holder is still alive
|
|
294
|
+
const alive = await isLockHolderAlive(state.holder);
|
|
295
|
+
if (!alive) {
|
|
296
|
+
// Stale lock - clean it up and report as not locked
|
|
297
|
+
try {
|
|
298
|
+
await fs.unlink(lockPath);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// Ignore
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return state;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get lock holder info for a workspace (for backwards compatibility).
|
|
309
|
+
*
|
|
310
|
+
* @param repoPath - Path to e3 repository
|
|
311
|
+
* @param workspace - Workspace name to check
|
|
312
|
+
* @returns Lock holder info if locked, null if not locked
|
|
313
|
+
* @deprecated Use getWorkspaceLockState for full lock information
|
|
314
|
+
*/
|
|
315
|
+
export async function getWorkspaceLockHolder(repoPath, workspace) {
|
|
316
|
+
const state = await getWorkspaceLockState(repoPath, workspace);
|
|
317
|
+
return state ? lockStateToHolderInfo(state) : null;
|
|
318
|
+
}
|
|
319
|
+
// =============================================================================
|
|
320
|
+
// LockService Interface Implementation
|
|
321
|
+
// =============================================================================
|
|
322
|
+
/**
|
|
323
|
+
* Local filesystem implementation of LockService.
|
|
324
|
+
*
|
|
325
|
+
* Uses flock() for kernel-managed locking with lock state
|
|
326
|
+
* stored in beast2 format using LockStateType.
|
|
327
|
+
* The `repo` parameter is the path to the e3 repository directory.
|
|
328
|
+
*/
|
|
329
|
+
export class LocalLockService {
|
|
330
|
+
async acquire(repo, resource, operation, options) {
|
|
331
|
+
const acquireOptions = {
|
|
332
|
+
wait: options?.wait ?? false,
|
|
333
|
+
timeout: options?.timeout,
|
|
334
|
+
mode: options?.mode ?? 'exclusive',
|
|
335
|
+
};
|
|
336
|
+
try {
|
|
337
|
+
const handle = await acquireWorkspaceLock(repo, resource, operation, acquireOptions);
|
|
338
|
+
return {
|
|
339
|
+
resource,
|
|
340
|
+
release: () => handle.release(),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// Lock couldn't be acquired
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
getState(repo, resource) {
|
|
349
|
+
return getWorkspaceLockState(repo, resource);
|
|
350
|
+
}
|
|
351
|
+
isHolderAlive(holder) {
|
|
352
|
+
return isLockHolderAlive(holder);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=LocalLockService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalLockService.js","sourceRoot":"","sources":["../../../../src/storage/local/LocalLockService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
import type { LogChunk, LogStore } from '../interfaces.js';
|
|
6
|
+
/**
|
|
7
|
+
* Local filesystem implementation of LogStore.
|
|
8
|
+
*
|
|
9
|
+
* Logs are stored as text files in the execution directory:
|
|
10
|
+
* executions/<taskHash>/<inputsHash>/<executionId>/stdout.txt
|
|
11
|
+
* executions/<taskHash>/<inputsHash>/<executionId>/stderr.txt
|
|
12
|
+
*
|
|
13
|
+
* The `repo` parameter is the path to the e3 repository directory.
|
|
14
|
+
*/
|
|
15
|
+
export declare class LocalLogStore implements LogStore {
|
|
16
|
+
private logPath;
|
|
17
|
+
append(repo: string, taskHash: string, inputsHash: string, executionId: string, stream: 'stdout' | 'stderr', data: string): Promise<void>;
|
|
18
|
+
read(repo: string, taskHash: string, inputsHash: string, executionId: string, stream: 'stdout' | 'stderr', options?: {
|
|
19
|
+
offset?: number;
|
|
20
|
+
limit?: number;
|
|
21
|
+
}): Promise<LogChunk>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=LocalLogStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalLogStore.d.ts","sourceRoot":"","sources":["../../../../src/storage/local/LocalLogStore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG3D;;;;;;;;GAQG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,OAAO,CAAC,OAAO;IAWT,MAAM,CACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC;IAQV,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAC3B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,QAAQ,CAAC;CAwCrB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { isNotFoundError } from '../../errors.js';
|
|
8
|
+
/**
|
|
9
|
+
* Local filesystem implementation of LogStore.
|
|
10
|
+
*
|
|
11
|
+
* Logs are stored as text files in the execution directory:
|
|
12
|
+
* executions/<taskHash>/<inputsHash>/<executionId>/stdout.txt
|
|
13
|
+
* executions/<taskHash>/<inputsHash>/<executionId>/stderr.txt
|
|
14
|
+
*
|
|
15
|
+
* The `repo` parameter is the path to the e3 repository directory.
|
|
16
|
+
*/
|
|
17
|
+
export class LocalLogStore {
|
|
18
|
+
logPath(repo, taskHash, inputsHash, executionId, stream) {
|
|
19
|
+
return path.join(repo, 'executions', taskHash, inputsHash, executionId, `${stream}.txt`);
|
|
20
|
+
}
|
|
21
|
+
async append(repo, taskHash, inputsHash, executionId, stream, data) {
|
|
22
|
+
const logFile = this.logPath(repo, taskHash, inputsHash, executionId, stream);
|
|
23
|
+
const dir = path.dirname(logFile);
|
|
24
|
+
await fs.mkdir(dir, { recursive: true });
|
|
25
|
+
await fs.appendFile(logFile, data);
|
|
26
|
+
}
|
|
27
|
+
async read(repo, taskHash, inputsHash, executionId, stream, options) {
|
|
28
|
+
const logFile = this.logPath(repo, taskHash, inputsHash, executionId, stream);
|
|
29
|
+
const offset = options?.offset ?? 0;
|
|
30
|
+
const limit = options?.limit ?? 65536; // 64KB default
|
|
31
|
+
try {
|
|
32
|
+
const stat = await fs.stat(logFile);
|
|
33
|
+
const totalSize = stat.size;
|
|
34
|
+
// Open file and read chunk
|
|
35
|
+
const fd = await fs.open(logFile, 'r');
|
|
36
|
+
try {
|
|
37
|
+
const buffer = Buffer.alloc(Math.min(limit, Math.max(0, totalSize - offset)));
|
|
38
|
+
const { bytesRead } = await fd.read(buffer, 0, buffer.length, offset);
|
|
39
|
+
return {
|
|
40
|
+
data: buffer.slice(0, bytesRead).toString('utf-8'),
|
|
41
|
+
offset,
|
|
42
|
+
size: bytesRead,
|
|
43
|
+
totalSize,
|
|
44
|
+
complete: offset + bytesRead >= totalSize,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await fd.close();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (isNotFoundError(err)) {
|
|
53
|
+
// Log file doesn't exist yet
|
|
54
|
+
return {
|
|
55
|
+
data: '',
|
|
56
|
+
offset: 0,
|
|
57
|
+
size: 0,
|
|
58
|
+
totalSize: 0,
|
|
59
|
+
complete: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=LocalLogStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalLogStore.js","sourceRoot":"","sources":["../../../../src/storage/local/LocalLogStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
import type { ObjectStore } from '../interfaces.js';
|
|
6
|
+
/**
|
|
7
|
+
* Atomically write an object to the repository.
|
|
8
|
+
*
|
|
9
|
+
* @param repoPath - Path to e3 repository
|
|
10
|
+
* @param data - Data to store
|
|
11
|
+
* @returns SHA256 hash of the data
|
|
12
|
+
*/
|
|
13
|
+
export declare function objectWrite(repoPath: string, data: Uint8Array): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Atomically write a stream to the repository.
|
|
16
|
+
*
|
|
17
|
+
* @param repoPath - Path to e3 repository
|
|
18
|
+
* @param stream - Stream to store
|
|
19
|
+
* @returns SHA256 hash of the data
|
|
20
|
+
*/
|
|
21
|
+
export declare function objectWriteStream(repoPath: string, stream: ReadableStream<Uint8Array>): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Read an object from the repository.
|
|
24
|
+
*
|
|
25
|
+
* @param repoPath - Path to e3 repository
|
|
26
|
+
* @param hash - SHA256 hash of the object
|
|
27
|
+
* @returns Object data
|
|
28
|
+
* @throws {ObjectNotFoundError} If object not found
|
|
29
|
+
*/
|
|
30
|
+
export declare function objectRead(repoPath: string, hash: string): Promise<Uint8Array>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if an object exists in the repository.
|
|
33
|
+
*
|
|
34
|
+
* @param repoPath - Path to e3 repository
|
|
35
|
+
* @param hash - SHA256 hash of the object
|
|
36
|
+
* @returns true if object exists
|
|
37
|
+
*/
|
|
38
|
+
export declare function objectExists(repoPath: string, hash: string): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Local filesystem implementation of ObjectStore.
|
|
41
|
+
*
|
|
42
|
+
* The `repo` parameter is the path to the e3 repository directory.
|
|
43
|
+
*/
|
|
44
|
+
export declare class LocalObjectStore implements ObjectStore {
|
|
45
|
+
write(repo: string, data: Uint8Array): Promise<string>;
|
|
46
|
+
writeStream(repo: string, stream: AsyncIterable<Uint8Array>): Promise<string>;
|
|
47
|
+
read(repo: string, hash: string): Promise<Uint8Array>;
|
|
48
|
+
exists(repo: string, hash: string): Promise<boolean>;
|
|
49
|
+
stat(repo: string, hash: string): Promise<{
|
|
50
|
+
size: number;
|
|
51
|
+
}>;
|
|
52
|
+
list(repo: string): Promise<string[]>;
|
|
53
|
+
count(repo: string): Promise<number>;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=LocalObjectStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalObjectStore.d.ts","sourceRoot":"","sources":["../../../../src/storage/local/LocalObjectStore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAoCpD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,MAAM,CAAC,CAkDjB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACjC,OAAO,CAAC,MAAM,CAAC,CAuDjB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,CAAC,CAerB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CASlB;AAMD;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,WAAW;IAC5C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAItD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAa7E,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAIrD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIpD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAa3D,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiCrC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CA0B3C"}
|