Skip to content

Commit

Permalink
feat: allow debugging worker threads
Browse files Browse the repository at this point in the history
Fixes #885
  • Loading branch information
connor4312 committed Dec 9, 2020
1 parent 2e41de7 commit 4f5bafd
Show file tree
Hide file tree
Showing 21 changed files with 1,551 additions and 121 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"editor.formatOnSave": true
"editor.formatOnSave": true,
"python.formatting.provider": "black"
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This changelog records changes to stable releases since 1.50.2. "TBA" changes he

## TBA

- feat: allow debugging node worker_threads
- feat: make the line on log messages take into account skipFiles ([#882](https://github.com/microsoft/vscode-js-debug/issues/882))
- fix: persist state in the diagnostic tool ([#879](https://github.com/microsoft/vscode-js-debug/issues/879))
- fix: allow outdated node dialog to be bypassed ([ref](https://github.com/microsoft/vscode/issues/111642))
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"package": "gulp package",
"publish": "gulp publish",
"updatetypes": "cd src/typings && vscode-dts dev && vscode-dts master",
"updatenodeapi": "python src/build/getNodePdl.py && prettier --write src/build/nodeCustom.ts",
"generateapis": "node out/src/build/generateDap.js && node out/src/build/generateCdp.js",
"test": "gulp && npm-run-all --parallel test:unit test:types test:golden test:lint",
"test:types": "tsc --noEmit",
Expand Down
6 changes: 2 additions & 4 deletions src/adapter/threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,10 @@ export type Script = {
export interface IThreadDelegate {
name(): string;
supportsCustomBreakpoints(): boolean;
shouldCheckContentHash(): boolean;
scriptUrlToUrl(url: string): string;
executionContextName(description: Cdp.Runtime.ExecutionContextDescription): string;
initialize(): Promise<void>;
entryBreakpoint: IBreakpointPathAndId | undefined;
entryBreakpoint?: IBreakpointPathAndId;
}

export type ScriptWithSourceMapHandler = (
Expand Down Expand Up @@ -1164,14 +1163,13 @@ export class Thread implements IVariableStoreDelegate {
}
}

const hash = this._delegate.shouldCheckContentHash() ? event.hash : undefined;
const source = await this._sourceContainer.addSource(
event.url,
contentGetter,
resolvedSourceMapUrl,
inlineSourceOffset,
runtimeScriptOffset,
hash,
event.hash,
);

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand Down
2 changes: 1 addition & 1 deletion src/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export class Binder implements IDisposable {
return;
}

await new Promise(resolve =>
await new Promise<void>(resolve =>
this.onTargetListChanged(() => {
if (didTerminate()) {
resolve();
Expand Down
2 changes: 2 additions & 0 deletions src/build/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/__pycache__
/pdl.py
18 changes: 11 additions & 7 deletions src/build/generateCdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import { writeCodeToFile, autoGeneratedFileHeader } from './generateUtils';
import got from 'got';
import { autoGeneratedFileHeader, writeCodeToFile } from './generateUtils';
import nodeCustom from './nodeCustom';

// generic hack -- I would prefer to union/omit, ProtocolType loses the ability
// to discriminate union types when passing through in that way.
interface IProtocolType<WithId> {
name: WithId extends true ? never : string;
id: WithId extends false ? never : string;
description: string;
description?: string;
optional?: true;
deprecated?: true;
experimental?: true;
Expand Down Expand Up @@ -62,8 +63,8 @@ interface IProtocolEvent {
interface IProtocolDomain {
domain: string;
experimental: boolean;
dependencies: ReadonlyArray<string>;
types: ReadonlyArray<ProtocolType<true>>;
dependencies?: ReadonlyArray<string>;
types?: ReadonlyArray<ProtocolType<true>>;
commands: ReadonlyArray<IProtocolCommand>;
events: ReadonlyArray<IProtocolEvent>;
}
Expand Down Expand Up @@ -92,7 +93,10 @@ async function generate() {

const compareDomains = (a: IProtocolDomain, b: IProtocolDomain) =>
a.domain.toUpperCase() < b.domain.toUpperCase() ? -1 : 1;
const domains = jsProtocol.domains.concat(browserProtocol.domains).sort(compareDomains);
const domains = jsProtocol.domains
.concat(browserProtocol.domains)
.concat((nodeCustom.domains as unknown) as IProtocolDomain[])
.sort(compareDomains);
const result = [];
const interfaceSeparator = createSeparator();

Expand Down Expand Up @@ -151,7 +155,7 @@ async function generate() {
const separator = createSeparator();
for (const prop of props) {
separator();
appendText(prop.description, { deprecated: !!prop.deprecated });
appendText(prop.description ?? '', { deprecated: !!prop.deprecated });
result.push(`${prop.name}${prop.optional ? '?' : ''}: ${generateType(prop)};`);
}
}
Expand Down Expand Up @@ -210,7 +214,7 @@ async function generate() {
}
for (const type of types) {
typesSeparator();
appendText(type.description, { deprecated: !!type.deprecated });
appendText(type.description ?? '', { deprecated: !!type.deprecated });
if (type.type === 'object') {
result.push(`export interface ${toTitleCase(type.id)} {`);
if (type.properties) appendProps(type.properties);
Expand Down
18 changes: 18 additions & 0 deletions src/build/getNodePdl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import json
import os.path
import urllib.request

# To use this, download and copy pdl.py from here beside this file
# https://github.com/nodejs/node/blob/e31a99f01b8a92615ce79b845441949424cd1dda/tools/inspector_protocol/pdl.py
import pdl

inspector_pdl_url = "https://raw.githubusercontent.com/nodejs/node/master/src/inspector/node_protocol.pdl"
with urllib.request.urlopen(inspector_pdl_url) as r:
pdl_contents = pdl.loads(r.read().decode("utf-8"), "node_protocol.pdl", True)
with open(os.path.join(os.path.dirname(__file__), "nodeCustom.ts"), "w") as o:
o.write("/*---------------------------------------------------------\n")
o.write(" * Copyright (C) Microsoft Corporation. All rights reserved.\n")
o.write(" *--------------------------------------------------------*/\n")
o.write("\n")
o.write("export default ")
json.dump(pdl_contents, o, indent=2, separators=(",", ": "))
245 changes: 245 additions & 0 deletions src/build/nodeCustom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

export default {
version: {
major: '1',
minor: '0',
},
domains: [
{
domain: 'NodeTracing',
experimental: true,
types: [
{
id: 'TraceConfig',
type: 'object',
properties: [
{
name: 'recordMode',
description: 'Controls how the trace buffer stores data.',
optional: true,
type: 'string',
enum: ['recordUntilFull', 'recordContinuously', 'recordAsMuchAsPossible'],
},
{
name: 'includedCategories',
description: 'Included category filters.',
type: 'array',
items: {
type: 'string',
},
},
],
},
],
commands: [
{
name: 'getCategories',
description: 'Gets supported tracing categories.',
returns: [
{
name: 'categories',
description: 'A list of supported tracing categories.',
type: 'array',
items: {
type: 'string',
},
},
],
},
{
name: 'start',
description: 'Start trace events collection.',
parameters: [
{
name: 'traceConfig',
$ref: 'TraceConfig',
},
],
},
{
name: 'stop',
description:
'Stop trace events collection. Remaining collected events will be sent as a sequence of\ndataCollected events followed by tracingComplete event.',
},
],
events: [
{
name: 'dataCollected',
description: 'Contains an bucket of collected trace events.',
parameters: [
{
name: 'value',
type: 'array',
items: {
type: 'object',
},
},
],
},
{
name: 'tracingComplete',
description:
'Signals that tracing is stopped and there is no trace buffers pending flush, all data were\ndelivered via dataCollected events.',
},
],
},
{
domain: 'NodeWorker',
description: 'Support for sending messages to Node worker Inspector instances.',
experimental: true,
types: [
{
id: 'WorkerID',
type: 'string',
},
{
id: 'SessionID',
description: 'Unique identifier of attached debugging session.',
type: 'string',
},
{
id: 'WorkerInfo',
type: 'object',
properties: [
{
name: 'workerId',
$ref: 'WorkerID',
},
{
name: 'type',
type: 'string',
},
{
name: 'title',
type: 'string',
},
{
name: 'url',
type: 'string',
},
],
},
],
commands: [
{
name: 'sendMessageToWorker',
description: 'Sends protocol message over session with given id.',
parameters: [
{
name: 'message',
type: 'string',
},
{
name: 'sessionId',
description: 'Identifier of the session.',
$ref: 'SessionID',
},
],
},
{
name: 'enable',
description:
'Instructs the inspector to attach to running workers. Will also attach to new workers\nas they start',
parameters: [
{
name: 'waitForDebuggerOnStart',
description:
'Whether to new workers should be paused until the frontend sends `Runtime.runIfWaitingForDebugger`\nmessage to run them.',
type: 'boolean',
},
],
},
{
name: 'disable',
description:
'Detaches from all running workers and disables attaching to new workers as they are started.',
},
{
name: 'detach',
description: 'Detached from the worker with given sessionId.',
parameters: [
{
name: 'sessionId',
$ref: 'SessionID',
},
],
},
],
events: [
{
name: 'attachedToWorker',
description: 'Issued when attached to a worker.',
parameters: [
{
name: 'sessionId',
description: 'Identifier assigned to the session used to send/receive messages.',
$ref: 'SessionID',
},
{
name: 'workerInfo',
$ref: 'WorkerInfo',
},
{
name: 'waitingForDebugger',
type: 'boolean',
},
],
},
{
name: 'detachedFromWorker',
description: 'Issued when detached from the worker.',
parameters: [
{
name: 'sessionId',
description: 'Detached session identifier.',
$ref: 'SessionID',
},
],
},
{
name: 'receivedMessageFromWorker',
description:
'Notifies about a new protocol message received from the session\n(session ID is provided in attachedToWorker notification).',
parameters: [
{
name: 'sessionId',
description: 'Identifier of a session which sends a message.',
$ref: 'SessionID',
},
{
name: 'message',
type: 'string',
},
],
},
],
},
{
domain: 'NodeRuntime',
description: 'Support for inspecting node process state.',
experimental: true,
commands: [
{
name: 'notifyWhenWaitingForDisconnect',
description: 'Enable the `NodeRuntime.waitingForDisconnect`.',
parameters: [
{
name: 'enabled',
type: 'boolean',
},
],
},
],
events: [
{
name: 'waitingForDisconnect',
description:
'This event is fired instead of `Runtime.executionContextDestroyed` when\nenabled.\nIt is fired when the Node process finished all code execution and is\nwaiting for all frontends to disconnect.',
},
],
},
],
};
Loading

0 comments on commit 4f5bafd

Please sign in to comment.