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 assert.throws( 104 () => { 105 const session = new Session(); 106 session.connectToMainThread(); 107 }, 108 { 109 code: 'ERR_INSPECTOR_NOT_WORKER', 110 name: 'Error', 111 message: 'Current thread is not a worker' 112 } 113 ); 114 const sharedBuffer = new SharedArrayBuffer(1); 115 const arrayBuffer = new Uint8Array(sharedBuffer); 116 arrayBuffer[0] = 1; 117 const worker = await startWorker(false, sharedBuffer); 118 worker.onConsoleRequest.then(doConsoleLog.bind(null, arrayBuffer)); 119 assert.strictEqual(toDebug(), 400); 120 assert.deepStrictEqual(await worker.onMessagesSent, [ 121 'Debugger.enable', 122 'Runtime.enable', 123 'Debugger.setBreakpointByUrl', 124 'Debugger.evaluateOnCallFrame', 125 'Debugger.resume', 126 ]); 127} 128 129async function childMain() { 130 // Ensures the worker does not terminate too soon 131 parentPort.on('message', () => { }); 132 await (await startWorker(true)).onMessagesSent; 133 const session = new Session(); 134 session.connectToMainThread(); 135 assert.throws( 136 () => { 137 session.connectToMainThread(); 138 }, 139 { 140 code: 'ERR_INSPECTOR_ALREADY_CONNECTED', 141 name: 'Error', 142 message: 'The inspector session is already connected' 143 } 144 ); 145 await post(session, 'Debugger.enable'); 146 await post(session, 'Runtime.enable'); 147 await post(session, 'Debugger.setBreakpointByUrl', { 148 'lineNumber': 18, 149 'url': pathToFileURL(path.resolve(__dirname, __filename)).toString(), 150 'columnNumber': 0, 151 'condition': '' 152 }); 153 const pausedPromise = waitForNotification(session, 'Debugger.paused'); 154 parentPort.postMessage('Ready'); 155 const callFrameId = (await pausedPromise).params.callFrames[0].callFrameId; 156 157 // Delay to ensure main thread is truly suspended 158 await new Promise((resolve) => setTimeout(resolve, 50)); 159 160 const { result: { value } } = 161 await post(session, 162 'Debugger.evaluateOnCallFrame', 163 { callFrameId, expression: 'a * 100' }); 164 assert.strictEqual(value, 100); 165 await post(session, 'Debugger.resume'); 166 await ensureListenerDoesNotInterrupt(session); 167 parentPort.postMessage({ messagesSent }); 168 parentPort.close(); 169 console.log('Worker is done'); 170} 171 172async function skipChildMain() { 173 // Ensures the worker does not terminate too soon 174 parentPort.on('message', () => { }); 175 176 const session = new Session(); 177 session.connectToMainThread(); 178 const notifications = []; 179 session.on('NodeWorker.attachedToWorker', (n) => notifications.push(n)); 180 await post(session, 'NodeWorker.enable', { waitForDebuggerOnStart: false }); 181 // 2 notifications mean there are 2 workers so we are connected to a main 182 // thread 183 assert.strictEqual(notifications.length, 2); 184 parentPort.postMessage('Ready'); 185 parentPort.postMessage({ messagesSent }); 186 parentPort.close(); 187 console.log('Skip child is done'); 188} 189 190if (isMainThread) { 191 main().then(common.mustCall()); 192} else if (workerData.skipChild) { 193 skipChildMain().then(common.mustCall()); 194} else { 195 childMain().then(common.mustCall()); 196} 197