mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-16 05:05:30 +02:00
fix(state-store): keep cleanup diagnostics off caller errors
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
const { parentPort, workerData } = require('worker_threads');
|
||||
const { createStateStore } = require('../state-store');
|
||||
const { attachCleanupError } = require('../state-store/file-lock');
|
||||
const { recordCleanupError } = require('../state-store/file-lock');
|
||||
const { claimWorkItem, moveWorkItem } = require('./work-item-mutations');
|
||||
|
||||
/** Complete one board mutation away from the HTTP event loop, then close. */
|
||||
@@ -18,7 +18,7 @@ async function mutate() {
|
||||
catch (error) { failed = true; primary = error; }
|
||||
try { store.close(); }
|
||||
catch (error) {
|
||||
if (failed) attachCleanupError(primary, 'closeError', error);
|
||||
if (failed) recordCleanupError(primary, 'closeError', error);
|
||||
else { failed = true; primary = error; }
|
||||
}
|
||||
if (failed) throw primary;
|
||||
|
||||
@@ -7,12 +7,19 @@ const { performance } = require('perf_hooks');
|
||||
const WAIT_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
||||
const DEFAULT_TIMEOUT_MS = 5000;
|
||||
|
||||
// Cleanup must never replace an arbitrary primary throw (including frozen errors,
|
||||
// primitives, or objects with throwing property traps). Diagnostics are best effort.
|
||||
function attachCleanupError(primary, name, secondary) {
|
||||
const CLEANUP_ERRORS = new WeakMap();
|
||||
const EMPTY_CLEANUP_ERRORS = Object.freeze({});
|
||||
|
||||
// Preserve the original throw's identity and properties, including frozen errors
|
||||
// and proxies. Primitive throws cannot own diagnostics and are left unchanged.
|
||||
function recordCleanupError(primary, name, secondary) {
|
||||
if (primary === null || (typeof primary !== 'object' && typeof primary !== 'function')) return;
|
||||
try { Object.defineProperty(primary, name, { value: secondary, configurable: true }); }
|
||||
catch (_error) { /* Preserve the primary when it cannot accept diagnostics. */ }
|
||||
CLEANUP_ERRORS.set(primary, Object.freeze({ ...getCleanupErrors(primary), [name]: secondary }));
|
||||
}
|
||||
|
||||
// Frozen snapshots are local to this module instance; they do not cross workers.
|
||||
function getCleanupErrors(primary) {
|
||||
return CLEANUP_ERRORS.get(primary) || EMPTY_CLEANUP_ERRORS;
|
||||
}
|
||||
|
||||
function hasCode(error, code) {
|
||||
@@ -53,7 +60,7 @@ function releaseOwnedLock(lockPath, descriptor, identity) {
|
||||
fs.closeSync(descriptor); // Exactly one attempt, even after a release failure.
|
||||
} catch (error) {
|
||||
closeFailed = true;
|
||||
if (failed) attachCleanupError(primary, 'closeError', error);
|
||||
if (failed) recordCleanupError(primary, 'closeError', error);
|
||||
else { failed = true; primary = error; }
|
||||
}
|
||||
if (pendingDelete && !closeFailed) {
|
||||
@@ -100,7 +107,7 @@ function acquireLock(dbPath, timeoutMs) {
|
||||
if (identity) releaseOwnedLock(lockPath, descriptor, identity);
|
||||
else fs.closeSync(descriptor);
|
||||
} catch (releaseError) {
|
||||
attachCleanupError(error, 'releaseError', releaseError);
|
||||
recordCleanupError(error, 'releaseError', releaseError);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -127,10 +134,10 @@ function withStateStoreLock(dbPath, callback, { timeoutMs = DEFAULT_TIMEOUT_MS }
|
||||
release();
|
||||
} catch (releaseError) {
|
||||
if (!failed) throw releaseError;
|
||||
attachCleanupError(primaryError, 'releaseError', releaseError);
|
||||
recordCleanupError(primaryError, 'releaseError', releaseError);
|
||||
}
|
||||
if (failed) throw primaryError;
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = { withStateStoreLock, attachCleanupError };
|
||||
module.exports = { withStateStoreLock, recordCleanupError, getCleanupErrors };
|
||||
|
||||
@@ -5,7 +5,7 @@ const crypto = require('crypto');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const initSqlJs = require('sql.js');
|
||||
const { withStateStoreLock, attachCleanupError } = require('./file-lock');
|
||||
const { withStateStoreLock, recordCleanupError } = require('./file-lock');
|
||||
|
||||
const { applyMigrations, getAppliedMigrations } = require('./migrations');
|
||||
const { createQueryApi } = require('./queries');
|
||||
@@ -229,7 +229,7 @@ function wrapSqlJsDatabase(SQL, dbPath) {
|
||||
} catch (error) {
|
||||
// The replacement is not adopted until both initialization and the old
|
||||
// handle's close succeed. Never reuse an uncertain previous handle.
|
||||
try { latest.close(); } catch (closeError) { attachCleanupError(error, 'closeError', closeError); }
|
||||
try { latest.close(); } catch (closeError) { recordCleanupError(error, 'closeError', closeError); }
|
||||
throw error;
|
||||
}
|
||||
rawDb = latest;
|
||||
@@ -324,9 +324,9 @@ function wrapSqlJsDatabase(SQL, dbPath) {
|
||||
} catch (rollbackError) {
|
||||
// Never reuse an uncertain transaction, including an in-memory store.
|
||||
closed = true;
|
||||
try { rawDb.close(); } catch (closeError) { attachCleanupError(error, 'closeError', closeError); }
|
||||
try { rawDb.close(); } catch (closeError) { recordCleanupError(error, 'closeError', closeError); }
|
||||
rawDb = null;
|
||||
attachCleanupError(error, 'rollbackError', rollbackError);
|
||||
recordCleanupError(error, 'rollbackError', rollbackError);
|
||||
}
|
||||
dirty = previouslyDirty;
|
||||
throw error;
|
||||
@@ -398,7 +398,7 @@ async function createStateStore(options = {}) {
|
||||
try {
|
||||
appliedMigrations = db.withSnapshot(() => applyMigrations(db));
|
||||
} catch (error) {
|
||||
try { db.close(); } catch (closeError) { attachCleanupError(error, 'closeError', closeError); }
|
||||
try { db.close(); } catch (closeError) { recordCleanupError(error, 'closeError', closeError); }
|
||||
throw error;
|
||||
}
|
||||
const queryApi = createQueryApi(db);
|
||||
|
||||
Reference in New Issue
Block a user