mustflow 2.112.8 → 2.112.9
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.
|
@@ -9,6 +9,7 @@ const LOCK_ROOT_RELATIVE_PATH = '.mustflow/state/locks';
|
|
|
9
9
|
export const ACTIVE_RUN_LOCK_ID_ENV = 'MUSTFLOW_ACTIVE_RUN_LOCK_ID';
|
|
10
10
|
const LOCK_MUTEX_STALE_MS = 30_000;
|
|
11
11
|
const LOCK_MUTEX_WAIT_MS = 1_000;
|
|
12
|
+
const RUN_STATE_UPDATE_MUTEX_WAIT_MS = 35_000;
|
|
12
13
|
const LOCK_MUTEX_SLEEP_MS = 25;
|
|
13
14
|
const LOCK_MUTEX_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
14
15
|
const LOCK_MUTEX_RECOVERY_DIRECTORY = 'mutex.recovery';
|
|
@@ -342,13 +343,14 @@ function recoverStaleMutexWithoutOwner(mutex) {
|
|
|
342
343
|
releaseRecovery();
|
|
343
344
|
}
|
|
344
345
|
}
|
|
345
|
-
function acquireMutex(projectRoot) {
|
|
346
|
+
function acquireMutex(projectRoot, options = {}) {
|
|
346
347
|
const root = activeLockRoot(projectRoot);
|
|
347
348
|
const mutex = activeLockMutexDirectory(projectRoot);
|
|
348
349
|
const ownerPath = path.join(mutex, 'owner.json');
|
|
349
350
|
const ownerToken = sha256(`${process.pid}:${Date.now()}:${process.hrtime.bigint()}`);
|
|
350
351
|
mkdirSync(root, { recursive: true });
|
|
351
352
|
const startedAt = Date.now();
|
|
353
|
+
const waitMs = options.waitMs ?? LOCK_MUTEX_WAIT_MS;
|
|
352
354
|
while (true) {
|
|
353
355
|
try {
|
|
354
356
|
mkdirSync(mutex);
|
|
@@ -378,7 +380,7 @@ function acquireMutex(projectRoot) {
|
|
|
378
380
|
if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'EEXIST') {
|
|
379
381
|
throw error;
|
|
380
382
|
}
|
|
381
|
-
if (Date.now() - startedAt >
|
|
383
|
+
if (Date.now() - startedAt > waitMs) {
|
|
382
384
|
const owner = readMutexOwner(ownerPath);
|
|
383
385
|
if (owner) {
|
|
384
386
|
if (mutexOwnerIsStale(owner) && recoverStaleMutexWithOwner(mutex, ownerPath, owner)) {
|
|
@@ -414,6 +416,15 @@ function acquireMutex(projectRoot) {
|
|
|
414
416
|
}
|
|
415
417
|
}
|
|
416
418
|
}
|
|
419
|
+
export function withRunStateUpdateMutex(projectRoot, callback) {
|
|
420
|
+
const releaseMutex = acquireMutex(projectRoot, { waitMs: RUN_STATE_UPDATE_MUTEX_WAIT_MS });
|
|
421
|
+
try {
|
|
422
|
+
return callback();
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
releaseMutex();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
417
428
|
export function inspectActiveRunLocks(projectRoot, contract, intentName) {
|
|
418
429
|
const effects = normalizeCommandEffects(projectRoot, contract, intentName);
|
|
419
430
|
const records = readActiveRecords(projectRoot);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { withRunStateUpdateMutex } from './active-run-locks.js';
|
|
3
4
|
import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
4
5
|
const PERFORMANCE_HISTORY_SCHEMA_VERSION = '1';
|
|
5
6
|
const PERFORMANCE_HISTORY_DIR = path.join('.mustflow', 'state', 'perf');
|
|
@@ -318,14 +319,16 @@ export function recordRunPerformanceHistory(projectRoot, receipt) {
|
|
|
318
319
|
return;
|
|
319
320
|
}
|
|
320
321
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
322
|
+
withRunStateUpdateMutex(projectRoot, () => {
|
|
323
|
+
const historyDir = path.join(projectRoot, PERFORMANCE_HISTORY_DIR);
|
|
324
|
+
const samplesPath = path.join(historyDir, PERFORMANCE_SAMPLES_FILE);
|
|
325
|
+
const summaryPath = path.join(historyDir, PERFORMANCE_SUMMARY_FILE);
|
|
326
|
+
const samples = enforceSizeLimit(pruneSamples([...readSamples(samplesPath), sample], sample.observed_day), sample.observed_day);
|
|
327
|
+
const samplesFile = createSamplesFile(samples);
|
|
328
|
+
const summaryFile = createSummary(samples, sample.observed_day);
|
|
329
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, samplesPath, samplesFile);
|
|
330
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, summaryPath, summaryFile);
|
|
331
|
+
});
|
|
329
332
|
}
|
|
330
333
|
catch {
|
|
331
334
|
// Performance history is a local optimization hint. A write failure must not affect command execution.
|
package/dist/core/run-profile.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { withRunStateUpdateMutex } from './active-run-locks.js';
|
|
3
4
|
import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
4
5
|
const RUN_PROFILE_SCHEMA_VERSION = '1';
|
|
5
6
|
const RUN_PROFILE_ENV = 'MUSTFLOW_RUN_PROFILE';
|
|
@@ -75,7 +76,9 @@ export class RunProfiler {
|
|
|
75
76
|
profile_path: getProfileRelativePath(),
|
|
76
77
|
};
|
|
77
78
|
const profilePath = path.join(input.projectRoot, RUN_PROFILE_DIR, LATEST_RUN_PROFILE);
|
|
78
|
-
|
|
79
|
+
withRunStateUpdateMutex(input.projectRoot, () => {
|
|
80
|
+
writeJsonFileInsideWithoutSymlinks(input.projectRoot, profilePath, profile);
|
|
81
|
+
});
|
|
79
82
|
}
|
|
80
83
|
recordPhase(name, startedAtMs) {
|
|
81
84
|
this.phases.push({
|
package/dist/core/run-receipt.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { withRunStateUpdateMutex } from './active-run-locks.js';
|
|
3
4
|
import { createStateRunId } from './atomic-state-write.js';
|
|
4
5
|
import { COMMAND_OUTPUT_LIMIT_SCOPE } from './command-output-limits.js';
|
|
5
6
|
import { decodeUtf8Tail } from './bounded-output.js';
|
|
@@ -295,9 +296,11 @@ export function writeRunReceipt(projectRoot, receipt, policy) {
|
|
|
295
296
|
if (relativeToRunDir.startsWith('..') || path.isAbsolute(relativeToRunDir)) {
|
|
296
297
|
throw new Error(`Run receipt path must stay inside ${RUN_RECEIPT_DIR}`);
|
|
297
298
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
withRunStateUpdateMutex(projectRoot, () => {
|
|
300
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, receiptPath, receipt);
|
|
301
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, latestPath, receipt);
|
|
302
|
+
if (policy) {
|
|
303
|
+
updateRunReceiptState(projectRoot, policy);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
303
306
|
}
|
package/package.json
CHANGED