🎁 Add killOnServerStop to debug configuration (#163779)

* 🎁 Add `killOnServerStop` to schema

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 📜 Add description for `killOnServerStop`

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Stop created debug session on server stop

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Push kill listeners into another disposable container

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🐛 Prevent leak when new debug session fails to start

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Use more verbose name for debug session tracker ID

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
This commit is contained in:
Babak K. Shandiz
2022-11-17 17:50:08 +03:30
committed by GitHub
parent 328ed10651
commit 9f56e365d7
3 changed files with 128 additions and 10 deletions

View File

@@ -5,6 +5,7 @@
import * as vscode from 'vscode';
import * as util from 'util';
import { randomUUID } from 'crypto';
const PATTERN = 'listening on.* (https?://\\S+|[0-9]+)'; // matches "listening on port 3000" or "Now listening on: https://localhost:5001"
const URI_PORT_FORMAT = 'http://localhost:%s';
@@ -17,6 +18,7 @@ interface ServerReadyAction {
uriFormat?: string;
webRoot?: string;
name?: string;
killOnServerStop?: boolean;
}
class Trigger {
@@ -40,6 +42,7 @@ class ServerReadyDetector extends vscode.Disposable {
private shellPid?: number;
private regexp: RegExp;
private disposables: vscode.Disposable[] = [];
private lateDisposables = new Set<vscode.Disposable>([]);
static start(session: vscode.DebugSession): ServerReadyDetector | undefined {
if (session.configuration.serverReadyAction) {
@@ -109,6 +112,11 @@ class ServerReadyDetector extends vscode.Disposable {
this.disposables = [];
}
override dispose() {
this.lateDisposables.forEach(d => d.dispose());
return super.dispose();
}
detectPattern(s: string): boolean {
if (!this.trigger.hasFired) {
const matches = this.regexp.exec(s);
@@ -153,25 +161,25 @@ class ServerReadyDetector extends vscode.Disposable {
this.openExternalWithUri(session, uri);
}
private openExternalWithUri(session: vscode.DebugSession, uri: string) {
private async openExternalWithUri(session: vscode.DebugSession, uri: string) {
const args: ServerReadyAction = session.configuration.serverReadyAction;
switch (args.action || 'openExternally') {
case 'openExternally':
vscode.env.openExternal(vscode.Uri.parse(uri));
await vscode.env.openExternal(vscode.Uri.parse(uri));
break;
case 'debugWithChrome':
this.debugWithBrowser('pwa-chrome', session, uri);
await this.debugWithBrowser('pwa-chrome', session, uri);
break;
case 'debugWithEdge':
this.debugWithBrowser('pwa-msedge', session, uri);
await this.debugWithBrowser('pwa-msedge', session, uri);
break;
case 'startDebugging':
vscode.debug.startDebugging(session.workspaceFolder, args.name || 'unspecified');
await this.startNamedDebugSession(session, args.name || 'unspecified');
break;
default:
@@ -180,13 +188,104 @@ class ServerReadyDetector extends vscode.Disposable {
}
}
private debugWithBrowser(type: string, session: vscode.DebugSession, uri: string) {
private async debugWithBrowser(type: string, session: vscode.DebugSession, uri: string) {
const args = session.configuration.serverReadyAction as ServerReadyAction;
if (!args.killOnServerStop) {
await this.startBrowserDebugSession(type, session, uri);
return;
}
const trackerId = randomUUID();
const cts = new vscode.CancellationTokenSource();
const newSessionPromise = this.catchStartedDebugSession(session => session.configuration._debugServerReadySessionId === trackerId, cts.token);
if (!await this.startBrowserDebugSession(type, session, uri, trackerId)) {
cts.cancel();
cts.dispose();
return;
}
const createdSession = await newSessionPromise;
cts.dispose();
if (!createdSession) {
return;
}
const stopListener = vscode.debug.onDidTerminateDebugSession(async (terminated) => {
if (terminated === session) {
stopListener.dispose();
this.lateDisposables.delete(stopListener);
await vscode.debug.stopDebugging(createdSession);
}
});
this.lateDisposables.add(stopListener);
}
private startBrowserDebugSession(type: string, session: vscode.DebugSession, uri: string, trackerId?: string) {
return vscode.debug.startDebugging(session.workspaceFolder, {
type,
name: 'Browser Debug',
request: 'launch',
url: uri,
webRoot: session.configuration.serverReadyAction.webRoot || WEB_ROOT
webRoot: session.configuration.serverReadyAction.webRoot || WEB_ROOT,
_debugServerReadySessionId: trackerId,
});
}
private async startNamedDebugSession(session: vscode.DebugSession, name: string) {
const args = session.configuration.serverReadyAction as ServerReadyAction;
if (!args.killOnServerStop) {
await vscode.debug.startDebugging(session.workspaceFolder, name);
return;
}
const cts = new vscode.CancellationTokenSource();
const newSessionPromise = this.catchStartedDebugSession(x => x.name === name, cts.token);
if (!await vscode.debug.startDebugging(session.workspaceFolder, name)) {
cts.cancel();
cts.dispose();
return;
}
const createdSession = await newSessionPromise;
cts.dispose();
if (!createdSession) {
return;
}
const stopListener = vscode.debug.onDidTerminateDebugSession(async (terminated) => {
if (terminated === session) {
stopListener.dispose();
this.lateDisposables.delete(stopListener);
await vscode.debug.stopDebugging(createdSession);
}
});
this.lateDisposables.add(stopListener);
}
private catchStartedDebugSession(predicate: (session: vscode.DebugSession) => boolean, cancellationToken: vscode.CancellationToken): Promise<vscode.DebugSession | undefined> {
return new Promise<vscode.DebugSession | undefined>(_resolve => {
const done = (value?: vscode.DebugSession) => {
listener.dispose();
cancellationListener.dispose();
this.lateDisposables.delete(listener);
this.lateDisposables.delete(cancellationListener);
_resolve(value);
};
const cancellationListener = cancellationToken.onCancellationRequested(done);
const listener = vscode.debug.onDidStartDebugSession(session => {
if (predicate(session)) {
done(session);
}
});
// In case the debug session of interest was never caught anyhow.
this.lateDisposables.add(listener);
this.lateDisposables.add(cancellationListener);
});
}
}