Within my electron application, I make use of fork from the child_process library to fork the process so I can have the functionality to stop/return the function instantly, but the problem I'm having is, it works perfectly fine within VSCode, but whenever I package the application into an exe using electron-builder, the script just prematurely exits instead of executing and there's no errors being logged as to why it's exiting. Does anyone have any ideas as to how can I properly do this? I read somewhere that spawning child processes in electron application in production can be problematic due to pathing issues.

index.js

ipcMain.handle('run-login', async (event, args) => {
  const taskProcessesPath = path.join(__dirname, '../build/taskProcesses.js');
  const child = fork(taskProcessesPath, [JSON.stringify({ ...args, taskType: 'login' })]);
  taskProcesses[args.id] = child;

  child.on('message', (message) => {
    console.log('Message From Task:', message);
  });

  child.on('exit', (code) => {
    statusUpdate(args.id, 'Task Stopped', 'error');
    taskGroupUpdate(args.taskGroupId, 'error');
    runningUpdate(args.id, false);
    delete taskProcesses[args.id];
  });
});

taskProcesses.js

const { login } = require('../Modules/login');

async function runTask(args) {
    try {
        switch (args.taskType) {
            case 'login':
                await login(args.taskGroupId, args.id, args.proxyGroup);
                break;
            default:
                throw new Error(`Unsupported task type: ${args.taskType}`);
        }
        process.send({ status: 'completed' });
        process.exit(0);
    } catch (error) {
        console.error(`Error executing ${args.taskType} task:`, error);
        process.send({ status: 'error', error: error.message });
        process.exit(1);
    }
}

if (require.main === module) {
    const args = JSON.parse(process.argv[2]);
runTask(args);
}

module.exports = { runTask };
2

There are 2 best solutions below

0
leitning On

It doesn't look like your process is spawning at all, you're not seeing an error from it because you aren't listening for it. I imagine you'll see something if you adjust your code like so.

ipcMain.handle('run-login', async (event, args) => {
  const taskProcessesPath = path.join(__dirname, '../build/taskProcesses.js');
  const child = fork(taskProcessesPath, [JSON.stringify({ ...args, taskType: 'login' })]);
  child.once('error', (err) => {
    console.error(err);
  });
  child.once('spawn', () => {
    taskProcesses[args.id] = child;

    child.on('message', (message) => {
      console.log('Message From Task:', message);
    });

    child.on('exit', (code) => {
      statusUpdate(args.id, 'Task Stopped', 'error');
      taskGroupUpdate(args.taskGroupId, 'error');
      runningUpdate(args.id, false);
      delete taskProcesses[args.id];
    });
  });
});

You may be running into an issue with using linux path separators on windows, you're already using path.join, you should use it fully. i.e. path.join(__dirname, '..','build','taskProcesses.js'), or you can lose the '..' in the end result with path.resolve(__dirname,'..','build','taskProcesses.js').

You may also be running into not having taskProcesses.js exist in your build at all, which would be an issue in your package.json setup.

0
Gary Archer On

My answer is focused on reliability, since dealing correctly with child processes is tricky. You usually need to establish an input / output mechanism.

EXAMPLE

I have an API test project where I can only call APIs locally using an sls invoke child process. To get it playing nicely with mocha tests I used this helper method:

private static async _runChildProcess(command: string, args: string[]): Promise<void> {

    return new Promise((resolve, reject) => {

        let childProcessOutput = '';
        const child = spawn(command, args);

        child.stdout.on('data', data => {
            childProcessOutput += data;
        });

        child.stderr.on('data', data => {
            childProcessOutput += data;
        });

        child.on('close', async code => {

            if (code === 0) {

                await fs.writeFile('test/output.txt', childProcessOutput);
                resolve();

            } else {

                await fs.writeFile('test/output.txt', childProcessOutput);
                reject(`Child process failed with exit code ${code}: ${childProcessOutput}`);
            }
        });

        child.on('error', async e => {

            if (e && e.message) {
                    childProcessOutput += `, Error: ${e.message}`;
            }

            await fs.writeFile('test/output.txt', childProcessOutput);
                reject(`Child process failed: ${childProcessOutput}`);
        });
    });
}

RELIABILITY

The exact code could of course be written differently. I would recommend making it go wrong before you allow it to succeed though, and ensuring solid error handling as I do. This should pay off when your app goes live.