mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-04-19 12:57:17 +00:00
fix: retry compilation if grpc client needs to be reinitialized (#2548)
* fix: use `Status` enum for status code in `ServiceError` type guards This change resolves the issue where the intersection of `ServiceError` error codes of type `number` resulted in the `never` type due to conflict between number and `State` enum if `StatusObject` * feat: add `isInvalidArgument` type guard to `ServiceError` * fix: retry compilation if grpc client needs to be reinitialized See https://github.com/arduino/arduino-ide/issues/2547
This commit is contained in:
parent
41844c9470
commit
4cf9909a07
@ -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<void>((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
|
||||
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())
|
||||
);
|
||||
this.sendResponse(
|
||||
error.details + '\n\n' + message,
|
||||
OutputMessage.Severity.Error
|
||||
);
|
||||
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)) {
|
||||
|
@ -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<string, ProtoError>([
|
||||
@ -9,15 +11,27 @@ const protoErrorsMap = new Map<string, ProtoError>([
|
||||
'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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user