Skip to content

Commit 8c27a23

Browse files
authored
Try to kill zombie daemon IDE-59 (#52)
* Try to kill zombie daemon * Add way to kill daemon in local ssh ipc * Support windows * Bump up daemon version * Fix win32 insider process lookup
1 parent da2fd9b commit 8c27a23

File tree

9 files changed

+381
-8
lines changed

9 files changed

+381
-8
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "Gitpod Support",
55
"publisher": "gitpod",
66
"version": "0.0.102",
7-
"daemonVersion": "0.0.3",
7+
"daemonVersion": "0.0.4",
88
"license": "MIT",
99
"icon": "resources/gitpod.png",
1010
"repository": {
@@ -158,6 +158,7 @@
158158
"@types/js-yaml": "^4.0.5",
159159
"@types/mocha": "^9.1.1",
160160
"@types/node": "16.x",
161+
"@types/ps-node": "^0.1.1",
161162
"@types/semver": "^7.3.10",
162163
"@types/ssh2": "^0.5.52",
163164
"@types/sshpk": "^1.17.1",
@@ -204,6 +205,7 @@
204205
"pkce-challenge": "^3.0.0",
205206
"prom-client": "^14.1.1",
206207
"protobufjs": "^7.2.2",
208+
"ps-node": "^0.1.6",
207209
"semver": "^7.3.7",
208210
"ssh-config": "^4.1.6",
209211
"ssh2": "^1.10.0",

src/configuration.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,15 @@ function getLocalSshIpcPort() {
4444
return vscode.workspace.getConfiguration('gitpod').get<number>('lsshIpcPort', defaultPort) || defaultPort;
4545
}
4646

47-
function getDaemonLogPath(): string {
47+
function getDaemonLogFileName(): string {
4848
if (vscode.env.appName.includes('Insiders')) {
49-
return join(tmpdir(), 'gitpod-vscode-daemon-insiders.log');
49+
return 'gitpod-vscode-daemon-insiders.log';
5050
}
51-
return join(tmpdir(), 'gitpod-vscode-daemon.log');
51+
return 'gitpod-vscode-daemon.log';
52+
}
53+
54+
function getDaemonLogPath(): string {
55+
return join(tmpdir(), getDaemonLogFileName());
5256
}
5357

5458
export const Configuration = {
@@ -58,4 +62,5 @@ export const Configuration = {
5862
getLocalSSHServerPort,
5963
getLocalSshIpcPort,
6064
getDaemonLogPath,
65+
getDaemonLogFileName,
6166
};

src/daemonStarter.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { join } from 'path';
7-
import { spawn } from 'child_process';
7+
import { spawn, exec } from 'child_process';
88
import { DaemonOptions, ExitCode } from './local-ssh/common';
99
import { ILogService } from './services/logService';
1010
import { timeout } from './common/async';
1111
import { Configuration } from './configuration';
1212
import { ITelemetryService } from './services/telemetryService';
13+
import { kill, lookup } from 'ps-node';
1314

1415
export async function ensureDaemonStarted(logService: ILogService, telemetryService: ITelemetryService, retry = 10) {
1516
if (retry < 0) {
@@ -23,6 +24,7 @@ export async function ensureDaemonStarted(logService: ILogService, telemetryServ
2324
switch (code) {
2425
case ExitCode.OK:
2526
case ExitCode.ListenPortFailed:
27+
case ExitCode.AskedToQuit:
2628
resolve(true);
2729
return;
2830
}
@@ -66,3 +68,32 @@ export async function tryStartDaemon(logService: ILogService, options?: Partial<
6668
daemon.unref();
6769
return daemon;
6870
}
71+
72+
export function killDaemon(logService: ILogService) {
73+
const logName = Configuration.getDaemonLogFileName();
74+
switch (process.platform) {
75+
case 'win32': {
76+
lookup({
77+
arguments: 'local-ssh'
78+
}, (err, resultList) => {
79+
if (err) {
80+
throw err;
81+
}
82+
const process = resultList.find(process => process.arguments.join(' ').includes(logName));
83+
if (!process) {
84+
return;
85+
}
86+
kill(process.pid);
87+
});
88+
return;
89+
}
90+
case 'darwin':
91+
case 'linux': {
92+
const regex = `node.*local-ssh.*daemon.js.*${logName}`;
93+
exec(`pkill -f ${regex}`);
94+
return;
95+
}
96+
default:
97+
logService.warn('failed to kill daemon: unsupported platform');
98+
}
99+
}

src/local-ssh/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export enum ExitCode {
2323
ListenPortFailed = 100,
2424
UnexpectedError = 101,
2525
InvalidOptions = 102,
26+
AskedToQuit = 103,
2627
}
2728

2829
export function exitProcess(code: ExitCode) {

src/local-ssh/ipc/extension.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ExtensionServiceDefinition, ExtensionServiceImplementation, GetWorkspac
88
import { Disposable } from '../../common/dispose';
99
import { retry, timeout } from '../../common/async';
1010
export { ExtensionServiceDefinition } from '../../proto/typescript/ipc/v1/ipc';
11-
import { ensureDaemonStarted } from '../../daemonStarter';
11+
import { ensureDaemonStarted, killDaemon } from '../../daemonStarter';
1212
import { withServerApi } from '../../internalApi';
1313
import { Workspace, WorkspaceInstanceStatus_Phase } from '@gitpod/public-api/lib/gitpod/experimental/v1';
1414
import { WorkspaceInfo, WorkspaceInstancePhase } from '@gitpod/gitpod-protocol';
@@ -22,6 +22,7 @@ import { getGitpodRemoteWindowConnectionInfo, showWsNotRunningDialog } from '../
2222
import { ITelemetryService, UserFlowTelemetry } from '../../services/telemetryService';
2323
import { ExperimentalSettings } from '../../experiments';
2424
import { Configuration } from '../../configuration';
25+
import { SemVer } from 'semver';
2526

2627
const phaseMap: Record<WorkspaceInstanceStatus_Phase, WorkspaceInstancePhase | undefined> = {
2728
[WorkspaceInstanceStatus_Phase.CREATING]: 'pending',
@@ -69,7 +70,7 @@ export class ExtensionServiceImpl implements ExtensionServiceImplementation {
6970
}
7071
const userId = this.sessionService.getUserId();
7172
const workspaceId = request.workspaceId;
72-
73+
7374
const gitpodHost = this.hostService.gitpodHost;
7475
const usePublicApi = await this.experiments.getUsePublicAPI(gitpodHost);
7576
const [workspace, ownerToken] = await withServerApi(accessToken, gitpodHost, svc => Promise.all([
@@ -171,6 +172,7 @@ export class ExtensionServiceServer extends Disposable {
171172
) {
172173
super();
173174
this.server = this.getServer();
175+
this.tryForceKillZombieDaemon();
174176
this.tryActive();
175177
this.hostService.onDidChangeHost(() => {
176178
this.tryActive();
@@ -259,4 +261,18 @@ export class ExtensionServiceServer extends Disposable {
259261
// this.logService.info('restart vscode to get latest features of local ssh');
260262
// // await this.notificationService.showWarningMessage('Restart VSCode to use latest local ssh daemon', { id: 'daemon_needs_restart', flow: { flow: 'daemon_needs_restart' } });
261263
// }
264+
265+
private async tryForceKillZombieDaemon() {
266+
try {
267+
// force kill daemon if its version less than 0.0.3
268+
const resp = await this.localSSHServiceClient.getDaemonVersion({});
269+
const runningDaemonVersion = new SemVer(resp.version);
270+
if (runningDaemonVersion.compare(new SemVer('0.0.3')) >= 0) {
271+
return;
272+
}
273+
killDaemon(this.logService);
274+
} catch (e) {
275+
this.logService.error(e, 'failed to force kill zombie daemon');
276+
}
277+
}
262278
}

src/local-ssh/ipc/localssh.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { ActiveRequest, ExtensionServiceDefinition, GetDaemonVersionRequest, InactiveRequest, LocalSSHServiceDefinition, LocalSSHServiceImplementation, PingRequest, SendErrorReportRequest, SendLocalSSHUserFlowStatusRequest } from '../../proto/typescript/ipc/v1/ipc';
6+
import { ActiveRequest, ExtensionServiceDefinition, GetDaemonInfoRequest, GetDaemonVersionRequest, InactiveRequest, KillDaemonRequest, LocalSSHServiceDefinition, LocalSSHServiceImplementation, PingRequest, SendErrorReportRequest, SendLocalSSHUserFlowStatusRequest } from '../../proto/typescript/ipc/v1/ipc';
77
import { CallContext, Client, ServerError, Status, createChannel, createClient, createServer } from 'nice-grpc';
88
import { ExitCode, exitProcess, getDaemonVersion, getRunningExtensionVersion } from '../common';
99
import { retryWithStop } from '../../common/async';
@@ -142,6 +142,20 @@ export class LocalSSHServiceImpl implements LocalSSHServiceImplementation {
142142
}
143143
}
144144
}
145+
146+
async getDaemonInfo(_request: GetDaemonInfoRequest, _context: CallContext): Promise<{ daemonVersion?: string | undefined; runningExtensionVersion?: string | undefined; pid?: number | undefined }> {
147+
return {
148+
daemonVersion: getDaemonVersion(),
149+
runningExtensionVersion: getRunningExtensionVersion(),
150+
pid: process.pid,
151+
};
152+
}
153+
154+
public async killDaemon(request: KillDaemonRequest, _context: CallContext): Promise<{}> {
155+
this.logger.info('received kill daemon request, reason: ' + request.reason);
156+
exitProcess(ExitCode.AskedToQuit);
157+
return {};
158+
}
145159
}
146160

147161
export async function startLocalSSHService(logger: ILogService, port: number, serviceImpl: LocalSSHServiceImpl) {

src/proto/ipc/v1/ipc.proto

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ service LocalSSHService {
1313

1414
// GetDaemonVersion returns the version of the daemon
1515
rpc GetDaemonVersion(GetDaemonVersionRequest) returns (GetDaemonVersionResponse) {}
16+
17+
// GetDaemonInfo returns the infomation of daemon
18+
rpc GetDaemonInfo(GetDaemonInfoRequest) returns (GetDaemonInfoResponse) {}
19+
20+
// KillDaemon kills the daemon
21+
rpc KillDaemon(KillDaemonRequest) returns (KillDaemonResponse) {}
1622
}
1723

1824
service ExtensionService {
@@ -22,6 +28,18 @@ service ExtensionService {
2228
rpc SendErrorReport (SendErrorReportRequest) returns (SendErrorReportResponse) {}
2329
}
2430

31+
message GetDaemonInfoRequest {}
32+
message GetDaemonInfoResponse {
33+
string daemon_version = 1;
34+
string running_extension_version = 2;
35+
uint32 pid = 3;
36+
}
37+
38+
message KillDaemonRequest {
39+
string reason = 1;
40+
}
41+
message KillDaemonResponse {}
42+
2543
message SendErrorReportRequest {
2644
string workspace_id = 1;
2745
string instance_id = 2;

0 commit comments

Comments
 (0)