1'use strict'; 2const common = require('../common'); 3 4common.skipIfInspectorDisabled(); 5 6const assert = require('assert'); 7const { Session } = require('inspector'); 8const path = require('path'); 9const { pathToFileURL } = require('url'); 10const { isMainThread, parentPort, Worker, workerData } = 11 require('worker_threads'); 12 13if (!workerData) { 14 common.skipIfWorker(); 15} 16 17function toDebug() { 18 let a = 1; 19 a = a + 1; 20 return a * 200; 21} 22 23const messagesSent = []; 24 25async function post(session, method, params) { 26 return new Promise((resolve, reject) => { 27 session.post(method, params, (error, success) => { 28 messagesSent.push(method); 29 if (error) { 30 process._rawDebug(`Message ${method} produced an error`); 31 reject(error); 32 } else { 33 process._rawDebug(`Message ${method} was sent`); 34 resolve(success); 35 } 36 }); 37 }); 38} 39 40async function waitForNotification(session, notification) { 41 return new Promise((resolve) => session.once(notification, resolve)); 42} 43 44function startWorker(skipChild, sharedBuffer) { 45 return new Promise((resolve) => { 46 const worker = new Worker(__filename, { 47 workerData: { skipChild, sharedBuffer } 48 }); 49 worker.on('error', (e) => { 50 console.error(e); 51 throw e; 52 }); 53 // Add 2 promises to the worker, one resolved when a message with a 54 // .doConsoleLog property is received and one resolved when a .messagesSent 55 // property is received. 56 let resolveConsoleRequest; 57 let resolveMessagesSent; 58 worker.onConsoleRequest = 59 new Promise((resolve) => resolveConsoleRequest = resolve); 60 worker.onMessagesSent = 61 new Promise((resolve) => resolveMessagesSent = resolve); 62 worker.on('message', (m) => { 63 resolve(worker); 64 if (m.doConsoleLog) resolveConsoleRequest(); 65 if (m.messagesSent) resolveMessagesSent(m.messagesSent); 66 }); 67 }); 68} 69 70function doConsoleLog(arrayBuffer) { 71 console.log('Message for a test'); 72 arrayBuffer[0] = 128; 73} 74 75// This tests that inspector callbacks are called in a microtask 76// and do not interrupt the main code. Interrupting the code flow 77// can lead to unexpected behaviors. 78async function ensureListenerDoesNotInterrupt(session) { 79 // Make sure that the following code is not affected by the fact that it may 80 // run inside an inspector message callback, during which other inspector 81 // message callbacks (such as the one triggered by doConsoleLog()) would 82 // not be processed. 83 await new Promise(setImmediate); 84 85 const currentTime = Date.now(); 86 let consoleLogHappened = false; 87 session.once('Runtime.consoleAPICalled', 88 () => { consoleLogHappened = true; }); 89 const buf = new Uint8Array(workerData.sharedBuffer); 90 parentPort.postMessage({ doConsoleLog: true }); 91 while (buf[0] === 1) { 92 // Making sure the console.log was executed 93 } 94 while ((Date.now() - currentTime) < 50) { 95 // Spin wait for 50ms, assume that was enough to get inspector message 96 } 97 assert.strictEqual(consoleLogHappened, false); 98 await new Promise(queueMicrotask); 99 assert.strictEqual(consoleLogHappened, true); 100} 101 102async function main() { 103 const sharedBuffer = new SharedArrayBuffer(1); 104 const arrayBuffer = new Uint8Array(sharedBuffer); 105 arrayBuffer[0] = 1; 106 const worker = await startWorker(false, sharedBuffer); 107 worker.onConsoleRequest.then(doConsoleLog.bind(null, arrayBuffer)); 108 assert.strictEqual(toDebug(), 400); 109 assert.deepStrictEqual(await worker.onMessagesSent, [ 110 'Debugger.enable', 111 'Runtime.enable', 112 'Debugger.setBreakpointByUrl', 113 'Debugger.evaluateOnCallFrame', 114 'Debugger.resume', 115 ]); 116} 117 118async function childMain() { 119 // Ensures the worker does not terminate too soon 120 parentPort.on('message', () => { }); 121 await (await startWorker(true)).onMessagesSent; 122 const session = new Session(); 123 session.connectToMainThread(); 124 await post(session, 'Debugger.enable'); 125 await post(session, 'Runtime.enable'); 126 await post(session, 'Debugger.setBreakpointByUrl', { 127 'lineNumber': 18, 128 'url': pathToFileURL(path.resolve(__dirname, __filename)).toString(), 129 'columnNumber': 0, 130 'condition': '' 131 }); 132 const pausedPromise = waitForNotification(session, 'Debugger.paused'); 133 parentPort.postMessage('Ready'); 134 const callFrameId = (await pausedPromise).params.callFrames[0].callFrameId; 135 136 // Delay to ensure main thread is truly suspended 137 await new Promise((resolve) => setTimeout(resolve, 50)); 138 139 const { result: { value } } = 140 await post(session, 141 'Debugger.evaluateOnCallFrame', 142 { callFrameId, expression: 'a * 100' }); 143 assert.strictEqual(value, 100); 144 await post(session, 'Debugger.resume'); 145 await ensureListenerDoesNotInterrupt(session); 146 parentPort.postMessage({ messagesSent }); 147 parentPort.close(); 148 console.log('Worker is done'); 149} 150 151async function skipChildMain() { 152 // Ensures the worker does not terminate too soon 153 parentPort.on('message', () => { }); 154 155 const session = new Session(); 156 session.connectToMainThread(); 157 const notifications = []; 158 session.on('NodeWorker.attachedToWorker', (n) => notifications.push(n)); 159 await post(session, 'NodeWorker.enable', { waitForDebuggerOnStart: false }); 160 // 2 notifications mean there are 2 workers so we are connected to a main 161 // thread 162 assert.strictEqual(notifications.length, 2); 163 parentPort.postMessage('Ready'); 164 parentPort.postMessage({ messagesSent }); 165 parentPort.close(); 166 console.log('Skip child is done'); 167} 168 169if (isMainThread) { 170 main().then(common.mustCall()); 171} else if (workerData.skipChild) { 172 skipChildMain().then(common.mustCall()); 173} else { 174 childMain().then(common.mustCall()); 175} 176