-
Notifications
You must be signed in to change notification settings - Fork 611
Description
Hi,
I was recently trying out UWS and loved the performance gains, but wanted to make sure it worked with .exe.
(https://nodejs.org/api/single-executable-applications.html#single-executable-applications)
First, I tried using it with npx webpack. Works great!
Then, I tried using it by injecting the blob into my executable. This works for other .node files perfectly, but with
the same uws_win32_x64_120.node we used for our bundle.js that worked- we see it crash our exe with no error.
I've managed to strip away everything and get it down to the one function causing the error, so other than require()
or createRequire(), the real problem is dlopen(). For whatever reason, even with the same system, seconds after running
the working dlopen on a bundled JS file, the same method fails when run inside an executable. I've debugged to making
sure the node file exists and is found properly, but can't go much further without C code, as NodeJS exe refuses to catch
the error, crashing instead, making me think some kind of segfault or otherwise is happening on UWS's side.
I've created a working minimal example below.
To run it, just do:
node server.js
which should work and show:
node server.js
Starting server... before UWS
__dirname: C:\Users\slee\Downloads\minimal
UWS path: C:\Users\slee\Downloads\minimal\uws_win32_x64_120.node
Attempting to load UWS native addon...
UWS loaded successfully
module.exports keys: [
'App',
'SSLApp',
'H3App',
...
'DEDICATED_DECOMPRESSOR_1KB',
'DEDICATED_DECOMPRESSOR_512B',
'LIBUS_LISTEN_EXCLUSIVE_PORT'
]
Starting server... after UWS
App instance created successfully
Server started on port 3000
Then you can do npx webpack
and node bundle.js
and see the same output, indicating packing works fine.
However, if we compiled the executable by doing:
node -e "require('fs').copyFileSync(process.execPath, 'test.exe')"
> Windows command
cp $(command -v node) test
> Linux command
node --experimental-sea-config sea-config.json
npx postject test.exe NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
> Windows command
npx postject test NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
> Linux command
then running test.exe
shows:
C:\Users\slee\Downloads\minimal>test.exe
Starting server... before UWS
__dirname: C:\Users\slee\Downloads\minimal
UWS path: C:\Users\slee\Downloads\minimal\uws_win32_x64_120.node
Attempting to load UWS native addon...C:\Users\slee\Downloads\minimal>
Just crashing, despite our try/catch, indicating the dlopen or C code is faulting.
Here's a sample server.js:
const { dlopen } = require('node:process');
const { constants } = require('node:os');
const { join } = require('node:path');
const fs = require('fs');
console.log('Starting server... before UWS');
let uws;
try {
console.log('__dirname:', __dirname);
const uwsPath = join(__dirname, 'uws_linux_x64_120.node');
console.log('UWS path:', uwsPath);
if (!fs.existsSync(uwsPath)) {
throw new Error(`UWS native addon not found at ${uwsPath}`);
}
console.log('Attempting to load UWS native addon...');
const module = { exports: {} };
dlopen(module, uwsPath, constants.dlopen.RTLD_NOW | constants.dlopen.RTLD_GLOBAL);
uws = module.exports;
console.log('UWS loaded successfully');
console.log('module.exports keys:', Object.keys(uws));
if (!uws.App) {
throw new Error('App is not defined in the loaded module');
}
console.log('Starting server... after UWS');
const app = new uws.App();
console.log('App instance created successfully');
app.get('/*', (res, req) => {
res.end('Hello World!');
});
app.listen(3000, (token) => {
if (token) {
console.log('Server started on port 3000');
}
});
} catch (error) {
console.error('Error occurred:', error);
fs.writeFileSync('error.log', `${error.stack || error.toString()}\n\nModule exports: ${JSON.stringify(uws, null, 2)}`);
process.exit(1);
}
module.exports = { uws };
And here is a webpack.config.js and sea-config.json to put in the same directory,
webpack.config.js:
const path = require('path');
module.exports = {
target: 'node',
mode: 'production',
entry: './server.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname),
},
module: {
rules: [
{
test: /\.node$/,
use: 'node-loader',
},
],
},
resolve: {
extensions: ['.js', '.json', '.node'],
},
externals: {
'uWebSockets.js': 'commonjs uWebSockets.js', // Treat uWebSockets as an external dependency
},
};
sea-config.json:
{
"main": "bundle.js",
"output": "sea-prep.blob",
"assets": {}
}