Skip to content

Commit df42020

Browse files
authored
fix(ids): drain queued registry updates during active flush
Refs #621
1 parent 5c70e91 commit df42020

3 files changed

Lines changed: 69 additions & 12 deletions

File tree

.aiox-core/core/ids/registry-updater.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,22 @@ class RegistryUpdater {
203203
const normalized = path.resolve(filePath).replace(/\\/g, '/');
204204
this._pendingUpdates.set(normalized, { action, filePath: normalized, timestamp: Date.now() });
205205

206+
this._scheduleFlush('Flush failed');
207+
}
208+
209+
_scheduleFlush(errorLabel) {
206210
if (this._debounceTimer) {
207211
clearTimeout(this._debounceTimer);
208212
}
213+
209214
this._debounceTimer = setTimeout(() => {
215+
this._debounceTimer = null;
210216
this._flushPending().catch((err) => {
211-
console.error(`[IDS-Updater] Flush failed: ${err.message}`);
217+
console.error(`[IDS-Updater] ${errorLabel}: ${err.message}`);
212218
this._isProcessing = false;
219+
if (this._pendingUpdates.size > 0) {
220+
this._scheduleFlush('Deferred flush failed');
221+
}
213222
});
214223
}, this._debounceMs);
215224
}
@@ -219,14 +228,7 @@ class RegistryUpdater {
219228

220229
if (this._isProcessing) {
221230
// Re-schedule flush — don't drop pending updates
222-
if (!this._debounceTimer) {
223-
this._debounceTimer = setTimeout(() => {
224-
this._flushPending().catch((err) => {
225-
console.error(`[IDS-Updater] Deferred flush failed: ${err.message}`);
226-
this._isProcessing = false;
227-
});
228-
}, this._debounceMs);
229-
}
231+
this._scheduleFlush('Deferred flush failed');
230232
return;
231233
}
232234

.aiox-core/install-manifest.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# - File types for categorization
99
#
1010
version: 5.1.15
11-
generated_at: "2026-05-08T00:32:00.816Z"
11+
generated_at: "2026-05-08T02:21:46.248Z"
1212
generator: scripts/generate-install-manifest.js
1313
file_count: 1104
1414
files:
@@ -745,9 +745,9 @@ files:
745745
type: core
746746
size: 8096
747747
- path: core/ids/registry-updater.js
748-
hash: sha256:4873507324efab32ba59e1ade70aaa79f318f9c5d689bdb747d8721effba38d1
748+
hash: sha256:194ecce0795fbf8654a7c69799252765d738d50274dd406f575d796c21c3b754
749749
type: core
750-
size: 24542
750+
size: 24513
751751
- path: core/ids/verification-gate.js
752752
hash: sha256:96050661c90fa52bfc755911d02c9194ec35c00e71fc6bbc92a13686dd53bb91
753753
type: core

tests/core/ids/registry-updater.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,61 @@ describe('RegistryUpdater', () => {
703703
expect(registry.entities).toBeDefined();
704704
expect(registry.entities.tasks).toBeDefined();
705705
});
706+
707+
it('drains watcher updates queued while a batch is processing', async () => {
708+
jest.useFakeTimers();
709+
710+
try {
711+
const updater = createUpdater({ debounceMs: 10 });
712+
const batches = [];
713+
let releaseFirstBatch;
714+
const firstBatchGate = new Promise((resolve) => {
715+
releaseFirstBatch = resolve;
716+
});
717+
718+
updater._executeBatch = jest.fn(async (batch) => {
719+
batches.push(batch.map((item) => path.basename(item.filePath)));
720+
updater._isProcessing = true;
721+
722+
try {
723+
if (batches.length === 1) {
724+
await firstBatchGate;
725+
}
726+
return { updated: batch.length, errors: [] };
727+
} finally {
728+
updater._isProcessing = false;
729+
}
730+
});
731+
732+
const firstFile = createTempFile(
733+
'.aiox-core/development/tasks/queued-a.md',
734+
'# Queued A\n\n## Purpose\nFirst queued update.\n',
735+
);
736+
updater._queueUpdate('add', firstFile);
737+
await jest.advanceTimersByTimeAsync(10);
738+
739+
expect(batches).toEqual([['queued-a.md']]);
740+
741+
const secondFile = createTempFile(
742+
'.aiox-core/development/tasks/queued-b.md',
743+
'# Queued B\n\n## Purpose\nSecond queued update.\n',
744+
);
745+
updater._queueUpdate('add', secondFile);
746+
await jest.advanceTimersByTimeAsync(10);
747+
748+
expect(updater.getStats().pendingUpdates).toBe(1);
749+
750+
releaseFirstBatch();
751+
await Promise.resolve();
752+
await jest.advanceTimersByTimeAsync(10);
753+
754+
expect(updater._executeBatch).toHaveBeenCalledTimes(2);
755+
expect(batches).toEqual([['queued-a.md'], ['queued-b.md']]);
756+
expect(updater.getStats().pendingUpdates).toBe(0);
757+
} finally {
758+
jest.useRealTimers();
759+
}
760+
});
706761
});
707762

708763
describe('Performance (AC: 7)', () => {

0 commit comments

Comments
 (0)