whitphx HF Staff commited on
Commit
0a2c317
·
1 Parent(s): 62d9954

Set benchmark timeout

Browse files
Files changed (2) hide show
  1. bench/src/server/queue.ts +99 -32
  2. bench/src/web/cli.ts +94 -61
bench/src/server/queue.ts CHANGED
@@ -92,14 +92,17 @@ export class BenchmarkQueue extends EventEmitter {
92
  }
93
  pending.completedAt = Date.now();
94
  this.emit("failed", pending);
 
 
 
 
 
95
  }
96
-
97
- this.isProcessing = false;
98
- // Process next item
99
- setImmediate(() => this.processQueue());
100
  }
101
 
102
  private async runBenchmark(request: BenchmarkRequest): Promise<BenchmarkResult> {
 
 
103
  if (request.platform === "node") {
104
  // Use spawn instead of dynamic import to avoid import.meta.url issues
105
  const { spawn } = await import("child_process");
@@ -121,6 +124,19 @@ export class BenchmarkQueue extends EventEmitter {
121
  const proc = spawn("tsx", args, { cwd: process.cwd() });
122
  let stdout = "";
123
  let stderr = "";
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
  proc.stdout.on("data", (data) => {
126
  stdout += data.toString();
@@ -130,23 +146,42 @@ export class BenchmarkQueue extends EventEmitter {
130
  stderr += data.toString();
131
  });
132
 
133
- proc.on("close", (code) => {
134
- if (code !== 0) {
135
- reject(new Error(`Benchmark failed with code ${code}: ${stderr}`));
136
- return;
 
 
137
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
- // Extract JSON from stdout (last JSON object)
140
- const jsonMatch = stdout.match(/\{[\s\S]*"platform"[\s\S]*\}/);
141
- if (jsonMatch) {
142
- try {
143
- const result = JSON.parse(jsonMatch[0]);
144
- resolve(result);
145
- } catch (e) {
146
- reject(new Error(`Failed to parse benchmark result: ${e}`));
 
 
 
147
  }
148
- } else {
149
- reject(new Error("No benchmark result found in output"));
150
  }
151
  });
152
  });
@@ -174,6 +209,19 @@ export class BenchmarkQueue extends EventEmitter {
174
  const proc = spawn("tsx", args, { cwd: process.cwd() });
175
  let stdout = "";
176
  let stderr = "";
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
  proc.stdout.on("data", (data) => {
179
  stdout += data.toString();
@@ -183,23 +231,42 @@ export class BenchmarkQueue extends EventEmitter {
183
  stderr += data.toString();
184
  });
185
 
186
- proc.on("close", (code) => {
187
- if (code !== 0) {
188
- reject(new Error(`Benchmark failed with code ${code}: ${stderr}`));
189
- return;
 
 
190
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
- // Extract JSON from stdout (last JSON object)
193
- const jsonMatch = stdout.match(/\{[\s\S]*"platform"[\s\S]*\}/);
194
- if (jsonMatch) {
195
- try {
196
- const result = JSON.parse(jsonMatch[0]);
197
- resolve(result);
198
- } catch (e) {
199
- reject(new Error(`Failed to parse benchmark result: ${e}`));
 
 
 
200
  }
201
- } else {
202
- reject(new Error("No benchmark result found in output"));
203
  }
204
  });
205
  });
 
92
  }
93
  pending.completedAt = Date.now();
94
  this.emit("failed", pending);
95
+ } finally {
96
+ // Ensure isProcessing is always reset
97
+ this.isProcessing = false;
98
+ // Process next item
99
+ setImmediate(() => this.processQueue());
100
  }
 
 
 
 
101
  }
102
 
103
  private async runBenchmark(request: BenchmarkRequest): Promise<BenchmarkResult> {
104
+ const BENCHMARK_TIMEOUT = parseInt(process.env.BENCHMARK_TIMEOUT || String(10 * 60 * 1000), 10); // 10 minutes default
105
+
106
  if (request.platform === "node") {
107
  // Use spawn instead of dynamic import to avoid import.meta.url issues
108
  const { spawn } = await import("child_process");
 
124
  const proc = spawn("tsx", args, { cwd: process.cwd() });
125
  let stdout = "";
126
  let stderr = "";
127
+ let isResolved = false;
128
+
129
+ // Timeout handler
130
+ const timeoutId = setTimeout(() => {
131
+ if (!isResolved) {
132
+ isResolved = true;
133
+ logger.error(`[Queue] Benchmark ${request.id} timed out after ${BENCHMARK_TIMEOUT / 1000}s`);
134
+ proc.kill('SIGTERM');
135
+ // Give it 5 seconds to clean up, then force kill
136
+ setTimeout(() => proc.kill('SIGKILL'), 5000);
137
+ reject(new Error(`Benchmark timed out after ${BENCHMARK_TIMEOUT / 1000}s`));
138
+ }
139
+ }, BENCHMARK_TIMEOUT);
140
 
141
  proc.stdout.on("data", (data) => {
142
  stdout += data.toString();
 
146
  stderr += data.toString();
147
  });
148
 
149
+ // Error handler for spawn failures
150
+ proc.on("error", (error) => {
151
+ if (!isResolved) {
152
+ isResolved = true;
153
+ clearTimeout(timeoutId);
154
+ reject(new Error(`Failed to spawn process: ${error.message}`));
155
  }
156
+ });
157
+
158
+ proc.on("close", (code, signal) => {
159
+ if (!isResolved) {
160
+ isResolved = true;
161
+ clearTimeout(timeoutId);
162
+
163
+ if (signal) {
164
+ reject(new Error(`Benchmark killed by signal ${signal}`));
165
+ return;
166
+ }
167
+
168
+ if (code !== 0) {
169
+ reject(new Error(`Benchmark failed with code ${code}: ${stderr}`));
170
+ return;
171
+ }
172
 
173
+ // Extract JSON from stdout (last JSON object)
174
+ const jsonMatch = stdout.match(/\{[\s\S]*"platform"[\s\S]*\}/);
175
+ if (jsonMatch) {
176
+ try {
177
+ const result = JSON.parse(jsonMatch[0]);
178
+ resolve(result);
179
+ } catch (e) {
180
+ reject(new Error(`Failed to parse benchmark result: ${e}`));
181
+ }
182
+ } else {
183
+ reject(new Error("No benchmark result found in output"));
184
  }
 
 
185
  }
186
  });
187
  });
 
209
  const proc = spawn("tsx", args, { cwd: process.cwd() });
210
  let stdout = "";
211
  let stderr = "";
212
+ let isResolved = false;
213
+
214
+ // Timeout handler
215
+ const timeoutId = setTimeout(() => {
216
+ if (!isResolved) {
217
+ isResolved = true;
218
+ logger.error(`[Queue] Benchmark ${request.id} timed out after ${BENCHMARK_TIMEOUT / 1000}s`);
219
+ proc.kill('SIGTERM');
220
+ // Give it 5 seconds to clean up, then force kill
221
+ setTimeout(() => proc.kill('SIGKILL'), 5000);
222
+ reject(new Error(`Benchmark timed out after ${BENCHMARK_TIMEOUT / 1000}s`));
223
+ }
224
+ }, BENCHMARK_TIMEOUT);
225
 
226
  proc.stdout.on("data", (data) => {
227
  stdout += data.toString();
 
231
  stderr += data.toString();
232
  });
233
 
234
+ // Error handler for spawn failures
235
+ proc.on("error", (error) => {
236
+ if (!isResolved) {
237
+ isResolved = true;
238
+ clearTimeout(timeoutId);
239
+ reject(new Error(`Failed to spawn process: ${error.message}`));
240
  }
241
+ });
242
+
243
+ proc.on("close", (code, signal) => {
244
+ if (!isResolved) {
245
+ isResolved = true;
246
+ clearTimeout(timeoutId);
247
+
248
+ if (signal) {
249
+ reject(new Error(`Benchmark killed by signal ${signal}`));
250
+ return;
251
+ }
252
+
253
+ if (code !== 0) {
254
+ reject(new Error(`Benchmark failed with code ${code}: ${stderr}`));
255
+ return;
256
+ }
257
 
258
+ // Extract JSON from stdout (last JSON object)
259
+ const jsonMatch = stdout.match(/\{[\s\S]*"platform"[\s\S]*\}/);
260
+ if (jsonMatch) {
261
+ try {
262
+ const result = JSON.parse(jsonMatch[0]);
263
+ resolve(result);
264
+ } catch (e) {
265
+ reject(new Error(`Failed to parse benchmark result: ${e}`));
266
+ }
267
+ } else {
268
+ reject(new Error("No benchmark result found in output"));
269
  }
 
 
270
  }
271
  });
272
  });
