bookworm-smart-assistant/hooks/lib/safe-append.js

92 lines
2.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 共享安全追加写入模块 (E-01/E-02 修复版)
*
* 修复:
* - 锁获取失败时重试 3 次 (10ms 间隔),而非静默降级
* - 孤儿锁检测: 锁文件超过 10 秒自动清理
* - fd 级操作减少 Windows NTFS 竞争窗口
*/
const fs = require('fs');
const path = require('path');
const LOCK_STALE_MS = 10000; // 10 秒锁超时
const LOCK_RETRIES = 3;
const LOCK_RETRY_MS = 10;
/**
* 尝试获取 O_EXCL 锁,带孤儿锁检测和重试
* @returns {number|null} 锁文件 fd 或 null
*/
function acquireAppendLock(lockFile) {
for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
try {
return fs.openSync(lockFile, 'wx');
} catch (e) {
// 锁已存在 — 检查是否为孤儿锁
try {
const stat = fs.statSync(lockFile);
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
// 孤儿锁: 删除后重试
try { fs.unlinkSync(lockFile); } catch {}
continue;
}
} catch {}
// 锁被持有且未过期 — 无进程短 sleep (替代 execSync 子进程开销)
if (attempt < LOCK_RETRIES - 1) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
}
}
}
return null; // 重试耗尽
}
/**
* 安全追加一行 JSONL 到文件
* @param {string} filePath - 目标文件路径
* @param {object} entry - 要序列化的 JSON 对象
* @param {object} [opts]
* @param {boolean} [opts.useLock=false] - 是否使用文件锁
*/
function safeAppendJsonl(filePath, entry, opts = {}) {
const line = JSON.stringify(entry) + '\n';
const dir = path.dirname(filePath);
try {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
} catch {}
if (opts.useLock) {
const lockFile = filePath + '.append.lock';
const lockFd = acquireAppendLock(lockFile);
if (lockFd !== null) {
// 持锁写入
try {
fs.appendFileSync(filePath, line);
} finally {
try { fs.closeSync(lockFd); fs.unlinkSync(lockFile); } catch {}
}
} else {
// H3 修复: 锁耗尽改 fail-close写入 fallback 文件而非主日志,防止行交错
const fallbackFile = filePath + '.lock-failed.jsonl';
try {
process.stderr.write('[safe-append] lock exhausted for ' + path.basename(filePath) + ', wrote to fallback\n');
} catch {}
try { fs.appendFileSync(fallbackFile, line); } catch {}
}
} else {
// 无锁模式: fd 级操作
let fd;
try {
fd = fs.openSync(filePath, 'a');
fs.writeSync(fd, line);
} catch {}
finally {
if (fd !== undefined) try { fs.closeSync(fd); } catch {}
}
}
}
module.exports = { safeAppendJsonl };