import { fileURLToPath } from 'node:url'; | |
import { Worker, parentPort } from 'node:worker_threads'; | |
/** | |
* Runs a task in a subprocess so any dangling stuff gets killed upon completion. | |
* The subprocess needs to be the file `forked` is called in, and `forked` needs to be called eagerly at the top level. | |
* @template T | |
* @template U | |
* @param {string} module `import.meta.url` of the file | |
* @param {(opts: T) => Promise<U>} callback The function that is invoked in the subprocess | |
* @returns {(opts: T) => Promise<U>} A function that when called starts the subprocess | |
*/ | |
export function forked(module, callback) { | |
if (process.env.SVELTEKIT_FORK && parentPort) { | |
parentPort.on( | |
'message', | |
/** @param {any} data */ async (data) => { | |
if (data?.type === 'args' && data.module === module) { | |
parentPort?.postMessage({ | |
type: 'result', | |
module, | |
payload: await callback(data.payload) | |
}); | |
} | |
} | |
); | |
parentPort.postMessage({ type: 'ready', module }); | |
} | |
/** | |
* @param {T} opts | |
* @returns {Promise<U>} | |
*/ | |
return function (opts) { | |
return new Promise((fulfil, reject) => { | |
const worker = new Worker(fileURLToPath(module), { | |
env: { | |
...process.env, | |
SVELTEKIT_FORK: 'true' | |
} | |
}); | |
worker.on( | |
'message', | |
/** @param {any} data */ (data) => { | |
if (data?.type === 'ready' && data.module === module) { | |
worker.postMessage({ | |
type: 'args', | |
module, | |
payload: opts | |
}); | |
} | |
if (data?.type === 'result' && data.module === module) { | |
worker.unref(); | |
fulfil(data.payload); | |
} | |
} | |
); | |
worker.on('exit', (code) => { | |
if (code) { | |
reject(new Error(`Failed with code ${code}`)); | |
} | |
}); | |
}); | |
}; | |
} | |