Skip to content

Commit b8f195b

Browse files
committed
chore: agent lock
1 parent 103c122 commit b8f195b

File tree

5 files changed

+44
-4
lines changed

5 files changed

+44
-4
lines changed

packages/blink/src/agent/client/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { convertResponseToUIMessageStream } from "../internal/convert-response-t
1212
import type { ID } from "../types";
1313
import type { UIOptions, UIOptionsSchema } from "../ui";
1414
import { APIServerURLEnvironmentVariable } from "../constants";
15+
import { RWLock } from "../../local/rw-lock";
1516

1617
export { APIServerURLEnvironmentVariable };
1718

@@ -30,10 +31,12 @@ export type CapabilitiesResponse = Awaited<ReturnType<Client["capabilities"]>>;
3031
export class Client {
3132
public readonly baseUrl: string;
3233
private readonly client: ReturnType<typeof hc<typeof api>>;
34+
public readonly agentLock: RWLock;
3335

3436
public constructor(options: ClientOptions) {
3537
this.client = hc<typeof api>(options.baseUrl);
3638
this.baseUrl = options.baseUrl;
39+
this.agentLock = new RWLock();
3740
}
3841

3942
/**

packages/blink/src/local/chat-manager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,8 @@ export class ChatManager {
383383
};
384384
});
385385

386+
// Acquire read lock on agent to prevent it from being disposed while streaming.
387+
using _agentLock = await this.agent.agentLock.read();
386388
// Stream agent response
387389
const streamStartTime = performance.now();
388390
const stream = await runAgent({
@@ -541,7 +543,7 @@ export class ChatManager {
541543
this.disposed = true;
542544
this.watcher.dispose();
543545
this.listeners.clear();
544-
this.abortController?.abort();
546+
this.stopStreaming();
545547
}
546548

547549
private resetChatState(): void {

packages/blink/src/react/use-agent.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type CapabilitiesResponse,
88
APIServerURLEnvironmentVariable,
99
} from "../agent/client";
10+
import { RWLock } from "../local/rw-lock";
1011

1112
export interface AgentLog {
1213
readonly level: "log" | "error";
@@ -53,6 +54,8 @@ export default function useAgent(options: UseAgentOptions) {
5354
setAgent(undefined);
5455
setCapabilities(undefined);
5556

57+
let lock: RWLock | undefined;
58+
5659
(async () => {
5760
const port = await getRandomPort();
5861
const proc = spawn("node", ["--no-deprecation", buildResult.entry], {
@@ -64,11 +67,27 @@ export default function useAgent(options: UseAgentOptions) {
6467
PORT: port.toString(),
6568
HOST: "127.0.0.1",
6669
},
70+
// keep the child process tied to the parent process
71+
detached: false,
6772
});
68-
controller.signal.addEventListener("abort", () => {
73+
const cleanup = () => {
6974
try {
7075
proc.kill();
7176
} catch {}
77+
};
78+
79+
// Clean up - when the parent process exits, kill the child process.
80+
process.once("exit", cleanup);
81+
process.once("SIGINT", cleanup);
82+
process.once("SIGTERM", cleanup);
83+
process.once("uncaughtException", cleanup);
84+
85+
controller.signal.addEventListener("abort", () => {
86+
process.off("exit", cleanup);
87+
process.off("SIGINT", cleanup);
88+
process.off("SIGTERM", cleanup);
89+
process.off("uncaughtException", cleanup);
90+
cleanup();
7291
});
7392
let ready = false;
7493
proc.stdout.on("data", (data) => {
@@ -114,6 +133,7 @@ export default function useAgent(options: UseAgentOptions) {
114133
const client = new Client({
115134
baseUrl: `http://127.0.0.1:${port}`,
116135
});
136+
lock = client.agentLock;
117137
// Wait for the health endpoint to be alive.
118138
while (!controller.signal.aborted) {
119139
try {
@@ -139,7 +159,12 @@ export default function useAgent(options: UseAgentOptions) {
139159
});
140160
return () => {
141161
isCleanup = true;
142-
controller.abort();
162+
(async () => {
163+
// Acquire write lock before cleaning up this agent instance
164+
// This waits for any active streams using this agent to complete
165+
using _writeLock = await lock?.write();
166+
controller.abort();
167+
})();
143168
};
144169
}, [buildResult, env, apiServerUrl]);
145170

packages/blink/src/react/use-chat.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Client } from "../agent/client";
44
import { ChatManager, type ChatState } from "../local/chat-manager";
55
import type { StoredMessage } from "../local/types";
66
import type { ID } from "../agent/types";
7+
import type { RWLock } from "../local/rw-lock";
78

89
export type { ChatStatus } from "../local/chat-manager";
910

packages/blink/src/react/use-edit-agent.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getEditModeModel,
77
type EditAgent,
88
} from "../edit/agent";
9+
import { RWLock } from "../local/rw-lock";
910

1011
export interface UseEditAgentOptions {
1112
readonly directory: string;
@@ -35,6 +36,8 @@ export default function useEditAgent(options: UseEditAgentOptions) {
3536

3637
setMissingApiKey(false);
3738

39+
let lock: RWLock | undefined;
40+
3841
(async () => {
3942
// Create the edit agent
4043
editAgentRef.current = createEditAgent({
@@ -64,6 +67,7 @@ export default function useEditAgent(options: UseEditAgentOptions) {
6467
const editClient = new Client({
6568
baseUrl: `http://127.0.0.1:${port}`,
6669
});
70+
lock = editClient.agentLock;
6771

6872
// Wait for health check
6973
while (!controller.signal.aborted) {
@@ -88,7 +92,12 @@ export default function useEditAgent(options: UseEditAgentOptions) {
8892

8993
return () => {
9094
isCleanup = true;
91-
controller.abort();
95+
(async () => {
96+
// Acquire write lock before cleaning up this edit agent instance
97+
// This waits for any active streams using this agent to complete
98+
using _writeLock = await lock?.write();
99+
controller.abort();
100+
})();
92101
};
93102
}, [
94103
options.directory,

0 commit comments

Comments
 (0)