perf: prevent import hanging with timeouts and batching
- verifyAndFixInteractionModesCount: paginated DB queries (500/batch) instead of loading all items into memory at once - withFurniDataLock: add 60s chain timeout to prevent deadlocks when a lock holder stalls or crashes - withGamedataLock: same timeout protection for gamedata locks - conversion-pool: add 60s per-job timeout, fall back to main thread - furni/batch: abort signal + post_import progress events + skip post-import steps when client disconnects - furni/batch-regen: abort signal + early exit when disconnected - clone/sync-all: pre-fetch furnidata once per source instead of per item (eliminates 2000+ redundant fetches) - sse-client: add 60s idle timeout to prevent infinite hangs
This commit is contained in:
1 parent
f70d96b81c
commit
7556b1f3fa
5 files changed
+222
-93
No files matched your search
@@ -184,14 +184,42 @@ export async function findFurniDataIdConflict(
|
||||
}
|
||||
}
|
||||
|
||||
const CHAIN_TIMEOUT_MS = 60_000;
|
||||
|
||||
/**
|
||||
* Wait for the previous lock holder with a timeout.
|
||||
* If the holder stalled (e.g. process crash, unhandled rejection),
|
||||
* we break the chain instead of waiting forever.
|
||||
*/
|
||||
async function waitForFurniDataLock(): Promise<void> {
|
||||
const prev = furniDataLock;
|
||||
if (prev === Promise.resolve()) return;
|
||||
|
||||
try {
|
||||
await Promise.race([
|
||||
prev,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error("furni-data chain timeout")),
|
||||
CHAIN_TIMEOUT_MS,
|
||||
),
|
||||
),
|
||||
]);
|
||||
} catch {
|
||||
// Chain holder timed out or crashed — break the chain
|
||||
furniDataLock = Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export async function withFurniDataLock<T>(fn: () => Promise<T>): Promise<T> {
|
||||
let release!: () => void;
|
||||
const acquired = new Promise<void>((r) => {
|
||||
release = r;
|
||||
});
|
||||
const prev = furniDataLock;
|
||||
|
||||
await waitForFurniDataLock();
|
||||
furniDataLock = acquired;
|
||||
await prev;
|
||||
|
||||
const lockBase = await getFurnitureDataReadPath();
|
||||
await fs.mkdir(path.dirname(lockBase), { recursive: true });
|
||||
const lockPath = `${lockBase}.lock`;
|
||||
@@ -200,6 +228,7 @@ export async function withFurniDataLock<T>(fn: () => Promise<T>): Promise<T> {
|
||||
return await fn();
|
||||
} finally {
|
||||
await fs.unlink(lockPath).catch(() => {});
|
||||
furniDataLock = Promise.resolve();
|
||||
release?.();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user