Skip to content

Commit c3544ce

Browse files
fix: wait for all persistent-state deletions to settle before advancing clear queue (review feedback)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b7253a6a-a3c5-4606-b9cf-ec52db1e605f
1 parent 3df425a commit c3544ce

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

src/common/persistentState.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ class PersistentStateImpl implements PersistentState {
4141
const preservedKeys = new Set(options?.preserveKeys ?? []);
4242
const operation = this.clearQueue.then(async () => {
4343
const keysToClear = (requestedKeys ?? this.momento.keys()).filter((key) => !preservedKeys.has(key));
44-
await Promise.all(keysToClear.map((key) => this.momento.update(key, undefined)));
44+
const results = await Promise.allSettled(keysToClear.map((key) => this.momento.update(key, undefined)));
45+
const failure = results.find(
46+
(result): result is PromiseRejectedResult => result.status === 'rejected',
47+
);
48+
if (failure) {
49+
throw failure.reason;
50+
}
4551
});
4652
this.clearQueue = operation.catch(() => undefined);
4753
return operation;

src/test/common/persistentState.unit.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,46 @@ suite('persistent state clearing', () => {
136136
await workspaceState.clear(['new-key']);
137137
assert.strictEqual(await workspaceState.get('new-key'), undefined);
138138
});
139+
140+
test('holds the queue until every deletion settles when a clear partially fails', async () => {
141+
workspace.reset({
142+
'fail-fast': 'unused',
143+
'slow-delete': 'stale',
144+
});
145+
const gate = createGate();
146+
workspace.beforeUpdate = async (key, value) => {
147+
if (key === 'fail-fast' && value === undefined) {
148+
throw new Error('memento update failed');
149+
}
150+
if (key === 'slow-delete' && value === undefined) {
151+
gate.started.resolve();
152+
await gate.release.promise;
153+
}
154+
};
155+
156+
const failedClear = workspaceState.clear(['fail-fast', 'slow-delete']);
157+
await gate.started.promise;
158+
159+
// A later write is queued while the failing clear's slow deletion is still pending.
160+
const laterSet = workspaceState.set('slow-delete', 'written-later');
161+
162+
// The queue must not advance past the clear until the slow deletion settles,
163+
// so the later write has not been applied yet.
164+
assert.strictEqual(workspace.values.get('slow-delete'), 'stale');
165+
166+
gate.release.resolve();
167+
await assert.rejects(failedClear, /memento update failed/);
168+
await laterSet;
169+
170+
// The later write wins because it was serialized strictly after the deletion settled;
171+
// it is not clobbered by a late in-flight deletion from the failed clear.
172+
assert.strictEqual(workspace.values.get('slow-delete'), 'written-later');
173+
assert.strictEqual(await workspaceState.get('slow-delete'), 'written-later');
174+
assert.deepStrictEqual(
175+
workspace.updates.filter((update) => update.key === 'slow-delete').map((update) => update.value),
176+
[undefined, 'written-later'],
177+
);
178+
});
139179
});
140180

141181
interface TestMemento {

0 commit comments

Comments
 (0)