bench/src/web/cli.ts CHANGED
@@ -45,70 +45,104 @@ async function main() {
45
  console.log(`Headed : ${headed}`);
46
  console.log(`Timeout : ${PLAYWRIGHT_TIMEOUT / 1000}s (navigation: ${NAVIGATION_TIMEOUT / 1000}s)`);
47
 
48
- // Start Vite dev server
49
- const server = await createServer({
50
- configFile: false, // Don't load vite.config.ts to avoid permission issues in read-only filesystems
51
- server: {
52
- port: 5173,
53
- strictPort: false,
54
- },
55
- logLevel: "error",
56
- cacheDir: process.env.VITE_CACHE_DIR || "node_modules/.vite",
57
- });
58
-
59
- await server.listen();
60
-
61
- const port = server.config.server.port || 5173;
62
- const url = `http://localhost:${port}`;
63
-
64
- console.log(`Vite server started at ${url}`);
65
-
66
- let browser: Browser;
67
-
68
- // Build args based on mode
69
- const args = device === "wasm"
70
- ? [
71
- "--disable-gpu",
72
- "--disable-software-rasterizer",
73
- // Increase WASM memory limits for large models
74
- "--js-flags=--max-old-space-size=8192",
75
- ]
76
- : [
77
- // Official WebGPU flags from Chrome team
78
- // https://developer.chrome.com/blog/supercharge-web-ai-testing#enable-webgpu
79
- "--enable-unsafe-webgpu",
80
- "--enable-features=Vulkan",
81
- ];
82
-
83
- // Add headless-specific flags only in headless mode
84
- if (!headed && device !== "wasm") {
85
- args.push(
86
- "--no-sandbox",
87
- "--headless=new",
88
- "--use-angle=vulkan",
89
- "--disable-vulkan-surface"
90
- );
91
- }
92
 
93
- const launchOptions = {
94
- headless: !headed,
95
- args,
 
 
 
 
96
  };
97
 
98
- switch (browserType) {
99
- case "firefox":
100
- browser = await firefox.launch(launchOptions);
101
- break;
102
- case "webkit":
103
- browser = await webkit.launch(launchOptions);
104
- break;
105
- case "chromium":
106
- default:
107
- browser = await chromium.launch(launchOptions);
108
- break;
109
- }
110
 
111
  try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  const context = await browser.newContext();
113
  const page = await context.newPage();
114
 
@@ -202,8 +236,7 @@ async function main() {
202
  }
203
 
204
  } finally {
205
- await browser.close();
206
- await server.close();
207
  }
