• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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