From 162a12ae130d535f023f1d2f49763de488596874 Mon Sep 17 00:00:00 2001 From: Scott Dickerson Date: Fri, 6 Dec 2024 14:51:29 -0500 Subject: [PATCH] :sparkles: Add commands for server stop and restart (#138) - New commands to be able to stop or restart (stop then start) the kai rpc server are now available via the command pallet - The server start now waits for the process to be spawned (or in error) before continuing to setup the server - Once running the server start will wait for either 5 seconds, or for the server to report it is ready. After that race, the server is considered started and ready to be initialized. - Server status is changed to "stopped" when the spawned server process is fully closed. --------- Signed-off-by: Scott J Dickerson --- vscode/package.json | 12 ++++++ vscode/src/client/analyzerClient.ts | 59 +++++++++++++++++++++-------- vscode/src/commands.ts | 29 +++++++++++++- 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/vscode/package.json b/vscode/package.json index 7a9fda6e..06aabbeb 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -75,6 +75,18 @@ "category": "Konveyor", "icon": "$(play)" }, + { + "command": "konveyor.stopServer", + "title": "Stop Server", + "category": "Konveyor", + "icon": "$(stop)" + }, + { + "command": "konveyor.restartServer", + "title": "Restart Server", + "category": "Konveyor", + "icon": "$(restart)" + }, { "command": "konveyor.runAnalysis", "title": "Run Analysis", diff --git a/vscode/src/client/analyzerClient.ts b/vscode/src/client/analyzerClient.ts index 0a3f167d..7446a46c 100644 --- a/vscode/src/client/analyzerClient.ts +++ b/vscode/src/client/analyzerClient.ts @@ -83,19 +83,25 @@ export class AnalyzerClient { this.outputChannel.appendLine(`server args:`); this.getKaiRpcServerArgs().forEach((arg) => this.outputChannel.appendLine(` ${arg}`)); - this.kaiRpcServer = spawn(this.getKaiRpcServerPath(), this.getKaiRpcServerArgs(), { + const kaiRpcServer = spawn(this.getKaiRpcServerPath(), this.getKaiRpcServerArgs(), { cwd: serverCwd, env: this.getKaiRpcServerEnv(), }); + this.kaiRpcServer = kaiRpcServer; + + const pid = await new Promise((resolve, reject) => { + kaiRpcServer.on("spawn", () => { + this.outputChannel.appendLine( + `kai rpc server has been spawned! [${this.kaiRpcServer?.pid}]`, + ); + resolve(this.kaiRpcServer?.pid); + }); - this.kaiRpcServer.on("spawn", () => { - this.outputChannel.appendLine(`kai rpc server has been spawned! [${this.kaiRpcServer?.pid}]`); - }); - - this.kaiRpcServer.on("error", (err) => { - this.outputChannel.appendLine( - `[error] - error in process[${this.kaiRpcServer?.spawnfile}]: ${err}`, - ); + kaiRpcServer.on("error", (err) => { + const message = `error in process[${this.kaiRpcServer?.spawnfile}]: ${err}`; + this.outputChannel.appendLine(`[error] - ${message}}`); + reject(); + }); }); this.kaiRpcServer.on("exit", (code, signal) => { @@ -104,10 +110,18 @@ export class AnalyzerClient { this.kaiRpcServer.on("close", (code, signal) => { this.outputChannel.appendLine(`kai rpc server closed with signal ${signal}, code ${code}`); + this.fireStateChange("stopped"); }); + let seenServerIsReady = false; this.kaiRpcServer.stderr.on("data", (data) => { - this.outputChannel.appendLine(`${data.toString()}`); + const asString: string = data.toString(); + this.outputChannel.appendLine(`${asString}`); + + if (!seenServerIsReady && asString.match(/kai-rpc-logger .*Started kai RPC Server/)) { + seenServerIsReady = true; + this.kaiRpcServer?.emit("serverReportsReady", pid); + } }); // Set up the JSON-RPC connection @@ -117,19 +131,28 @@ export class AnalyzerClient { ); this.rpcConnection.listen(); - this.outputChannel.appendLine(`Started the kai rpc server, pid: ${this.kaiRpcServer.pid}`); + await Promise.race([ + new Promise((resolve) => { + kaiRpcServer.on("serverReportsReady", (pid) => { + this.outputChannel.appendLine(`*** kai rpc server [${pid}] reports ready`); + resolve(); + }); + }), + setTimeout(5000), + ]); + + this.outputChannel.appendLine(`Started the kai rpc server, pid: [${pid}]`); } // Stops the analyzer server public stop(): void { this.fireStateChange("stopping"); this.outputChannel.appendLine(`Stopping the kai rpc server ...`); - if (this.kaiRpcServer) { + if (this.kaiRpcServer && !this.kaiRpcServer.killed) { this.kaiRpcServer.kill(); } this.rpcConnection?.dispose(); this.kaiRpcServer = null; - this.fireStateChange("stopped"); this.outputChannel.appendLine(`kai rpc server stopped`); } @@ -206,7 +229,9 @@ export class AnalyzerClient { "initialize", initializeParams, ); - this.outputChannel.appendLine(`${response}`); + this.outputChannel.appendLine( + `'initialize' response: ${JSON.stringify(response, null, 2)}`, + ); progress.report({ message: "RPC Server initialized" }); this.fireStateChange("running"); return; @@ -324,7 +349,8 @@ export class AnalyzerClient { // Shutdown the server public async shutdown(): Promise { try { - await this.rpcConnection!.sendRequest("shutdown", {}); + this.outputChannel.appendLine(`Requesting kai rpc server shutdown...`); + await this.rpcConnection?.sendRequest("shutdown", {}); } catch (err: any) { this.outputChannel.appendLine(`Error during shutdown: ${err.message}`); vscode.window.showErrorMessage("Shutdown failed. See the output channel for details."); @@ -334,7 +360,8 @@ export class AnalyzerClient { // Exit the server public async exit(): Promise { try { - await this.rpcConnection!.sendRequest("exit", {}); + this.outputChannel.appendLine(`Requesting kai rpc server exit...`); + await this.rpcConnection?.sendRequest("exit", {}); } catch (err: any) { this.outputChannel.appendLine(`Error during exit: ${err.message}`); vscode.window.showErrorMessage("Exit failed. See the output channel for details."); diff --git a/vscode/src/commands.ts b/vscode/src/commands.ts index a8f61a3c..5e9bdcbf 100644 --- a/vscode/src/commands.ts +++ b/vscode/src/commands.ts @@ -51,12 +51,37 @@ const commandsMap: (state: ExtensionState) => { if (!(await analyzerClient.canAnalyzeInteractive())) { return; } - try { await analyzerClient.start(); await analyzerClient.initialize(); } catch (e) { - console.error("Could not start the analyzer", e); + console.error("Could not start the server", e); + } + }, + "konveyor.stopServer": async () => { + const analyzerClient = state.analyzerClient; + try { + await analyzerClient.shutdown(); + await analyzerClient.stop(); + } catch (e) { + console.error("Could not shutdown and stop the server", e); + } + }, + "konveyor.restartServer": async () => { + const analyzerClient = state.analyzerClient; + try { + if (analyzerClient.isServerRunning()) { + await analyzerClient.shutdown(); + await analyzerClient.stop(); + } + + if (!(await analyzerClient.canAnalyzeInteractive())) { + return; + } + await analyzerClient.start(); + await analyzerClient.initialize(); + } catch (e) { + console.error("Could not restart the server", e); } }, "konveyor.runAnalysis": async () => {