Reconnect on interrupted system call.

However, we have to figure out why does it happen at all.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2019-11-30 15:54:49 +01:00
parent 9efcbcf2ae
commit eb7b3ad683
4 changed files with 76 additions and 47 deletions

View File

@ -32,13 +32,14 @@ export class MonitorConnection {
@postConstruct() @postConstruct()
protected init(): void { protected init(): void {
this.monitorServiceClient.onError(error => { this.monitorServiceClient.onError(async error => {
let shouldReconnect = false;
if (this.state) { if (this.state) {
const { code, connectionId, config } = error; const { code, connectionId, config } = error;
if (this.state.connectionId === connectionId) { if (this.state.connectionId === connectionId) {
switch (code) { switch (code) {
case MonitorError.ErrorCodes.CLIENT_CANCEL: { case MonitorError.ErrorCodes.CLIENT_CANCEL: {
console.log(`Connection was canceled by client: ${MonitorConnection.State.toString(this.state)}.`); console.debug(`Connection was canceled by client: ${MonitorConnection.State.toString(this.state)}.`);
break; break;
} }
case MonitorError.ErrorCodes.DEVICE_BUSY: { case MonitorError.ErrorCodes.DEVICE_BUSY: {
@ -51,8 +52,17 @@ export class MonitorConnection {
this.messageService.info(`Disconnected from ${Port.toString(port)}.`); this.messageService.info(`Disconnected from ${Port.toString(port)}.`);
break; break;
} }
case MonitorError.ErrorCodes.interrupted_system_call: {
const { board, port } = config;
this.messageService.warn(`Unexpectedly interrupted by backend. Reconnecting ${Board.toString(board)} on port ${Port.toString(port)}.`);
shouldReconnect = true;
}
} }
const oldState = this.state;
this.state = undefined; this.state = undefined;
if (shouldReconnect) {
await this.connect(oldState.config);
}
} else { } else {
console.warn(`Received an error from unexpected connection: ${MonitorConnection.State.toString({ connectionId, config })}.`); console.warn(`Received an error from unexpected connection: ${MonitorConnection.State.toString({ connectionId, config })}.`);
} }

View File

@ -1,40 +1,6 @@
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { Board, Port } from './boards-service'; import { Board, Port } from './boards-service';
export interface MonitorError {
readonly connectionId: string;
readonly message: string;
readonly code: number;
readonly config: MonitorConfig;
}
export namespace MonitorError {
export namespace ErrorCodes {
/**
* The frontend has refreshed the browser, for instance.
*/
export const CLIENT_CANCEL = 1;
/**
* When detaching a physical device when the duplex channel is still opened.
*/
export const DEVICE_NOT_CONFIGURED = 2;
/**
* Another serial monitor was opened on this port. For another electron-instance, Java IDE.
*/
export const DEVICE_BUSY = 3;
}
}
export interface MonitorReadEvent {
readonly connectionId: string;
readonly data: string;
}
export const MonitorServiceClient = Symbol('MonitorServiceClient');
export interface MonitorServiceClient {
notifyRead(event: MonitorReadEvent): void;
notifyError(event: MonitorError): void;
}
export const MonitorServicePath = '/services/serial-monitor'; export const MonitorServicePath = '/services/serial-monitor';
export const MonitorService = Symbol('MonitorService'); export const MonitorService = Symbol('MonitorService');
export interface MonitorService extends JsonRpcServer<MonitorServiceClient> { export interface MonitorService extends JsonRpcServer<MonitorServiceClient> {
@ -69,3 +35,40 @@ export namespace MonitorConfig {
} }
export const MonitorServiceClient = Symbol('MonitorServiceClient');
export interface MonitorServiceClient {
notifyRead(event: MonitorReadEvent): void;
notifyError(event: MonitorError): void;
}
export interface MonitorReadEvent {
readonly connectionId: string;
readonly data: string;
}
export interface MonitorError {
readonly connectionId: string;
readonly message: string;
readonly code: number;
readonly config: MonitorConfig;
}
export namespace MonitorError {
export namespace ErrorCodes {
/**
* The frontend has refreshed the browser, for instance.
*/
export const CLIENT_CANCEL = 1;
/**
* When detaching a physical device when the duplex channel is still opened.
*/
export const DEVICE_NOT_CONFIGURED = 2;
/**
* Another serial monitor was opened on this port. For another electron-instance, Java IDE.
*/
export const DEVICE_BUSY = 3;
/**
* Another serial monitor was opened on this port. For another electron-instance, Java IDE.
*/
export const interrupted_system_call = 3;
}
}

View File

@ -109,13 +109,14 @@ export class BoardsServiceImpl implements BoardsService {
} }
dispose(): void { dispose(): void {
this.logger.info('>>> Disposing boards service...') this.logger.info('>>> Disposing boards service...');
this.queue.pause(); this.queue.pause();
this.queue.clear(); this.queue.clear();
if (this.discoveryTimer !== undefined) { if (this.discoveryTimer !== undefined) {
clearInterval(this.discoveryTimer); clearInterval(this.discoveryTimer);
} }
this.logger.info('<<< Disposed boards service.') this.logger.info('<<< Disposed boards service.');
this.client = undefined;
} }
async getAttachedBoards(): Promise<{ boards: Board[] }> { async getAttachedBoards(): Promise<{ boards: Board[] }> {

View File

@ -30,7 +30,6 @@ namespace ErrorWithCode {
return { return {
connectionId, connectionId,
message, message,
// message: `Cancelled on client. ${Board.toString(board)} from port ${Port.toString(port)}.`,
code: MonitorError.ErrorCodes.CLIENT_CANCEL, code: MonitorError.ErrorCodes.CLIENT_CANCEL,
config config
}; };
@ -40,7 +39,6 @@ namespace ErrorWithCode {
case 'device not configured': { case 'device not configured': {
return { return {
connectionId, connectionId,
// message: ``,
message, message,
code: MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED, code: MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED,
config config
@ -49,12 +47,19 @@ namespace ErrorWithCode {
case 'error opening serial monitor: Serial port busy': { case 'error opening serial monitor: Serial port busy': {
return { return {
connectionId, connectionId,
// message: `Connection failed. Serial port is busy: ${Port.toString(port)}.`,
message, message,
code: MonitorError.ErrorCodes.DEVICE_BUSY, code: MonitorError.ErrorCodes.DEVICE_BUSY,
config config
} }
} }
case 'interrupted system call': {
return {
connectionId,
message,
code: MonitorError.ErrorCodes.interrupted_system_call,
config
}
}
} }
} }
console.warn(`Unhandled error with code:`, error); console.warn(`Unhandled error with code:`, error);
@ -81,9 +86,12 @@ export class MonitorServiceImpl implements MonitorService {
} }
dispose(): void { dispose(): void {
this.logger.info('>>> Disposing monitor service...');
for (const [connectionId, duplex] of this.connections.entries()) { for (const [connectionId, duplex] of this.connections.entries()) {
this.doDisconnect(connectionId, duplex); this.doDisconnect(connectionId, duplex);
} }
this.logger.info('<<< Disposing monitor service...');
this.client = undefined;
} }
async connect(config: MonitorConfig): Promise<{ connectionId: string }> { async connect(config: MonitorConfig): Promise<{ connectionId: string }> {
@ -96,12 +104,6 @@ export class MonitorServiceImpl implements MonitorService {
); );
duplex.on('error', ((error: Error) => { duplex.on('error', ((error: Error) => {
// Dispose the connection on error.
// If the client has disconnected, we call `disconnect` and hit this error
// no need to disconnect once more.
if (!toDispose.disposed) {
toDispose.dispose();
}
if (ErrorWithCode.is(error)) { if (ErrorWithCode.is(error)) {
const monitorError = ErrorWithCode.toMonitorError(error, connectionId, config); const monitorError = ErrorWithCode.toMonitorError(error, connectionId, config);
if (monitorError) { if (monitorError) {
@ -109,9 +111,22 @@ export class MonitorServiceImpl implements MonitorService {
this.client.notifyError(monitorError); this.client.notifyError(monitorError);
} }
// Do not log the error, it was expected. The client will take care of the rest. // Do not log the error, it was expected. The client will take care of the rest.
if (monitorError.code === MonitorError.ErrorCodes.interrupted_system_call) {
console.log('jajjajaja');
if (!toDispose.disposed) {
toDispose.dispose();
}
}
return; return;
} }
} }
if (error.message === 'interrupted system call') {
this.logger.info('TODO: reduce to debug, INTERRUPTED SYSTEM CALL');
return; // Continue.
}
if (!toDispose.disposed) {
toDispose.dispose();
}
this.logger.error(`Error occurred for connection ${connectionId}.`, error); this.logger.error(`Error occurred for connection ${connectionId}.`, error);
}).bind(this)); }).bind(this));