Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

child_process.fork doesn't work #70

Closed
cage1618 opened this issue Jun 11, 2024 · 10 comments · Fixed by #90
Closed

child_process.fork doesn't work #70

cage1618 opened this issue Jun 11, 2024 · 10 comments · Fixed by #90

Comments

@cage1618
Copy link

cage1618 commented Jun 11, 2024

What version of pkg are you using?

5.11.12

What version of Node.js are you using?

20.11.1

What operating system are you using?

mac os

What CPU architecture are you using?

x86_64

What Node versions, OSs and CPU architectures are you building for?

node20

Describe the Bug

I want to fork a child process in packaged executable, but something error occoued:

{
    "errno": -2,
    "code": "ENOENT",
    "syscall": "spawn /Applications/budding.app/Contents/MacOS/app",
    "path": "/Applications/budding.app/Contents/MacOS/app",
    "spawnargs": [
        "/snapshot/budding/dist/services/broker.js",
        1,
        "channel=fb"
    ]
}

services/broker.js has set to assets but no effect

Expected Behavior

fork process can work

To Reproduce

// package.json
{
  "name": "pkg-test",
  "version": "1.0.0",
  "main": "index.js",
  "bin": "dist/server.js",
  "license": "MIT",
  "dependencies": {},
  "devDependencies": {
    "@yao-pkg/pkg": "^5.12.0",
    "esbuild": "^0.21.5",
    "execa": "^9.2.0",
    "fs-extra": "^11.2.0",
    "ora": "^8.0.1"
  },
  "pkg": {
    "assets": [
      "./child.js"
    ]
  },
  "pkg.debug": {
    "assets": [
      "./child.js"
    ]
  }
}
// index.js
const path = require('path');
const {fork} = require('child_process');

fork(path.resolve(__dirname, '../child.js'), [], {
  cwd: __dirname
});
// child.js
setInterval(() => {
  console.log('child process');
}, 3000);
// script/build.js
import fs from 'node:fs';
import {execa} from 'execa';
import path from 'node:path';
import { oraPromise } from 'ora';

const isDebug = true;

const isWin = process.platform === 'win32';
const PKG_CONFIG_FILENAME = isDebug
  ? '.pkg.debug.config.json' : '.pkg.config.json';

async function createBundle() {
  return execa('node_modules/.bin/esbuild', [
    './index.js',
    '--bundle',
    ...(isDebug ? [] : ['--minify', '--keep-names']),
    '--outfile=dist/server.js',
    '--platform=node',
    '--loader:.node=file',
    ...((cwd) => {
      const {dependencies} = JSON.parse(
        fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf8')
      );

      return Object.keys(dependencies).map((pkg) => {
        return `--external:${pkg}`;
      });
    })(process.cwd())
  ]);
}

async function createPkgConfig(debug, configFile) {
  const cwd = process.cwd();

  const {
    bin,
    name,
    version,
    description,
    dependencies,
    ...packageJson
  } = JSON.parse(
    fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf8')
  );

  let pkgConfig;
  if (debug) {
    pkgConfig = packageJson['pkg.debug'];
  } else {
    pkgConfig = packageJson.pkg;
  }

  for (let pkg of Object.keys(dependencies)) {
    pkgConfig.assets.push(`node_modules/${pkg}`);
  }

  fs.writeFileSync(
    path.resolve(cwd, configFile), JSON.stringify({
      bin,
      name,
      version,
      description,
      dependencies,
      pkg: pkgConfig
    }, void(0), 2), 'utf8'
  );
}

async function createServerPackage(configFile) {
  return execa('node_modules/.bin/pkg', [
    configFile,
    ...(isDebug ? [] : ['--compress', 'Brotli']),
    '--output', isWin ? 'bin/app.exe' : 'bin/app'
  ]);
}


async function main() {
  try {
    await createPkgConfig(isDebug, PKG_CONFIG_FILENAME);

    await createBundle();

    await createServerPackage(PKG_CONFIG_FILENAME);
  } catch (e) {
    console.log(e);
    throw e;
  }
}

oraPromise(main, {
  text: 'Building server...\n',
  successText: 'Done\n',
  failText: 'Cannot build server'
});
// script/package.json
{
  "type": "module"
}
// build executable
node script/build.js

// run 
./bin/app

node:events:496
      throw er; // Unhandled 'error' event
      ^

Error: spawn /Users/kjh/Projects/test/pkg-test/bin/app ENOENT
    at ChildProcess._handle.onexit (node:internal/child_process:286:19)
    at onErrorNT (node:internal/child_process:484:16)
    at processTicksAndRejections (node:internal/process/task_queues:82:21)
    at process.runNextTicks [as _tickCallback] (node:internal/process/task_queues:64:3)
    at Function.runMain (pkg/prelude/bootstrap.js:1984:13)
    at node:internal/main/run_main_module:28:49
