Skip to content

Commit

Permalink
src/goDebugFactory: start dlv-dap lazily
Browse files Browse the repository at this point in the history
Do not start dlv-dap until we receive a message from the client
and have to send a message to the dlv-dap. This is because we want
to start dlv dap through RunInTerminal for console:integrated/external
and we cannot know whether client-side session setup (including
handling RunInTerminal request) is ready until the first message
from the client (i.e. the `initialize` request) arrives.

Updates #124

Change-Id: Ia9ce375bb07d852f705b0f95eb0ceb380521ef01
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/358536
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
  • Loading branch information
hyangah committed Dec 29, 2021
1 parent cd0a462 commit f4e5154
Showing 1 changed file with 69 additions and 60 deletions.
129 changes: 69 additions & 60 deletions src/goDebugFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ export class ProxyDebugAdapter implements vscode.DebugAdapter {
export class DelveDAPOutputAdapter extends ProxyDebugAdapter {
constructor(private configuration: vscode.DebugConfiguration, logger?: Logger) {
super(logger);
this.connected = this.startAndConnectToServer();
}

private connected: Promise<{ connected: boolean; reason?: any }>;
Expand All @@ -230,6 +229,9 @@ export class DelveDAPOutputAdapter extends ProxyDebugAdapter {
private terminatedOnError = false;

protected async sendMessageToServer(message: vscode.DebugProtocolMessage): Promise<void> {
if (!this.connected) {
this.connected = this.startAndConnectToServer();
}
const { connected, reason } = await this.connected;
if (connected) {
super.sendMessageToServer(message);
Expand Down Expand Up @@ -299,19 +301,7 @@ export class DelveDAPOutputAdapter extends ProxyDebugAdapter {

private async startAndConnectToServer() {
try {
const { dlvDapServer, socket } = await startDapServer(
this.configuration,
(msg) => this.outputEvent('stdout', msg),
(msg) => this.outputEvent('stderr', msg),
(msg) => {
this.outputEvent('console', msg);
// Some log messages generated after vscode stops the debug session
// may not appear in the DEBUG CONSOLE. For easier debugging, log
// the messages through the logger that prints to Go Debug output
// channel.
this.logger?.info(msg);
}
);
const { dlvDapServer, socket } = await this.startDapServer(this.configuration);

this.dlvDapServer = dlvDapServer;
this.socket = socket;
Expand All @@ -326,42 +316,53 @@ export class DelveDAPOutputAdapter extends ProxyDebugAdapter {
private outputEvent(dest: string, output: string, data?: any) {
this.sendMessageToClient(new OutputEvent(output, dest, data));
}
}

async function startDapServer(
configuration: vscode.DebugConfiguration,
log: (msg: string) => void,
logErr: (msg: string) => void,
logConsole: (msg: string) => void
): Promise<{ dlvDapServer?: ChildProcessWithoutNullStreams; socket: net.Socket }> {
// If a port has been specified, assume there is an already
// running dap server to connect to. Otherwise, we start the dlv dap server.
const dlvExternallyLaunched = !!configuration.port;
async startDapServer(
configuration: vscode.DebugConfiguration
): Promise<{ dlvDapServer?: ChildProcessWithoutNullStreams; socket: net.Socket }> {
const log = (msg: string) => this.outputEvent('stdout', msg);
const logErr = (msg: string) => this.outputEvent('stderr', msg);
const logConsole = (msg: string) => {
this.outputEvent('console', msg);
// Some log messages generated after vscode stops the debug session
// may not appear in the DEBUG CONSOLE. For easier debugging, log
// the messages through the logger that prints to Go Debug output
// channel.
this.logger?.info(msg);
};

if (!dlvExternallyLaunched && (configuration.console === 'integrated' || configuration.console === 'external')) {
return startDAPServerWithClientAddrFlag(configuration, log, logErr, logConsole);
}
const host = configuration.host || '127.0.0.1';
const port = configuration.port || (await getPort());

const dlvDapServer = dlvExternallyLaunched
? undefined
: await spawnDlvDapServerProcess(configuration, host, port, log, logErr, logConsole);

const socket = await new Promise<net.Socket>((resolve, reject) => {
// eslint-disable-next-line prefer-const
let timer: NodeJS.Timeout;
const s = net.createConnection(port, host, () => {
clearTimeout(timer);
resolve(s);
// If a port has been specified, assume there is an already
// running dap server to connect to. Otherwise, we start the dlv dap server.
const dlvExternallyLaunched = !!configuration.port;

if (
!dlvExternallyLaunched &&
(configuration.console === 'integrated' || configuration.console === 'external')
) {
return startDAPServerWithClientAddrFlag(configuration, log, logErr, logConsole);
}
const host = configuration.host || '127.0.0.1';
const port = configuration.port || (await getPort());

const dlvDapServer = dlvExternallyLaunched
? undefined
: await spawnDlvDapServerProcess(configuration, host, port, log, logErr, logConsole);

const socket = await new Promise<net.Socket>((resolve, reject) => {
// eslint-disable-next-line prefer-const
let timer: NodeJS.Timeout;
const s = net.createConnection(port, host, () => {
clearTimeout(timer);
resolve(s);
});
timer = setTimeout(() => {
reject('connection timeout');
s?.destroy();
}, 1000);
});
timer = setTimeout(() => {
reject('connection timeout');
s?.destroy();
}, 1000);
});

return { dlvDapServer, socket };
return { dlvDapServer, socket };
}
}

async function startDAPServerWithClientAddrFlag(
Expand All @@ -385,33 +386,41 @@ async function startDAPServerWithClientAddrFlag(
// 1) launch dlv in the super-module module directory and adjust launchArgs.cwd (--wd).
// 2) introduce a new buildDir launch attribute.
return new Promise<{ dlvDapServer: ChildProcessWithoutNullStreams; socket: net.Socket }>((resolve, reject) => {
let p: ChildProcessWithoutNullStreams = undefined;

const s = net.createServer();
s.on('close', () => {
logVerbose('server is closed');
});
s.on('error', (err) => {
logVerbose(`server got error ${err}`);
reject(err);
});

s.maxConnections = 1;
let s: net.Server = undefined; // temporary server that waits for p to connect to.
let p: ChildProcessWithoutNullStreams = undefined; // dlv dap

const timeoutToken: NodeJS.Timer = setTimeout(() => {
s.close();
if (s?.listening) {
s.close();
}
if (p) {
p.kill('SIGINT');
}
reject(new Error('timed out while waiting for DAP server to start'));
}, 30_000);

s.on('connection', (socket) => {
s = net.createServer({ pauseOnConnect: true }, (socket) => {
if (!p) {
logVerbose('unexpected connection, ignoring...');
socket.resume();
socket.destroy(new Error('unexpected connection'));
return;
}
logVerbose(`connected: ${port}`);
clearTimeout(timeoutToken);
s.close(); // accept no more connection
socket.resume();
resolve({ dlvDapServer: p, socket });
});
s.on('close', () => {
logVerbose('server is closed');
});
s.on('error', (err) => {
logVerbose(`server got error ${err}`);
reject(err);
});

s.maxConnections = 1;

s.listen(port);

Expand Down

0 comments on commit f4e5154

Please sign in to comment.