@elaraai/e3-core 1.0.1 → 1.0.2
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/dist/src/execution/processHelpers.d.ts +4 -4
- package/dist/src/execution/processHelpers.d.ts.map +1 -1
- package/dist/src/execution/processHelpers.js +47 -24
- package/dist/src/execution/processHelpers.js.map +1 -1
- package/dist/src/storage/local/LocalLockService.d.ts +8 -66
- package/dist/src/storage/local/LocalLockService.d.ts.map +1 -1
- package/dist/src/storage/local/LocalLockService.js +174 -223
- package/dist/src/storage/local/LocalLockService.js.map +1 -1
- package/package.json +4 -4
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function getBootId(): Promise<string>;
|
|
10
10
|
/**
|
|
11
|
-
* Get process start time
|
|
12
|
-
* Returns the
|
|
13
|
-
* Used together with boot ID to uniquely identify a process (handles PID reuse).
|
|
11
|
+
* Get process start time as an opaque integer for PID-reuse detection.
|
|
12
|
+
* Returns 0 if the process doesn't exist or the platform isn't supported.
|
|
14
13
|
*/
|
|
15
14
|
export declare function getPidStartTime(pid: number): Promise<number>;
|
|
16
15
|
/**
|
|
17
|
-
* Check if a process is still alive based on stored identification
|
|
16
|
+
* Check if a process is still alive based on stored identification.
|
|
17
|
+
* Falls back to process.kill(pid, 0) when /proc is unavailable (macOS, Windows).
|
|
18
18
|
*/
|
|
19
19
|
export declare function isProcessAlive(pid: number, pidStartTime: number, bootId: string): Promise<boolean>;
|
|
20
20
|
//# sourceMappingURL=processHelpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processHelpers.d.ts","sourceRoot":"","sources":["../../../src/execution/processHelpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"processHelpers.d.ts","sourceRoot":"","sources":["../../../src/execution/processHelpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAeH;;;GAGG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAYjD;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBlE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAqBlB"}
|
|
@@ -9,54 +9,77 @@
|
|
|
9
9
|
* to detect stale processes and locks after crashes or reboots.
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from 'fs/promises';
|
|
12
|
+
import { execFile } from 'child_process';
|
|
13
|
+
import { promisify } from 'util';
|
|
14
|
+
const execFileAsync = promisify(execFile);
|
|
12
15
|
/**
|
|
13
16
|
* Get the current system boot ID.
|
|
14
17
|
* Used for detecting stale locks/processes after system reboot.
|
|
15
18
|
*/
|
|
16
19
|
export async function getBootId() {
|
|
20
|
+
// Linux
|
|
17
21
|
try {
|
|
18
|
-
|
|
19
|
-
return data.trim();
|
|
22
|
+
return (await fs.readFile('/proc/sys/kernel/random/boot_id', 'utf-8')).trim();
|
|
20
23
|
}
|
|
21
|
-
catch {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
catch { }
|
|
25
|
+
// macOS: sysctl kern.boottime → "{ sec = 1234567890, usec = 0 } ..."
|
|
26
|
+
try {
|
|
27
|
+
const { stdout } = await execFileAsync('sysctl', ['-n', 'kern.boottime']);
|
|
28
|
+
const m = stdout.match(/\bsec\s*=\s*(\d+)/);
|
|
29
|
+
if (m)
|
|
30
|
+
return `macos-${m[1]}`;
|
|
24
31
|
}
|
|
32
|
+
catch { }
|
|
33
|
+
return 'unknown-boot-id';
|
|
25
34
|
}
|
|
26
35
|
/**
|
|
27
|
-
* Get process start time
|
|
28
|
-
* Returns the
|
|
29
|
-
* Used together with boot ID to uniquely identify a process (handles PID reuse).
|
|
36
|
+
* Get process start time as an opaque integer for PID-reuse detection.
|
|
37
|
+
* Returns 0 if the process doesn't exist or the platform isn't supported.
|
|
30
38
|
*/
|
|
31
39
|
export async function getPidStartTime(pid) {
|
|
40
|
+
// Linux: /proc/<pid>/stat field 22 (starttime in jiffies since boot)
|
|
32
41
|
try {
|
|
33
42
|
const data = await fs.readFile(`/proc/${pid}/stat`, 'utf-8');
|
|
34
|
-
// Fields are space-separated, but comm (field 2) can contain spaces and is in parens
|
|
35
|
-
// Find the closing paren, then split the rest
|
|
36
43
|
const closeParen = data.lastIndexOf(')');
|
|
37
44
|
const fields = data.slice(closeParen + 2).split(' ');
|
|
38
|
-
// After the closing paren, field index 0 is state (field 3), so starttime is at index 19
|
|
39
|
-
// (field 22 - 3 = 19)
|
|
40
45
|
return parseInt(fields[19], 10);
|
|
41
46
|
}
|
|
42
|
-
catch {
|
|
43
|
-
|
|
47
|
+
catch { }
|
|
48
|
+
// macOS: ps -p <pid> -o lstart= → "Wed Nov 6 12:34:56 2024" or empty if dead
|
|
49
|
+
try {
|
|
50
|
+
const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'lstart=']);
|
|
51
|
+
const trimmed = stdout.trim();
|
|
52
|
+
if (!trimmed)
|
|
53
|
+
return 0;
|
|
54
|
+
const t = new Date(trimmed).getTime();
|
|
55
|
+
return isNaN(t) ? 0 : Math.floor(t / 1000);
|
|
44
56
|
}
|
|
57
|
+
catch { }
|
|
58
|
+
return 0;
|
|
45
59
|
}
|
|
46
60
|
/**
|
|
47
|
-
* Check if a process is still alive based on stored identification
|
|
61
|
+
* Check if a process is still alive based on stored identification.
|
|
62
|
+
* Falls back to process.kill(pid, 0) when /proc is unavailable (macOS, Windows).
|
|
48
63
|
*/
|
|
49
64
|
export async function isProcessAlive(pid, pidStartTime, bootId) {
|
|
50
|
-
// Different boot? Process is dead
|
|
51
65
|
const currentBootId = await getBootId();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
// Only use boot ID comparison when both sides have real values
|
|
67
|
+
if (currentBootId !== 'unknown-boot-id' && bootId !== 'unknown-boot-id') {
|
|
68
|
+
if (currentBootId !== bootId)
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
55
71
|
const currentStartTime = await getPidStartTime(pid);
|
|
56
|
-
if (currentStartTime
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
72
|
+
if (currentStartTime !== 0) {
|
|
73
|
+
// Start time available — use it for precise PID-reuse detection
|
|
74
|
+
return currentStartTime === pidStartTime;
|
|
75
|
+
}
|
|
76
|
+
// Fallback: signal 0 checks existence without sending a signal (cross-platform)
|
|
77
|
+
try {
|
|
78
|
+
process.kill(pid, 0);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
61
84
|
}
|
|
62
85
|
//# sourceMappingURL=processHelpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processHelpers.js","sourceRoot":"","sources":["../../../src/execution/processHelpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"processHelpers.js","sourceRoot":"","sources":["../../../src/execution/processHelpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,95 +10,37 @@ import type { LockHandle, LockService } from '../interfaces.js';
|
|
|
10
10
|
* Call release() when done to free the lock.
|
|
11
11
|
*/
|
|
12
12
|
export interface WorkspaceLockHandle {
|
|
13
|
-
/** The resource (workspace name) this lock is for - compatible with LockHandle */
|
|
14
13
|
readonly resource: string;
|
|
15
|
-
/** The workspace name this lock is for */
|
|
16
14
|
readonly workspace: string;
|
|
17
|
-
/** Path to the lock file */
|
|
18
15
|
readonly lockPath: string;
|
|
19
|
-
/** Release the lock. Safe to call multiple times. */
|
|
20
16
|
release(): Promise<void>;
|
|
21
17
|
}
|
|
22
18
|
/**
|
|
23
19
|
* Options for acquiring a workspace lock.
|
|
24
20
|
*/
|
|
25
21
|
export interface AcquireLockOptions {
|
|
26
|
-
/**
|
|
27
|
-
* If true, wait for the lock to become available instead of failing immediately.
|
|
28
|
-
* Default: false (fail fast if locked)
|
|
29
|
-
*/
|
|
22
|
+
/** If true, wait for the lock to become available. Default: false */
|
|
30
23
|
wait?: boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Timeout in milliseconds when wait=true. Default: 30000 (30 seconds)
|
|
33
|
-
*/
|
|
24
|
+
/** Timeout in milliseconds when wait=true. Default: 30000 */
|
|
34
25
|
timeout?: number;
|
|
35
|
-
/**
|
|
36
|
-
* Lock mode: 'shared' allows concurrent shared holders, 'exclusive' is mutually exclusive.
|
|
37
|
-
* Default: 'exclusive'
|
|
38
|
-
*/
|
|
26
|
+
/** Lock mode. Default: 'exclusive' */
|
|
39
27
|
mode?: 'shared' | 'exclusive';
|
|
40
28
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Get the lock file path for a workspace.
|
|
43
|
-
*/
|
|
29
|
+
/** Path to the exclusive lock file for a workspace. */
|
|
44
30
|
export declare function workspaceLockPath(repoPath: string, workspace: string): string;
|
|
45
|
-
/**
|
|
46
|
-
* Convert LockState to LockHolderInfo for error display.
|
|
47
|
-
*/
|
|
48
31
|
export declare function lockStateToHolderInfo(state: LockState): LockHolderInfo;
|
|
49
|
-
/**
|
|
50
|
-
* Check if a lock holder is still alive.
|
|
51
|
-
* @param holderStr - East text-encoded holder string
|
|
52
|
-
*/
|
|
53
32
|
export declare function isLockHolderAlive(holderStr: string): Promise<boolean>;
|
|
54
33
|
/**
|
|
55
|
-
* Acquire an exclusive lock on a workspace.
|
|
34
|
+
* Acquire an exclusive or shared lock on a workspace.
|
|
56
35
|
*
|
|
57
|
-
* Uses
|
|
58
|
-
*
|
|
36
|
+
* Uses atomic O_CREAT|O_EXCL file creation (exclusive) or per-holder slock
|
|
37
|
+
* files (shared). Works on Linux, macOS, and Windows without external commands.
|
|
59
38
|
*
|
|
60
|
-
* @
|
|
61
|
-
* @param workspace - Workspace name to lock
|
|
62
|
-
* @param operation - What operation is acquiring the lock
|
|
63
|
-
* @param options - Lock acquisition options
|
|
64
|
-
* @returns Lock handle - call release() when done
|
|
65
|
-
* @throws {WorkspaceLockError} If workspace is locked by another process
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```typescript
|
|
69
|
-
* const lock = await acquireWorkspaceLock(repoPath, 'production', { type: 'dataflow', value: null });
|
|
70
|
-
* try {
|
|
71
|
-
* await dataflowExecute(repoPath, 'production', { lock });
|
|
72
|
-
* } finally {
|
|
73
|
-
* await lock.release();
|
|
74
|
-
* }
|
|
75
|
-
* ```
|
|
39
|
+
* @throws {WorkspaceLockError} If the lock cannot be acquired
|
|
76
40
|
*/
|
|
77
41
|
export declare function acquireWorkspaceLock(repoPath: string, workspace: string, operation: LockOperation, options?: AcquireLockOptions): Promise<WorkspaceLockHandle>;
|
|
78
|
-
/**
|
|
79
|
-
* Get the lock state for a workspace.
|
|
80
|
-
*
|
|
81
|
-
* @param repoPath - Path to e3 repository
|
|
82
|
-
* @param workspace - Workspace name to check
|
|
83
|
-
* @returns Lock state if locked, null if not locked
|
|
84
|
-
*/
|
|
85
42
|
export declare function getWorkspaceLockState(repoPath: string, workspace: string): Promise<LockState | null>;
|
|
86
|
-
/**
|
|
87
|
-
* Get lock holder info for a workspace (for backwards compatibility).
|
|
88
|
-
*
|
|
89
|
-
* @param repoPath - Path to e3 repository
|
|
90
|
-
* @param workspace - Workspace name to check
|
|
91
|
-
* @returns Lock holder info if locked, null if not locked
|
|
92
|
-
* @deprecated Use getWorkspaceLockState for full lock information
|
|
93
|
-
*/
|
|
94
43
|
export declare function getWorkspaceLockHolder(repoPath: string, workspace: string): Promise<LockHolderInfo | null>;
|
|
95
|
-
/**
|
|
96
|
-
* Local filesystem implementation of LockService.
|
|
97
|
-
*
|
|
98
|
-
* Uses flock() for kernel-managed locking with lock state
|
|
99
|
-
* stored in beast2 format using LockStateType.
|
|
100
|
-
* The `repo` parameter is the path to the e3 repository directory.
|
|
101
|
-
*/
|
|
102
44
|
export declare class LocalLockService implements LockService {
|
|
103
45
|
acquire(repo: string, resource: string, operation: LockOperation, options?: {
|
|
104
46
|
wait?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalLockService.d.ts","sourceRoot":"","sources":["../../../../src/storage/local/LocalLockService.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"LocalLockService.d.ts","sourceRoot":"","sources":["../../../../src/storage/local/LocalLockService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAmBH,OAAO,EAAoC,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACzG,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAyBhE;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;CAC/B;AAMD,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7E;AAqCD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,GAAG,cAAc,CAatE;AAMD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3E;AA+HD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,aAAa,EACxB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAmC9B;AAMD,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAS3B;AAED,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAGhC;AAMD,qBAAa,gBAAiB,YAAW,WAAW;IAC5C,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAA;KAAE,GAC5E,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAa7B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAInE,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGhD"}
|
|
@@ -5,20 +5,18 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Local filesystem implementation of workspace locking.
|
|
7
7
|
*
|
|
8
|
-
* Provides exclusive locks on workspaces
|
|
9
|
-
*
|
|
10
|
-
* flock() for automatic lock release on process death.
|
|
8
|
+
* Provides exclusive and shared locks on workspaces using pure Node.js
|
|
9
|
+
* primitives — no external commands required (works on Linux, macOS, Windows).
|
|
11
10
|
*
|
|
12
11
|
* Lock mechanism:
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* - Stale lock detection via bootId comparison (handles system restarts)
|
|
12
|
+
* - Exclusive: atomic O_CREAT|O_EXCL file creation (`fs.open('wx')`)
|
|
13
|
+
* - Shared: per-holder slock files (`{workspace}.{pid}.{token}.slock`)
|
|
14
|
+
* - Stale detection: process.kill(pid, 0) with /proc fallback for precise detection
|
|
15
|
+
* - Release: unlink the lock file
|
|
18
16
|
*/
|
|
19
17
|
import * as fs from 'fs/promises';
|
|
20
18
|
import * as path from 'path';
|
|
21
|
-
import {
|
|
19
|
+
import { randomBytes } from 'crypto';
|
|
22
20
|
import { encodeBeast2For, decodeBeast2For, printFor, parseInferred, variant, none, VariantType } from '@elaraai/east';
|
|
23
21
|
import { LockStateType, ProcessHolderType } from '@elaraai/e3-types';
|
|
24
22
|
import { WorkspaceLockError } from '../../errors.js';
|
|
@@ -26,19 +24,10 @@ import { getBootId, getPidStartTime, isProcessAlive } from '../../execution/proc
|
|
|
26
24
|
// =============================================================================
|
|
27
25
|
// Holder Encoding
|
|
28
26
|
// =============================================================================
|
|
29
|
-
/**
|
|
30
|
-
* Variant type for encoding holder as East text.
|
|
31
|
-
* The holder string stores `.process (...)` or other backend-specific variants.
|
|
32
|
-
*/
|
|
33
27
|
const HolderVariantType = VariantType({
|
|
34
28
|
process: ProcessHolderType,
|
|
35
29
|
});
|
|
36
|
-
/** Print a process holder to East text format */
|
|
37
30
|
const printProcessHolder = printFor(HolderVariantType);
|
|
38
|
-
/**
|
|
39
|
-
* Parse an East text holder string.
|
|
40
|
-
* Returns the parsed variant or null if parsing fails.
|
|
41
|
-
*/
|
|
42
31
|
function parseHolder(holderStr) {
|
|
43
32
|
try {
|
|
44
33
|
const [_type, value] = parseInferred(holderStr);
|
|
@@ -49,18 +38,27 @@ function parseHolder(holderStr) {
|
|
|
49
38
|
}
|
|
50
39
|
}
|
|
51
40
|
// =============================================================================
|
|
52
|
-
// Lock File
|
|
41
|
+
// Lock File Paths
|
|
53
42
|
// =============================================================================
|
|
54
|
-
/**
|
|
55
|
-
* Get the lock file path for a workspace.
|
|
56
|
-
*/
|
|
43
|
+
/** Path to the exclusive lock file for a workspace. */
|
|
57
44
|
export function workspaceLockPath(repoPath, workspace) {
|
|
58
45
|
return path.join(repoPath, 'workspaces', `${workspace}.lock`);
|
|
59
46
|
}
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
/** Directory containing lock files for a workspace. */
|
|
48
|
+
function workspaceLockDir(repoPath, _workspace) {
|
|
49
|
+
return path.join(repoPath, 'workspaces');
|
|
50
|
+
}
|
|
51
|
+
/** Path to a shared lock file for a specific holder. */
|
|
52
|
+
function sharedLockPath(repoPath, workspace, pid, token) {
|
|
53
|
+
return path.join(repoPath, 'workspaces', `${workspace}.${pid}.${token}.slock`);
|
|
54
|
+
}
|
|
55
|
+
/** Glob prefix used to find all shared lock files for a workspace. */
|
|
56
|
+
function sharedLockPrefix(workspace) {
|
|
57
|
+
return `${workspace}.`;
|
|
58
|
+
}
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Lock File I/O
|
|
61
|
+
// =============================================================================
|
|
64
62
|
async function readLockState(lockPath) {
|
|
65
63
|
try {
|
|
66
64
|
const data = await fs.readFile(lockPath);
|
|
@@ -73,23 +71,15 @@ async function readLockState(lockPath) {
|
|
|
73
71
|
return null;
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Write lock state to a lock file in beast2 format.
|
|
78
|
-
*/
|
|
79
74
|
async function writeLockState(lockPath, state) {
|
|
80
75
|
const encoder = encodeBeast2For(LockStateType);
|
|
81
|
-
|
|
82
|
-
await fs.writeFile(lockPath, data);
|
|
76
|
+
await fs.writeFile(lockPath, encoder(state));
|
|
83
77
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Convert LockState to LockHolderInfo for error display.
|
|
86
|
-
*/
|
|
87
78
|
export function lockStateToHolderInfo(state) {
|
|
88
79
|
const info = {
|
|
89
80
|
acquiredAt: state.acquiredAt.toISOString(),
|
|
90
81
|
operation: state.operation.type,
|
|
91
82
|
};
|
|
92
|
-
// Parse the holder string to extract process-specific fields
|
|
93
83
|
const holder = parseHolder(state.holder);
|
|
94
84
|
if (holder?.type === 'process') {
|
|
95
85
|
info.pid = Number(holder.value.pid);
|
|
@@ -99,82 +89,170 @@ export function lockStateToHolderInfo(state) {
|
|
|
99
89
|
}
|
|
100
90
|
return info;
|
|
101
91
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*/
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// Stale Lock Detection
|
|
94
|
+
// =============================================================================
|
|
106
95
|
export async function isLockHolderAlive(holderStr) {
|
|
107
96
|
const holder = parseHolder(holderStr);
|
|
108
97
|
if (!holder)
|
|
109
|
-
return true; // Can't parse
|
|
98
|
+
return true; // Can't parse — assume alive
|
|
110
99
|
if (holder.type === 'process') {
|
|
111
100
|
return isProcessAlive(Number(holder.value.pid), Number(holder.value.startTime), holder.value.bootId);
|
|
112
101
|
}
|
|
113
|
-
// Unknown
|
|
114
|
-
|
|
102
|
+
return true; // Unknown type — assume alive
|
|
103
|
+
}
|
|
104
|
+
/** Delete a lock file if its holder process is no longer alive. */
|
|
105
|
+
async function cleanIfStale(lockPath) {
|
|
106
|
+
const state = await readLockState(lockPath);
|
|
107
|
+
if (!state) {
|
|
108
|
+
// Empty or unreadable — treat as stale
|
|
109
|
+
try {
|
|
110
|
+
await fs.unlink(lockPath);
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!(await isLockHolderAlive(state.holder))) {
|
|
116
|
+
try {
|
|
117
|
+
await fs.unlink(lockPath);
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Find and clean all stale shared lock files for a workspace. */
|
|
123
|
+
async function cleanStaleSharedLocks(repoPath, workspace) {
|
|
124
|
+
const dir = workspaceLockDir(repoPath, workspace);
|
|
125
|
+
const prefix = sharedLockPrefix(workspace);
|
|
126
|
+
let entries;
|
|
127
|
+
try {
|
|
128
|
+
entries = await fs.readdir(dir);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const slocks = entries.filter(e => e.startsWith(prefix) && e.endsWith('.slock'));
|
|
134
|
+
await Promise.all(slocks.map(e => cleanIfStale(path.join(dir, e))));
|
|
135
|
+
}
|
|
136
|
+
/** Return paths of all live shared lock files for a workspace. */
|
|
137
|
+
async function liveSharedLocks(repoPath, workspace) {
|
|
138
|
+
await cleanStaleSharedLocks(repoPath, workspace);
|
|
139
|
+
const dir = workspaceLockDir(repoPath, workspace);
|
|
140
|
+
const prefix = sharedLockPrefix(workspace);
|
|
141
|
+
try {
|
|
142
|
+
const entries = await fs.readdir(dir);
|
|
143
|
+
return entries
|
|
144
|
+
.filter(e => e.startsWith(prefix) && e.endsWith('.slock'))
|
|
145
|
+
.map(e => path.join(dir, e));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
115
150
|
}
|
|
116
151
|
// =============================================================================
|
|
117
152
|
// Lock Acquisition
|
|
118
153
|
// =============================================================================
|
|
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
|
|
154
|
+
async function buildLockState(operation) {
|
|
147
155
|
const pid = process.pid;
|
|
148
156
|
const bootId = await getBootId();
|
|
149
157
|
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
158
|
const holderVariant = variant('process', {
|
|
154
159
|
pid: BigInt(pid),
|
|
155
160
|
bootId,
|
|
156
161
|
startTime: BigInt(startTime),
|
|
157
|
-
command,
|
|
162
|
+
command: process.argv.join(' '),
|
|
158
163
|
});
|
|
159
164
|
const holder = printProcessHolder(holderVariant);
|
|
160
|
-
const
|
|
165
|
+
const state = {
|
|
161
166
|
operation,
|
|
162
167
|
holder,
|
|
163
|
-
acquiredAt,
|
|
168
|
+
acquiredAt: new Date(),
|
|
164
169
|
expiresAt: none,
|
|
165
170
|
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
return { state, holder };
|
|
172
|
+
}
|
|
173
|
+
/** Try once to acquire an exclusive lock. Returns lock path or null. */
|
|
174
|
+
async function tryExclusiveOnce(repoPath, workspace, operation) {
|
|
175
|
+
const lockPath = workspaceLockPath(repoPath, workspace);
|
|
176
|
+
// Clean stale exclusive lock
|
|
177
|
+
await cleanIfStale(lockPath);
|
|
178
|
+
// Fail if any live shared locks exist
|
|
179
|
+
const shared = await liveSharedLocks(repoPath, workspace);
|
|
180
|
+
if (shared.length > 0)
|
|
181
|
+
return null;
|
|
182
|
+
// Atomic create — fails with EEXIST if another process beat us
|
|
183
|
+
const { state } = await buildLockState(operation);
|
|
184
|
+
try {
|
|
185
|
+
const fd = await fs.open(lockPath, 'wx');
|
|
186
|
+
try {
|
|
187
|
+
const encoder = encodeBeast2For(LockStateType);
|
|
188
|
+
await fd.write(encoder(state));
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await fd.close();
|
|
192
|
+
}
|
|
193
|
+
return lockPath;
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
if (err.code === 'EEXIST')
|
|
197
|
+
return null;
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Try once to acquire a shared lock. Returns lock path or null. */
|
|
202
|
+
async function trySharedOnce(repoPath, workspace, operation) {
|
|
203
|
+
const exclusivePath = workspaceLockPath(repoPath, workspace);
|
|
204
|
+
// Clean stale exclusive lock
|
|
205
|
+
await cleanIfStale(exclusivePath);
|
|
206
|
+
// Fail if a live exclusive lock exists
|
|
207
|
+
const exclusiveState = await readLockState(exclusivePath);
|
|
208
|
+
if (exclusiveState && (await isLockHolderAlive(exclusiveState.holder))) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
// Create our shared lock file
|
|
212
|
+
const token = randomBytes(4).toString('hex');
|
|
213
|
+
const sPath = sharedLockPath(repoPath, workspace, process.pid, token);
|
|
214
|
+
const { state } = await buildLockState(operation);
|
|
215
|
+
await writeLockState(sPath, state);
|
|
216
|
+
// Re-check that no exclusive lock appeared between our check and our write
|
|
217
|
+
const recheckState = await readLockState(exclusivePath);
|
|
218
|
+
if (recheckState && (await isLockHolderAlive(recheckState.holder))) {
|
|
219
|
+
try {
|
|
220
|
+
await fs.unlink(sPath);
|
|
221
|
+
}
|
|
222
|
+
catch { }
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return sPath;
|
|
226
|
+
}
|
|
227
|
+
const POLL_INTERVAL_MS = 100;
|
|
228
|
+
/**
|
|
229
|
+
* Acquire an exclusive or shared lock on a workspace.
|
|
230
|
+
*
|
|
231
|
+
* Uses atomic O_CREAT|O_EXCL file creation (exclusive) or per-holder slock
|
|
232
|
+
* files (shared). Works on Linux, macOS, and Windows without external commands.
|
|
233
|
+
*
|
|
234
|
+
* @throws {WorkspaceLockError} If the lock cannot be acquired
|
|
235
|
+
*/
|
|
236
|
+
export async function acquireWorkspaceLock(repoPath, workspace, operation, options = {}) {
|
|
237
|
+
await fs.mkdir(path.join(repoPath, 'workspaces'), { recursive: true });
|
|
238
|
+
const isShared = options.mode === 'shared';
|
|
239
|
+
const deadline = Date.now() + (options.wait ? (options.timeout ?? 30000) : 0);
|
|
240
|
+
const tryOnce = isShared
|
|
241
|
+
? () => trySharedOnce(repoPath, workspace, operation)
|
|
242
|
+
: () => tryExclusiveOnce(repoPath, workspace, operation);
|
|
243
|
+
let lockPath = await tryOnce();
|
|
244
|
+
while (lockPath === null && Date.now() < deadline) {
|
|
245
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
246
|
+
lockPath = await tryOnce();
|
|
247
|
+
}
|
|
248
|
+
if (lockPath === null) {
|
|
249
|
+
const exclusivePath = workspaceLockPath(repoPath, workspace);
|
|
250
|
+
const existingState = await readLockState(exclusivePath);
|
|
172
251
|
const holderInfo = existingState ? lockStateToHolderInfo(existingState) : undefined;
|
|
173
252
|
throw new WorkspaceLockError(workspace, holderInfo);
|
|
174
253
|
}
|
|
175
|
-
// Lock acquired! Create handle
|
|
176
254
|
let released = false;
|
|
177
|
-
|
|
255
|
+
return {
|
|
178
256
|
resource: workspace,
|
|
179
257
|
workspace,
|
|
180
258
|
lockPath,
|
|
@@ -182,145 +260,30 @@ export async function acquireWorkspaceLock(repoPath, workspace, operation, optio
|
|
|
182
260
|
if (released)
|
|
183
261
|
return;
|
|
184
262
|
released = true;
|
|
185
|
-
// Kill the flock subprocess to release the lock
|
|
186
|
-
flockProcess.kill('SIGTERM');
|
|
187
|
-
// Clean up lock file (best effort)
|
|
188
263
|
try {
|
|
189
264
|
await fs.unlink(lockPath);
|
|
190
265
|
}
|
|
191
|
-
catch {
|
|
192
|
-
// Ignore - file might already be gone
|
|
193
|
-
}
|
|
266
|
+
catch { }
|
|
194
267
|
},
|
|
195
268
|
};
|
|
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
|
-
// Use 'sh -c "echo ready && cat"' as the inner command so that "ready"
|
|
227
|
-
// on stdout is a deterministic signal that flock acquired the lock and
|
|
228
|
-
// started the inner command. `cat` then blocks on stdin to keep the
|
|
229
|
-
// subprocess (and therefore the lock) alive until we kill it.
|
|
230
|
-
args.push(lockPath, 'sh', '-c', 'echo ready && cat');
|
|
231
|
-
const child = spawn('flock', args, {
|
|
232
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
233
|
-
detached: false,
|
|
234
|
-
});
|
|
235
|
-
return new Promise((resolve) => {
|
|
236
|
-
let resolved = false;
|
|
237
|
-
// If flock fails to acquire, it exits with code 1
|
|
238
|
-
child.on('error', () => {
|
|
239
|
-
if (!resolved) {
|
|
240
|
-
resolved = true;
|
|
241
|
-
resolve(null);
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
child.on('exit', () => {
|
|
245
|
-
if (!resolved) {
|
|
246
|
-
resolved = true;
|
|
247
|
-
// Exit code 1 means lock is held by another
|
|
248
|
-
resolve(null);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
// When flock acquires the lock, the inner command prints "ready" to
|
|
252
|
-
// stdout. This is a deterministic signal — no timing assumptions.
|
|
253
|
-
child.stdout.on('data', (data) => {
|
|
254
|
-
if (!resolved && data.toString().includes('ready')) {
|
|
255
|
-
resolved = true;
|
|
256
|
-
// Write lock state before resolving so release() can safely unlink
|
|
257
|
-
writeLockState(lockPath, lockState)
|
|
258
|
-
.then(() => {
|
|
259
|
-
resolve(child);
|
|
260
|
-
})
|
|
261
|
-
.catch(() => {
|
|
262
|
-
// Can't write state — release the kernel lock and report failure
|
|
263
|
-
child.kill('SIGTERM');
|
|
264
|
-
resolve(null);
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
269
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
*/
|
|
274
|
-
async function checkAndCleanStaleLock(lockPath) {
|
|
275
|
-
const state = await readLockState(lockPath);
|
|
276
|
-
if (!state)
|
|
277
|
-
return;
|
|
278
|
-
// Check if the holder is still alive
|
|
279
|
-
const alive = await isLockHolderAlive(state.holder);
|
|
280
|
-
if (!alive) {
|
|
281
|
-
// Stale lock - try to remove it
|
|
282
|
-
try {
|
|
283
|
-
await fs.unlink(lockPath);
|
|
284
|
-
}
|
|
285
|
-
catch {
|
|
286
|
-
// Ignore - another process might have cleaned it up
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Get the lock state for a workspace.
|
|
292
|
-
*
|
|
293
|
-
* @param repoPath - Path to e3 repository
|
|
294
|
-
* @param workspace - Workspace name to check
|
|
295
|
-
* @returns Lock state if locked, null if not locked
|
|
296
|
-
*/
|
|
270
|
+
// =============================================================================
|
|
271
|
+
// Status Queries
|
|
272
|
+
// =============================================================================
|
|
297
273
|
export async function getWorkspaceLockState(repoPath, workspace) {
|
|
298
274
|
const lockPath = workspaceLockPath(repoPath, workspace);
|
|
299
275
|
const state = await readLockState(lockPath);
|
|
300
276
|
if (!state)
|
|
301
277
|
return null;
|
|
302
|
-
|
|
303
|
-
const alive = await isLockHolderAlive(state.holder);
|
|
304
|
-
if (!alive) {
|
|
305
|
-
// Stale lock - clean it up and report as not locked
|
|
278
|
+
if (!(await isLockHolderAlive(state.holder))) {
|
|
306
279
|
try {
|
|
307
280
|
await fs.unlink(lockPath);
|
|
308
281
|
}
|
|
309
|
-
catch {
|
|
310
|
-
// Ignore
|
|
311
|
-
}
|
|
282
|
+
catch { }
|
|
312
283
|
return null;
|
|
313
284
|
}
|
|
314
285
|
return state;
|
|
315
286
|
}
|
|
316
|
-
/**
|
|
317
|
-
* Get lock holder info for a workspace (for backwards compatibility).
|
|
318
|
-
*
|
|
319
|
-
* @param repoPath - Path to e3 repository
|
|
320
|
-
* @param workspace - Workspace name to check
|
|
321
|
-
* @returns Lock holder info if locked, null if not locked
|
|
322
|
-
* @deprecated Use getWorkspaceLockState for full lock information
|
|
323
|
-
*/
|
|
324
287
|
export async function getWorkspaceLockHolder(repoPath, workspace) {
|
|
325
288
|
const state = await getWorkspaceLockState(repoPath, workspace);
|
|
326
289
|
return state ? lockStateToHolderInfo(state) : null;
|
|
@@ -328,29 +291,17 @@ export async function getWorkspaceLockHolder(repoPath, workspace) {
|
|
|
328
291
|
// =============================================================================
|
|
329
292
|
// LockService Interface Implementation
|
|
330
293
|
// =============================================================================
|
|
331
|
-
/**
|
|
332
|
-
* Local filesystem implementation of LockService.
|
|
333
|
-
*
|
|
334
|
-
* Uses flock() for kernel-managed locking with lock state
|
|
335
|
-
* stored in beast2 format using LockStateType.
|
|
336
|
-
* The `repo` parameter is the path to the e3 repository directory.
|
|
337
|
-
*/
|
|
338
294
|
export class LocalLockService {
|
|
339
295
|
async acquire(repo, resource, operation, options) {
|
|
340
|
-
const acquireOptions = {
|
|
341
|
-
wait: options?.wait ?? false,
|
|
342
|
-
timeout: options?.timeout,
|
|
343
|
-
mode: options?.mode ?? 'exclusive',
|
|
344
|
-
};
|
|
345
296
|
try {
|
|
346
|
-
const handle = await acquireWorkspaceLock(repo, resource, operation,
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
};
|
|
297
|
+
const handle = await acquireWorkspaceLock(repo, resource, operation, {
|
|
298
|
+
wait: options?.wait ?? false,
|
|
299
|
+
timeout: options?.timeout,
|
|
300
|
+
mode: options?.mode ?? 'exclusive',
|
|
301
|
+
});
|
|
302
|
+
return { resource, release: () => handle.release() };
|
|
351
303
|
}
|
|
352
304
|
catch {
|
|
353
|
-
// Lock couldn't be acquired
|
|
354
305
|
return null;
|
|
355
306
|
}
|
|
356
307
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalLockService.js","sourceRoot":"","sources":["../../../../src/storage/local/LocalLockService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"LocalLockService.js","sourceRoot":"","sources":["../../../../src/storage/local/LocalLockService.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elaraai/e3-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "East Execution Engine Core - Programmatic API for e3 repository operations",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"yauzl": "^3.2.0",
|
|
38
38
|
"yazl": "^2.5.1",
|
|
39
|
-
"@elaraai/e3
|
|
40
|
-
"@elaraai/e3": "1.0.
|
|
39
|
+
"@elaraai/e3": "1.0.2",
|
|
40
|
+
"@elaraai/e3-types": "1.0.2"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@elaraai/east": "1.0.
|
|
43
|
+
"@elaraai/east": "1.0.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^22.0.0",
|