TooTallNate / proxy-agents

Node.js HTTP Proxy Agents Monorepo
https://proxy-agents.n8.io
944 stars 243 forks source link

Crash after 32k calls to findProxyForURL due to quickjs in pac-resolver #333

Open weikinhuang opened 2 months ago

weikinhuang commented 2 months ago

Hi, I'm running into an issue where quickjs will crash with an uncatchable error after 32k (2^15) calls to the compiled findProxyForURL function.

32759 SOCKS5 127.0.0.1:29001
32760 SOCKS5 127.0.0.1:29001
[C to host error: returning null] Error: QuickJSContext had no callback with id -32768
    at Object.callFunction (/Users/wehuang/source/http-proxy-via-socks/node_modules/@tootallnate/quickjs-emscripten/dist/context.js:117:27)
    at /Users/wehuang/source/http-proxy-via-socks/node_modules/@tootallnate/quickjs-emscripten/dist/module.js:36:31
    at QuickJSModuleCallbacks.handleAsyncify (/Users/wehuang/source/http-proxy-via-socks/node_modules/@tootallnate/quickjs-emscripten/dist/module.js:145:23)
    at QuickJSEmscriptenModuleCallbacks.callFunction (/Users/wehuang/source/http-proxy-via-socks/node_modules/@tootallnate/quickjs-emscripten/dist/module.js:30:80)
    at o (/Users/wehuang/source/http-proxy-via-socks/node_modules/@tootallnate/quickjs-emscripten/dist/generated/emscripten-module.WASM_RELEASE_SYNC.js:277:92)
    at wasm://wasm/001bd82a:wasm-function[1084]:0x54415
    at wasm://wasm/001bd82a:wasm-function[893]:0x49249
    at wasm://wasm/001bd82a:wasm-function[216]:0xc7e6
    at wasm://wasm/001bd82a:wasm-function[33]:0x2002
    at wasm://wasm/001bd82a:wasm-function[1008]:0x512d6

Here's the test script I'm using.

import { createPacResolver } from 'pac-resolver';
import { getQuickJS } from '@tootallnate/quickjs-emscripten';

const pac = `
function FindProxyForURL(url, host) {
  if (shExpMatch(host, "127.0.0.1")) {
    return "DIRECT";
  }
  if (shExpMatch(url, "*foo.*")) {
    return "SOCKS5 127.0.0.1:29001";
  }
  return "DIRECT";
}
`;

process.on('unhandledRejection', (reason, promise) => {
  // never gets here
  console.log('Unhandled Rejection at:', promise, 'reason:', reason);
  process.exit(1);
});
process.on('uncaughtException', (err) => {
  // never gets here
  console.error('There was an uncaught error', err);
  process.exit(1);
});

async function main() {
  const findProxyForURLPromise = Promise.all([getQuickJS()]).then(([qjs]) => createPacResolver(qjs, pac));
  const findProxyForURL = await findProxyForURLPromise;

  for (let i = 0; i < 100000; i++) {
    try {
      const result = await findProxyForURL('http://foo.com/', 'foo.com');
      console.log(i, result);
    } catch (e) {
      // never gets here
      console.log(`Error at ${i}`);
      console.error(e);
      throw e;
    }
  }
  // never gets here
  console.log('im done');
}

try {
   main();
} catch (e) {
  // never gets here
  console.log('main exited with error', e);
}

I'm running this in a server application, if I create a new findProxyForURLPromise on each request, it will cause a memory leak.