Skip to content
Merged
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
17 changes: 16 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
# production
/build
/dist
/public/modules/build/
/public/modules/node_modules/
/public/modules/package-lock.json

# misc
.DS_Store
Expand Down
42 changes: 42 additions & 0 deletions public/electron.js
Original file line number Diff line number Diff line change
@@ -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') {
Expand All @@ -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;
Expand Down Expand Up @@ -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();

Expand Down
31 changes: 31 additions & 0 deletions public/modules/README.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions public/modules/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"targets": [
{
"target_name": "set_limits",
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"sources": [ "set_limits.cc" ],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
}
]
}
12 changes: 8 additions & 4 deletions public/modules/chainManager.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
const { app, shell } = require("electron");
const fs = require("fs-extra");
const path = require("path");
const { spawn } = require("child_process");
const { spawn, execSync } = require("child_process");
const BitcoinMonitor = require("./bitcoinMonitor");
const BitWindowClient = require("./bitWindowClient");
const EnforcerClient = require("./enforcerClient");
const os = require('os');
const fdLimitManager = require('./fdLimitManager');

// Get current file descriptor limit
const currentFdLimit = fdLimitManager.getCurrentLimit();
console.log(`Current file descriptor limit: ${currentFdLimit}`);

class ChainManager {
constructor(mainWindow, config, downloadManager) {
Expand Down Expand Up @@ -622,9 +628,7 @@ class ChainManager {
'-rpcport=38332',
'-rpcbind=0.0.0.0',
'stop'
], {
shell: true
});
], { shell: true });

stopProcess.stderr.on('data', (data) => {
console.error('bitcoin-cli error:', data.toString());
Expand Down
89 changes: 89 additions & 0 deletions public/modules/fdLimitManager.js
Original file line number Diff line number Diff line change
@@ -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();
13 changes: 13 additions & 0 deletions public/modules/package.json
Original file line number Diff line number Diff line change
@@ -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
}
68 changes: 68 additions & 0 deletions public/modules/set_limits.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <napi.h>
#include <sys/resource.h>

Napi::Value GetFileDescriptorLimit(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();

// Get current limits
struct rlimit current_rlim;
if (getrlimit(RLIMIT_NOFILE, &current_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<Napi::Number>().Int32Value();

// Get current limits
struct rlimit current_rlim;
if (getrlimit(RLIMIT_NOFILE, &current_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)