├── .gitignore ├── README.md ├── ch1-c-threads ├── happycoin-threads.c └── happycoin.c ├── ch2-patterns ├── index.html ├── main.js ├── rpc-worker.js └── worker.js ├── ch2-service-workers ├── index.html ├── main.js └── sw.js ├── ch2-shared-workers ├── blue.html ├── blue.js ├── foo │ └── index.html ├── red.html ├── red.js └── shared-worker.js ├── ch2-web-workers ├── index.html ├── main.js └── worker.js ├── ch3-cluster ├── hello-world-cluster.js └── hello-world.js ├── ch3-happycoin ├── happycoin-piscina.js ├── happycoin-threads.js └── happycoin.js ├── ch4-serialization └── booleans.js ├── ch4-web-workers ├── index.html ├── main-node.js ├── main.js ├── worker-node.js └── worker.js ├── ch5-game-of-life ├── gol.html ├── gol.js ├── thread-gol.html └── thread-gol.js ├── ch5-node-block └── main.js ├── ch5-notify-order ├── index.html ├── main.js └── worker.js ├── ch5-notify-when-ready ├── index.html ├── main.js └── worker.js ├── ch6-actors ├── actor.js ├── rpc-worker.js ├── server.js └── worker.js ├── ch6-mutex ├── mutex.js ├── thread-product-mutex.js └── thread-product.js ├── ch6-ring-buffer └── ring-buffer.js ├── ch6-thread-pool ├── main.js ├── rpc-worker.js └── worker.js ├── ch7-happycoin-as ├── happycoin.mjs └── happycoin.ts ├── ch7-wasm-add ├── add.js └── add.wat ├── ch8-memory-overhead ├── leader.js └── worker.js └── ch8-template-render ├── .gitignore ├── package-lock.json ├── package.json ├── rpc-worker.js ├── server.js ├── template.js └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multithreaded JavaScript Code Samples 2 | 3 | These are the code samples to accompany the book [Multithreaded JavaScript](https://learning.oreilly.com/library/view/multithreaded-javascript/9781098104429/). 4 | 5 | If you find an issue with the book, please use the [Errata for Multithreaded JavaScript](https://www.oreilly.com/catalog/errata.csp?isbn=0636920523987) page to submit the problem. 6 | However, if you find an issue directly with the code, then you may [submit an issue](https://github.com/MultithreadedJSBook/code-samples/issues) to this repository. 7 | -------------------------------------------------------------------------------- /ch1-c-threads/happycoin-threads.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct happy_result { 9 | size_t count; 10 | uint64_t * nums; 11 | }; 12 | 13 | uint64_t random64(uint32_t * seed) { 14 | uint64_t result; 15 | uint8_t * result8 = (uint8_t *)&result; 16 | for (size_t i = 0; i < sizeof(result); i++) { 17 | result8[i] = rand_r(seed); 18 | } 19 | return result; 20 | } 21 | 22 | uint64_t sum_digits_squared(uint64_t num) { 23 | uint64_t total = 0; 24 | while (num > 0) { 25 | uint64_t num_mod_base = num % 10; 26 | total += num_mod_base * num_mod_base; 27 | num = num / 10; 28 | } 29 | return total; 30 | } 31 | 32 | bool is_happy(uint64_t num) { 33 | while (num != 1 && num != 4) { 34 | num = sum_digits_squared(num); 35 | } 36 | return num == 1; 37 | } 38 | 39 | bool is_happycoin(uint64_t num) { 40 | return is_happy(num) && num % 10000 == 0; 41 | } 42 | 43 | void * get_happycoins(void * arg) { 44 | int attempts = *(int *)arg; // <1> 45 | int limit = attempts/10000; 46 | uint32_t seed = time(NULL); 47 | uint64_t * nums = malloc(limit * sizeof(uint64_t)); 48 | struct happy_result * result = malloc(sizeof(struct happy_result)); 49 | result->nums = nums; 50 | result->count = 0; 51 | for (int i = 1; i < attempts; i++) { 52 | if (result->count == limit) { 53 | break; 54 | } 55 | uint64_t random_num = random64(&seed); 56 | if (is_happycoin(random_num)) { 57 | result->nums[result->count++] = random_num; 58 | } 59 | } 60 | return (void *)result; 61 | } 62 | 63 | #define THREAD_COUNT 4 64 | 65 | int main() { 66 | pthread_t thread [THREAD_COUNT]; 67 | 68 | int attempts = 10000000/THREAD_COUNT; 69 | int count = 0; 70 | for (int i = 0; i < THREAD_COUNT; i++) { 71 | pthread_create(&thread[i], NULL, get_happycoins, &attempts); 72 | } 73 | for (int j = 0; j < THREAD_COUNT; j++) { 74 | struct happy_result * result; 75 | pthread_join(thread[j], (void **)&result); 76 | count += result->count; 77 | for (int k = 0; k < result->count; k++) { 78 | printf("%" PRIu64 " ", result->nums[k]); 79 | } 80 | } 81 | printf("\ncount %d\n", count); 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /ch1-c-threads/happycoin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | uint64_t random64(uint32_t * seed) { 8 | uint64_t result; 9 | uint8_t * result8 = (uint8_t *)&result; // <1> 10 | for (size_t i = 0; i < sizeof(result); i++) { 11 | result8[i] = rand_r(seed); 12 | } 13 | return result; 14 | } 15 | 16 | uint64_t sum_digits_squared(uint64_t num) { 17 | uint64_t total = 0; 18 | while (num > 0) { 19 | uint64_t num_mod_base = num % 10; 20 | total += num_mod_base * num_mod_base; 21 | num = num / 10; 22 | } 23 | return total; 24 | } 25 | 26 | bool is_happy(uint64_t num) { 27 | while (num != 1 && num != 4) { 28 | num = sum_digits_squared(num); 29 | } 30 | return num == 1; 31 | } 32 | 33 | bool is_happycoin(uint64_t num) { 34 | return is_happy(num) && num % 10000 == 0; 35 | } 36 | 37 | int main() { 38 | uint32_t seed = time(NULL); 39 | int count = 0; 40 | for (int i = 1; i < 10000000; i++) { 41 | uint64_t random_num = random64(&seed); 42 | if (is_happycoin(random_num)) { 43 | printf("%" PRIu64 " ", random_num); 44 | count++; 45 | } 46 | } 47 | printf("\ncount %d\n", count); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /ch2-patterns/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Worker Patterns 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch2-patterns/main.js: -------------------------------------------------------------------------------- 1 | const worker = new RpcWorker('worker.js'); 2 | 3 | Promise.allSettled([ 4 | worker.exec('square_sum', 1_000_000), 5 | worker.exec('fibonacci', 1_000), 6 | worker.exec('fake_method'), 7 | worker.exec('bad'), 8 | ]).then(([square_sum, fibonacci, fake, bad]) => { 9 | console.log('square sum', square_sum); 10 | console.log('fibonacci', fibonacci); 11 | console.log('fake', fake); 12 | console.log('bad', bad); 13 | }); 14 | -------------------------------------------------------------------------------- /ch2-patterns/rpc-worker.js: -------------------------------------------------------------------------------- 1 | class RpcWorker { 2 | constructor(path) { 3 | this.next_command_id = 0; 4 | this.in_flight_commands = new Map(); 5 | this.worker = new Worker(path); 6 | this.worker.onmessage = this.onMessageHandler.bind(this); 7 | } 8 | // THIS LINE SHOULD NOT APPEAR IN PRINT 9 | onMessageHandler(msg) { 10 | const { result, error, id } = msg.data; 11 | const { resolve, reject } = this.in_flight_commands.get(id); 12 | this.in_flight_commands.delete(id); 13 | if (error) reject(error); 14 | else resolve(result); 15 | } 16 | // THIS LINE SHOULD NOT APPEAR IN PRINT 17 | exec(method, ...args) { 18 | const id = ++this.next_command_id; 19 | let resolve, reject; 20 | const promise = new Promise((res, rej) => { 21 | resolve = res; 22 | reject = rej; 23 | }); 24 | this.in_flight_commands.set(id, { resolve, reject }); 25 | this.worker.postMessage({ method, params: args, id }); 26 | return promise; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch2-patterns/worker.js: -------------------------------------------------------------------------------- 1 | const sleep = (ms) => new Promise((res) => setTimeout(res, ms)); // <1> 2 | 3 | function asyncOnMessageWrap(fn) { // <2> 4 | return async function(msg) { 5 | postMessage(await fn(msg.data)); 6 | } 7 | } 8 | 9 | const commands = { 10 | async square_sum(max) { 11 | await sleep(Math.random() * 100); // <3> 12 | let sum = 0; for (let i = 0; i < max; i++) sum += Math.sqrt(i); 13 | return sum; 14 | }, 15 | async fibonacci(limit) { 16 | await sleep(Math.random() * 100); 17 | let prev = 1n, next = 0n, swap; 18 | while (limit) { swap = prev; prev = prev + next; next = swap; limit--; } 19 | return String(next); // <4> 20 | }, 21 | async bad() { 22 | await sleep(Math.random() * 10); 23 | throw new Error('oh no'); 24 | } 25 | }; 26 | 27 | self.onmessage = asyncOnMessageWrap(async (rpc) => { // <5> 28 | const { method, params, id } = rpc; 29 | 30 | if (commands.hasOwnProperty(method)) { 31 | try { 32 | const result = await commands[method](...params); 33 | return { id, result }; // <6> 34 | } catch (err) { 35 | return { id, error: { code: -32000, message: err.message }}; 36 | } 37 | } else { 38 | return { // <7> 39 | id, error: { 40 | code: -32601, 41 | message: `method ${method} not found` 42 | } 43 | }; 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /ch2-service-workers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Service Workers Example 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ch2-service-workers/main.js: -------------------------------------------------------------------------------- 1 | navigator.serviceWorker.register('/sw.js', { // <1> 2 | scope: '/' 3 | }); 4 | 5 | navigator.serviceWorker.oncontrollerchange = () => { // <2> 6 | console.log('controller change'); 7 | }; 8 | 9 | async function makeRequest() { // <3> 10 | const result = await fetch('/data.json'); 11 | const payload = await result.json(); 12 | console.log(payload); 13 | } 14 | -------------------------------------------------------------------------------- /ch2-service-workers/sw.js: -------------------------------------------------------------------------------- 1 | let counter = 0; 2 | 3 | self.oninstall = (event) => { 4 | console.log('service worker install'); 5 | }; 6 | 7 | self.onactivate = (event) => { 8 | console.log('service worker activate'); 9 | event.waitUntil(self.clients.claim()); // <1> 10 | }; 11 | 12 | self.onfetch = (event) => { 13 | console.log('fetch', event.request.url); 14 | 15 | if (event.request.url.endsWith('/data.json')) { 16 | counter++; 17 | event.respondWith( // <2> 18 | new Response(JSON.stringify({counter}), { 19 | headers: { 20 | 'Content-Type': 'application/json' 21 | } 22 | }) 23 | ); 24 | return; 25 | } 26 | 27 | // fallback to normal HTTP request 28 | event.respondWith(fetch(event.request)); // <3> 29 | }; 30 | -------------------------------------------------------------------------------- /ch2-shared-workers/blue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shared Workers Blue 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ch2-shared-workers/blue.js: -------------------------------------------------------------------------------- 1 | console.log('blue.js'); 2 | 3 | const worker = new SharedWorker('shared-worker.js'); 4 | 5 | worker.port.onmessage = (event) => { 6 | console.log('EVENT', event.data); 7 | }; 8 | -------------------------------------------------------------------------------- /ch2-shared-workers/foo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shared Workers Blue 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ch2-shared-workers/red.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shared Workers Red 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ch2-shared-workers/red.js: -------------------------------------------------------------------------------- 1 | console.log('red.js'); 2 | 3 | const worker = new SharedWorker('shared-worker.js'); // <1> 4 | 5 | worker.port.onmessage = (event) => { // <2> 6 | console.log('EVENT', event.data); 7 | }; 8 | -------------------------------------------------------------------------------- /ch2-shared-workers/shared-worker.js: -------------------------------------------------------------------------------- 1 | const ID = Math.floor(Math.random() * 999999); // <1> 2 | console.log('shared-worker.js', ID); 3 | 4 | const ports = new Set(); // <2> 5 | 6 | self.onconnect = (event) => { // <3> 7 | const port = event.ports[0]; 8 | ports.add(port); 9 | console.log('CONN', ID, ports.size); 10 | 11 | port.onmessage = (event) => { // <4> 12 | console.log('MESSAGE', ID, event.data); 13 | 14 | for (let p of ports) { // <5> 15 | p.postMessage([ID, event.data]); 16 | } 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /ch2-web-workers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Web Workers Hello World 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ch2-web-workers/main.js: -------------------------------------------------------------------------------- 1 | console.log('hello from main.js'); 2 | 3 | const worker = new Worker('worker.js'); // <1> 4 | 5 | worker.onmessage = (msg) => { // <2> 6 | console.log('message received from worker', msg.data); 7 | }; 8 | 9 | worker.postMessage('message sent to worker'); // <3> 10 | 11 | console.log('hello from end of main.js'); 12 | -------------------------------------------------------------------------------- /ch2-web-workers/worker.js: -------------------------------------------------------------------------------- 1 | console.log('hello from worker.js'); 2 | 3 | self.onmessage = (msg) => { 4 | console.log('message from main', msg.data); 5 | 6 | postMessage('message sent from worker'); 7 | }; 8 | -------------------------------------------------------------------------------- /ch3-cluster/hello-world-cluster.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const cluster = require('cluster'); // <1> 3 | 4 | if (cluster.isPrimary) { // <2> 5 | cluster.fork(); // <3> 6 | cluster.fork(); 7 | cluster.fork(); 8 | cluster.fork(); 9 | } else { 10 | http.createServer((req, res) => { 11 | res.end('Hello, World!\n'); 12 | }).listen(3000); // <4> 13 | } 14 | -------------------------------------------------------------------------------- /ch3-cluster/hello-world.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | 3 | http.createServer((req, res) => { 4 | res.end('Hello, World!\n'); 5 | }).listen(3000); 6 | -------------------------------------------------------------------------------- /ch3-happycoin/happycoin-piscina.js: -------------------------------------------------------------------------------- 1 | const Piscina = require('piscina'); 2 | const crypto = require('crypto'); 3 | 4 | const big64arr = new BigUint64Array(1) 5 | function random64() { 6 | crypto.randomFillSync(big64arr); 7 | return big64arr[0]; 8 | } 9 | 10 | function sumDigitsSquared(num) { 11 | let total = 0n; 12 | while (num > 0) { 13 | const numModBase = num % 10n; 14 | total += numModBase ** 2n; 15 | num = num / 10n; 16 | } 17 | return total; 18 | } 19 | 20 | function isHappy(num) { 21 | while (num != 1n && num != 4n) { 22 | num = sumDigitsSquared(num); 23 | } 24 | return num === 1n; 25 | } 26 | 27 | function isHappycoin(num) { 28 | return isHappy(num) && num % 10000n === 0n; 29 | } 30 | 31 | const THREAD_COUNT = 4; 32 | 33 | if (!Piscina.isWorkerThread) { // <1> 34 | const piscina = new Piscina({ 35 | filename: __filename, // <2> 36 | minThreads: THREAD_COUNT, // <3> 37 | maxThreads: THREAD_COUNT 38 | }) 39 | let done = 0; 40 | let count = 0; 41 | for (let i = 0; i < THREAD_COUNT; i++) { // <4> 42 | (async () => { 43 | const { total, happycoins } = await piscina.run() // <5> 44 | process.stdout.write(happycoins); 45 | count += total; 46 | if (++done === THREAD_COUNT) { // <6> 47 | console.log('\ncount', count); 48 | } 49 | })(); 50 | } 51 | } 52 | 53 | module.exports = () => { 54 | let happycoins = ''; 55 | let total = 0; 56 | for (let i = 0; i < 10_000_000/THREAD_COUNT; i++) { // <1> 57 | const randomNum = random64(); 58 | if (isHappycoin(randomNum)) { 59 | happycoins += randomNum.toString() + ' '; 60 | total++; 61 | } 62 | } 63 | return { total, happycoins }; // <2> 64 | } 65 | -------------------------------------------------------------------------------- /ch3-happycoin/happycoin-threads.js: -------------------------------------------------------------------------------- 1 | const { 2 | Worker, 3 | isMainThread, 4 | parentPort 5 | } = require('worker_threads'); 6 | const crypto = require('crypto'); 7 | 8 | const big64arr = new BigUint64Array(1); 9 | function random64() { 10 | crypto.randomFillSync(big64arr); 11 | return big64arr[0]; 12 | } 13 | 14 | function sumDigitsSquared(num) { 15 | let total = 0n; 16 | while (num > 0) { 17 | const numModBase = num % 10n; 18 | total += numModBase ** 2n; 19 | num = num / 10n; 20 | } 21 | return total; 22 | } 23 | 24 | function isHappy(num) { 25 | while (num != 1n && num != 4n) { 26 | num = sumDigitsSquared(num); 27 | } 28 | return num === 1n; 29 | } 30 | 31 | function isHappycoin(num) { 32 | return isHappy(num) && num % 10000n === 0n; 33 | } 34 | 35 | const THREAD_COUNT = 4; 36 | 37 | if (isMainThread) { 38 | let inFlight = THREAD_COUNT; 39 | let count = 0; 40 | for (let i = 0; i < THREAD_COUNT; i++) { 41 | const worker = new Worker(__filename); 42 | worker.on('message', msg => { 43 | if (msg === 'done') { 44 | if (--inFlight === 0) { 45 | process.stdout.write('\ncount ' + count + '\n'); 46 | } 47 | } else if (typeof msg === 'bigint') { 48 | process.stdout.write(msg.toString() + ' '); 49 | count++; 50 | } 51 | }) 52 | } 53 | } else { 54 | for (let i = 1; i < 10_000_000/THREAD_COUNT; i++) { 55 | const randomNum = random64(); 56 | if (isHappycoin(randomNum)) { 57 | parentPort.postMessage(randomNum); 58 | } 59 | } 60 | parentPort.postMessage('done'); 61 | } 62 | -------------------------------------------------------------------------------- /ch3-happycoin/happycoin.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | const big64arr = new BigUint64Array(1) 4 | function random64() { 5 | crypto.randomFillSync(big64arr); 6 | return big64arr[0]; 7 | } 8 | 9 | function sumDigitsSquared(num) { 10 | let total = 0n; 11 | while (num > 0) { 12 | const numModBase = num % 10n; 13 | total += numModBase ** 2n; 14 | num = num / 10n; 15 | } 16 | return total; 17 | } 18 | 19 | function isHappy(num) { 20 | while (num != 1n && num != 4n) { 21 | num = sumDigitsSquared(num); 22 | } 23 | return num === 1n; 24 | } 25 | 26 | function isHappycoin(num) { 27 | return isHappy(num) && num % 10000n === 0n; 28 | } 29 | 30 | let count = 0; 31 | for (let i = 1; i < 10_000_000; i++) { 32 | const randomNum = random64(); 33 | if (isHappycoin(randomNum)) { 34 | process.stdout.write(randomNum.toString() + ' '); 35 | count++; 36 | } 37 | } 38 | 39 | process.stdout.write('\ncount ' + count + '\n'); 40 | -------------------------------------------------------------------------------- /ch4-serialization/booleans.js: -------------------------------------------------------------------------------- 1 | const buffer = new ArrayBuffer(1); 2 | const view = new Uint8Array(buffer); 3 | function setBool(slot, value) { 4 | view[0] = (view[0] & ~(1 << slot)) | ((value|0) << slot); 5 | } 6 | function getBool(slot) { 7 | return !((view[0] & (1 << slot)) === 0); 8 | } 9 | // THIS SHOULD NOT APPEAR IN PRINT 10 | 11 | console.log(view[0]); // 0 12 | setBool(0, true); 13 | console.log(view[0]); // 1 14 | setBool(1, true); 15 | console.log(view[0]); // 3 16 | setBool(0, false); 17 | console.log(view[0]); // 2 18 | 19 | console.log(getBool(0)); // false 20 | console.log(getBool(1)); // true 21 | console.log(getBool(2)); // false 22 | 23 | setBool(7, true); 24 | console.log(view[0]); // 130 25 | -------------------------------------------------------------------------------- /ch4-web-workers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shared Memory Hello World 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ch4-web-workers/main-node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { Worker } = require('worker_threads'); 4 | const worker = new Worker(__dirname + '/worker-node.js'); 5 | 6 | const buffer = new SharedArrayBuffer(1024); 7 | const view = new Uint8Array(buffer); 8 | 9 | console.log('now', view[0]); 10 | 11 | worker.postMessage(buffer); 12 | 13 | setTimeout(() => { 14 | console.log('later', view[0]); 15 | console.log('prop', buffer.foo); 16 | worker.unref(); 17 | }, 500); 18 | -------------------------------------------------------------------------------- /ch4-web-workers/main.js: -------------------------------------------------------------------------------- 1 | if (!crossOriginIsolated) { // <1> 2 | throw new Error('Cannot use SharedArrayBuffer'); 3 | } 4 | 5 | const worker = new Worker('worker.js'); 6 | 7 | const buffer = new SharedArrayBuffer(1024); // <2> 8 | const view = new Uint8Array(buffer); // <3> 9 | 10 | console.log('now', view[0]); 11 | 12 | worker.postMessage(buffer); 13 | 14 | setTimeout(() => { 15 | console.log('later', view[0]); 16 | console.log('prop', buffer.foo); // <4> 17 | }, 500); 18 | -------------------------------------------------------------------------------- /ch4-web-workers/worker-node.js: -------------------------------------------------------------------------------- 1 | const { parentPort } = require('worker_threads'); 2 | 3 | parentPort.on('message', (buffer) => { 4 | buffer.foo = 42; 5 | const view = new Uint8Array(buffer); 6 | view[0] = 2; 7 | console.log('updated in worker'); 8 | }); 9 | -------------------------------------------------------------------------------- /ch4-web-workers/worker.js: -------------------------------------------------------------------------------- 1 | self.onmessage = ({data: buffer}) => { 2 | buffer.foo = 42; // <1> 3 | const view = new Uint8Array(buffer); 4 | view[0] = 2; // <2> 5 | console.log('updated in worker'); 6 | }; 7 | -------------------------------------------------------------------------------- /ch5-game-of-life/gol.html: -------------------------------------------------------------------------------- 1 |

Iteration: 0

2 | 3 | 4 | -------------------------------------------------------------------------------- /ch5-game-of-life/gol.js: -------------------------------------------------------------------------------- 1 | class Grid { 2 | constructor(size, buffer, paint = () => {}) { 3 | const sizeSquared = size * size; 4 | this.buffer = buffer; 5 | this.size = size; 6 | this.cells = new Uint8Array(this.buffer, 0, sizeSquared); 7 | this.nextCells = new Uint8Array(this.buffer, sizeSquared, sizeSquared); 8 | this.paint = paint; 9 | } 10 | 11 | getCell(x, y) { 12 | const size = this.size; 13 | const sizeM1 = size - 1; 14 | x = x < 0 ? sizeM1 : x > sizeM1 ? 0 : x; 15 | y = y < 0 ? sizeM1 : y > sizeM1 ? 0 : y; 16 | return this.cells[size * x + y]; 17 | } 18 | 19 | static NEIGHBORS = [ // <1> 20 | [-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1] 21 | ]; 22 | 23 | iterate(minX, minY, maxX, maxY) { // <2> 24 | const size = this.size; 25 | 26 | for (let x = minX; x < maxX; x++) { 27 | for (let y = minY; y < maxY; y++) { 28 | const cell = this.cells[size * x + y]; 29 | let alive = 0; 30 | for (const [i, j] of Grid.NEIGHBORS) { 31 | alive += this.getCell(x + i, y + j); 32 | } 33 | const newCell = alive === 3 || (cell && alive === 2) ? 1 : 0; 34 | this.nextCells[size * x + y] = newCell; 35 | this.paint(newCell, x, y); 36 | } 37 | } 38 | 39 | const cells = this.nextCells; 40 | this.nextCells = this.cells; 41 | this.cells = cells; 42 | } 43 | } 44 | 45 | const BLACK = 0xFF000000; // <1> 46 | const WHITE = 0xFFFFFFFF; 47 | const SIZE = 1000; 48 | 49 | const iterationCounter = document.getElementById('iteration'); // <2> 50 | const gridCanvas = document.getElementById('gridcanvas'); 51 | gridCanvas.height = SIZE; 52 | gridCanvas.width = SIZE; 53 | const ctx = gridCanvas.getContext('2d'); 54 | const data = ctx.createImageData(SIZE, SIZE); // <3> 55 | const buf = new Uint32Array(data.data.buffer); 56 | 57 | function paint(cell, x, y) { // <4> 58 | buf[SIZE * x + y] = cell ? BLACK : WHITE; 59 | } 60 | 61 | const grid = new Grid(SIZE, new ArrayBuffer(2 * SIZE * SIZE), paint); // <5> 62 | for (let x = 0; x < SIZE; x++) { // <6> 63 | for (let y = 0; y < SIZE; y++) { 64 | const cell = Math.random() < 0.5 ? 0 : 1; 65 | grid.cells[SIZE * x + y] = cell; 66 | paint(cell, x, y); 67 | } 68 | } 69 | 70 | ctx.putImageData(data, 0, 0); // <7> 71 | 72 | let iteration = 0; 73 | function iterate(...args) { 74 | grid.iterate(...args); 75 | ctx.putImageData(data, 0, 0); 76 | iterationCounter.innerHTML = ++iteration; 77 | window.requestAnimationFrame(() => iterate(...args)); 78 | } 79 | 80 | iterate(0, 0, SIZE, SIZE); 81 | -------------------------------------------------------------------------------- /ch5-game-of-life/thread-gol.html: -------------------------------------------------------------------------------- 1 |

Iteration: 0

2 | 3 | 4 | -------------------------------------------------------------------------------- /ch5-game-of-life/thread-gol.js: -------------------------------------------------------------------------------- 1 | class Grid { 2 | constructor(size, buffer, paint = () => {}) { 3 | const sizeSquared = size * size; 4 | this.buffer = buffer; 5 | this.size = size; 6 | this.cells = new Uint8Array(this.buffer, 0, sizeSquared); 7 | this.nextCells = new Uint8Array(this.buffer, sizeSquared, sizeSquared); 8 | this.paint = paint; 9 | } 10 | 11 | getCell(x, y) { 12 | const size = this.size; 13 | const sizeM1 = size - 1; 14 | x = x < 0 ? sizeM1 : x > sizeM1 ? 0 : x; 15 | y = y < 0 ? sizeM1 : y > sizeM1 ? 0 : y; 16 | return this.cells[size * x + y]; 17 | } 18 | 19 | static NEIGHBORS = [ 20 | [-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1] 21 | ]; 22 | 23 | iterate(minX, minY, maxX, maxY) { 24 | const size = this.size; 25 | 26 | for (let x = minX; x < maxX; x++) { 27 | for (let y = minY; y < maxY; y++) { 28 | const cell = this.cells[size * x + y]; 29 | let alive = 0; 30 | for (const [i, j] of Grid.NEIGHBORS) { 31 | alive += this.getCell(x + i, y + j); 32 | } 33 | const newCell = alive === 3 || (cell && alive === 2) ? 1 : 0; 34 | this.nextCells[size * x + y] = newCell; 35 | this.paint(newCell, x, y); 36 | } 37 | } 38 | 39 | const cells = this.nextCells; 40 | this.nextCells = this.cells; 41 | this.cells = cells; 42 | } 43 | } 44 | 45 | const BLACK = 0xFF000000; 46 | const WHITE = 0xFFFFFFFF; 47 | const SIZE = 1000; 48 | const THREADS = 5; // must be a divisor of SIZE 49 | 50 | const imageOffset = 2 * SIZE * SIZE 51 | const syncOffset = imageOffset + 4 * SIZE * SIZE; 52 | 53 | const isMainThread = !!self.window; 54 | 55 | if (isMainThread) { 56 | const gridCanvas = document.getElementById('gridcanvas'); 57 | gridCanvas.height = SIZE; 58 | gridCanvas.width = SIZE; 59 | const ctx = gridCanvas.getContext('2d'); 60 | const iterationCounter = document.getElementById('iteration'); 61 | 62 | const sharedMemory = new SharedArrayBuffer( // <1> 63 | syncOffset + // data + imageData 64 | THREADS * 4 // synchronizationi 65 | ); 66 | const imageData = new ImageData(SIZE, SIZE); 67 | const cells = new Uint8Array(sharedMemory, 0, imageOffset); 68 | const sharedImageBuf = new Uint32Array(sharedMemory, imageOffset); 69 | const sharedImageBuf8 = 70 | new Uint8ClampedArray(sharedMemory, imageOffset, 4 * SIZE * SIZE); 71 | 72 | for (let x = 0; x < SIZE; x++) { 73 | for (let y = 0; y < SIZE; y++) { 74 | // 50% chance of cell being alive 75 | const cell = Math.random() < 0.5 ? 0 : 1; 76 | cells[SIZE * x + y] = cell; 77 | sharedImageBuf[SIZE * x + y] = cell ? BLACK : WHITE; 78 | } 79 | } 80 | 81 | imageData.data.set(sharedImageBuf8); 82 | ctx.putImageData(imageData, 0, 0); 83 | 84 | const chunkSize = SIZE / THREADS; 85 | for (let i = 0; i < THREADS; i++) { 86 | const worker = new Worker('thread-gol.js', { name: `gol-worker-${i}` }); 87 | worker.postMessage({ 88 | range: [0, chunkSize * i, SIZE, chunkSize * (i + 1)], 89 | sharedMemory, 90 | i 91 | }); 92 | } 93 | 94 | const coordWorker = new Worker('thread-gol.js', { name: 'gol-coordination' }); 95 | coordWorker.postMessage({ coord: true, sharedMemory }); 96 | 97 | let iteration = 0; 98 | coordWorker.addEventListener('message', () => { 99 | imageData.data.set(sharedImageBuf8); 100 | ctx.putImageData(imageData, 0, 0); 101 | iterationCounter.innerHTML = ++iteration; 102 | window.requestAnimationFrame(() => coordWorker.postMessage({})); 103 | }); 104 | } else { 105 | let sharedMemory; 106 | let sync; 107 | let sharedImageBuf; 108 | let cells; 109 | let nextCells; 110 | 111 | self.addEventListener('message', initListener); 112 | 113 | function initListener(msg) { 114 | const opts = msg.data; 115 | sharedMemory = opts.sharedMemory; 116 | sync = new Int32Array(sharedMemory, syncOffset); 117 | self.removeEventListener('message', initListener); 118 | if (opts.coord) { 119 | self.addEventListener('message', runCoord); 120 | cells = new Uint8Array(sharedMemory); 121 | nextCells = new Uint8Array(sharedMemory, SIZE * SIZE); 122 | sharedImageBuf = new Uint32Array(sharedMemory, imageOffset); 123 | runCoord(); 124 | } else { 125 | runWorker(opts); 126 | } 127 | } 128 | 129 | function runWorker({ range, i }) { 130 | const grid = new Grid(SIZE, sharedMemory); 131 | while (true) { 132 | Atomics.wait(sync, i, 0); 133 | grid.iterate(...range); 134 | Atomics.store(sync, i, 0); 135 | Atomics.notify(sync, i); 136 | } 137 | } 138 | 139 | function runCoord() { 140 | for (let i = 0; i < THREADS; i++) { 141 | Atomics.store(sync, i, 1); 142 | Atomics.notify(sync, i); 143 | } 144 | for (let i = 0; i < THREADS; i++) { 145 | Atomics.wait(sync, i, 1); 146 | } 147 | const oldCells = cells; 148 | cells = nextCells; 149 | nextCells = oldCells; 150 | for (let x = 0; x < SIZE; x++) { 151 | for (let y = 0; y < SIZE; y++) { 152 | sharedImageBuf[SIZE * x + y] = cells[SIZE * x + y] ? BLACK : WHITE; 153 | } 154 | } 155 | self.postMessage({}); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /ch5-node-block/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const http = require('http'); 4 | 5 | const view = new Int32Array(new SharedArrayBuffer(4)); 6 | setInterval(() => Atomics.wait(view, 0, 0, 1900), 2000); // <1> 7 | 8 | const server = http.createServer((req, res) => { 9 | res.end('Hello World'); 10 | }); 11 | 12 | server.listen(1337, (err, addr) => { 13 | if (err) throw err; 14 | console.log('http://localhost:1337/'); 15 | }); 16 | -------------------------------------------------------------------------------- /ch5-notify-order/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shared Memory for Coordination 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ch5-notify-order/main.js: -------------------------------------------------------------------------------- 1 | if (!crossOriginIsolated) throw new Error('Cannot use SharedArrayBuffer'); 2 | 3 | const buffer = new SharedArrayBuffer(4); 4 | const view = new Int32Array(buffer); 5 | 6 | for (let i = 0; i < 4; i++) { // <1> 7 | const worker = new Worker('worker.js'); 8 | worker.postMessage({buffer, name: i}); 9 | } 10 | 11 | setTimeout(() => { 12 | Atomics.notify(view, 0, 3); // <2> 13 | }, 500); // <3> 14 | -------------------------------------------------------------------------------- /ch5-notify-order/worker.js: -------------------------------------------------------------------------------- 1 | self.onmessage = ({data: {buffer, name}}) => { 2 | const view = new Int32Array(buffer); 3 | console.log(`Worker ${name} started`); 4 | const result = Atomics.wait(view, 0, 0, 1000); // <1> 5 | console.log(`Worker ${name} awoken with ${result}`); 6 | }; 7 | -------------------------------------------------------------------------------- /ch5-notify-when-ready/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shared Memory for Coordination 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ch5-notify-when-ready/main.js: -------------------------------------------------------------------------------- 1 | if (!crossOriginIsolated) throw new Error('Cannot use SharedArrayBuffer'); 2 | 3 | const buffer = new SharedArrayBuffer(4); 4 | const view = new Int32Array(buffer); 5 | const now = Date.now(); 6 | let count = 4; 7 | 8 | for (let i = 0; i < 4; i++) { // <1> 9 | const worker = new Worker('worker.js'); 10 | worker.postMessage({buffer, name: i}); // <2> 11 | worker.onmessage = () => { 12 | console.log(`Ready; id=${i}, count=${--count}, time=${Date.now() - now}ms`); 13 | if (count === 0) { // <3> 14 | Atomics.notify(view, 0); 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /ch5-notify-when-ready/worker.js: -------------------------------------------------------------------------------- 1 | self.onmessage = ({data: {buffer, name}}) => { 2 | postMessage('ready'); // <1> 3 | const view = new Int32Array(buffer); 4 | console.log(`Worker ${name} started`); 5 | const result = Atomics.wait(view, 0, 0); // <2> 6 | console.log(`Worker ${name} awoken with ${result}`); 7 | }; 8 | -------------------------------------------------------------------------------- /ch6-actors/actor.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const net = require('net'); 4 | const RpcWorkerPool = require('./rpc-worker.js'); 5 | 6 | const [,, host] = process.argv; 7 | const [hostname, port] = host.split(':'); 8 | const worker = new RpcWorkerPool('./worker.js', 4, 'leastbusy'); 9 | // THIS TEXT SHOULD NOT APPEAR IN BOOK 10 | const upstream = net.connect(port, hostname, () => { 11 | console.log('connected to server'); 12 | }).on('data', async (raw_data) => { 13 | const chunks = String(raw_data).split('\0'); // <1> 14 | chunks.pop(); 15 | for (let chunk of chunks) { 16 | const data = JSON.parse(chunk); 17 | const value = await worker.exec(data.method, ...data.args); 18 | upstream.write(JSON.stringify({ 19 | id: data.id, 20 | value, 21 | pid: process.pid 22 | }) + '\0'); 23 | } 24 | }).on('end', () => { 25 | console.log('disconnect from server'); 26 | }); 27 | -------------------------------------------------------------------------------- /ch6-actors/rpc-worker.js: -------------------------------------------------------------------------------- 1 | ../ch6-thread-pool/rpc-worker.js -------------------------------------------------------------------------------- /ch6-actors/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const http = require('http'); 4 | const net = require('net'); 5 | 6 | const [,, web_host, actor_host] = process.argv; 7 | const [web_hostname, web_port] = web_host.split(':'); 8 | const [actor_hostname, actor_port] = actor_host.split(':'); 9 | 10 | let message_id = 0; 11 | let actors = new Set(); // collection of actor handlers 12 | let messages = new Map(); // message ID -> HTTP response 13 | // THIS TEXT SHOULD NOT APPEAR IN BOOK 14 | net.createServer((client) => { 15 | const handler = data => client.write(JSON.stringify(data) + '\0'); // <1> 16 | actors.add(handler); 17 | console.log('actor pool connected', actors.size); 18 | client.on('end', () => { 19 | actors.delete(handler); // <2> 20 | console.log('actor pool disconnected', actors.size); 21 | }).on('data', (raw_data) => { 22 | const chunks = String(raw_data).split('\0'); // <3> 23 | chunks.pop(); // <4> 24 | for (let chunk of chunks) { 25 | const data = JSON.parse(chunk); 26 | const res = messages.get(data.id); 27 | res.end(JSON.stringify(data) + '\0'); 28 | messages.delete(data.id); 29 | } 30 | }); 31 | }).listen(actor_port, actor_hostname, () => { 32 | console.log(`actor: tcp://${actor_hostname}:${actor_port}`); 33 | }); 34 | // THIS TEXT SHOULD NOT APPEAR IN BOOK 35 | http.createServer(async (req, res) => { 36 | message_id++; 37 | if (actors.size === 0) return res.end('ERROR: EMPTY ACTOR POOL'); 38 | const actor = randomActor(); 39 | messages.set(message_id, res); 40 | actor({ 41 | id: message_id, 42 | method: 'square_sum', 43 | args: [Number(req.url.substr(1))] 44 | }); 45 | }).listen(web_port, web_hostname, () => { 46 | console.log(`web: http://${web_hostname}:${web_port}`); 47 | }); 48 | // THIS TEXT SHOULD NOT APPEAR IN BOOK 49 | function randomActor() { 50 | const pool = Array.from(actors); 51 | return pool[Math.floor(Math.random() * pool.length)]; 52 | } 53 | 54 | // PART TWO BEGINS BELOW. THIS TEXT SHOULD NOT APPEAR IN BOOK 55 | const RpcWorkerPool = require('./rpc-worker.js'); 56 | const worker = new RpcWorkerPool('./worker.js', 4, 'leastbusy'); 57 | actors.add(async (data) => { 58 | const value = await worker.exec(data.method, ...data.args); 59 | messages.get(data.id).end(JSON.stringify({ 60 | id: data.id, 61 | value, 62 | pid: 'server' 63 | }) + '\0'); 64 | messages.delete(data.id); 65 | }); 66 | -------------------------------------------------------------------------------- /ch6-actors/worker.js: -------------------------------------------------------------------------------- 1 | ../ch6-thread-pool/worker.js -------------------------------------------------------------------------------- /ch6-mutex/mutex.js: -------------------------------------------------------------------------------- 1 | const UNLOCKED = 0; 2 | const LOCKED = 1; 3 | 4 | const { 5 | compareExchange, wait, notify 6 | } = Atomics; 7 | 8 | class Mutex { 9 | constructor(shared, index) { 10 | this.shared = shared; 11 | this.index = index; 12 | } 13 | 14 | acquire() { 15 | if (compareExchange(this.shared, this.index, UNLOCKED, LOCKED) === UNLOCKED) { 16 | return; 17 | } 18 | wait(this.shared, this.index, LOCKED); 19 | this.acquire(); 20 | } 21 | 22 | release() { 23 | if (compareExchange(this.shared, this.index, LOCKED, UNLOCKED) !== LOCKED) { 24 | throw new Error('was not acquired'); 25 | } 26 | notify(this.shared, this.index, 1); 27 | } 28 | 29 | exec(fn) { 30 | this.acquire(); 31 | try { 32 | return fn(); 33 | } finally { 34 | this.release(); 35 | } 36 | } 37 | } 38 | 39 | module.exports = Mutex; 40 | -------------------------------------------------------------------------------- /ch6-mutex/thread-product-mutex.js: -------------------------------------------------------------------------------- 1 | const { 2 | Worker, isMainThread, workerData 3 | } = require('worker_threads'); 4 | const assert = require('assert'); 5 | const Mutex = require('./mutex.js'); 6 | 7 | if (isMainThread) { 8 | const shared = new SharedArrayBuffer(4 * 5); 9 | const sharedInts = new Int32Array(shared); 10 | sharedInts.set([2, 3, 5, 7, 0]); 11 | for (let i = 0; i < 3; i++) { 12 | new Worker(__filename, { workerData: { i, shared } }); 13 | } 14 | } else { 15 | const { i, shared } = workerData; 16 | const sharedInts = new Int32Array(shared); 17 | const mutex = new Mutex(sharedInts, 4); // <1> 18 | mutex.exec(() => { 19 | const a = sharedInts[i]; // <2> 20 | for (let j = 0; j < 1000000; j++) {} 21 | const b = sharedInts[3]; 22 | sharedInts[3] = a * b; 23 | assert.strictEqual(sharedInts[3], a * b); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /ch6-mutex/thread-product.js: -------------------------------------------------------------------------------- 1 | const { 2 | Worker, isMainThread, workerData 3 | } = require('worker_threads'); 4 | const assert = require('assert'); 5 | 6 | if (isMainThread) { 7 | const shared = new SharedArrayBuffer(4 * 4); // <1> 8 | const sharedInts = new Int32Array(shared); 9 | sharedInts.set([2, 3, 5, 7]); 10 | for (let i = 0; i < 3; i++) { 11 | new Worker(__filename, { workerData: { i, shared } }); 12 | } 13 | } else { 14 | const { i, shared } = workerData; 15 | const sharedInts = new Int32Array(shared); 16 | const a = Atomics.load(sharedInts, i); 17 | for (let j = 0; j < 1000000; j++) {} 18 | const b = Atomics.load(sharedInts, 3); 19 | Atomics.store(sharedInts, 3, a * b); 20 | assert.strictEqual(Atomics.load(sharedInts, 3), a * b); // <2> 21 | } 22 | -------------------------------------------------------------------------------- /ch6-ring-buffer/ring-buffer.js: -------------------------------------------------------------------------------- 1 | class RingBuffer { 2 | constructor(meta/*: Uint32Array[3]*/, buffer /*: Uint8Array */) { 3 | this.meta = meta; 4 | this.buffer = buffer; 5 | } 6 | 7 | get head() { 8 | return this.meta[0]; 9 | } 10 | 11 | set head(n) { 12 | this.meta[0] = n; 13 | } 14 | 15 | get tail() { 16 | return this.meta[1]; 17 | } 18 | 19 | set tail(n) { 20 | this.meta[1] = n; 21 | } 22 | 23 | get length() { 24 | return this.meta[2]; 25 | } 26 | 27 | set length(n) { 28 | this.meta[2] = n; 29 | } 30 | 31 | write(data /*: Uint8Array */) { // <1> 32 | let bytesWritten = data.length; 33 | if (bytesWritten > this.buffer.length - this.length) { // <2> 34 | bytesWritten = this.buffer.length - this.length; 35 | data = data.subarray(0, bytesWritten); 36 | } 37 | if (bytesWritten === 0) { 38 | return bytesWritten; 39 | } 40 | if ( 41 | (this.head >= this.tail && this.buffer.length - this.head >= bytesWritten) || 42 | (this.head < this.tail && bytesWritten <= this.tail - this.head) // <3> 43 | ) { 44 | // Enough space after the head. Just write it in and increase the head. 45 | this.buffer.set(data, this.head); 46 | this.head += bytesWritten; 47 | } else { // <4> 48 | // We need to split the chunk into two. 49 | const endSpaceAvailable = this.buffer.length - this.head; 50 | const endChunk = data.subarray(0, endSpaceAvailable); 51 | const beginChunk = data.subarray(endSpaceAvailable); 52 | this.buffer.set(endChunk, this.head); 53 | this.buffer.set(beginChunk, 0); 54 | this.head = beginChunk.length; 55 | } 56 | this.length += bytesWritten; 57 | return bytesWritten; 58 | } 59 | 60 | read(bytes) { 61 | if (bytes > this.length) { // <1> 62 | bytes = this.length; 63 | } 64 | if (bytes === 0) { 65 | return new Uint8Array(0); 66 | } 67 | let readData; 68 | if ( 69 | this.head > this.tail || this.buffer.length - this.tail >= bytes // <2> 70 | ) { 71 | // The data is in a contiguous chunk. 72 | readData = this.buffer.slice(this.tail, bytes) 73 | this.tail += bytes; 74 | } else { // <3> 75 | // Read from the end and the beginning. 76 | readData = new Uint8Array(bytes); 77 | const endBytesToRead = this.buffer.length - this.tail; 78 | readData.set(this.buffer.subarray(this.tail, this.buffer.length)); 79 | readData.set(this.buffer.subarray(0, bytes - endBytesToRead), endBytesToRead); 80 | this.tail = bytes - endBytesToRead; 81 | } 82 | this.length -= bytes; 83 | return readData; 84 | } 85 | } 86 | 87 | const Mutex = require('../ch6-mutex/mutex.js'); 88 | 89 | class SharedRingBuffer { 90 | constructor(shared/*: number | SharedArrayBuffer*/) { 91 | this.shared = typeof shared === 'number' ? 92 | new SharedArrayBuffer(shared + 16) : shared; 93 | this.ringBuffer = new RingBuffer( 94 | new Uint32Array(this.shared, 4, 3), 95 | new Uint8Array(this.shared, 16) 96 | ); 97 | this.lock = new Mutex(new Int32Array(this.shared, 0, 1)); 98 | } 99 | 100 | write(data) { 101 | return this.lock.exec(() => this.ringBuffer.write(data)); 102 | } 103 | 104 | read(bytes) { 105 | return this.lock.exec(() => this.ringBuffer.read(bytes)); 106 | } 107 | } 108 | 109 | const { isMainThread, Worker, workerData } = require('worker_threads'); 110 | const fs = require('fs'); 111 | 112 | if (isMainThread) { 113 | const shared = new SharedArrayBuffer(116); 114 | const threads = [ 115 | new Worker(__filename, { workerData: { shared, isProducer: true } }), 116 | new Worker(__filename, { workerData: { shared, isProducer: true } }), 117 | new Worker(__filename, { workerData: { shared, isProducer: false } }), 118 | new Worker(__filename, { workerData: { shared, isProducer: false } }) 119 | ]; 120 | } else { 121 | const { shared, isProducer } = workerData; 122 | const ringBuffer = new SharedRingBuffer(shared); 123 | 124 | if (isProducer) { 125 | const buffer = Buffer.from('Hello, World!\n'); 126 | while (true) { 127 | ringBuffer.write(buffer); 128 | } 129 | } else { 130 | while (true) { 131 | const readBytes = ringBuffer.read(20); 132 | fs.writeSync(1, `Read ${readBytes.length} bytes\n`); // <1> 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /ch6-thread-pool/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const http = require('http'); 3 | const RpcWorkerPool = require('./rpc-worker.js'); 4 | const worker = new RpcWorkerPool('./worker.js', 5 | Number(process.env.THREADS), // <1> 6 | process.env.STRATEGY); // <2> 7 | 8 | const server = http.createServer(async (req, res) => { 9 | const value = Math.floor(Math.random() * 100_000_000); 10 | const sum = await worker.exec('square_sum', value); 11 | res.end(JSON.stringify({ sum, value })); 12 | }); 13 | 14 | server.listen(1337, (err) => { 15 | if (err) throw err; 16 | console.log('http://localhost:1337/'); 17 | }); 18 | -------------------------------------------------------------------------------- /ch6-thread-pool/rpc-worker.js: -------------------------------------------------------------------------------- 1 | const { Worker } = require('worker_threads'); 2 | const CORES = require('os').cpus().length; 3 | const STRATEGIES = new Set([ 'roundrobin', 'random', 'leastbusy' ]); 4 | 5 | module.exports = class RpcWorkerPool { 6 | constructor(path, size = 0, strategy = 'roundrobin') { 7 | if (size === 0) this.size = CORES; // <1> 8 | else if (size < 0) this.size = Math.max(CORES + size, 1); 9 | else this.size = size; 10 | 11 | if (!STRATEGIES.has(strategy)) throw new TypeError('invalid strategy'); 12 | this.strategy = strategy; // <2> 13 | this.rr_index = -1; 14 | 15 | this.next_command_id = 0; 16 | this.workers = []; // <3> 17 | for (let i = 0; i < this.size; i++) { 18 | const worker = new Worker(path); 19 | this.workers.push({ worker, in_flight_commands: new Map() }); // <4> 20 | worker.on('message', (msg) => { 21 | this.onMessageHandler(msg, i); 22 | }); 23 | } 24 | } 25 | // THIS LINE SHOULD NOT APPEAR IN PRINT 26 | onMessageHandler(msg, worker_id) { 27 | const worker = this.workers[worker_id]; 28 | const { result, error, id } = msg; 29 | const { resolve, reject } = worker.in_flight_commands.get(id); 30 | worker.in_flight_commands.delete(id); 31 | if (error) reject(error); 32 | else resolve(result); 33 | } 34 | // THIS LINE SHOULD NOT APPEAR IN PRINT 35 | exec(method, ...args) { 36 | const id = ++this.next_command_id; 37 | let resolve, reject; 38 | const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); 39 | const worker = this.getWorker(); // <1> 40 | worker.in_flight_commands.set(id, { resolve, reject }); 41 | worker.worker.postMessage({ method, params: args, id }); 42 | return promise; 43 | } 44 | // THIS LINE SHOULD NOT APPEAR IN PRINT 45 | getWorker() { 46 | let id; 47 | if (this.strategy === 'random') { 48 | id = Math.floor(Math.random() * this.size); 49 | } else if (this.strategy === 'roundrobin') { 50 | this.rr_index++; 51 | if (this.rr_index >= this.size) this.rr_index = 0; 52 | id = this.rr_index; 53 | } else if (this.strategy === 'leastbusy') { 54 | let min = Infinity; 55 | for (let i = 0; i < this.size; i++) { 56 | let worker = this.workers[i]; 57 | if (worker.in_flight_commands.size < min) { 58 | min = worker.in_flight_commands.size; 59 | id = i; 60 | } 61 | } 62 | } 63 | console.log('Selected Worker:', id); 64 | return this.workers[id]; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /ch6-thread-pool/worker.js: -------------------------------------------------------------------------------- 1 | const { parentPort } = require('worker_threads'); 2 | 3 | function asyncOnMessageWrap(fn) { 4 | return async function(msg) { 5 | parentPort.postMessage(await fn(msg)); 6 | } 7 | } 8 | 9 | const commands = { 10 | async square_sum(max) { 11 | await new Promise((res) => setTimeout(res, 100)); 12 | let sum = 0; for (let i = 0; i < max; i++) sum += Math.sqrt(i); 13 | return sum; 14 | } 15 | }; 16 | 17 | parentPort.on('message', asyncOnMessageWrap(async ({ method, params, id }) => ({ 18 | result: await commands[method](...params), id 19 | }))); 20 | -------------------------------------------------------------------------------- /ch7-happycoin-as/happycoin.mjs: -------------------------------------------------------------------------------- 1 | import { WASI } from 'wasi'; // <1> 2 | import fs from 'fs/promises'; 3 | import loader from '@assemblyscript/loader'; 4 | import { Worker, isMainThread, parentPort } from 'worker_threads'; 5 | 6 | const THREAD_COUNT = 4; 7 | 8 | if (isMainThread) { 9 | let inFlight = THREAD_COUNT; 10 | let count = 0; 11 | for (let i = 0; i < THREAD_COUNT; i++) { 12 | const worker = new Worker(new URL(import.meta.url)); // <2> 13 | worker.on('message', msg => { 14 | count += msg.length; 15 | process.stdout.write(msg.join(' ') + ' '); 16 | if (--inFlight === 0) { 17 | process.stdout.write('\ncount ' + count + '\n'); 18 | } 19 | }); 20 | } 21 | } else { 22 | const wasi = new WASI(); 23 | const importObject = { wasi_snapshot_preview1: wasi.wasiImport }; 24 | const wasmFile = await fs.readFile('./happycoin.wasm'); 25 | const happycoinModule = await loader.instantiate(wasmFile, importObject); 26 | wasi.start(happycoinModule); 27 | 28 | const happycoinsWasmArray = 29 | happycoinModule.exports.getHappycoins(10_000_000/THREAD_COUNT); 30 | const happycoins = happycoinModule.exports.__getArray(happycoinsWasmArray); 31 | parentPort.postMessage(happycoins); 32 | } 33 | -------------------------------------------------------------------------------- /ch7-happycoin-as/happycoin.ts: -------------------------------------------------------------------------------- 1 | import 'wasi'; // <1> 2 | 3 | const randArr64 = new Uint64Array(1); 4 | const randArr8 = Uint8Array.wrap(randArr64.buffer, 0, 8); // <2> 5 | function random64(): u64 { 6 | crypto.getRandomValues(randArr8); // <3> 7 | return randArr64[0]; 8 | } 9 | 10 | function sumDigitsSquared(num: u64): u64 { 11 | let total: u64 = 0; 12 | while (num > 0) { 13 | const numModBase = num % 10; 14 | total += numModBase ** 2; 15 | num = num / 10; 16 | } 17 | return total; 18 | } 19 | 20 | function isHappy(num: u64): boolean { 21 | while (num != 1 && num != 4) { 22 | num = sumDigitsSquared(num); 23 | } 24 | return num === 1; 25 | } 26 | 27 | function isHappycoin(num: u64): boolean { 28 | return isHappy(num) && num % 10000 === 0; 29 | } 30 | 31 | export function getHappycoins(num: u32): Array { 32 | const result = new Array(); 33 | for (let i: u32 = 1; i < num; i++) { 34 | const randomNum = random64(); 35 | if (isHappycoin(randomNum)) { 36 | result.push(randomNum); 37 | } 38 | } 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /ch7-wasm-add/add.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs/promises'); // Needs Node.js v14 or higher. 2 | 3 | (async () => { 4 | const wasm = await fs.readFile('./add.wasm'); 5 | const { instance: { exports: { add } } } = await WebAssembly.instantiate(wasm); 6 | console.log(add(2, 3)); 7 | })(); 8 | -------------------------------------------------------------------------------- /ch7-wasm-add/add.wat: -------------------------------------------------------------------------------- 1 | (module ;; <1> 2 | (func $add (param $a i32) (param $b i32) (result i32) ;; <2> 3 | local.get $a ;; <3> 4 | local.get $b 5 | i32.add) 6 | (export "add" (func $add)) ;; <4> 7 | ) 8 | -------------------------------------------------------------------------------- /ch8-memory-overhead/leader.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { Worker } = require('worker_threads'); 4 | const count = Number(process.argv[2]) || 0; 5 | 6 | for (let i = 0; i < count; i++) { 7 | new Worker(__dirname + '/worker.js'); 8 | } 9 | 10 | console.log(`PID: ${process.pid}, ADD THREADS: ${count}`); 11 | setTimeout(() => {}, 1 * 60 * 60 * 1000); 12 | -------------------------------------------------------------------------------- /ch8-memory-overhead/worker.js: -------------------------------------------------------------------------------- 1 | console.log(`WPID: ${process.pid}`); 2 | setTimeout(() => {}, 1 * 60 * 60 * 1000); 3 | -------------------------------------------------------------------------------- /ch8-template-render/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /ch8-template-render/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ch8-template-render", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "1.0.0", 9 | "license": "ISC", 10 | "dependencies": { 11 | "fastify": "^3.19.1", 12 | "mustache": "^4.2.0" 13 | } 14 | }, 15 | "node_modules/@fastify/ajv-compiler": { 16 | "version": "1.1.0", 17 | "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", 18 | "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", 19 | "dependencies": { 20 | "ajv": "^6.12.6" 21 | } 22 | }, 23 | "node_modules/abstract-logging": { 24 | "version": "2.0.1", 25 | "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", 26 | "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" 27 | }, 28 | "node_modules/ajv": { 29 | "version": "6.12.6", 30 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 31 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 32 | "dependencies": { 33 | "fast-deep-equal": "^3.1.1", 34 | "fast-json-stable-stringify": "^2.0.0", 35 | "json-schema-traverse": "^0.4.1", 36 | "uri-js": "^4.2.2" 37 | }, 38 | "funding": { 39 | "type": "github", 40 | "url": "https://github.com/sponsors/epoberezkin" 41 | } 42 | }, 43 | "node_modules/archy": { 44 | "version": "1.0.0", 45 | "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", 46 | "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" 47 | }, 48 | "node_modules/atomic-sleep": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 51 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", 52 | "engines": { 53 | "node": ">=8.0.0" 54 | } 55 | }, 56 | "node_modules/avvio": { 57 | "version": "7.2.2", 58 | "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", 59 | "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", 60 | "dependencies": { 61 | "archy": "^1.0.0", 62 | "debug": "^4.0.0", 63 | "fastq": "^1.6.1", 64 | "queue-microtask": "^1.1.2" 65 | } 66 | }, 67 | "node_modules/cookie": { 68 | "version": "0.4.1", 69 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 70 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", 71 | "engines": { 72 | "node": ">= 0.6" 73 | } 74 | }, 75 | "node_modules/debug": { 76 | "version": "4.3.2", 77 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", 78 | "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", 79 | "dependencies": { 80 | "ms": "2.1.2" 81 | }, 82 | "engines": { 83 | "node": ">=6.0" 84 | }, 85 | "peerDependenciesMeta": { 86 | "supports-color": { 87 | "optional": true 88 | } 89 | } 90 | }, 91 | "node_modules/deepmerge": { 92 | "version": "4.2.2", 93 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 94 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 95 | "engines": { 96 | "node": ">=0.10.0" 97 | } 98 | }, 99 | "node_modules/fast-decode-uri-component": { 100 | "version": "1.0.1", 101 | "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", 102 | "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" 103 | }, 104 | "node_modules/fast-deep-equal": { 105 | "version": "3.1.3", 106 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 107 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 108 | }, 109 | "node_modules/fast-json-stable-stringify": { 110 | "version": "2.1.0", 111 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 112 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 113 | }, 114 | "node_modules/fast-json-stringify": { 115 | "version": "2.7.7", 116 | "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.7.tgz", 117 | "integrity": "sha512-2kiwC/hBlK7QiGALsvj0QxtYwaReLOmAwOWJIxt5WHBB9EwXsqbsu8LCel47yh8NV8CEcFmnZYcXh4BionJcwQ==", 118 | "dependencies": { 119 | "ajv": "^6.11.0", 120 | "deepmerge": "^4.2.2", 121 | "rfdc": "^1.2.0", 122 | "string-similarity": "^4.0.1" 123 | }, 124 | "engines": { 125 | "node": ">= 10.0.0" 126 | } 127 | }, 128 | "node_modules/fast-redact": { 129 | "version": "3.0.1", 130 | "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.1.tgz", 131 | "integrity": "sha512-kYpn4Y/valC9MdrISg47tZOpYBNoTXKgT9GYXFpHN/jYFs+lFkPoisY+LcBODdKVMY96ATzvzsWv+ES/4Kmufw==", 132 | "engines": { 133 | "node": ">=6" 134 | } 135 | }, 136 | "node_modules/fast-safe-stringify": { 137 | "version": "2.0.8", 138 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz", 139 | "integrity": "sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag==" 140 | }, 141 | "node_modules/fastify": { 142 | "version": "3.19.1", 143 | "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.19.1.tgz", 144 | "integrity": "sha512-vyoRO/TQ+ta3OKShx5CHZ2qXXUXzBG4ZOVB0AEKlmf+DoMmP0ftuaEnzc7DPLVXtRYZ1A3Tbcn/6ja5e+sa2lw==", 145 | "dependencies": { 146 | "@fastify/ajv-compiler": "^1.0.0", 147 | "abstract-logging": "^2.0.0", 148 | "avvio": "^7.1.2", 149 | "fast-json-stringify": "^2.5.2", 150 | "fastify-error": "^0.3.0", 151 | "fastify-warning": "^0.2.0", 152 | "find-my-way": "^4.0.0", 153 | "flatstr": "^1.0.12", 154 | "light-my-request": "^4.2.0", 155 | "pino": "^6.2.1", 156 | "proxy-addr": "^2.0.7", 157 | "readable-stream": "^3.4.0", 158 | "rfdc": "^1.1.4", 159 | "secure-json-parse": "^2.0.0", 160 | "semver": "^7.3.2", 161 | "tiny-lru": "^7.0.0" 162 | } 163 | }, 164 | "node_modules/fastify-error": { 165 | "version": "0.3.1", 166 | "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", 167 | "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" 168 | }, 169 | "node_modules/fastify-warning": { 170 | "version": "0.2.0", 171 | "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", 172 | "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" 173 | }, 174 | "node_modules/fastq": { 175 | "version": "1.11.1", 176 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", 177 | "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", 178 | "dependencies": { 179 | "reusify": "^1.0.4" 180 | } 181 | }, 182 | "node_modules/find-my-way": { 183 | "version": "4.3.3", 184 | "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", 185 | "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", 186 | "dependencies": { 187 | "fast-decode-uri-component": "^1.0.1", 188 | "fast-deep-equal": "^3.1.3", 189 | "safe-regex2": "^2.0.0", 190 | "semver-store": "^0.3.0" 191 | }, 192 | "engines": { 193 | "node": ">=10" 194 | } 195 | }, 196 | "node_modules/flatstr": { 197 | "version": "1.0.12", 198 | "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", 199 | "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" 200 | }, 201 | "node_modules/forwarded": { 202 | "version": "0.2.0", 203 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 204 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 205 | "engines": { 206 | "node": ">= 0.6" 207 | } 208 | }, 209 | "node_modules/inherits": { 210 | "version": "2.0.4", 211 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 212 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 213 | }, 214 | "node_modules/ipaddr.js": { 215 | "version": "1.9.1", 216 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 217 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 218 | "engines": { 219 | "node": ">= 0.10" 220 | } 221 | }, 222 | "node_modules/json-schema-traverse": { 223 | "version": "0.4.1", 224 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 225 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 226 | }, 227 | "node_modules/light-my-request": { 228 | "version": "4.4.1", 229 | "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.4.1.tgz", 230 | "integrity": "sha512-FDNRF2mYjthIRWE7O8d/X7AzDx4otQHl4/QXbu3Q/FRwBFcgb+ZoDaUd5HwN53uQXLAiw76osN+Va0NEaOW6rQ==", 231 | "dependencies": { 232 | "ajv": "^6.12.2", 233 | "cookie": "^0.4.0", 234 | "fastify-warning": "^0.2.0", 235 | "readable-stream": "^3.6.0", 236 | "set-cookie-parser": "^2.4.1" 237 | } 238 | }, 239 | "node_modules/lru-cache": { 240 | "version": "6.0.0", 241 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 242 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 243 | "dependencies": { 244 | "yallist": "^4.0.0" 245 | }, 246 | "engines": { 247 | "node": ">=10" 248 | } 249 | }, 250 | "node_modules/ms": { 251 | "version": "2.1.2", 252 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 253 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 254 | }, 255 | "node_modules/mustache": { 256 | "version": "4.2.0", 257 | "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", 258 | "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", 259 | "bin": { 260 | "mustache": "bin/mustache" 261 | } 262 | }, 263 | "node_modules/pino": { 264 | "version": "6.12.0", 265 | "resolved": "https://registry.npmjs.org/pino/-/pino-6.12.0.tgz", 266 | "integrity": "sha512-5NGopOcUusGuklGHVVv9az0Hv/Dj3urHhD3G+zhl5pBGIRYAeGCi/Ej6YCl16Q2ko28cmYiJz+/qRoJiwy62Rw==", 267 | "dependencies": { 268 | "fast-redact": "^3.0.0", 269 | "fast-safe-stringify": "^2.0.8", 270 | "flatstr": "^1.0.12", 271 | "pino-std-serializers": "^3.1.0", 272 | "quick-format-unescaped": "^4.0.3", 273 | "sonic-boom": "^1.0.2" 274 | }, 275 | "bin": { 276 | "pino": "bin.js" 277 | } 278 | }, 279 | "node_modules/pino-std-serializers": { 280 | "version": "3.2.0", 281 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", 282 | "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" 283 | }, 284 | "node_modules/proxy-addr": { 285 | "version": "2.0.7", 286 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 287 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 288 | "dependencies": { 289 | "forwarded": "0.2.0", 290 | "ipaddr.js": "1.9.1" 291 | }, 292 | "engines": { 293 | "node": ">= 0.10" 294 | } 295 | }, 296 | "node_modules/punycode": { 297 | "version": "2.1.1", 298 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 299 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 300 | "engines": { 301 | "node": ">=6" 302 | } 303 | }, 304 | "node_modules/queue-microtask": { 305 | "version": "1.2.3", 306 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 307 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 308 | "funding": [ 309 | { 310 | "type": "github", 311 | "url": "https://github.com/sponsors/feross" 312 | }, 313 | { 314 | "type": "patreon", 315 | "url": "https://www.patreon.com/feross" 316 | }, 317 | { 318 | "type": "consulting", 319 | "url": "https://feross.org/support" 320 | } 321 | ] 322 | }, 323 | "node_modules/quick-format-unescaped": { 324 | "version": "4.0.3", 325 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz", 326 | "integrity": "sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg==" 327 | }, 328 | "node_modules/readable-stream": { 329 | "version": "3.6.0", 330 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 331 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 332 | "dependencies": { 333 | "inherits": "^2.0.3", 334 | "string_decoder": "^1.1.1", 335 | "util-deprecate": "^1.0.1" 336 | }, 337 | "engines": { 338 | "node": ">= 6" 339 | } 340 | }, 341 | "node_modules/ret": { 342 | "version": "0.2.2", 343 | "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", 344 | "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", 345 | "engines": { 346 | "node": ">=4" 347 | } 348 | }, 349 | "node_modules/reusify": { 350 | "version": "1.0.4", 351 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 352 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 353 | "engines": { 354 | "iojs": ">=1.0.0", 355 | "node": ">=0.10.0" 356 | } 357 | }, 358 | "node_modules/rfdc": { 359 | "version": "1.3.0", 360 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", 361 | "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" 362 | }, 363 | "node_modules/safe-buffer": { 364 | "version": "5.2.1", 365 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 366 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 367 | "funding": [ 368 | { 369 | "type": "github", 370 | "url": "https://github.com/sponsors/feross" 371 | }, 372 | { 373 | "type": "patreon", 374 | "url": "https://www.patreon.com/feross" 375 | }, 376 | { 377 | "type": "consulting", 378 | "url": "https://feross.org/support" 379 | } 380 | ] 381 | }, 382 | "node_modules/safe-regex2": { 383 | "version": "2.0.0", 384 | "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", 385 | "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", 386 | "dependencies": { 387 | "ret": "~0.2.0" 388 | } 389 | }, 390 | "node_modules/secure-json-parse": { 391 | "version": "2.4.0", 392 | "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.4.0.tgz", 393 | "integrity": "sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg==" 394 | }, 395 | "node_modules/semver": { 396 | "version": "7.3.5", 397 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 398 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 399 | "dependencies": { 400 | "lru-cache": "^6.0.0" 401 | }, 402 | "bin": { 403 | "semver": "bin/semver.js" 404 | }, 405 | "engines": { 406 | "node": ">=10" 407 | } 408 | }, 409 | "node_modules/semver-store": { 410 | "version": "0.3.0", 411 | "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", 412 | "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" 413 | }, 414 | "node_modules/set-cookie-parser": { 415 | "version": "2.4.8", 416 | "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", 417 | "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" 418 | }, 419 | "node_modules/sonic-boom": { 420 | "version": "1.4.1", 421 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", 422 | "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", 423 | "dependencies": { 424 | "atomic-sleep": "^1.0.0", 425 | "flatstr": "^1.0.12" 426 | } 427 | }, 428 | "node_modules/string_decoder": { 429 | "version": "1.3.0", 430 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 431 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 432 | "dependencies": { 433 | "safe-buffer": "~5.2.0" 434 | } 435 | }, 436 | "node_modules/string-similarity": { 437 | "version": "4.0.4", 438 | "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", 439 | "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" 440 | }, 441 | "node_modules/tiny-lru": { 442 | "version": "7.0.6", 443 | "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", 444 | "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==", 445 | "engines": { 446 | "node": ">=6" 447 | } 448 | }, 449 | "node_modules/uri-js": { 450 | "version": "4.4.1", 451 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 452 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 453 | "dependencies": { 454 | "punycode": "^2.1.0" 455 | } 456 | }, 457 | "node_modules/util-deprecate": { 458 | "version": "1.0.2", 459 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 460 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 461 | }, 462 | "node_modules/yallist": { 463 | "version": "4.0.0", 464 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 465 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 466 | } 467 | }, 468 | "dependencies": { 469 | "@fastify/ajv-compiler": { 470 | "version": "1.1.0", 471 | "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", 472 | "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", 473 | "requires": { 474 | "ajv": "^6.12.6" 475 | } 476 | }, 477 | "abstract-logging": { 478 | "version": "2.0.1", 479 | "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", 480 | "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" 481 | }, 482 | "ajv": { 483 | "version": "6.12.6", 484 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 485 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 486 | "requires": { 487 | "fast-deep-equal": "^3.1.1", 488 | "fast-json-stable-stringify": "^2.0.0", 489 | "json-schema-traverse": "^0.4.1", 490 | "uri-js": "^4.2.2" 491 | } 492 | }, 493 | "archy": { 494 | "version": "1.0.0", 495 | "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", 496 | "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" 497 | }, 498 | "atomic-sleep": { 499 | "version": "1.0.0", 500 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 501 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" 502 | }, 503 | "avvio": { 504 | "version": "7.2.2", 505 | "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", 506 | "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", 507 | "requires": { 508 | "archy": "^1.0.0", 509 | "debug": "^4.0.0", 510 | "fastq": "^1.6.1", 511 | "queue-microtask": "^1.1.2" 512 | } 513 | }, 514 | "cookie": { 515 | "version": "0.4.1", 516 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 517 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" 518 | }, 519 | "debug": { 520 | "version": "4.3.2", 521 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", 522 | "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", 523 | "requires": { 524 | "ms": "2.1.2" 525 | } 526 | }, 527 | "deepmerge": { 528 | "version": "4.2.2", 529 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 530 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" 531 | }, 532 | "fast-decode-uri-component": { 533 | "version": "1.0.1", 534 | "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", 535 | "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" 536 | }, 537 | "fast-deep-equal": { 538 | "version": "3.1.3", 539 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 540 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 541 | }, 542 | "fast-json-stable-stringify": { 543 | "version": "2.1.0", 544 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 545 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 546 | }, 547 | "fast-json-stringify": { 548 | "version": "2.7.7", 549 | "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.7.tgz", 550 | "integrity": "sha512-2kiwC/hBlK7QiGALsvj0QxtYwaReLOmAwOWJIxt5WHBB9EwXsqbsu8LCel47yh8NV8CEcFmnZYcXh4BionJcwQ==", 551 | "requires": { 552 | "ajv": "^6.11.0", 553 | "deepmerge": "^4.2.2", 554 | "rfdc": "^1.2.0", 555 | "string-similarity": "^4.0.1" 556 | } 557 | }, 558 | "fast-redact": { 559 | "version": "3.0.1", 560 | "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.1.tgz", 561 | "integrity": "sha512-kYpn4Y/valC9MdrISg47tZOpYBNoTXKgT9GYXFpHN/jYFs+lFkPoisY+LcBODdKVMY96ATzvzsWv+ES/4Kmufw==" 562 | }, 563 | "fast-safe-stringify": { 564 | "version": "2.0.8", 565 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz", 566 | "integrity": "sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag==" 567 | }, 568 | "fastify": { 569 | "version": "3.19.1", 570 | "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.19.1.tgz", 571 | "integrity": "sha512-vyoRO/TQ+ta3OKShx5CHZ2qXXUXzBG4ZOVB0AEKlmf+DoMmP0ftuaEnzc7DPLVXtRYZ1A3Tbcn/6ja5e+sa2lw==", 572 | "requires": { 573 | "@fastify/ajv-compiler": "^1.0.0", 574 | "abstract-logging": "^2.0.0", 575 | "avvio": "^7.1.2", 576 | "fast-json-stringify": "^2.5.2", 577 | "fastify-error": "^0.3.0", 578 | "fastify-warning": "^0.2.0", 579 | "find-my-way": "^4.0.0", 580 | "flatstr": "^1.0.12", 581 | "light-my-request": "^4.2.0", 582 | "pino": "^6.2.1", 583 | "proxy-addr": "^2.0.7", 584 | "readable-stream": "^3.4.0", 585 | "rfdc": "^1.1.4", 586 | "secure-json-parse": "^2.0.0", 587 | "semver": "^7.3.2", 588 | "tiny-lru": "^7.0.0" 589 | } 590 | }, 591 | "fastify-error": { 592 | "version": "0.3.1", 593 | "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", 594 | "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" 595 | }, 596 | "fastify-warning": { 597 | "version": "0.2.0", 598 | "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", 599 | "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" 600 | }, 601 | "fastq": { 602 | "version": "1.11.1", 603 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", 604 | "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", 605 | "requires": { 606 | "reusify": "^1.0.4" 607 | } 608 | }, 609 | "find-my-way": { 610 | "version": "4.3.3", 611 | "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", 612 | "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", 613 | "requires": { 614 | "fast-decode-uri-component": "^1.0.1", 615 | "fast-deep-equal": "^3.1.3", 616 | "safe-regex2": "^2.0.0", 617 | "semver-store": "^0.3.0" 618 | } 619 | }, 620 | "flatstr": { 621 | "version": "1.0.12", 622 | "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", 623 | "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" 624 | }, 625 | "forwarded": { 626 | "version": "0.2.0", 627 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 628 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 629 | }, 630 | "inherits": { 631 | "version": "2.0.4", 632 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 633 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 634 | }, 635 | "ipaddr.js": { 636 | "version": "1.9.1", 637 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 638 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 639 | }, 640 | "json-schema-traverse": { 641 | "version": "0.4.1", 642 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 643 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 644 | }, 645 | "light-my-request": { 646 | "version": "4.4.1", 647 | "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.4.1.tgz", 648 | "integrity": "sha512-FDNRF2mYjthIRWE7O8d/X7AzDx4otQHl4/QXbu3Q/FRwBFcgb+ZoDaUd5HwN53uQXLAiw76osN+Va0NEaOW6rQ==", 649 | "requires": { 650 | "ajv": "^6.12.2", 651 | "cookie": "^0.4.0", 652 | "fastify-warning": "^0.2.0", 653 | "readable-stream": "^3.6.0", 654 | "set-cookie-parser": "^2.4.1" 655 | } 656 | }, 657 | "lru-cache": { 658 | "version": "6.0.0", 659 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 660 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 661 | "requires": { 662 | "yallist": "^4.0.0" 663 | } 664 | }, 665 | "ms": { 666 | "version": "2.1.2", 667 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 668 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 669 | }, 670 | "mustache": { 671 | "version": "4.2.0", 672 | "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", 673 | "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" 674 | }, 675 | "pino": { 676 | "version": "6.12.0", 677 | "resolved": "https://registry.npmjs.org/pino/-/pino-6.12.0.tgz", 678 | "integrity": "sha512-5NGopOcUusGuklGHVVv9az0Hv/Dj3urHhD3G+zhl5pBGIRYAeGCi/Ej6YCl16Q2ko28cmYiJz+/qRoJiwy62Rw==", 679 | "requires": { 680 | "fast-redact": "^3.0.0", 681 | "fast-safe-stringify": "^2.0.8", 682 | "flatstr": "^1.0.12", 683 | "pino-std-serializers": "^3.1.0", 684 | "quick-format-unescaped": "^4.0.3", 685 | "sonic-boom": "^1.0.2" 686 | } 687 | }, 688 | "pino-std-serializers": { 689 | "version": "3.2.0", 690 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", 691 | "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" 692 | }, 693 | "proxy-addr": { 694 | "version": "2.0.7", 695 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 696 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 697 | "requires": { 698 | "forwarded": "0.2.0", 699 | "ipaddr.js": "1.9.1" 700 | } 701 | }, 702 | "punycode": { 703 | "version": "2.1.1", 704 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 705 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 706 | }, 707 | "queue-microtask": { 708 | "version": "1.2.3", 709 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 710 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" 711 | }, 712 | "quick-format-unescaped": { 713 | "version": "4.0.3", 714 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz", 715 | "integrity": "sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg==" 716 | }, 717 | "readable-stream": { 718 | "version": "3.6.0", 719 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 720 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 721 | "requires": { 722 | "inherits": "^2.0.3", 723 | "string_decoder": "^1.1.1", 724 | "util-deprecate": "^1.0.1" 725 | } 726 | }, 727 | "ret": { 728 | "version": "0.2.2", 729 | "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", 730 | "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==" 731 | }, 732 | "reusify": { 733 | "version": "1.0.4", 734 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 735 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" 736 | }, 737 | "rfdc": { 738 | "version": "1.3.0", 739 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", 740 | "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" 741 | }, 742 | "safe-buffer": { 743 | "version": "5.2.1", 744 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 745 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 746 | }, 747 | "safe-regex2": { 748 | "version": "2.0.0", 749 | "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", 750 | "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", 751 | "requires": { 752 | "ret": "~0.2.0" 753 | } 754 | }, 755 | "secure-json-parse": { 756 | "version": "2.4.0", 757 | "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.4.0.tgz", 758 | "integrity": "sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg==" 759 | }, 760 | "semver": { 761 | "version": "7.3.5", 762 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 763 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 764 | "requires": { 765 | "lru-cache": "^6.0.0" 766 | } 767 | }, 768 | "semver-store": { 769 | "version": "0.3.0", 770 | "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", 771 | "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" 772 | }, 773 | "set-cookie-parser": { 774 | "version": "2.4.8", 775 | "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", 776 | "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" 777 | }, 778 | "sonic-boom": { 779 | "version": "1.4.1", 780 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", 781 | "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", 782 | "requires": { 783 | "atomic-sleep": "^1.0.0", 784 | "flatstr": "^1.0.12" 785 | } 786 | }, 787 | "string_decoder": { 788 | "version": "1.3.0", 789 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 790 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 791 | "requires": { 792 | "safe-buffer": "~5.2.0" 793 | } 794 | }, 795 | "string-similarity": { 796 | "version": "4.0.4", 797 | "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", 798 | "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" 799 | }, 800 | "tiny-lru": { 801 | "version": "7.0.6", 802 | "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", 803 | "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==" 804 | }, 805 | "uri-js": { 806 | "version": "4.4.1", 807 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 808 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 809 | "requires": { 810 | "punycode": "^2.1.0" 811 | } 812 | }, 813 | "util-deprecate": { 814 | "version": "1.0.2", 815 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 816 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 817 | }, 818 | "yallist": { 819 | "version": "4.0.0", 820 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 821 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 822 | } 823 | } 824 | } 825 | -------------------------------------------------------------------------------- /ch8-template-render/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ch8-template-render", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "fastify": "^3.19.1", 14 | "mustache": "^4.2.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ch8-template-render/rpc-worker.js: -------------------------------------------------------------------------------- 1 | ../ch6-thread-pool/rpc-worker.js -------------------------------------------------------------------------------- /ch8-template-render/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // npm install fastify@3 mustache@4 3 | 4 | const Fastify = require('fastify'); 5 | const RpcWorkerPool = require('./rpc-worker.js'); 6 | const worker = new RpcWorkerPool('./worker.js', 4, 'leastbusy'); 7 | const template = require('./template.js'); 8 | const server = Fastify(); 9 | // THIS LINE SHOULD NOT APPEAR IN BOOK 10 | server.get('/main', async (request, reply) => 11 | template.renderLove({ me: 'Thomas', you: 'Katelyn' })); 12 | 13 | server.get('/offload', async (request, reply) => 14 | worker.exec('renderLove', { me: 'Thomas', you: 'Katelyn' })); 15 | 16 | server.listen(3000, (err, address) => { 17 | if (err) throw err; 18 | console.log(`listening on: ${address}`); 19 | }); 20 | // THIS LINE SHOULD NOT APPEAR IN BOOK 21 | const timer = process.hrtime.bigint; 22 | setInterval(() => { 23 | const start = timer(); 24 | setImmediate(() => { 25 | console.log(`delay: ${(timer() - start).toLocaleString()}ns`); 26 | }); 27 | }, 1000); 28 | -------------------------------------------------------------------------------- /ch8-template-render/template.js: -------------------------------------------------------------------------------- 1 | const Mustache = require('mustache'); 2 | const love_template = "{{me}} loves {{you}} ".repeat(80); 3 | 4 | module.exports.renderLove = (data) => { 5 | const result = Mustache.render(love_template, data); 6 | // Mustache.clearCache(); 7 | return result; 8 | }; 9 | -------------------------------------------------------------------------------- /ch8-template-render/worker.js: -------------------------------------------------------------------------------- 1 | const { parentPort } = require('worker_threads'); 2 | const template = require('./template.js'); 3 | 4 | function asyncOnMessageWrap(fn) { 5 | return async function(msg) { 6 | parentPort.postMessage(await fn(msg)); 7 | } 8 | } 9 | 10 | const commands = { 11 | renderLove: (data) => template.renderLove(data) 12 | }; 13 | 14 | parentPort.on('message', asyncOnMessageWrap(async ({ method, params, id }) => ({ 15 | result: await commands[method](...params), id 16 | }))); 17 | --------------------------------------------------------------------------------