208
  }
209
 
 
45
  console.log(`Headed : ${headed}`);
46
  console.log(`Timeout : ${PLAYWRIGHT_TIMEOUT / 1000}s (navigation: ${NAVIGATION_TIMEOUT / 1000}s)`);
47
 
48
+ let browser: Browser | undefined;
49
+ let server: Awaited<ReturnType<typeof createServer>> | undefined;
50
+ let cleanupHandled = false;
51
+
52
+ const cleanup = async () => {
53
+ if (cleanupHandled) return;
54
+ cleanupHandled = true;
55
+
56
+ console.log('\nCleaning up...');
57
+ try {
58
+ if (browser) {
59
+ await browser.close();
60
+ }
61
+ } catch (e) {
62
+ console.error("Failed to close browser:", e);
63
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ try {
66
+ if (server) {
67
+ await server.close();
68
+ }
69
+ } catch (e) {
70
+ console.error("Failed to close Vite server:", e);
71
+ }
72
  };
73
 
74
+ // Handle termination signals
75
+ process.on('SIGTERM', async () => {
76
+ await cleanup();
77
+ process.exit(0);
78
+ });
79
+ process.on('SIGINT', async () => {
80
+ await cleanup();
81
+ process.exit(0);
82
+ });
 
 
 
83
 
84
  try {
85
+ // Start Vite dev server
86
+ server = await createServer({
87
+ configFile: false, // Don't load vite.config.ts to avoid permission issues in read-only filesystems
88
+ server: {
89
+ port: 5173,
90
+ strictPort: false,
91
+ },
92
+ logLevel: "error",
93
+ cacheDir: process.env.VITE_CACHE_DIR || "node_modules/.vite",
94
+ });
95
+
96
+ await server.listen();
97
+
98
+ const port = server.config.server.port || 5173;
99
+ const url = `http://localhost:${port}`;
100
+
101
+ console.log(`Vite server started at ${url}`);
102
+
103
+ // Build args based on mode
104
+ const args = device === "wasm"
105
+ ? [
106
+ "--disable-gpu",
107
+ "--disable-software-rasterizer",
108
+ // Increase WASM memory limits for large models
109
+ "--js-flags=--max-old-space-size=8192",
110
+ ]
111
+ : [
112
+ // Official WebGPU flags from Chrome team
113
+ // https://developer.chrome.com/blog/supercharge-web-ai-testing#enable-webgpu
114
+ "--enable-unsafe-webgpu",
115
+ "--enable-features=Vulkan",
116
+ ];
117
+
118
+ // Add headless-specific flags only in headless mode
119
+ if (!headed && device !== "wasm") {
120
+ args.push(
121
+ "--no-sandbox",
122
+ "--headless=new",
123
+ "--use-angle=vulkan",
124
+ "--disable-vulkan-surface"
125
+ );
126
+ }
127
+
128
+ const launchOptions = {
129
+ headless: !headed,
130
+ args,
131
+ };
132
+
133
+ switch (browserType) {
134
+ case "firefox":
135
+ browser = await firefox.launch(launchOptions);
136
+ break;
137
+ case "webkit":
138
+ browser = await webkit.launch(launchOptions);
139
+ break;
140
+ case "chromium":
141
+ default:
142
+ browser = await chromium.launch(launchOptions);
143
+ break;
144
+ }
145
+
146
  const context = await browser.newContext();
147
  const page = await context.newPage();
148
 
 
236
  }
237
 
238
  } finally {
239
+ await cleanup();
 
240
  }
241
  }
242