Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/nx/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
}
},
"build-native": {
"inputs": ["native", "{projectRoot}/src/native/index.js"],
"outputs": [
"{projectRoot}/src/native/*.node",
"{projectRoot}/src/native/*.js",
Expand Down
77 changes: 72 additions & 5 deletions packages/nx/src/native/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
const { join, basename } = require('path');
const { copyFileSync, existsSync, mkdirSync, renameSync } = require('fs');
const {
copyFileSync,
existsSync,
mkdirSync,
renameSync,
openSync,
closeSync,
unlinkSync,
} = require('fs');
const Module = require('module');
const { nxVersion } = require('../utils/versions');
const { getNativeFileCacheLocation } = require('./native-file-cache-location');
Expand Down Expand Up @@ -83,19 +91,78 @@ Module._load = function (request, parent, isMain) {
);
// This is the path that will get loaded
const tmpFile = join(nativeFileCacheLocation, nxVersion + '-' + fileName);
// Lock file to prevent race conditions
const lockFile = join(nativeFileCacheLocation, '.lock');

// If the file to be loaded already exists, just load it
if (existsSync(tmpFile)) {
return originalLoad.apply(this, [tmpFile, parent, isMain]);
}

// If lock file exists, wait for it to disappear (another process is working)
if (existsSync(lockFile)) {
const maxWaitMs = 5000;
const startTime = Date.now();
while (existsSync(lockFile)) {
if (Date.now() - startTime > maxWaitMs) {
break; // Timeout - maybe stale lock, proceed anyway
}
const sleep = 10;
const start = Date.now();
while (Date.now() - start < sleep) {
// busy wait
}
}

// After lock disappears, check if file exists now
if (existsSync(tmpFile)) {
return originalLoad.apply(this, [tmpFile, parent, isMain]);
}
}

// Create cache dir if needed
if (!existsSync(nativeFileCacheLocation)) {
mkdirSync(nativeFileCacheLocation, { recursive: true });
}
// First copy to a unique location for each process
copyFileSync(nativeLocation, tmpTmpFile);

// Then rename to the final location
renameSync(tmpTmpFile, tmpFile);
// Try to create lock
try {
const lockHandle = openSync(lockFile, 'wx');
try {
// Double-check file doesn't exist (race between wait ending and lock creation)
if (existsSync(tmpFile)) {
closeSync(lockHandle);
unlinkSync(lockFile);
return originalLoad.apply(this, [tmpFile, parent, isMain]);
}

// We have the lock - do the copy-rename
copyFileSync(nativeLocation, tmpTmpFile);
renameSync(tmpTmpFile, tmpFile);
} finally {
// Release lock
closeSync(lockHandle);
unlinkSync(lockFile);
}
} catch (err) {
if (err.code === 'EEXIST') {
// Lock was created between our check and now - wait for tmpFile
const maxWaitMs = 5000;
const startTime = Date.now();
while (!existsSync(tmpFile)) {
if (Date.now() - startTime > maxWaitMs) {
throw new Error(`Timeout waiting for native module: ${tmpFile}`);
}
const sleep = 10;
const start = Date.now();
while (Date.now() - start < sleep) {
// busy wait
}
}
} else {
throw err;
}
}

// Load from the final location
return originalLoad.apply(this, [tmpFile, parent, isMain]);
Expand Down
Loading