Broken with Compiled Windows Exe (Node Single Executable Application SEA)
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": {}
}
You don't want to be on Windows when debugging things like these. If you swap to Linux (or even just WSL2) you have tons more and better tools to see what is wrong.
But at the same time - never heard of SEA and I know there are many bundlers that don't work with uWS. They often neglect native addons.
Btw, nobody is going to run an .exe attached to your post. I removed it. Please do not attach .exe files here.
Sorry!! Updated for just code, along with Windows and Linux commands. I went ahead and did WSL2 with Linux binary via SEA, and surprisingly- it worked.
./test
Starting server... before UWS
__dirname: /mnt/c/users/slee/downloads/minimal/linux
UWS path: /mnt/c/users/slee/downloads/minimal/linux/uws_linux_x64_120.node
Attempting to load UWS native addon...
UWS loaded successfully
module.exports keys: [
'App',
...
'DEDICATED_DECOMPRESSOR_512B',
'LIBUS_LISTEN_EXCLUSIVE_PORT'
]
Starting server... after UWS
App instance created successfully
Server started on port 3000
So that narrows down the issue to just Windows, but it doesn't make debugging any easier- because now I really have to figure out how to debug on Windows instead (since exe is my target platform) and running the compiled exe doesn't show any logs despite all our attempts to catch the segfault. I also checked w/ Dependency Walker (Some other issue raised mentioned needing MSVC140), but the Walker and MSVC installers claimed I had everything installed.
Since it works on Linux, I imagine it's some kind of dependency issue on Windows. I've used other native addons that work just fine with SEA, so just need to debug UWS.node loading on Windows exe to see what crashes it- any ideas? Any custom development .node that just spits out info before it crashes, maybe?
Ty for the quick reply!