Skip to content

Commit 029e3a5

Browse files
fix: properly dispose and prevent wakeups (#170)
1 parent 6b50aaa commit 029e3a5

File tree

2 files changed

+62
-8
lines changed

2 files changed

+62
-8
lines changed

src/AgentClient/AgentConnection.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class AgentConnection {
3838
}
3939

4040
private _state: IAgentClientState = "CONNECTED";
41+
private _isDisposed = false;
4142

4243
get state() {
4344
return this._state;
@@ -172,6 +173,10 @@ export class AgentConnection {
172173
pitcherRequest: T,
173174
options: IRequestOptions = {}
174175
) {
176+
if (this._isDisposed) {
177+
throw new Error("Cannot perform operation: SandboxClient has been disposed");
178+
}
179+
175180
const { timeoutMs } = options;
176181
const request = this.createRequest(pitcherRequest, timeoutMs);
177182

@@ -265,6 +270,7 @@ export class AgentConnection {
265270
}
266271

267272
dispose(): void {
273+
this._isDisposed = true;
268274
this.errorEmitter.dispose();
269275
this.messageEmitter.dispose();
270276
this.connection.dispose();

src/SandboxClient/index.ts

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,12 @@ export class SandboxClient {
190190
}
191191
this.keepAliveInterval = null;
192192

193-
// Only attempt auto-reconnect on DISCONNECTED, not HIBERNATED
194-
if (state === "DISCONNECTED" && !this.isExplicitlyDisconnected) {
193+
// Only attempt auto-reconnect on DISCONNECTED, not HIBERNATED, and not if disposed
194+
if (
195+
state === "DISCONNECTED" &&
196+
!this.isExplicitlyDisconnected &&
197+
!this.disposable.isDisposed
198+
) {
195199
this.attemptAutoReconnect();
196200
}
197201
} else if (state === "CONNECTED") {
@@ -332,6 +336,10 @@ export class SandboxClient {
332336
"sandboxClient.disconnect",
333337
{ "sandbox.id": this.id },
334338
async () => {
339+
if (this.disposable.isDisposed) {
340+
throw new Error("Cannot disconnect: SandboxClient has been disposed");
341+
}
342+
335343
this.isExplicitlyDisconnected = true;
336344
if (this.keepAliveInterval) {
337345
clearInterval(this.keepAliveInterval);
@@ -351,6 +359,10 @@ export class SandboxClient {
351359
"sandboxClient.reconnect",
352360
{ "sandbox.id": this.id },
353361
async () => {
362+
if (this.disposable.isDisposed) {
363+
throw new Error("Cannot reconnect: SandboxClient has been disposed");
364+
}
365+
354366
this.isExplicitlyDisconnected = false;
355367
return this.agentClient.reconnect();
356368
}
@@ -368,6 +380,9 @@ export class SandboxClient {
368380
try {
369381
await retryWithDelay(
370382
async () => {
383+
if (this.disposable.isDisposed) {
384+
throw new Error("Client disposed - stopping auto-reconnect");
385+
}
371386
if (this.isExplicitlyDisconnected) {
372387
throw new Error(
373388
"Explicit disconnect - stopping auto-reconnect"
@@ -397,31 +412,53 @@ export class SandboxClient {
397412
* If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it.
398413
*/
399414
public keepActiveWhileConnected(enabled: boolean) {
415+
if (this.disposable.isDisposed) {
416+
throw new Error("Client disposed");
417+
}
418+
400419
// Used to manage the interval when disconnects happen
401420
this.shouldKeepAlive = enabled;
402421

403422
if (enabled) {
404423
if (!this.keepAliveInterval) {
405424
this.keepAliveInterval = setInterval(() => {
406-
this.agentClient.system.update()
425+
if (this.disposable.isDisposed) {
426+
if (this.keepAliveInterval) {
427+
clearInterval(this.keepAliveInterval);
428+
this.keepAliveInterval = null;
429+
}
430+
return;
431+
}
432+
433+
this.agentClient.system
434+
.update()
407435
.then(() => {
408436
// Reset failure count on success
409437
this.keepAliveFailures = 0;
410438
})
411439
.catch((error) => {
412440
this.keepAliveFailures++;
413-
console.warn(`Keep-alive failed (${this.keepAliveFailures}/${this.maxKeepAliveFailures}):`, error);
414-
441+
console.warn(
442+
`Keep-alive failed (${this.keepAliveFailures}/${this.maxKeepAliveFailures}):`,
443+
error
444+
);
445+
415446
// If we've hit max failures, stop aggressive keep-alive to prevent connection thrashing
416447
if (this.keepAliveFailures >= this.maxKeepAliveFailures) {
417-
console.warn("Max keep-alive failures reached, reducing frequency to prevent connection issues");
448+
console.warn(
449+
"Max keep-alive failures reached, reducing frequency to prevent connection issues"
450+
);
418451
if (this.keepAliveInterval) {
419452
clearInterval(this.keepAliveInterval);
420453
this.keepAliveInterval = null;
421454
}
422-
// Restart with longer interval after failures
455+
// Restart with longer interval after failures, but only if not disposed
423456
setTimeout(() => {
424-
if (this.shouldKeepAlive && !this.keepAliveInterval) {
457+
if (
458+
this.shouldKeepAlive &&
459+
!this.keepAliveInterval &&
460+
!this.disposable.isDisposed
461+
) {
425462
this.keepActiveWhileConnected(true);
426463
this.keepAliveFailures = 0; // Reset for retry
427464
}
@@ -441,6 +478,17 @@ export class SandboxClient {
441478
* Dispose the session, this will disconnect from the sandbox and dispose all resources. If you want to do a clean disconnect, await "disconnect" method first.
442479
*/
443480
dispose() {
481+
// Prevent any future reconnection attempts
482+
this.isExplicitlyDisconnected = true;
483+
484+
// Clear keep-alive settings to prevent restart
485+
this.shouldKeepAlive = false;
486+
487+
if (this.keepAliveInterval) {
488+
clearInterval(this.keepAliveInterval);
489+
this.keepAliveInterval = null;
490+
}
491+
444492
this.disposable.dispose();
445493
}
446494
}

0 commit comments

Comments
 (0)