Skip to content

Commit

Permalink
add pending message, cleanup status contribution
Browse files Browse the repository at this point in the history
Signed-off-by: Colin Grant <colin.grant@ericsson.com>
  • Loading branch information
colin-grant-work authored and bhufmann committed Jan 26, 2021
1 parent f565daf commit b6906b1
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 44 deletions.
102 changes: 62 additions & 40 deletions viewer-prototype/src/browser/trace-server-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
import { inject, injectable, postConstruct } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { DefaultFrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { StatusBar, StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar';
import { Disposable, DisposableCollection } from '@theia/core/lib//common';
import { ConnectionStatusService, ConnectionStatus, AbstractConnectionStatusService } from '@theia/core/lib/browser/connection-status-service';
import { StatusBar, StatusBarAlignment, StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
import { ConnectionStatus, AbstractConnectionStatusService } from '@theia/core/lib/browser/connection-status-service';
import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client';
import { TspClientProvider } from './tsp-client-provider';
import { TraceServerConfigService } from '../common/trace-server-config';
import { PreferenceService } from '@theia/core/lib/browser';
import { TRACE_PATH, TRACE_PORT } from './trace-server-preference';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { MessageService } from '@theia/core';

@injectable()
export class TraceServerConnectionStatusService extends AbstractConnectionStatusService {
Expand All @@ -41,7 +42,8 @@ export class TraceServerConnectionStatusService extends AbstractConnectionStatus
}
this.scheduledPing = this.setTimeout(async () => {
try {
await this.tspClient.fetchExperiments();
const pingTimeout = new Promise((_, reject) => { setTimeout(reject, this.options.offlineTimeout); });
await Promise.race([this.tspClient.fetchExperiments(), pingTimeout]);
this.updateStatus(true);
} catch (e) {
this.updateStatus(false);
Expand All @@ -55,17 +57,24 @@ export class TraceServerConnectionStatusService extends AbstractConnectionStatus
@injectable()
export class TraceServerConnectionStatusContribution extends DefaultFrontendApplicationContribution {

static readonly STATUS_BAR_ID = 'trace-connection-status';
static readonly SERVER_OFFLINE_CLASSNAME = 'traceserver-mod-offline';

@inject(StatusBar) protected readonly statusBar: StatusBar;
@inject(ILogger) protected readonly logger: ILogger;
@inject(PreferenceService) protected readonly preferenceService: PreferenceService;
@inject(TraceServerConfigService) protected readonly traceServerConfigService: TraceServerConfigService;
@inject(TraceServerConnectionStatusService) protected readonly connectionStatusService: TraceServerConnectionStatusService;
@inject(MessageService) protected readonly messageService: MessageService;

private path: string | undefined;
private port: number | undefined;
protected path: string | undefined;
protected port: number | undefined;

@postConstruct()
async init(): Promise<void> {

this.path = this.preferenceService.get(TRACE_PATH);
this.port = this.preferenceService.get(TRACE_PORT);
protected serverPending = new Deferred<void>();

@postConstruct()
protected async init(): Promise<void> {
this.connectionStatusService.onStatusChange(state => this.onStateChange(state));
this.preferenceService.onPreferenceChanged(event => {
if (event.preferenceName === TRACE_PORT) {
this.port = event.newValue;
Expand All @@ -74,19 +83,10 @@ export class TraceServerConnectionStatusContribution extends DefaultFrontendAppl
this.path = event.newValue;
}
});
}

@inject(TraceServerConfigService) protected readonly traceServerConfigService: TraceServerConfigService;

protected readonly toDisposeOnOnline = new DisposableCollection();
this.path = this.preferenceService.get(TRACE_PATH);
this.port = this.preferenceService.get(TRACE_PORT);

constructor(
@inject(TraceServerConnectionStatusService) protected readonly connectionStatusService: ConnectionStatusService,
@inject(StatusBar) protected readonly statusBar: StatusBar,
@inject(ILogger) protected readonly logger: ILogger
) {
super();
this.connectionStatusService.onStatusChange(state => this.onStateChange(state));
if (this.connectionStatusService.currentStatus === ConnectionStatus.ONLINE) {
this.handleOnline();
}
Expand All @@ -105,38 +105,60 @@ export class TraceServerConnectionStatusContribution extends DefaultFrontendAppl
}
}

private statusbarId = 'trace-connection-status';
protected async startServer(): Promise<void> {
this.updateStatusBar({ text: '$(sync~spin) Trace server starting' });

try {
await this.withTimeout(this.traceServerConfigService.startTraceServer(this.path, this.port));
} catch {
this.messageService.error('Failed to start trace server.');
this.handleOffline();
}
}

protected async stopServer(): Promise<void> {
this.updateStatusBar({ text: '$(sync~spin) Trace server stopping' });

try {
await this.withTimeout(this.traceServerConfigService.stopTraceServer(this.port));
} catch {
this.messageService.error('Failed to terminate trace server.');
this.handleOnline();
}
}

protected handleOnline(): void {
this.toDisposeOnOnline.dispose();
this.statusBar.setElement(this.statusbarId, {
alignment: StatusBarAlignment.LEFT,
this.updateStatusBar({
text: '$(fas fa-stop) Stop trace server',
tooltip: 'Click here to stop the trace server',
priority: 5003,
onclick: this.stopServer.bind(this)
onclick: this.stopServer.bind(this),
});

this.serverPending.resolve();
}

private async startServer() {
await this.traceServerConfigService.startTraceServer(this.path, this.port);
}
protected handleOffline(): void {
this.updateStatusBar({
text: '$(fas fa-play) Start trace server',
tooltip: 'Click here to start the trace server',
onclick: this.startServer.bind(this),
});

private async stopServer() {
await this.traceServerConfigService.stopTraceServer(this.port);
this.serverPending.resolve();
}

protected handleOffline(): void {
this.toDisposeOnOnline.dispose();
this.statusBar.setElement(this.statusbarId, {
// Must have text, other fields supplied
protected updateStatusBar(options: Partial<StatusBarEntry> & { text: string }): void {
this.statusBar.setElement(TraceServerConnectionStatusContribution.STATUS_BAR_ID, {
alignment: StatusBarAlignment.LEFT,
text: '$(fas fa-play) Start trace server',
tooltip: 'Click here to start the trace server',
priority: 5001,
onclick: this.startServer.bind(this)
...options,
});
}

this.toDisposeOnOnline.push(Disposable.create(() => this.statusBar.removeElement(this.statusbarId)));
protected withTimeout<T = void>(serverAction: Promise<T>): Promise<[T, void]> {
this.serverPending = new Deferred();
setTimeout(this.serverPending.reject, 10000);
return Promise.all([serverAction, this.serverPending.promise]);
}
}
28 changes: 24 additions & 4 deletions viewer-prototype/src/node/trace-server-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,31 @@ import { TraceServerConfigService } from '../common/trace-server-config';
@injectable()
export class TraceServerServiceImpl implements TraceServerConfigService {

async startTraceServer(path: string | undefined, port: number | undefined): Promise<void> {
spawn(`${path}`, ['-vmargs', `-Dtraceserver.port=${port}`]);
startTraceServer(path: string | undefined, port: number | undefined): Promise<void> {
const server = spawn(`${path}`, ['-vmargs', `-Dtraceserver.port=${port}`]);
return new Promise<void>((resolve, reject) => {
// If the server provides output on any channel before it exits, consider that a success.
// That doesn't mean that the server has really started. On the frontend, the `TraceServerConnectionStatusService`
// will ping the port until it receives a response, which is the official measure of success.
server.stdout.on('data', () => resolve());
server.stderr.on('data', () => resolve());
// If the server exits or errors before it outputs, consider it a failure.
server.on('exit', code => reject(code));
server.on('error', error => reject(error));
})
.finally(() => { server.removeAllListeners(); });
}

async stopTraceServer(port: number | undefined): Promise<void> {
exec(`kill -9 $(lsof -t -i:${port} -sTCP:LISTEN)`); // FIXME: Better approach to kill the server at a given port
stopTraceServer(port: number | undefined): Promise<void> {
const terminator = exec(`kill -9 $(lsof -t -i:${port} -sTCP:LISTEN)`); // FIXME: Better approach to kill the server at a given port
return new Promise((resolve, reject) => {
terminator.on('exit', code => {
if (code === 0 || code === 1) {
resolve();
} else {
reject(code);
}
});
});
}
}

0 comments on commit b6906b1

Please sign in to comment.