Skip to content

Commit d2bc79b

Browse files
authored
Introduce timeout for keeping connection contexts alive (#13082)
Fixes #12823 - refactor front end to allow for multiple reconnections - remove IWebsockt abstractions - separate front end connections from service channel management - introduce mechanism to reconnect front end to existing connection context based on timeouts Contributed on behalf of STMicroelectronics Signed-off-by: Thomas Mäder <t.s.maeder@gmail.com>
1 parent 2d30b29 commit d2bc79b

File tree

53 files changed

+1320
-722
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1320
-722
lines changed

dev-packages/application-package/src/application-props.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ export namespace FrontendApplicationConfig {
8686
defaultIconTheme: 'theia-file-icons',
8787
electron: ElectronFrontendApplicationConfig.DEFAULT,
8888
defaultLocale: '',
89-
validatePreferencesSchema: true
89+
validatePreferencesSchema: true,
90+
reloadOnReconnect: false
9091
};
9192
export interface Partial extends ApplicationConfig {
9293

@@ -132,6 +133,12 @@ export namespace FrontendApplicationConfig {
132133
* Defaults to `true`.
133134
*/
134135
readonly validatePreferencesSchema?: boolean;
136+
137+
/**
138+
* When 'true', the window will reload in case the front end reconnects to a back-end,
139+
* but the back end does not have a connection context for this front end anymore.
140+
*/
141+
readonly reloadOnReconnect?: boolean;
135142
}
136143
}
137144

@@ -142,6 +149,7 @@ export type BackendApplicationConfig = RequiredRecursive<BackendApplicationConfi
142149
export namespace BackendApplicationConfig {
143150
export const DEFAULT: BackendApplicationConfig = {
144151
singleInstance: false,
152+
frontendConnectionTimeout: 0
145153
};
146154
export interface Partial extends ApplicationConfig {
147155

@@ -151,6 +159,11 @@ export namespace BackendApplicationConfig {
151159
* Defaults to `false`.
152160
*/
153161
readonly singleInstance?: boolean;
162+
163+
/**
164+
* The time in ms the connection context will be preserved for reconnection after a front end disconnects.
165+
*/
166+
readonly frontendConnectionTimeout?: number;
154167
}
155168
}
156169

examples/api-samples/src/electron-browser/updater/sample-updater-frontend-module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// *****************************************************************************
1616

1717
import { ContainerModule } from '@theia/core/shared/inversify';
18-
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
18+
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-source';
1919
import { CommandContribution, MenuContribution } from '@theia/core/lib/common';
2020
import { SampleUpdater, SampleUpdaterPath, SampleUpdaterClient } from '../../common/updater/sample-updater';
2121
import { SampleUpdaterFrontendContribution, ElectronMenuUpdater, SampleUpdaterClientImpl } from './sample-updater-frontend-contribution';

examples/api-samples/src/electron-main/update/sample-updater-main-module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717
import { ContainerModule } from '@theia/core/shared/inversify';
1818
import { RpcConnectionHandler } from '@theia/core/lib/common/messaging/proxy-factory';
1919
import { ElectronMainApplicationContribution } from '@theia/core/lib/electron-main/electron-main-application';
20-
import { ElectronConnectionHandler } from '@theia/core/lib/electron-common/messaging/electron-connection-handler';
2120
import { SampleUpdaterPath, SampleUpdater, SampleUpdaterClient } from '../../common/updater/sample-updater';
2221
import { SampleUpdaterImpl } from './sample-updater-impl';
22+
import { ConnectionHandler } from '@theia/core';
2323

2424
export default new ContainerModule(bind => {
2525
bind(SampleUpdaterImpl).toSelf().inSingletonScope();
2626
bind(SampleUpdater).toService(SampleUpdaterImpl);
2727
bind(ElectronMainApplicationContribution).toService(SampleUpdater);
28-
bind(ElectronConnectionHandler).toDynamicValue(context =>
28+
bind(ConnectionHandler).toDynamicValue(context =>
2929
new RpcConnectionHandler<SampleUpdaterClient>(SampleUpdaterPath, client => {
3030
const server = context.container.get<SampleUpdater>(SampleUpdater);
3131
server.setClient(client);

examples/browser/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@
99
"applicationName": "Theia Browser Example",
1010
"preferences": {
1111
"files.enableTrash": false
12-
}
12+
},
13+
"reloadOnReconnect": true
1314
}
15+
},
16+
"backend": {
17+
"config": {
18+
"frontendConnectionTimeout": 3000
19+
}
20+
1421
}
1522
},
1623
"dependencies": {

examples/electron/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
"target": "electron",
1010
"frontend": {
1111
"config": {
12-
"applicationName": "Theia Electron Example"
12+
"applicationName": "Theia Electron Example",
13+
"reloadOnReconnect": true
14+
}
15+
},
16+
"backend": {
17+
"config": {
18+
"frontendConnectionTimeout": -1
1319
}
1420
}
1521
},

packages/core/src/browser/connection-status-service.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import { MockConnectionStatusService } from './test/mock-connection-status-servi
3333
import * as sinon from 'sinon';
3434

3535
import { Container } from 'inversify';
36-
import { WebSocketConnectionProvider } from './messaging/ws-connection-provider';
3736
import { ILogger, Emitter, Loggable } from '../common';
37+
import { WebSocketConnectionSource } from './messaging/ws-connection-source';
3838

3939
disableJSDOM();
4040

@@ -101,7 +101,7 @@ describe('frontend-connection-status', function (): void {
101101
let timer: sinon.SinonFakeTimers;
102102
let pingSpy: sinon.SinonSpy;
103103
beforeEach(() => {
104-
const mockWebSocketConnectionProvider = sinon.createStubInstance(WebSocketConnectionProvider);
104+
const mockWebSocketConnectionSource = sinon.createStubInstance(WebSocketConnectionSource);
105105
const mockPingService: PingService = <PingService>{
106106
ping(): Promise<void> {
107107
return Promise.resolve(undefined);
@@ -118,11 +118,11 @@ describe('frontend-connection-status', function (): void {
118118
testContainer.bind(PingService).toConstantValue(mockPingService);
119119
testContainer.bind(ILogger).toConstantValue(mockILogger);
120120
testContainer.bind(ConnectionStatusOptions).toConstantValue({ offlineTimeout: OFFLINE_TIMEOUT });
121-
testContainer.bind(WebSocketConnectionProvider).toConstantValue(mockWebSocketConnectionProvider);
121+
testContainer.bind(WebSocketConnectionSource).toConstantValue(mockWebSocketConnectionSource);
122122

123-
sinon.stub(mockWebSocketConnectionProvider, 'onSocketDidOpen').value(mockSocketOpenedEmitter.event);
124-
sinon.stub(mockWebSocketConnectionProvider, 'onSocketDidClose').value(mockSocketClosedEmitter.event);
125-
sinon.stub(mockWebSocketConnectionProvider, 'onIncomingMessageActivity').value(mockIncomingMessageActivityEmitter.event);
123+
sinon.stub(mockWebSocketConnectionSource, 'onSocketDidOpen').value(mockSocketOpenedEmitter.event);
124+
sinon.stub(mockWebSocketConnectionSource, 'onSocketDidClose').value(mockSocketClosedEmitter.event);
125+
sinon.stub(mockWebSocketConnectionSource, 'onIncomingMessageActivity').value(mockIncomingMessageActivityEmitter.event);
126126

127127
timer = sinon.useFakeTimers();
128128

packages/core/src/browser/connection-status-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import { ILogger } from '../common/logger';
1919
import { Event, Emitter } from '../common/event';
2020
import { DefaultFrontendApplicationContribution } from './frontend-application-contribution';
2121
import { StatusBar, StatusBarAlignment } from './status-bar/status-bar';
22-
import { WebSocketConnectionProvider } from './messaging/ws-connection-provider';
2322
import { Disposable, DisposableCollection, nls } from '../common';
23+
import { WebSocketConnectionSource } from './messaging/ws-connection-source';
2424

2525
/**
2626
* Service for listening on backend connection changes.
@@ -119,7 +119,7 @@ export class FrontendConnectionStatusService extends AbstractConnectionStatusSer
119119

120120
private scheduledPing: number | undefined;
121121

122-
@inject(WebSocketConnectionProvider) protected readonly wsConnectionProvider: WebSocketConnectionProvider;
122+
@inject(WebSocketConnectionSource) protected readonly wsConnectionProvider: WebSocketConnectionSource;
123123
@inject(PingService) protected readonly pingService: PingService;
124124

125125
@postConstruct()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2023 STMicroelectronics and others.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import { Channel, Event } from '../../common';
18+
19+
export const ConnectionSource = Symbol('ConnectionSource');
20+
21+
/**
22+
* A ConnectionSource creates a Channel. The channel is valid until it sends a close event.
23+
*/
24+
export interface ConnectionSource {
25+
onConnectionDidOpen: Event<Channel>;
26+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2023 STMicroelectronics and others.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import { injectable } from 'inversify';
18+
import { generateUuid } from '../../common/uuid';
19+
20+
export const FrontendIdProvider = Symbol('FrontendIdProvider');
21+
22+
/**
23+
* A FronendIdProvider computes an id for an instance of the front end that may be reconnected to a back end
24+
* connection context.
25+
*/
26+
export interface FrontendIdProvider {
27+
getId(): string;
28+
}
29+
30+
@injectable()
31+
export class BrowserFrontendIdProvider implements FrontendIdProvider {
32+
protected readonly id = generateUuid(); // generate a new id each time we load the application
33+
34+
getId(): string {
35+
return this.id;
36+
}
37+
}

packages/core/src/browser/messaging/messaging-frontend-module.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,27 @@
1515
// *****************************************************************************
1616

1717
import { ContainerModule } from 'inversify';
18-
import { LocalWebSocketConnectionProvider, WebSocketConnectionProvider } from './ws-connection-provider';
18+
import { BrowserFrontendIdProvider, FrontendIdProvider } from './frontend-id-provider';
19+
import { WebSocketConnectionSource } from './ws-connection-source';
20+
import { LocalConnectionProvider, RemoteConnectionProvider, ServiceConnectionProvider } from './service-connection-provider';
21+
import { ConnectionSource } from './connection-source';
22+
import { ConnectionCloseService, connectionCloseServicePath } from '../../common/messaging/connection-management';
23+
import { WebSocketConnectionProvider } from './ws-connection-provider';
24+
25+
const backendServiceProvider = Symbol('backendServiceProvider');
1926

2027
export const messagingFrontendModule = new ContainerModule(bind => {
28+
bind(ConnectionCloseService).toDynamicValue(ctx => WebSocketConnectionProvider.createProxy(ctx.container, connectionCloseServicePath)).inSingletonScope();
29+
bind(BrowserFrontendIdProvider).toSelf().inSingletonScope();
30+
bind(FrontendIdProvider).toService(BrowserFrontendIdProvider);
31+
bind(WebSocketConnectionSource).toSelf().inSingletonScope();
32+
bind(backendServiceProvider).toDynamicValue(ctx => {
33+
bind(ServiceConnectionProvider).toSelf().inSingletonScope();
34+
const container = ctx.container.createChild();
35+
container.bind(ConnectionSource).toService(WebSocketConnectionSource);
36+
return container.get(ServiceConnectionProvider);
37+
}).inSingletonScope();
38+
bind(LocalConnectionProvider).toService(backendServiceProvider);
39+
bind(RemoteConnectionProvider).toService(backendServiceProvider);
2140
bind(WebSocketConnectionProvider).toSelf().inSingletonScope();
22-
bind(LocalWebSocketConnectionProvider).toService(WebSocketConnectionProvider);
2341
});

0 commit comments

Comments
 (0)