Skip to content

Commit 6cd894d

Browse files
committed
workspace: retry ready after failed connect
Clear the cached ready promise when a connection attempt rejects so later ready, push, pull, or exec calls can rebuild the backend session. Add a regression test for a flaky backend that succeeds after an initial failure.
1 parent ca1b05a commit 6cd894d

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

packages/workspace/src/workspace.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,28 @@ describe("Workspace backend fallback", () => {
154154
await expect(ws.ready()).rejects.toThrow(/boom[\s\S]*kaboom/);
155155
});
156156

157+
it("does not cache a failed ready() attempt", async () => {
158+
let attempts = 0;
159+
const backend: WorkspaceBackend = {
160+
id: "flaky",
161+
async connect() {
162+
attempts++;
163+
if (attempts === 1) throw new Error("temporary container disconnect");
164+
return {
165+
rpc: composite(fakeRpc()),
166+
close: async () => {},
167+
};
168+
},
169+
};
170+
const ws = new Workspace({ storage: makeStorage(), backends: [backend] });
171+
172+
await expect(ws.ready()).rejects.toThrow(/temporary container disconnect/);
173+
await ws.ready();
174+
175+
expect(attempts).toBe(2);
176+
expect(ws.shell).toBeDefined();
177+
});
178+
157179
it("ready() is idempotent — subsequent calls reuse the same connection", async () => {
158180
const backend = makeBackend("only");
159181
const spy = vi.spyOn(backend, "connect");

packages/workspace/src/workspace.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,14 +219,21 @@ export class Workspace {
219219
// the same in-flight connection attempt.
220220
ready(): Promise<void> {
221221
if (this.#readyPromise) return this.#readyPromise;
222-
this.#readyPromise = (async () => {
222+
const ready = (async () => {
223223
await this.#connect();
224224
// Index after the backend is wired so reads of mounted paths
225225
// are populated before the first push() inside an exec()
226226
// bracket can ship them.
227227
await this.#mountIndex.ensureIndexed();
228228
})();
229-
return this.#readyPromise;
229+
this.#readyPromise = ready;
230+
ready.catch(() => {
231+
// A failed connection attempt must not poison this Workspace forever.
232+
// The next ready()/push()/pull()/exec should re-enter #connect(), giving
233+
// backend factories a chance to pick a fresh transport.
234+
if (this.#readyPromise === ready) this.#readyPromise = undefined;
235+
});
236+
return ready;
230237
}
231238

232239
// Wrap this workspace in a WorkspaceStub so it can be handed

0 commit comments

Comments
 (0)