refactor: correctly close session on send failure
This commit is contained in:
parent
ad50ec9392
commit
9fba2d083c
3 changed files with 53 additions and 8 deletions
|
|
@ -3,6 +3,9 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc --noEmit"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zerobyte/contracts": "workspace:*",
|
"@zerobyte/contracts": "workspace:*",
|
||||||
"@zerobyte/core": "workspace:*",
|
"@zerobyte/core": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -69,3 +69,25 @@ test("emits backup.failed when a backup command hits a restic error", async () =
|
||||||
session.close();
|
session.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("closes the websocket when an outbound send throws", async () => {
|
||||||
|
const close = mock(() => undefined);
|
||||||
|
const session = createControllerSession(
|
||||||
|
fromPartial({
|
||||||
|
send: () => {
|
||||||
|
throw new Error("socket write failed");
|
||||||
|
},
|
||||||
|
close,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
session.onOpen();
|
||||||
|
|
||||||
|
await waitForExpect(() => {
|
||||||
|
expect(close).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export type ControllerSession = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createControllerSession = (ws: WebSocket): ControllerSession => {
|
export const createControllerSession = (ws: WebSocket): ControllerSession => {
|
||||||
|
let isClosed = false;
|
||||||
const outboundQueue = Effect.runSync(Queue.bounded<AgentWireMessage>(64));
|
const outboundQueue = Effect.runSync(Queue.bounded<AgentWireMessage>(64));
|
||||||
const inboundQueue = Effect.runSync(Queue.bounded<ControllerWireMessage>(64));
|
const inboundQueue = Effect.runSync(Queue.bounded<ControllerWireMessage>(64));
|
||||||
const runningJobsRef = Effect.runSync(Ref.make<Map<string, RunningJob>>(new Map()));
|
const runningJobsRef = Effect.runSync(Ref.make<Map<string, RunningJob>>(new Map()));
|
||||||
|
|
@ -63,6 +64,31 @@ export const createControllerSession = (ws: WebSocket): ControllerSession => {
|
||||||
offerOutbound,
|
offerOutbound,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeSession = () => {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isClosed = true;
|
||||||
|
void Effect.runPromise(abortRunningJobs).catch(() => {});
|
||||||
|
void Effect.runPromise(Fiber.interrupt(writerFiber)).catch(() => {});
|
||||||
|
void Effect.runPromise(Fiber.interrupt(processorFiber)).catch(() => {});
|
||||||
|
void Effect.runPromise(Queue.shutdown(outboundQueue)).catch(() => {});
|
||||||
|
void Effect.runPromise(Queue.shutdown(inboundQueue)).catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendFailure = (reason: string) => {
|
||||||
|
logger.error(`Closing agent session after an outbound websocket send failed: ${reason}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ws.close();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Failed to close controller websocket after send failure: ${toMessage(error)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSession();
|
||||||
|
};
|
||||||
|
|
||||||
const writerFiber = Effect.runFork(
|
const writerFiber = Effect.runFork(
|
||||||
Effect.forever(
|
Effect.forever(
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
|
@ -71,7 +97,7 @@ export const createControllerSession = (ws: WebSocket): ControllerSession => {
|
||||||
try {
|
try {
|
||||||
ws.send(message);
|
ws.send(message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to send controller message: ${toMessage(error)}`);
|
handleSendFailure(toMessage(error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
@ -115,12 +141,6 @@ export const createControllerSession = (ws: WebSocket): ControllerSession => {
|
||||||
logger.error(`Failed to queue inbound message: ${toMessage(error)}`);
|
logger.error(`Failed to queue inbound message: ${toMessage(error)}`);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
close: () => {
|
close: closeSession,
|
||||||
void Effect.runPromise(abortRunningJobs).catch(() => {});
|
|
||||||
void Effect.runPromise(Fiber.interrupt(writerFiber)).catch(() => {});
|
|
||||||
void Effect.runPromise(Fiber.interrupt(processorFiber)).catch(() => {});
|
|
||||||
void Effect.runPromise(Queue.shutdown(outboundQueue)).catch(() => {});
|
|
||||||
void Effect.runPromise(Queue.shutdown(inboundQueue)).catch(() => {});
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue