perf: prevent import hanging with timeouts and batching
CI / check (push) Successful in 31s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m1s

- 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:
openhands committed 2026-08-31 18:25:32 +02:00
1 parent f70d96b81c
commit 7556b1f3fa
5 files changed
+222 -93

No files matched your search

+31 -2
View File
@@ -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?.();
}
}