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

js: fix read with timeout on windows #6

Open
milahu opened this issue Dec 24, 2023 · 0 comments
Open

js: fix read with timeout on windows #6

milahu opened this issue Dec 24, 2023 · 0 comments

Comments

@milahu
Copy link
Owner

milahu commented Dec 24, 2023

currently tokenpool.js only works on linux (and probably on macos)

  const reader = child_process.spawnSync('dd', ddArgs, {
    timeout,
    stdio,
    windowsHide: true,
    ...options,
  });

this calls dd to read 1 byte from the jobserver fd

dd bs=1 count=1 status=none

an alternative would be the head command

head -c1

but head -c is not posix-compliant
see also Can I read a single character from stdin in POSIX shell?
(maybe dd status=none is also not posix-compliant...)

possible solution

if the dd and head commands are not available, run node

draft added in 663f117

/**
* read with timeout
* @param {number | string} fdOrPath
* @param {number} size
* @param {number} timeout
* @param {Object} options
* @param {number} [options.blockSize=4096]
* @param {string=} options.encoding
*/
function readWithTimeout(fdOrPath, size, timeout, options = {}) {
  if (!options) options = {};
  if (typeof(fdOrPath) != 'number') {
    throw Error(`reading from path not implemented. fdOrPath must be number`);
  }
  if (typeof(size) != 'number') {
    throw Error(`invalid size: ${size}`);
  }
  size = size | 0; // enforce integer size
  if (size < 0) {
    // TODO "size == -1" means: read until EOF
    throw Error(`invalid size: ${size}`);
  }
  if (size == 0) {
    // for child_process.execSync the default encoding is "buffer"
    // https://nodejs.org/api/child_process.html#child_processexecsynccommand-options
    if (!options.encoding || options.encoding == 'buffer') {
      return Buffer.alloc(0);
    }
    // FIXME handle "unrecognized character encoding"
    // If encoding is 'buffer', or an unrecognized character encoding, Buffer objects will be passed to the callback instead.
    //if (is_unrecognized(options.encoding)) return Buffer.alloc(0);
    return '';
  }
  const blockSizeDefault = 4096; // TODO better? 64 * 1024?
  const blockSize = Math.min(size, (Number(options.blockSize) | 0) || blockSizeDefault);
  if (options.blockSize) delete options.blockSize;
  if (options.timeout) throw Error('dont set options.timeout, use the timeout parameter');
  const nodeCode = [
    "const {stdout} = require('process');",
    "const {readSync} = require('fs');",
    "const {Buffer} = require('buffer');",
    `const buf = Buffer.alloc(${blockSize});`,
    "let done = 0;",
    "const sleep = ms => new Promise(r => setTimeout(r, ms));",
    "async function main() {",
    `  while (done < ${size}) {`,
    `    const n = readSync(${fdOrPath}, buf, 0, ${blockSize}, -1);`,
    "    if (n > 0) stdout.write(buf);",
    "    done += n;",
    // reduce cpu time from 100% to 0%
    // 200ms is arbitrary, 50ms would be enough to reduce cpu time
    "    await sleep(200);",
    "  }",
    "}",
    "main();",
  ].join('\n');
  //console.log('nodeCode:\n' + nodeCode); // debug
  const nodeExe = process.argv[0];
  const nodeArgs = ['-e', nodeCode];
  // connect fdOrPath to stdin of the subprocess
  const stdio = [fdOrPath, 'pipe', 'pipe'];
  //console.dir({ fdOrPath, size, timeout, stdio, nodeArgs });
  const reader = child_process.spawnSync(nodeExe, nodeArgs, {
    timeout,
    stdio,
    windowsHide: true,
    ...options,
  });
  if (reader.error) throw reader.error;
  return reader.stdout;
}

i guess we still need child_process.spawnSync to enforce the read timeout

readSync is non-blocking on stdin because stdin is a non-blocking stream
but the jobserver fds or fifo can be blocking, so readSync could hang forever

the generated nodeCode looks like

const {stdout} = require('process');
const {readSync} = require('fs');
const {Buffer} = require('buffer');
const buf = Buffer.alloc(1);
let done = 0;
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function main() {
  while (done < 1) {
    const n = readSync(3, buf, 0, 1, -1);
    if (n > 0) stdout.write(buf);
    done += n;
    await sleep(200);
  }
}
main();

readSync(3, buf, 0, 1, -1);

3 is the fd number to read from, 0 would be stdin

@milahu milahu changed the title js: make it work on windows js: fix read with timeout on windows Mar 4, 2024
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

No branches or pull requests

1 participant