Skip to content

Commit a0ba779

Browse files
committed
workerのonOutputをawaitしていなかったのを修正
1 parent 23de989 commit a0ba779

File tree

7 files changed

+151
-124
lines changed

7 files changed

+151
-124
lines changed

packages/runtime/src/worker/jsEval.worker.ts

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { expose } from "comlink";
44
import type { ReplOutput, UpdatedFile } from "../interface";
5-
import type { WorkerCapabilities } from "./runtime";
5+
import type { WorkerAPI, WorkerCapabilities } from "./runtime";
66
import inspect from "object-inspect";
77
import { replLikeEval, checkSyntax } from "@my-code/js-eval";
88

@@ -11,20 +11,42 @@ function format(...args: unknown[]): string {
1111
// https://nodejs.org/api/util.html#utilformatformat-args
1212
return args.map((a) => (typeof a === "string" ? a : inspect(a))).join(" ");
1313
}
14-
let currentOutputCallback: ((output: ReplOutput) => void) | null = null;
14+
let currentOutputCallback: ((output: ReplOutput) => Promise<void>) | null =
15+
null;
16+
let pendingOutputPromise: Promise<void>[] = [];
1517

1618
// Helper function to capture console output
1719
const originalConsole = self.console;
1820
self.console = {
1921
...originalConsole,
20-
log: (...args: unknown[]) =>
21-
currentOutputCallback?.({ type: "stdout", message: format(...args) }),
22-
error: (...args: unknown[]) =>
23-
currentOutputCallback?.({ type: "stderr", message: format(...args) }),
24-
warn: (...args: unknown[]) =>
25-
currentOutputCallback?.({ type: "stderr", message: format(...args) }),
26-
info: (...args: unknown[]) =>
27-
currentOutputCallback?.({ type: "stdout", message: format(...args) }),
22+
log: (...args: unknown[]) => {
23+
if (currentOutputCallback) {
24+
pendingOutputPromise.push(
25+
currentOutputCallback({ type: "stdout", message: format(...args) })
26+
);
27+
}
28+
},
29+
error: (...args: unknown[]) => {
30+
if (currentOutputCallback) {
31+
pendingOutputPromise.push(
32+
currentOutputCallback({ type: "stderr", message: format(...args) })
33+
);
34+
}
35+
},
36+
warn: (...args: unknown[]) => {
37+
if (currentOutputCallback) {
38+
pendingOutputPromise.push(
39+
currentOutputCallback({ type: "stderr", message: format(...args) })
40+
);
41+
}
42+
},
43+
info: (...args: unknown[]) => {
44+
if (currentOutputCallback) {
45+
pendingOutputPromise.push(
46+
currentOutputCallback({ type: "stdout", message: format(...args) })
47+
);
48+
}
49+
},
2850
};
2951

3052
async function init(/*_interruptBuffer?: Uint8Array*/): Promise<{
@@ -37,51 +59,57 @@ async function init(/*_interruptBuffer?: Uint8Array*/): Promise<{
3759

3860
async function runCode(
3961
code: string,
40-
onOutput: (output: ReplOutput | UpdatedFile) => void
62+
onOutput: (output: ReplOutput | UpdatedFile) => Promise<void>
4163
): Promise<void> {
4264
currentOutputCallback = onOutput;
65+
pendingOutputPromise = [];
4366
try {
4467
const result = await replLikeEval(code);
45-
onOutput({
68+
await Promise.all(pendingOutputPromise);
69+
await onOutput({
4670
type: "return",
4771
message: inspect(result),
4872
});
4973
} catch (e) {
5074
originalConsole.log(e);
75+
await Promise.all(pendingOutputPromise);
5176
// TODO: stack trace?
5277
if (e instanceof Error) {
53-
onOutput({
78+
await onOutput({
5479
type: "error",
5580
message: `${e.name}: ${e.message}`,
5681
});
5782
} else {
58-
onOutput({
83+
await onOutput({
5984
type: "error",
6085
message: `${String(e)}`,
6186
});
6287
}
6388
}
6489
}
6590

66-
function runFile(
91+
async function runFile(
6792
name: string,
6893
files: Record<string, string>,
69-
onOutput: (output: ReplOutput | UpdatedFile) => void
70-
): void {
94+
onOutput: (output: ReplOutput | UpdatedFile) => Promise<void>
95+
): Promise<void> {
7196
// pyodide worker などと異なり、複数ファイルを読み込んでimportのようなことをするのには対応していません。
7297
currentOutputCallback = onOutput;
98+
pendingOutputPromise = [];
7399
try {
74100
self.eval(files[name]);
101+
await Promise.all(pendingOutputPromise);
75102
} catch (e) {
76103
originalConsole.log(e);
104+
await Promise.all(pendingOutputPromise);
77105
// TODO: stack trace?
78106
if (e instanceof Error) {
79-
onOutput({
107+
await onOutput({
80108
type: "error",
81109
message: `${e.name}: ${e.message}`,
82110
});
83111
} else {
84-
onOutput({
112+
await onOutput({
85113
type: "error",
86114
message: `${String(e)}`,
87115
});
@@ -103,7 +131,7 @@ async function restoreState(commands: string[]): Promise<object> {
103131
return {};
104132
}
105133

106-
const api = {
134+
const api: WorkerAPI = {
107135
init,
108136
runCode,
109137
runFile,

packages/runtime/src/worker/pyodide.worker.ts

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { PyodideInterface } from "pyodide";
66
import { loadPyodide } from "pyodide";
77
import { version as pyodideVersion } from "pyodide/package.json";
88
import type { PyCallable } from "pyodide/ffi";
9-
import type { WorkerCapabilities } from "./runtime";
9+
import type { WorkerAPI, WorkerCapabilities } from "./runtime";
1010
import type { ReplOutput, UpdatedFile } from "../interface";
1111

1212
import execfile_py from "./pyodide/execfile.py?raw";
@@ -15,7 +15,10 @@ import check_syntax_py from "./pyodide/check_syntax.py?raw";
1515
const HOME = `/home/pyodide/`;
1616

1717
let pyodide: PyodideInterface;
18-
let currentOutputCallback: ((output: ReplOutput | UpdatedFile) => void) | null = null;
18+
let pendingOutputPromise: Promise<void>[] = [];
19+
let currentOutputCallback:
20+
| ((output: ReplOutput | UpdatedFile) => Promise<void>)
21+
| null = null;
1922

2023
// Helper function to read all files from the Pyodide file system
2124
function readAllFiles(): Record<string, string> {
@@ -51,12 +54,22 @@ async function init(
5154
}
5255

5356
pyodide.setStdout({
54-
batched: (str: string) =>
55-
currentOutputCallback?.({ type: "stdout", message: str }),
57+
batched: (str: string) => {
58+
if (currentOutputCallback) {
59+
pendingOutputPromise.push(
60+
currentOutputCallback({ type: "stdout", message: str })
61+
);
62+
}
63+
},
5664
});
5765
pyodide.setStderr({
58-
batched: (str: string) =>
59-
currentOutputCallback?.({ type: "stderr", message: str }),
66+
batched: (str: string) => {
67+
if (currentOutputCallback) {
68+
pendingOutputPromise.push(
69+
currentOutputCallback({ type: "stderr", message: str })
70+
);
71+
}
72+
},
6073
});
6174

6275
pyodide.setInterruptBuffer(interruptBuffer);
@@ -66,30 +79,33 @@ async function init(
6679

6780
async function runCode(
6881
code: string,
69-
onOutput: (output: ReplOutput | UpdatedFile) => void
82+
onOutput: (output: ReplOutput | UpdatedFile) => Promise<void>
7083
): Promise<void> {
7184
if (!pyodide) {
7285
throw new Error("Pyodide not initialized");
7386
}
7487
currentOutputCallback = onOutput;
88+
pendingOutputPromise = [];
7589
try {
7690
const result = await pyodide.runPythonAsync(code);
91+
await Promise.all(pendingOutputPromise);
7792
if (result !== undefined) {
78-
onOutput({
93+
await onOutput({
7994
type: "return",
8095
message: String(result),
8196
});
8297
}
8398
} catch (e: unknown) {
8499
console.log(e);
100+
await Promise.all(pendingOutputPromise);
85101
if (e instanceof Error) {
86102
// エラーがPyodideのTracebackの場合、2行目から<exec>が出てくるまでを隠す
87103
if (e.name === "PythonError" && e.message.startsWith("Traceback")) {
88104
const lines = e.message.split("\n");
89105
const execLineIndex = lines.findIndex((line) =>
90106
line.includes("<exec>")
91107
);
92-
onOutput({
108+
await onOutput({
93109
type: "error",
94110
message: lines
95111
.slice(0, 1)
@@ -98,13 +114,13 @@ async function runCode(
98114
.trim(),
99115
});
100116
} else {
101-
onOutput({
117+
await onOutput({
102118
type: "error",
103119
message: `予期せぬエラー: ${e.message.trim()}`,
104120
});
105121
}
106122
} else {
107-
onOutput({
123+
await onOutput({
108124
type: "error",
109125
message: `予期せぬエラー: ${String(e).trim()}`,
110126
});
@@ -113,19 +129,20 @@ async function runCode(
113129

114130
const updatedFiles = readAllFiles();
115131
for (const [filename, content] of Object.entries(updatedFiles)) {
116-
onOutput({ type: "file", filename, content });
132+
await onOutput({ type: "file", filename, content });
117133
}
118134
}
119135

120136
async function runFile(
121137
name: string,
122138
files: Record<string, string>,
123-
onOutput: (output: ReplOutput | UpdatedFile) => void
139+
onOutput: (output: ReplOutput | UpdatedFile) => Promise<void>
124140
): Promise<void> {
125141
if (!pyodide) {
126142
throw new Error("Pyodide not initialized");
127143
}
128144
currentOutputCallback = onOutput;
145+
pendingOutputPromise = [];
129146
try {
130147
// Use Pyodide FS API to write files to the file system
131148
for (const filename of Object.keys(files)) {
@@ -136,8 +153,10 @@ async function runFile(
136153

137154
const pyExecFile = pyodide.runPython(execfile_py) as PyCallable;
138155
pyExecFile(`${HOME}/${name}`);
156+
await Promise.all(pendingOutputPromise);
139157
} catch (e: unknown) {
140158
console.log(e);
159+
await Promise.all(pendingOutputPromise);
141160
if (e instanceof Error) {
142161
// エラーがPyodideのTracebackの場合、2行目から<exec>が出てくるまでを隠す
143162
// <exec> 自身も隠す
@@ -146,7 +165,7 @@ async function runFile(
146165
const execLineIndex = lines.findLastIndex((line) =>
147166
line.includes("<exec>")
148167
);
149-
onOutput({
168+
await onOutput({
150169
type: "error",
151170
message: lines
152171
.slice(0, 1)
@@ -155,13 +174,13 @@ async function runFile(
155174
.trim(),
156175
});
157176
} else {
158-
onOutput({
177+
await onOutput({
159178
type: "error",
160179
message: `予期せぬエラー: ${e.message.trim()}`,
161180
});
162181
}
163182
} else {
164-
onOutput({
183+
await onOutput({
165184
type: "error",
166185
message: `予期せぬエラー: ${String(e).trim()}`,
167186
});
@@ -170,7 +189,7 @@ async function runFile(
170189

171190
const updatedFiles = readAllFiles();
172191
for (const [filename, content] of Object.entries(updatedFiles)) {
173-
onOutput({ type: "file", filename, content });
192+
await onOutput({ type: "file", filename, content });
174193
}
175194
}
176195

@@ -200,7 +219,7 @@ async function restoreState(): Promise<object> {
200219
throw new Error("not implemented");
201220
}
202221

203-
const api = {
222+
const api: WorkerAPI = {
204223
init,
205224
runCode,
206225
runFile,

0 commit comments

Comments
 (0)