diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts index b8eba0335..285c05f72 100644 --- a/arduino-ide-extension/src/node/core-service-impl.ts +++ b/arduino-ide-extension/src/node/core-service-impl.ts @@ -36,6 +36,7 @@ import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb'; import { CompileRequest, CompileResponse, + InstanceNeedsReinitializationError, } from './cli-protocol/cc/arduino/cli/commands/v1/compile_pb'; import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import { @@ -89,48 +90,84 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { compileSummaryHandler ); const toDisposeOnFinally = new DisposableCollection(handler); + return new Promise((resolve, reject) => { - const call = client.compile(request); - if (cancellationToken) { - toDisposeOnFinally.push( - cancellationToken.onCancellationRequested(() => call.cancel()) + let hasRetried = false; + + const handleUnexpectedError = (error: Error) => { + console.error( + 'Unexpected error occurred while compiling the sketch.', + error ); - } - call - .on('data', handler.onData) - .on('error', (error) => { - if (!ServiceError.is(error)) { - console.error( - 'Unexpected error occurred while compiling the sketch.', - error - ); - reject(error); - return; - } - if (ServiceError.isCancel(error)) { - console.log(userAbort); - reject(UserAbortApplicationError()); - return; - } - const compilerErrors = tryParseError({ - content: handler.content, - sketch: options.sketch, - }); - const message = nls.localize( - 'arduino/compile/error', - 'Compilation error: {0}', - compilerErrors - .map(({ message }) => message) - .filter(notEmpty) - .shift() ?? error.details - ); - this.sendResponse( - error.details + '\n\n' + message, - OutputMessage.Severity.Error + reject(error); + }; + + const handleCancellationError = () => { + console.log(userAbort); + reject(UserAbortApplicationError()); + }; + + const handleInstanceNeedsReinitializationError = async ( + error: ServiceError & InstanceNeedsReinitializationError + ) => { + if (hasRetried) { + // If error persists, send the error message to the output + return parseAndSendErrorResponse(error); + } + + hasRetried = true; + await this.refresh(); + return startCompileStream(); + }; + + const parseAndSendErrorResponse = (error: ServiceError) => { + const compilerErrors = tryParseError({ + content: handler.content, + sketch: options.sketch, + }); + const message = nls.localize( + 'arduino/compile/error', + 'Compilation error: {0}', + compilerErrors + .map(({ message }) => message) + .filter(notEmpty) + .shift() ?? error.details + ); + this.sendResponse( + error.details + '\n\n' + message, + OutputMessage.Severity.Error + ); + reject(CoreError.VerifyFailed(message, compilerErrors)); + }; + + const handleError = async (error: Error) => { + if (!ServiceError.is(error)) return handleUnexpectedError(error); + if (ServiceError.isCancel(error)) return handleCancellationError(); + + if ( + ServiceError.isInstanceOf(error, InstanceNeedsReinitializationError) + ) { + return await handleInstanceNeedsReinitializationError(error); + } + + parseAndSendErrorResponse(error); + }; + + const startCompileStream = () => { + const call = client.compile(request); + if (cancellationToken) { + toDisposeOnFinally.push( + cancellationToken.onCancellationRequested(() => call.cancel()) ); - reject(CoreError.VerifyFailed(message, compilerErrors)); - }) - .on('end', resolve); + } + + call + .on('data', handler.onData) + .on('error', handleError) + .on('end', resolve); + }; + + startCompileStream(); }).finally(() => { toDisposeOnFinally.dispose(); if (!isCompileSummary(compileSummary)) { diff --git a/arduino-ide-extension/src/node/service-error.ts b/arduino-ide-extension/src/node/service-error.ts index 0b6021ff0..681896a03 100644 --- a/arduino-ide-extension/src/node/service-error.ts +++ b/arduino-ide-extension/src/node/service-error.ts @@ -1,7 +1,9 @@ import { Metadata, StatusObject } from '@grpc/grpc-js'; import { Status } from './cli-protocol/google/rpc/status_pb'; import { stringToUint8Array } from '../common/utils'; +import { Status as StatusCode } from '@grpc/grpc-js/build/src/constants'; import { ProgrammerIsRequiredForUploadError } from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb'; +import { InstanceNeedsReinitializationError } from './cli-protocol/cc/arduino/cli/commands/v1/compile_pb'; type ProtoError = typeof ProgrammerIsRequiredForUploadError; const protoErrorsMap = new Map([ @@ -9,15 +11,27 @@ const protoErrorsMap = new Map([ 'cc.arduino.cli.commands.v1.ProgrammerIsRequiredForUploadError', ProgrammerIsRequiredForUploadError, ], + [ + 'cc.arduino.cli.commands.v1.InstanceNeedsReinitializationError', + InstanceNeedsReinitializationError, + ], // handle other cli defined errors here ]); export type ServiceError = StatusObject & Error; export namespace ServiceError { - export function isCancel(arg: unknown): arg is ServiceError & { code: 1 } { + export function isCancel( + arg: unknown + ): arg is ServiceError & { code: StatusCode.CANCELLED } { return is(arg) && arg.code === 1; // https://grpc.github.io/grpc/core/md_doc_statuscodes.html } + export function isInvalidArgument( + arg: unknown + ): arg is ServiceError & { code: StatusCode.INVALID_ARGUMENT } { + return is(arg) && arg.code === 3; // https://grpc.github.io/grpc/core/md_doc_statuscodes.html + } + export function is(arg: unknown): arg is ServiceError { return arg instanceof Error && isStatusObject(arg); }