|
1 | 1 | /* eslint-disable no-console */ |
2 | 2 | import type { AsyncLocalStorage } from 'node:async_hooks'; |
3 | 3 | import { spawnSync } from 'node:child_process'; |
| 4 | +import * as fs from 'node:fs'; |
4 | 5 | import * as path from 'node:path'; |
5 | 6 | import { env } from 'node:process'; |
6 | 7 | import { threadId } from 'node:worker_threads'; |
@@ -54,23 +55,24 @@ function clean(err: Buffer): string { |
54 | 55 |
|
55 | 56 | function recompileFromSource(): void { |
56 | 57 | const cwd = path.join(__dirname, '..'); |
| 58 | + // Resolve node-gyp from its package entry so it's found even when |
| 59 | + // node_modules/.bin/ is not in PATH (e.g. outside of npm run scripts). |
| 60 | + const nodeGyp = require.resolve('node-gyp/bin/node-gyp.js'); |
57 | 61 | console.log('Compiling from source...'); |
58 | | - let spawn = spawnSync('node-gyp', ['configure'], { |
| 62 | + let spawn = spawnSync(process.execPath, [nodeGyp, 'configure'], { |
59 | 63 | cwd, |
60 | 64 | stdio: ['inherit', 'inherit', 'pipe'], |
61 | 65 | env: process.env, |
62 | | - shell: true, |
63 | 66 | }); |
64 | 67 | if (spawn.status !== 0) { |
65 | 68 | console.log('Failed to configure gyp'); |
66 | 69 | if (spawn.stderr) console.log(clean(spawn.stderr)); |
67 | 70 | return; |
68 | 71 | } |
69 | | - spawn = spawnSync('node-gyp', ['build'], { |
| 72 | + spawn = spawnSync(process.execPath, [nodeGyp, 'build'], { |
70 | 73 | cwd, |
71 | 74 | stdio: ['inherit', 'inherit', 'pipe'], |
72 | 75 | env: process.env, |
73 | | - shell: true, |
74 | 76 | }); |
75 | 77 | if (spawn.status !== 0) { |
76 | 78 | console.log('Failed to build bindings'); |
@@ -255,13 +257,29 @@ function getNativeModule(): Native { |
255 | 257 | return nativeModule; |
256 | 258 | } |
257 | 259 |
|
258 | | - try { |
259 | | - recompileFromSource(); |
260 | | - } catch (e) { |
261 | | - console.warn('Failed to compile from source:', e); |
| 260 | + // Exclusive-create a lock file so only one process/thread runs node-gyp at a |
| 261 | + // time. Losers (EEXIST) wait for the winner to finish before trying to load. |
| 262 | + const lockFile = path.join(__dirname, '..', '.rebuild-lock'); |
| 263 | + let lockFd: number | undefined; |
| 264 | + try { lockFd = fs.openSync(lockFile, 'wx'); } catch { /* another caller holds the lock */ } |
| 265 | + |
| 266 | + if (lockFd !== undefined) { |
| 267 | + try { |
| 268 | + recompileFromSource(); |
| 269 | + } catch (e) { |
| 270 | + console.warn('Failed to compile from source:', e); |
| 271 | + } finally { |
| 272 | + try { fs.closeSync(lockFd); } catch { /* ignore */ } |
| 273 | + try { fs.unlinkSync(lockFile); } catch { /* ignore */ } |
| 274 | + } |
| 275 | + } else { |
| 276 | + const timer = new Int32Array(new SharedArrayBuffer(4)); |
| 277 | + while (fs.existsSync(lockFile)) { |
| 278 | + Atomics.wait(timer, 0, 0, 250); |
| 279 | + } |
262 | 280 | } |
263 | 281 |
|
264 | | - // Try again after attempting to recompile, in case the binary is now available. |
| 282 | + // Try again after recompile (or after another caller finished theirs). |
265 | 283 | nativeModule = tryLoad(); |
266 | 284 |
|
267 | 285 | if (nativeModule) { |
|
0 commit comments