Emitted 'error' event on ChildProcess instance at:
    at ChildProcess._handle.onexit (node:internal/child_process:292:12)
    at onErrorNT (node:internal/child_process:484:16)
    [... lines matching original stack trace ...]
    at node:internal/main/run_main_module:28:49 {
  errno: -2,
  code: 'ENOENT',
  syscall: 'spawn /Users/kjh/Projects/test/pkg-test/bin/app',
  path: '/Users/kjh/Projects/test/pkg-test/bin/app',
  spawnargs: [ '/snapshot/pkg-test/dist/child.js' ]
}
@rithvik-bosch
Copy link

@cage1618 did you figure out a workaround?

@cage1618
Copy link
Author

@cage1618 did you figure out a workaround?

yes,when I remove the cwd option resolved the issue.

Insted, pass cwd to args and then override property cwd of child process if you need.

example code:

// main.js
const {fork} = require('child_process');
const child = fork('/path-to-child-process-extry.js', ['/path-of-you-want-to-specify'], {
   ...options
});


// child.js
// override child process.cwd
// put this code at the begining of this file
Object.defineProperty(process, 'cwd', {
  writable: false,
  enumerable: false,
  configurable: false,
  value: function cwd() {
    return process.argv[2];
  }
});

@robertsLando
Copy link
Member

I would like to re-open a discussion here. Seems this problem has been already widely discussed on vercel#897 (comment)

The problem is that ATM when a pkg binary invokes itself it considers the invocation to pass the call down to nodejs runtime instead. I accept suggestions about how we could fix this in order to make it work in both scenarios

@robertsLando
Copy link
Member

Tagging here all partecipants of previous issue: @edvald
@ryanblock
@dacbd
@krische
@igorklopov
@zkochan
@stripedpajamas
@eysi09
@eGavr
@0x2b3bfa0
@lokrajahuja
@koraniar
@AndoGhevian
@Avivbens

This is the active fork of pkg (with all latest nodejs versions support) and I would like your opinion about the best way to fix this to keep back compatibility and also allow users to call the binary.

@igorklopov
Copy link

igorklopov commented Sep 17, 2024

If i remove cwd from fork call then it works well. Mostly because __dirname (and it's derivatives) can't be used as cwd. Because cwd is a real fs directory. __dirname is a real fs directory only when run as a regular node app. __dirname becomes a virtual fs (aka snapshot) directory when run in a pkged app and can't be used as cwd anymore. For example cwd is "/snapshot/pkg-test/dist" in the fork call above.

Unfortunately Error: spawn /Users/kjh/Projects/test/pkg-test/bin/app ENOENT stack trace is not descriptive enough and doesn't show the value of cwd, hence doesn't point that it's actually cwd (provided to the child instance) that is the cause.

@cage1618 why do you need to override cwd in the child?

UPD you can specify /path-of-you-want-to-specify as cwd in the fork call but it should be a real fs directory, not a __dirname.

@robertsLando
Copy link
Member

robertsLando commented Sep 17, 2024

@igorklopov thanks for your comment, didn't know you were still active here on GH 😄 Nice to see you, would be awesome to have you working at this (your) project again, PM me if you are interested and I will be more then pleased to make you maintainer/admin of the repos 🙏🏼

@igorklopov
Copy link

Thanks @robertsLando! Nice to see pkg maintained while i was away 🙂 I didn't have much free time and don't have it now, but feel free to ping me like this. We'll see how it goes.

@robertsLando
Copy link
Member

@igorklopov Sure thing! Thanks 🙏🏼

@robertsLando
Copy link
Member

An example issue is here: dotenvx/dotenvx#276

In this case them are using this patch to make it work and allow the program to call itself. Issue is that actually when this happens pkg by default invokes nodejs instead

@cage1618
Copy link
Author

If i remove cwd from fork call then it works well. Mostly because __dirname (and it's derivatives) can't be used as cwd. Because cwd is a real fs directory. __dirname is a real fs directory only when run as a regular node app. __dirname becomes a virtual fs (aka snapshot) directory when run in a pkged app and can't be used as cwd anymore. For example cwd is "/snapshot/pkg-test/dist" in the fork call above.

Unfortunately Error: spawn /Users/kjh/Projects/test/pkg-test/bin/app ENOENT stack trace is not descriptive enough and doesn't show the value of cwd, hence doesn't point that it's actually cwd (provided to the child instance) that is the cause.

@cage1618 why do you need to override cwd in the child?

UPD you can specify /path-of-you-want-to-specify as cwd in the fork call but it should be a real fs directory, not a __dirname.

I want to use process.cwd() get the cwd path in another file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants