diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index f445fa9f..aaa26792 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -32,13 +32,14 @@ export class MonitorConnection { @postConstruct() protected init(): void { - this.monitorServiceClient.onError(error => { + this.monitorServiceClient.onError(async error => { + let shouldReconnect = false; if (this.state) { const { code, connectionId, config } = error; if (this.state.connectionId === connectionId) { switch (code) { 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; } case MonitorError.ErrorCodes.DEVICE_BUSY: { @@ -51,8 +52,17 @@ export class MonitorConnection { this.messageService.info(`Disconnected from ${Port.toString(port)}.`); 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; + if (shouldReconnect) { + await this.connect(oldState.config); + } } else { console.warn(`Received an error from unexpected connection: ${MonitorConnection.State.toString({ connectionId, config })}.`); } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index ebc03c35..d0b882ad 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -1,40 +1,6 @@ import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; 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 MonitorService = Symbol('MonitorService'); export interface MonitorService extends JsonRpcServer { @@ -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; + } +} diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index a5cca9fb..42d46b9d 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -109,13 +109,14 @@ export class BoardsServiceImpl implements BoardsService { } dispose(): void { - this.logger.info('>>> Disposing boards service...') + this.logger.info('>>> Disposing boards service...'); this.queue.pause(); this.queue.clear(); if (this.discoveryTimer !== undefined) { clearInterval(this.discoveryTimer); } - this.logger.info('<<< Disposed boards service.') + this.logger.info('<<< Disposed boards service.'); + this.client = undefined; } async getAttachedBoards(): Promise<{ boards: Board[] }> { diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index 158c8467..a3388721 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -30,7 +30,6 @@ namespace ErrorWithCode { return { connectionId, message, - // message: `Cancelled on client. ${Board.toString(board)} from port ${Port.toString(port)}.`, code: MonitorError.ErrorCodes.CLIENT_CANCEL, config }; @@ -40,7 +39,6 @@ namespace ErrorWithCode { case 'device not configured': { return { connectionId, - // message: ``, message, code: MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED, config @@ -49,12 +47,19 @@ namespace ErrorWithCode { case 'error opening serial monitor: Serial port busy': { return { connectionId, - // message: `Connection failed. Serial port is busy: ${Port.toString(port)}.`, message, code: MonitorError.ErrorCodes.DEVICE_BUSY, config } } + case 'interrupted system call': { + return { + connectionId, + message, + code: MonitorError.ErrorCodes.interrupted_system_call, + config + } + } } } console.warn(`Unhandled error with code:`, error); @@ -81,9 +86,12 @@ export class MonitorServiceImpl implements MonitorService { } dispose(): void { + this.logger.info('>>> Disposing monitor service...'); for (const [connectionId, duplex] of this.connections.entries()) { this.doDisconnect(connectionId, duplex); } + this.logger.info('<<< Disposing monitor service...'); + this.client = undefined; } async connect(config: MonitorConfig): Promise<{ connectionId: string }> { @@ -96,12 +104,6 @@ export class MonitorServiceImpl implements MonitorService { ); 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)) { const monitorError = ErrorWithCode.toMonitorError(error, connectionId, config); if (monitorError) { @@ -109,9 +111,22 @@ export class MonitorServiceImpl implements MonitorService { this.client.notifyError(monitorError); } // 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; } } + 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); }).bind(this));