Skip to content

Commit bdfa2a1

Browse files
Copilotna-trium-144
andcommitted
Address PR feedback: fix payload types, move executedCommands to runtime, use mutex for interrupt, remove isInterrupted
Co-authored-by: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
1 parent d3dd4f4 commit bdfa2a1

File tree

2 files changed

+39
-64
lines changed

2 files changed

+39
-64
lines changed

app/terminal/javascript/runtime.tsx

Lines changed: 35 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function useJavaScript(): RuntimeContext {
2626
type MessageToWorker =
2727
| {
2828
type: "init";
29+
payload?: undefined;
2930
}
3031
| {
3132
type: "runJavaScript";
@@ -37,6 +38,7 @@ type MessageToWorker =
3738
}
3839
| {
3940
type: "restoreState";
41+
payload: { commands: string[] };
4042
};
4143

4244
type MessageFromWorker =
@@ -59,7 +61,7 @@ export function JavaScriptProvider({ children }: { children: ReactNode }) {
5961
Map<number, [(payload: any) => void, (error: string) => void]>
6062
>(new Map());
6163
const nextMessageId = useRef<number>(0);
62-
const isInterrupted = useRef<boolean>(false);
64+
const executedCommands = useRef<string[]>([]);
6365

6466
function postMessage<T>({ type, payload }: MessageToWorker) {
6567
const id = nextMessageId.current++;
@@ -86,70 +88,52 @@ export function JavaScriptProvider({ children }: { children: ReactNode }) {
8688
}
8789
};
8890

89-
postMessage<InitPayloadFromWorker>({
91+
return postMessage<InitPayloadFromWorker>({
9092
type: "init",
9193
}).then(({ success }) => {
9294
if (success) {
9395
setReady(true);
9496
}
97+
return worker;
9598
});
96-
97-
return worker;
9899
}, []);
99100

100101
useEffect(() => {
101-
const worker = initializeWorker();
102+
let worker: Worker | null = null;
103+
initializeWorker().then((w) => {
104+
worker = w;
105+
});
102106

103107
return () => {
104-
worker.terminate();
108+
worker?.terminate();
105109
};
106110
}, [initializeWorker]);
107111

108112
const interrupt = useCallback(async () => {
109113
// Since we can't interrupt JavaScript execution directly,
110114
// we terminate the worker and restart it, then restore state
111-
isInterrupted.current = true;
112-
113-
// Reject all pending callbacks before terminating
114-
const error = "Worker interrupted";
115-
messageCallbacks.current.forEach(([, reject]) => reject(error));
116-
messageCallbacks.current.clear();
117-
118-
// Terminate the current worker
119-
workerRef.current?.terminate();
120-
121-
// Reset ready state
122-
setReady(false);
123-
124-
// Create a new worker
125-
initializeWorker();
126-
127-
// Wait for initialization with timeout
128-
const maxRetries = 50; // 5 seconds total
129-
let retries = 0;
130-
131-
await new Promise<void>((resolve, reject) => {
132-
const checkInterval = setInterval(() => {
133-
retries++;
134-
if (retries > maxRetries) {
135-
clearInterval(checkInterval);
136-
reject(new Error("Worker initialization timeout"));
137-
return;
138-
}
139-
140-
if (workerRef.current) {
141-
// Try to restore state
142-
postMessage<{ success: boolean }>({
143-
type: "restoreState",
144-
}).then(() => {
145-
clearInterval(checkInterval);
146-
isInterrupted.current = false;
147-
resolve();
148-
}).catch(() => {
149-
// Keep trying
150-
});
151-
}
152-
}, 100);
115+
await mutex.current.runExclusive(async () => {
116+
// Reject all pending callbacks before terminating
117+
const error = "Worker interrupted";
118+
messageCallbacks.current.forEach(([, reject]) => reject(error));
119+
messageCallbacks.current.clear();
120+
121+
// Terminate the current worker
122+
workerRef.current?.terminate();
123+
124+
// Reset ready state
125+
setReady(false);
126+
127+
// Create a new worker and wait for it to be ready
128+
await initializeWorker();
129+
130+
// Restore state by re-executing previous commands
131+
if (executedCommands.current.length > 0) {
132+
await postMessage<{ success: boolean }>({
133+
type: "restoreState",
134+
payload: { commands: executedCommands.current },
135+
});
136+
}
153137
});
154138
}, [initializeWorker]);
155139

@@ -169,13 +153,11 @@ export function JavaScriptProvider({ children }: { children: ReactNode }) {
169153
type: "runJavaScript",
170154
payload: { code },
171155
});
156+
// Save successfully executed command
157+
executedCommands.current.push(code);
172158
return output;
173159
} catch (error) {
174-
// If interrupted or worker was terminated, return appropriate message
175-
if (isInterrupted.current) {
176-
return [{ type: "error", message: "実行が中断されました" }];
177-
}
178-
// Handle other errors
160+
// Handle errors (including "Worker interrupted")
179161
if (error instanceof Error) {
180162
return [{ type: "error", message: error.message }];
181163
}

public/javascript.worker.js

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// JavaScript web worker
22
let jsOutput = [];
3-
let executedCommands = []; // Store successfully executed commands for state recovery
43

54
// Helper function to capture console output
65
function createConsoleProxy() {
@@ -22,7 +21,6 @@ function createConsoleProxy() {
2221

2322
async function init(id) {
2423
// Initialize the worker
25-
executedCommands = [];
2624
self.postMessage({ id, payload: { success: true } });
2725
}
2826

@@ -42,9 +40,6 @@ async function runJavaScript(id, payload) {
4240
message: String(result),
4341
});
4442
}
45-
46-
// Save the successfully executed command for state recovery
47-
executedCommands.push(code);
4843
} catch (e) {
4944
// Use self.console to avoid recursion with our console proxy
5045
self.console.log(e);
@@ -93,17 +88,15 @@ async function checkSyntax(id, payload) {
9388
}
9489
}
9590

96-
async function restoreState(id) {
91+
async function restoreState(id, payload) {
9792
// Re-execute all previously successful commands to restore state
98-
const commandsToRestore = [...executedCommands];
99-
executedCommands = []; // Clear before re-executing
93+
const { commands } = payload;
10094
jsOutput = []; // Clear output for restoration
10195

102-
for (const command of commandsToRestore) {
96+
for (const command of commands) {
10397
try {
10498
const console = createConsoleProxy();
10599
eval(command);
106-
executedCommands.push(command);
107100
} catch (e) {
108101
// If restoration fails, we still continue with other commands
109102
// Use self.console to avoid recursion with our console proxy
@@ -128,7 +121,7 @@ self.onmessage = async (event) => {
128121
await checkSyntax(id, payload);
129122
return;
130123
case "restoreState":
131-
await restoreState(id);
124+
await restoreState(id, payload);
132125
return;
133126
default:
134127
console.error(`Unknown message type: ${type}`);

0 commit comments

Comments
 (0)