1'use strict'; 2 3const common = require('../../common'); 4const assert = require('assert'); 5const binding = require(`./build/${common.buildType}/binding`); 6const { fork } = require('child_process'); 7const expectedArray = (function(arrayLength) { 8 const result = []; 9 for (let index = 0; index < arrayLength; index++) { 10 result.push(arrayLength - 1 - index); 11 } 12 return result; 13})(binding.ARRAY_LENGTH); 14 15// Handle the rapid teardown test case as the child process. We unref the 16// thread-safe function after we have received two values. This causes the 17// process to exit and the environment cleanup handler to be invoked. 18if (process.argv[2] === 'child') { 19 let callCount = 0; 20 binding.StartThread((value) => { 21 callCount++; 22 console.log(value); 23 if (callCount === 2) { 24 binding.Unref(); 25 } 26 }, false /* abort */, true /* launchSecondary */, +process.argv[3]); 27 28 // Release the thread-safe function from the main thread so that it may be 29 // torn down via the environment cleanup handler. 30 binding.Release(); 31 return; 32} 33 34function testWithJSMarshaller({ 35 threadStarter, 36 quitAfter, 37 abort, 38 maxQueueSize, 39 launchSecondary, 40}) { 41 return new Promise((resolve) => { 42 const array = []; 43 binding[threadStarter](function testCallback(value) { 44 array.push(value); 45 if (array.length === quitAfter) { 46 setImmediate(() => { 47 binding.StopThread(common.mustCall(() => { 48 resolve(array); 49 }), !!abort); 50 }); 51 } 52 }, !!abort, !!launchSecondary, maxQueueSize); 53 if (threadStarter === 'StartThreadNonblocking') { 54 // Let's make this thread really busy for a short while to ensure that 55 // the queue fills and the thread receives a napi_queue_full. 56 const start = Date.now(); 57 while (Date.now() - start < 200); 58 } 59 }); 60} 61 62function testUnref(queueSize) { 63 return new Promise((resolve, reject) => { 64 let output = ''; 65 const child = fork(__filename, ['child', queueSize], { 66 stdio: [process.stdin, 'pipe', process.stderr, 'ipc'] 67 }); 68 child.on('close', (code) => { 69 if (code === 0) { 70 resolve(output.match(/\S+/g)); 71 } else { 72 reject(new Error('Child process died with code ' + code)); 73 } 74 }); 75 child.stdout.on('data', (data) => (output += data.toString())); 76 }) 77 .then((result) => assert.strictEqual(result.indexOf(0), -1)); 78} 79 80new Promise(function testWithoutJSMarshaller(resolve) { 81 let callCount = 0; 82 binding.StartThreadNoNative(function testCallback() { 83 callCount++; 84 85 // The default call-into-JS implementation passes no arguments. 86 assert.strictEqual(arguments.length, 0); 87 if (callCount === binding.ARRAY_LENGTH) { 88 setImmediate(() => { 89 binding.StopThread(common.mustCall(() => { 90 resolve(); 91 }), false); 92 }); 93 } 94 }, false /* abort */, false /* launchSecondary */, binding.MAX_QUEUE_SIZE); 95}) 96 97// Start the thread in blocking mode, and assert that all values are passed. 98// Quit after it's done. 99.then(() => testWithJSMarshaller({ 100 threadStarter: 'StartThread', 101 maxQueueSize: binding.MAX_QUEUE_SIZE, 102 quitAfter: binding.ARRAY_LENGTH 103})) 104.then((result) => assert.deepStrictEqual(result, expectedArray)) 105 106// Start the thread in blocking mode, and assert that all values are passed. 107// Quit after it's done. 108// Doesn't pass the callback js function to napi_create_threadsafe_function. 109// Instead, use an alternative reference to get js function called. 110.then(() => testWithJSMarshaller({ 111 threadStarter: 'StartThreadNoJsFunc', 112 maxQueueSize: binding.MAX_QUEUE_SIZE, 113 quitAfter: binding.ARRAY_LENGTH 114})) 115.then((result) => assert.deepStrictEqual(result, expectedArray)) 116 117// Start the thread in blocking mode with an infinite queue, and assert that all 118// values are passed. Quit after it's done. 119.then(() => testWithJSMarshaller({ 120 threadStarter: 'StartThread', 121 maxQueueSize: 0, 122 quitAfter: binding.ARRAY_LENGTH 123})) 124.then((result) => assert.deepStrictEqual(result, expectedArray)) 125 126// Start the thread in non-blocking mode, and assert that all values are passed. 127// Quit after it's done. 128.then(() => testWithJSMarshaller({ 129 threadStarter: 'StartThreadNonblocking', 130 maxQueueSize: binding.MAX_QUEUE_SIZE, 131 quitAfter: binding.ARRAY_LENGTH 132})) 133.then((result) => assert.deepStrictEqual(result, expectedArray)) 134 135// Start the thread in blocking mode, and assert that all values are passed. 136// Quit early, but let the thread finish. 137.then(() => testWithJSMarshaller({ 138 threadStarter: 'StartThread', 139 maxQueueSize: binding.MAX_QUEUE_SIZE, 140 quitAfter: 1 141})) 142.then((result) => assert.deepStrictEqual(result, expectedArray)) 143 144// Start the thread in blocking mode with an infinite queue, and assert that all 145// values are passed. Quit early, but let the thread finish. 146.then(() => testWithJSMarshaller({ 147 threadStarter: 'StartThread', 148 maxQueueSize: 0, 149 quitAfter: 1 150})) 151.then((result) => assert.deepStrictEqual(result, expectedArray)) 152 153// Start the thread in non-blocking mode, and assert that all values are passed. 154// Quit early, but let the thread finish. 155.then(() => testWithJSMarshaller({ 156 threadStarter: 'StartThreadNonblocking', 157 maxQueueSize: binding.MAX_QUEUE_SIZE, 158 quitAfter: 1 159})) 160.then((result) => assert.deepStrictEqual(result, expectedArray)) 161 162// Start the thread in blocking mode, and assert that all values are passed. 163// Quit early, but let the thread finish. Launch a secondary thread to test the 164// reference counter incrementing functionality. 165.then(() => testWithJSMarshaller({ 166 threadStarter: 'StartThread', 167 quitAfter: 1, 168 maxQueueSize: binding.MAX_QUEUE_SIZE, 169 launchSecondary: true 170})) 171.then((result) => assert.deepStrictEqual(result, expectedArray)) 172 173// Start the thread in non-blocking mode, and assert that all values are passed. 174// Quit early, but let the thread finish. Launch a secondary thread to test the 175// reference counter incrementing functionality. 176.then(() => testWithJSMarshaller({ 177 threadStarter: 'StartThreadNonblocking', 178 quitAfter: 1, 179 maxQueueSize: binding.MAX_QUEUE_SIZE, 180 launchSecondary: true 181})) 182.then((result) => assert.deepStrictEqual(result, expectedArray)) 183 184// Start the thread in blocking mode, and assert that it could not finish. 185// Quit early by aborting. 186.then(() => testWithJSMarshaller({ 187 threadStarter: 'StartThread', 188 quitAfter: 1, 189 maxQueueSize: binding.MAX_QUEUE_SIZE, 190 abort: true 191})) 192.then((result) => assert.strictEqual(result.indexOf(0), -1)) 193 194// Start the thread in blocking mode with an infinite queue, and assert that it 195// could not finish. Quit early by aborting. 196.then(() => testWithJSMarshaller({ 197 threadStarter: 'StartThread', 198 quitAfter: 1, 199 maxQueueSize: 0, 200 abort: true 201})) 202.then((result) => assert.strictEqual(result.indexOf(0), -1)) 203 204// Start the thread in non-blocking mode, and assert that it could not finish. 205// Quit early and aborting. 206.then(() => testWithJSMarshaller({ 207 threadStarter: 'StartThreadNonblocking', 208 quitAfter: 1, 209 maxQueueSize: binding.MAX_QUEUE_SIZE, 210 abort: true 211})) 212.then((result) => assert.strictEqual(result.indexOf(0), -1)) 213 214// Make sure that threadsafe function isn't stalled when we hit 215// `kMaxIterationCount` in `src/node_api.cc` 216.then(() => testWithJSMarshaller({ 217 threadStarter: 'StartThreadNonblocking', 218 maxQueueSize: binding.ARRAY_LENGTH >>> 1, 219 quitAfter: binding.ARRAY_LENGTH 220})) 221.then((result) => assert.deepStrictEqual(result, expectedArray)) 222 223// Start a child process to test rapid teardown 224.then(() => testUnref(binding.MAX_QUEUE_SIZE)) 225 226// Start a child process with an infinite queue to test rapid teardown 227.then(() => testUnref(0)); 228