Skip to content

Commit f70903a

Browse files
fix: include response handling in retries and dispose clients in build to avoid reconnecst (#162)
1 parent 0def4a3 commit f70903a

3 files changed

Lines changed: 43 additions & 70 deletions

File tree

src/API.ts

Lines changed: 19 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Client, Config } from "@hey-api/client-fetch";
2-
import { createClient, createConfig } from "@hey-api/client-fetch";
3-
import { handleResponse, retryWithDelay } from "./utils/api";
2+
import { handleResponse, retryWithDelay, createApiClient } from "./utils/api";
43
import { getInferredBaseUrl } from "./utils/constants";
54
import {
65
metaInfo,
@@ -56,54 +55,6 @@ import type {
5655
} from "./api-clients/client";
5756
import { PitcherManagerResponse } from "./types";
5857

59-
async function enhanceFetch(
60-
request: Request,
61-
instrumentation?: (request: Request) => Promise<Response>
62-
) {
63-
// Clone the request to modify headers
64-
const headers = new Headers(request.headers);
65-
const existingUserAgent = headers.get("User-Agent") || "";
66-
67-
// Extend User-Agent with SDK version
68-
headers.set(
69-
"User-Agent",
70-
`${existingUserAgent ? `${existingUserAgent} ` : ""}codesandbox-sdk/${
71-
// @ts-expect-error - Replaced at build time
72-
CSB_SDK_VERSION
73-
}`.trim()
74-
);
75-
76-
// Create new request with updated headers and optionally add instrumentation
77-
return instrumentation
78-
? instrumentation(
79-
new Request(request, {
80-
headers,
81-
})
82-
)
83-
: fetch(
84-
new Request(request, {
85-
headers,
86-
})
87-
);
88-
}
89-
90-
function createApiClient(
91-
apiKey: string,
92-
config: Config = {},
93-
instrumentation?: (request: Request) => Promise<Response>
94-
) {
95-
return createClient(
96-
createConfig({
97-
baseUrl: config.baseUrl || getInferredBaseUrl(apiKey),
98-
fetch: (request) => enhanceFetch(request, instrumentation),
99-
...config,
100-
headers: {
101-
Authorization: `Bearer ${apiKey}`,
102-
...config.headers,
103-
},
104-
})
105-
);
106-
}
10758

10859
export interface APIOptions {
10960
apiKey: string;
@@ -297,17 +248,18 @@ export class API {
297248
}
298249

299250
async hibernate(id: string, data?: VmHibernateData["body"]) {
300-
const response = await retryWithDelay(
301-
() =>
302-
vmHibernate({
251+
return retryWithDelay(
252+
async () => {
253+
const response = await vmHibernate({
303254
client: this.client,
304255
path: { id },
305256
body: data,
306-
}),
257+
});
258+
return handleResponse(response, `Failed to hibernate VM ${id}`);
259+
},
307260
3,
308261
200
309262
);
310-
return handleResponse(response, `Failed to hibernate VM ${id}`);
311263
}
312264

313265
async updateHibernationTimeout(
@@ -335,17 +287,18 @@ export class API {
335287
}
336288

337289
async shutdown(id: string, data?: VmShutdownData["body"]) {
338-
const response = await retryWithDelay(
339-
() =>
340-
vmShutdown({
290+
return retryWithDelay(
291+
async () => {
292+
const response = await vmShutdown({
341293
client: this.client,
342294
path: { id },
343295
body: data,
344-
}),
296+
});
297+
return handleResponse(response, `Failed to shutdown VM ${id}`);
298+
},
345299
3,
346300
200
347301
);
348-
return handleResponse(response, `Failed to shutdown VM ${id}`);
349302
}
350303

351304
async updateSpecs(id: string, data: VmUpdateSpecsData["body"]) {
@@ -359,20 +312,18 @@ export class API {
359312

360313
async startVm(id: string, options?: StartVmOptions) {
361314
const { retryDelay = 200, ...data } = options || {};
362-
const response = await retryWithDelay(
363-
() =>
364-
vmStart({
315+
const handledResponse = await retryWithDelay(
316+
async () => {
317+
const response = await vmStart({
365318
client: this.client,
366319
path: { id },
367320
body: data,
368-
}),
321+
});
322+
return handleResponse(response, `Failed to start VM ${id}`);
323+
},
369324
3,
370325
retryDelay
371326
);
372-
const handledResponse = handleResponse(
373-
response,
374-
`Failed to start VM ${id}`
375-
);
376327

377328
return {
378329
bootupType:

src/AgentClient/AgentConnection.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,11 @@ export class AgentConnection {
258258
}
259259

260260
dispose(): void {
261+
this.errorEmitter.dispose();
262+
this.messageEmitter.dispose();
261263
this.connection.dispose();
262264
this.disposePendingMessages();
263265
this.pendingMessages.clear();
264266
this.notificationListeners = {};
265-
this.errorEmitter.dispose();
266-
this.messageEmitter.dispose();
267267
}
268268
}

src/bin/commands/build.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ export const buildCommand: yargs.CommandModule<
215215
};
216216

217217
const tasks = templateData.sandboxes.map(async ({ id }, index) => {
218+
let currentSession: SandboxClient | null = null;
218219
try {
219220
spinner.start(updateSpinnerMessage(index, "Starting sandbox..."));
220221

@@ -225,6 +226,7 @@ export const buildCommand: yargs.CommandModule<
225226
let sandboxVM = new Sandbox(id, api, startResponse);
226227

227228
let session = await sandboxVM.connect();
229+
currentSession = session;
228230

229231
spinner.start(
230232
updateSpinnerMessage(index, "Writing files to sandbox...")
@@ -251,6 +253,10 @@ export const buildCommand: yargs.CommandModule<
251253
throw new Error(`Failed to write files to sandbox: ${error}`);
252254
});
253255

256+
// Dispose of the session after writing files to prevent reconnection
257+
session.dispose();
258+
currentSession = null;
259+
254260
spinner.start(updateSpinnerMessage(index, "Building sandbox..."));
255261

256262
sandboxVM = await withCustomError(
@@ -264,9 +270,14 @@ export const buildCommand: yargs.CommandModule<
264270
sandboxVM.connect(),
265271
"Failed to connect to sandbox after building"
266272
);
273+
currentSession = session;
267274

268275
await waitForSetup(session, index);
269276

277+
// Dispose of the session after setup to prevent reconnection
278+
session.dispose();
279+
currentSession = null;
280+
270281
spinner.start(
271282
updateSpinnerMessage(index, "Optimizing initial state...")
272283
);
@@ -281,6 +292,7 @@ export const buildCommand: yargs.CommandModule<
281292
sandboxVM.connect(),
282293
"Failed to connect to sandbox after optimizing initial state"
283294
);
295+
currentSession = session;
284296

285297
await waitForSetup(session, index);
286298

@@ -329,6 +341,10 @@ export const buildCommand: yargs.CommandModule<
329341
await new Promise((resolve) => setTimeout(resolve, 5000));
330342
}
331343

344+
// Dispose of the session after port operations to prevent reconnection
345+
session.dispose();
346+
currentSession = null;
347+
332348
spinner.start(updateSpinnerMessage(index, "Creating snapshot..."));
333349
await withCustomError(
334350
sdk.sandboxes.hibernate(id),
@@ -338,6 +354,12 @@ export const buildCommand: yargs.CommandModule<
338354

339355
return id;
340356
} catch (error) {
357+
// Dispose of any active session to prevent reconnection attempts
358+
if (currentSession) {
359+
currentSession.dispose();
360+
currentSession = null;
361+
}
362+
341363
spinner.start(
342364
updateSpinnerMessage(
343365
index,

0 commit comments

Comments
 (0)