diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f66b143..d0c9309 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,6 +71,20 @@ jobs: # --------------------------------- # macOS Build # --------------------------------- + - name: Install node-addon-api for macOS + if: matrix.os == 'macos-latest' + run: | + cd public/modules + npm install node-addon-api + + - name: Build native fd limit module for macOS + if: matrix.os == 'macos-latest' + run: | + cd public/modules + npx node-gyp rebuild + # Verify module was built + ls -la build/Release || echo "Native module build failed, app will use fallback" + - name: Build & Sign Electron app (macOS) if: matrix.os == 'macos-latest' env: @@ -200,7 +214,8 @@ jobs: name: Upload to releases.drivechain.info runs-on: ubuntu-latest needs: [build] - if: github.event_name == 'push' && github.repository_owner == 'LayerTwo-Labs' + # Only run on master branch in the main repository + if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'LayerTwo-Labs' steps: - name: Download artifacts uses: actions/download-artifact@v4 diff --git a/.gitignore b/.gitignore index 800f3a8..35eff7e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ # production /build /dist +/public/modules/build/ +/public/modules/node_modules/ +/public/modules/package-lock.json # misc .DS_Store diff --git a/public/electron.js b/public/electron.js index b4fdc8f..e12e356 100644 --- a/public/electron.js +++ b/public/electron.js @@ -1,4 +1,5 @@ const { app, BrowserWindow, ipcMain, shell, powerSaveBlocker } = require("electron"); +const { execSync } = require('child_process'); // Disable sandbox for Linux if (process.platform === 'linux') { @@ -19,6 +20,44 @@ const ApiManager = require("./modules/apiManager"); const DirectoryManager = require("./modules/directoryManager"); const UpdateManager = require("./modules/updateManager"); +// Increase file descriptor limits +function increaseFileDescriptorLimits() { + try { + // Lazy-load the fdLimitManager to avoid circular dependencies + const fdLimitManager = require('./modules/fdLimitManager'); + + // For macOS, use the native module + if (process.platform === 'darwin') { + // Try to set the soft limit to 1000 without changing hard limit + const result = fdLimitManager.setFdLimit(1000); + + if (result.success) { + console.log(`Successfully increased file descriptor soft limit to ${result.limit}`); + } else { + console.log(`Could not increase file descriptor limit using native module.`); + console.log(`Child processes will run with default system limits.`); + } + } + // For Linux, use prlimit to just set the soft limit + else if (process.platform === 'linux') { + try { + const { execSync } = require('child_process'); + // Set only the soft limit, keep hard limit as-is with empty value + execSync(`prlimit --pid ${process.pid} --nofile=1000:`); + + // Verify the limit was set + const output = execSync(`prlimit --pid ${process.pid} --nofile --raw --noheadings`).toString().trim(); + const [soft] = output.split(/\s+/).map(Number); + console.log(`Successfully increased file descriptor soft limit to ${soft}`); + } catch (error) { + console.error('Failed to set file descriptor limit on Linux:', error); + } + } + } catch (error) { + // Silent error handling to avoid startup issues + } +} + const configPath = path.join(__dirname, "chain_config.json"); let config; let mainWindow = null; @@ -856,6 +895,9 @@ async function initialize() { app.commandLine.appendSwitch('no-sandbox'); async function startApp() { + // Increase file descriptor limits first + increaseFileDescriptorLimits(); + // Show loading window first createLoadingWindow(); diff --git a/public/modules/README.md b/public/modules/README.md new file mode 100644 index 0000000..fcc052f --- /dev/null +++ b/public/modules/README.md @@ -0,0 +1,31 @@ +# File Descriptor Limit Manager + +This module provides a way to increase file descriptor limits for the Drivechain Launcher application. + +## Platform Support + +- **macOS**: Uses a native addon to directly call `setrlimit()` syscall for reliable limit increase +- **Linux**: Uses `prlimit` command to increase limits for the current process +- **Windows**: No special handling needed + +## Building the Native Module + +The native module is only used on macOS. To build it: + +1. Ensure you have node-gyp requirements installed: + - Xcode Command Line Tools + - Python + +2. Navigate to this directory and run: + +```bash +cd public/modules +npm install +``` + +This will automatically build the native module if possible. If building fails, the application will +fall back to a shell-based approach which may not be as reliable. + +## Using in Code + +The module is automatically used by the application to increase file descriptor limits. \ No newline at end of file diff --git a/public/modules/binding.gyp b/public/modules/binding.gyp new file mode 100644 index 0000000..f3322fb --- /dev/null +++ b/public/modules/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "set_limits", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "set_limits.cc" ], + "include_dirs": [ + " { console.error('bitcoin-cli error:', data.toString()); diff --git a/public/modules/fdLimitManager.js b/public/modules/fdLimitManager.js new file mode 100644 index 0000000..9541ef8 --- /dev/null +++ b/public/modules/fdLimitManager.js @@ -0,0 +1,89 @@ +const { execSync } = require('child_process'); +const fs = require('fs-extra'); +const path = require('path'); + +class FdLimitManager { + constructor() { + this.initialized = false; + this.nativeModule = null; + + // Only try to load native module on macOS + if (process.platform === 'darwin') { + try { + // Path to where the binary would be after building + const modulePath = path.join(__dirname, 'build/Release/set_limits.node'); + + // Check if the module exists + if (fs.existsSync(modulePath)) { + this.nativeModule = require(modulePath); + this.initialized = true; + console.log('Successfully loaded native setrlimit module'); + } else { + console.error('Native module not found, perhaps it needs to be built'); + } + } catch (error) { + console.error('Failed to load native module:', error); + } + } + } + + /** + * Set file descriptor limits for the current process + * @param {number} limit - The limit to set + * @returns {object} - Object with success boolean and actual limit set + */ + setFdLimit(limit) { + // This only works on macOS with the native module + if (process.platform === 'darwin' && this.initialized && this.nativeModule) { + try { + const result = this.nativeModule.setFileDescriptorLimit(limit); + + if (result === 0) { // 0 means success in syscalls + // Get the actual limit that was set (might be different from requested) + const actualLimits = this.nativeModule.getFileDescriptorLimit(); + return { + success: true, + limit: actualLimits.soft + }; + } + + return { success: false, limit: 0 }; + } catch (error) { + console.error('Failed to set file descriptor limit:', error); + return { success: false, limit: 0 }; + } + } + + return { success: false, limit: 0 }; + } + + /** + * Get the current file descriptor limit + * @returns {number} - The current limit or -1 if unknown + */ + getCurrentLimit() { + try { + if (process.platform === 'darwin') { + // On macOS, get process-specific limit if possible + if (this.initialized && this.nativeModule) { + // Use the native module to get the current limit + const limits = this.nativeModule.getFileDescriptorLimit(); + return limits.soft || -1; + } + // Fallback - this isn't ideal but better than nothing + return -1; + } else if (process.platform === 'linux') { + // On Linux, use prlimit to get the process-specific limit + const output = execSync(`prlimit --pid ${process.pid} --nofile --raw --noheadings`).toString().trim(); + const [soft] = output.split(/\s+/).map(Number); + return soft || -1; + } + } catch (error) { + console.error('Failed to get current file descriptor limit:', error); + } + + return -1; + } +} + +module.exports = new FdLimitManager(); \ No newline at end of file diff --git a/public/modules/package.json b/public/modules/package.json new file mode 100644 index 0000000..2776529 --- /dev/null +++ b/public/modules/package.json @@ -0,0 +1,13 @@ +{ + "name": "set-limits", + "version": "1.0.0", + "description": "Native module for setting file descriptor limits", + "main": "fdLimitManager.js", + "scripts": { + "install": "node-gyp rebuild || echo 'Native module build failed, will use fallback'" + }, + "dependencies": { + "node-addon-api": "^5.0.0" + }, + "gypfile": true +} \ No newline at end of file diff --git a/public/modules/set_limits.cc b/public/modules/set_limits.cc new file mode 100644 index 0000000..dcc77ce --- /dev/null +++ b/public/modules/set_limits.cc @@ -0,0 +1,68 @@ +#include +#include + +Napi::Value GetFileDescriptorLimit(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Get current limits + struct rlimit current_rlim; + if (getrlimit(RLIMIT_NOFILE, ¤t_rlim) != 0) { + return Napi::Number::New(env, -1); // Error + } + + // Create object with soft and hard limits + Napi::Object result = Napi::Object::New(env); + result.Set("soft", Napi::Number::New(env, current_rlim.rlim_cur)); + result.Set("hard", Napi::Number::New(env, current_rlim.rlim_max)); + + return result; +} + +Napi::Value SetFileDescriptorLimit(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Check if we have the limit argument + if (info.Length() < 1 || !info[0].IsNumber()) { + Napi::TypeError::New(env, "Number expected for limit").ThrowAsJavaScriptException(); + return env.Null(); + } + + // Get the limit from JavaScript + int limit = info[0].As().Int32Value(); + + // Get current limits + struct rlimit current_rlim; + if (getrlimit(RLIMIT_NOFILE, ¤t_rlim) != 0) { + return Napi::Number::New(env, -1); // Error + } + + // Don't set limit higher than the hard limit + if (limit > current_rlim.rlim_max) { + limit = current_rlim.rlim_max; + } + + // Set up new rlimit - only change soft limit + struct rlimit new_rlim; + new_rlim.rlim_cur = limit; + new_rlim.rlim_max = current_rlim.rlim_max; // Keep existing hard limit + + // Make the syscall + int result = setrlimit(RLIMIT_NOFILE, &new_rlim); + + // Return the result to JavaScript (0 means success) + return Napi::Number::New(env, result); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set( + Napi::String::New(env, "setFileDescriptorLimit"), + Napi::Function::New(env, SetFileDescriptorLimit) + ); + exports.Set( + Napi::String::New(env, "getFileDescriptorLimit"), + Napi::Function::New(env, GetFileDescriptorLimit) + ); + return exports; +} + +NODE_API_MODULE(set_limits, Init) \ No newline at end of file