Security News
Combatting Alert Fatigue by Prioritizing Malicious Intent
In 2023, data breaches surged 78% from zero-day and supply chain attacks, but developers are still buried under alerts that are unable to prevent these threats.
A worker_threads wrapper for node.js. Provides transparent fallback for
pre-v12.0.0 node.js (via child_process
) as well as browser web workers.
Browserifiable, webpack-able.
const threads = require('bthreads');
if (threads.isMainThread) {
const worker = new threads.Worker(__filename, {
workerData: 'foo'
});
worker.on('message', console.log);
worker.on('error', console.error);
worker.on('exit', (code) => {
if (code !== 0)
console.error(`Worker stopped with exit code ${code}.`);
});
} else {
threads.parentPort.postMessage(threads.workerData + 'bar');
}
Output:
$ node --experimental-worker threads.js
foobar
$ node threads.js
foobar
bthreads has 4 backends and a few layers of fallback:
worker_threads
- Uses the still experimental worker_threads module in
node.js. Currently only usable if --experimental-worker
is passed on the
command line.child_process
- Leverages the child_process module in node.js to emulate
worker threads.web_workers
- Web Workers API (browser only).polyfill
- A polyfill for the web workers API (note that usage of
importScripts
will potentially freeze the UI if this backend is loaded).The current backend is exposed as threads.backend
.
Some caveats for the child_process
backend:
options.workerData
probably has a limited size depending on platform (the
maximum size of an environment variable).SharedArrayBuffer
does not work and will throw an error if sent.Caveats for the web_workers
backend:
options.workerData
possibly has a limited size depending on the browser
(the maximum size of options.name
).options.eval
will create a data URI and execute a new worker from it. When
using a bundler, note that the bundler will not be able to compile the
eval'd code. This means that require
will have limited usability
(restricted to only core browserify modules and bthreads
itself).options.eval
requires that data:
be set for the worker-src
Content-Security-Policy. See content-security-policy.com for a guide.close
event for MessagePorts only has partial support (if a thread
suddenly terminates, close
will not be emitted for any remote ports).
This is because the close
event is not yet a part of the standard Web
Worker API. See https://github.com/whatwg/html/issues/1766 for more info.Caveats for the polyfill
backend:
importScripts
will perform a synchronous XMLHttpRequest and potentially
freeze the UI. Additionally, XHR is bound to certain cross-origin rules that
importScripts
is not.Finally, caveats for the worker_threads
backend:
worker_threads
is still experimental in node.js!The low-level node.js API is not very useful on its own. bthreads optionally provides an API similar to bsock.
Example (for brevity, the async wrapper is not included below):
const threads = require('bthreads');
if (threads.isMainThread) {
const thread = new threads.Thread(__filename);
thread.bind('event', (x, y) => {
console.log(x + y);
});
console.log(await thread.call('job', ['hello']));
} else {
const {parent} = threads;
parent.hook('job', async (arg) => {
return arg + ' world';
});
parent.fire('event', ['foo', 'bar']);
}
Output:
foobar
hello world
You may find yourself wanting to parallelize the same worker jobs. The
high-level API offers a thread pool object (threads.Pool
) which will
automatically load balance and scale to the number of CPU cores.
if (threads.isMainThread) {
const pool = new threads.Pool(threads.source);
const results = await Promise.all([
pool.call('job1'), // Runs on thread 1.
pool.call('job2'), // Runs on thread 2.
pool.call('job3') // Runs on thread 1.
]);
console.log(results);
} else {
Buffer.poolSize = 1; // Make buffers easily transferrable.
pool.hook('job1', async () => {
const buf = Buffer.from('job1 result');
return [buf, [buf.buffer]]; // Transfer the array buffer.
});
pool.hook('job2', async () => {
return 'job2 result';
});
pool.hook('job3', async () => {
return 'job3 result';
});
}
It's good to be aware of browserify and how it sets __filename
and
__dirname
.
For example:
const worker = new threads.Worker(`${__dirname}/worker.js`);
If your code resides in /root/project/lib/main.js
, the browserify generated
path will ultimately be /lib/worker.js
. Meaning /root/project/lib/worker.js
should exist for node and http://[host]/lib/worker.js
should exist for the
browser.
The browser backend also exposes a browser
flag for this situation.
Example:
const worker = new threads.Worker(threads.browser
? 'http://.../' + path.basename(file)
: file);
To make self-execution easier, bthreads also exposes a threads.source
property which refers to the main module's filename in node.js and the current
script URL in the browser.
In the browser, bthreads exposes a more useful version of importScripts
.
const threads = require('bthreads');
const _ = threads.importScripts('https://unpkg.com/underscore/underscore.js');
This should work for any bundle exposed as UMD or CommonJS. Note that
threads.importScripts
behaves more like require
in that it caches modules
by URL. The cache is accessible through threads.importScripts.cache
.
Note that if you are eval'ing some code inside a script you plan to bundle with
browserify or webpack, require
may get unintentionally transformed or
overridden. This generally happens when you are calling toString on a defined
function.
const threads = require('bthreads');
function myWorker() {
const threads = require('bthreads');
threads.parentPort.postMessage('foo');
}
const code = `(${myWorker})();`;
const worker = new threads.Worker(code, { eval: true });
The solution is to access global.require
instead of require
.
const threads = require('bthreads');
function myWorker() {
const threads = global.require('bthreads');
threads.parentPort.postMessage('foo');
}
const code = `(${myWorker})();`;
const worker = new threads.Worker(code, { eval: true });
If you contribute code to this project, you are implicitly allowing your code
to be distributed under the MIT license. You are also implicitly verifying that
all code is your original work. </legalese>
See LICENSE for more info.
FAQs
worker threads for javascript
The npm package bthreads receives a total of 2,274 weekly downloads. As such, bthreads popularity was classified as popular.
We found that bthreads demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
In 2023, data breaches surged 78% from zero-day and supply chain attacks, but developers are still buried under alerts that are unable to prevent these threats.
Security News
Solo open source maintainers face burnout and security challenges, with 60% unpaid and 60% considering quitting.
Security News
License exceptions modify the terms of open source licenses, impacting how software can be used, modified, and distributed. Developers should be aware of the legal implications of these exceptions.