From c8d0b0da0eb1ac06e0352374159e833fef548d4d Mon Sep 17 00:00:00 2001 From: Max Kless Date: Wed, 8 Oct 2025 15:28:50 +0200 Subject: [PATCH 1/2] initial impl wip --- packages/nx/project.json | 1 + packages/nx/src/native/index.js | 75 ++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/packages/nx/project.json b/packages/nx/project.json index df289752c572bb..57d399087c3d64 100644 --- a/packages/nx/project.json +++ b/packages/nx/project.json @@ -29,6 +29,7 @@ } }, "build-native": { + "inputs": ["native", "{projectRoot}/src/native/index.js"], "outputs": [ "{projectRoot}/src/native/*.node", "{projectRoot}/src/native/*.js", diff --git a/packages/nx/src/native/index.js b/packages/nx/src/native/index.js index 257054c6dcea8f..6f783d3660b87a 100644 --- a/packages/nx/src/native/index.js +++ b/packages/nx/src/native/index.js @@ -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'); @@ -83,19 +91,76 @@ 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 fd = openSync(lockFile, 'wx'); + closeSync(fd); + + // Double-check file doesn't exist (race between wait ending and lock creation) + if (existsSync(tmpFile)) { + unlinkSync(lockFile); + return originalLoad.apply(this, [tmpFile, parent, isMain]); + } + + // We have the lock - do the copy-rename + copyFileSync(nativeLocation, tmpTmpFile); + renameSync(tmpTmpFile, tmpFile); + + // Release lock + 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]); From 29491efe6cc757dc4a69a0cac8ad50ab0b5a8216 Mon Sep 17 00:00:00 2001 From: Max Kless Date: Wed, 8 Oct 2025 16:48:10 +0200 Subject: [PATCH 2/2] handle with proper writing of lock file wip --- packages/nx/src/native/index.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/nx/src/native/index.js b/packages/nx/src/native/index.js index 6f783d3660b87a..2d3bf39d774fca 100644 --- a/packages/nx/src/native/index.js +++ b/packages/nx/src/native/index.js @@ -127,21 +127,23 @@ Module._load = function (request, parent, isMain) { // Try to create lock try { - const fd = openSync(lockFile, 'wx'); - closeSync(fd); + 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]); + } - // Double-check file doesn't exist (race between wait ending and lock creation) - if (existsSync(tmpFile)) { + // We have the lock - do the copy-rename + copyFileSync(nativeLocation, tmpTmpFile); + renameSync(tmpTmpFile, tmpFile); + } finally { + // Release lock + 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); - - // Release lock - unlinkSync(lockFile); } catch (err) { if (err.code === 'EEXIST') { // Lock was created between our check and now - wait for tmpFile