mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
Retry transient Windows pet-file replacement without overwriting changes
Windows recorder CI now passes 56 of 57 tests; the remaining live-reader fixture hit intermittent rename EPERM after two successful rotations. Retry EPERM/EBUSY for under two seconds, rechecking destination identity, size and modification time before each attempt. Persistent denial keeps the old file and archive. Validation: pet typecheck/build/test: 57 passed, 0 failed on macOS. npm test: 66 package, 12 SDK, 446 web passed; 0 failed. npm run check:web: 0 errors, 2 existing image warnings. Native bundle unchanged. Corrected-head Windows CI remains required. Signed-off-by: CodeWhale Bot <[email protected]>
This commit is contained in:
1 parent
369ec14441
commit
6d9605259d
2 files changed
+20
-2
No files matched your search
@@ -163,7 +163,10 @@ open junctions; the same particle identities return to the whale at rest.
|
||||
tests now use CRLF assets and fail against the previous controller. CLI tests
|
||||
use a test-only IPC preload for Windows shutdown-handler coverage because
|
||||
`child.kill('SIGINT')` forcibly terminates a Windows process; POSIX tests retain
|
||||
the real signal. Current Windows CI is required to verify the platform fix.
|
||||
the real signal. The next Windows run passed 56/57 tests but found intermittent
|
||||
reader contention during replacement. Windows replacement now retries for
|
||||
under two seconds and rechecks destination identity, size and modification
|
||||
time before each attempt. Current Windows CI is required to verify that fix.
|
||||
- Bounded Runtime input: a real local HTTP/SSE fixture supplies 260,001 records
|
||||
spanning nearly 29 hours of event timestamps. The importer retains fewer than
|
||||
512 events and 256 KiB of metadata, including a still-open human request. The
|
||||
|
||||
@@ -2,6 +2,7 @@ import { open, link, rename, unlink, lstat } from 'node:fs/promises';
|
||||
import { constants } from 'node:fs';
|
||||
import { dirname, basename, resolve } from 'node:path';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
import { PET_BIN_MS, validatePetBucket, encodePetJSONL } from '../../dist/core/pet-telemetry.js';
|
||||
|
||||
async function syncDirectory(path) {
|
||||
@@ -51,7 +52,21 @@ export async function createPetRecorder(path, { maxBuckets = 216_000, maxBytes =
|
||||
// Windows can reject replacement while either writer handle is
|
||||
// open. Both files are synced and the old file is archived first.
|
||||
await output.close(); output = undefined;
|
||||
await rename(temporary, path); installed = true;
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
const destination = await lstat(path, { bigint: true });
|
||||
if (!destination.isFile() || destination.dev !== held.dev || destination.ino !== held.ino
|
||||
|| destination.size !== held.size || destination.mtimeNs !== held.mtimeNs)
|
||||
throw new Error('The live pet recording changed while rotating; existing files were preserved.');
|
||||
try { await rename(temporary, path); break; }
|
||||
catch (error) {
|
||||
// A reader or file scanner can briefly deny replacement on
|
||||
// Windows. Retry for under two seconds, checking identity each
|
||||
// time; persistent denial still stops without deleting history.
|
||||
if (process.platform !== 'win32' || !['EPERM', 'EBUSY'].includes(error.code) || attempt >= 20) throw error;
|
||||
await delay(Math.min(100, (attempt + 1) * 25));
|
||||
}
|
||||
}
|
||||
installed = true;
|
||||
// The first row is already published. Account for it before any
|
||||
// fallible cleanup/report so a later append sees the actual file.
|
||||
sequence = 1; bytes = size; segment++;
|
||||
|
||||
Reference in new issue
Block a user