Skip to content

Commit 7c2843f

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
Relaxed the error handling of the core client init
For example, `malformed custom board options` was incorrectly detected as loading JSON index file error. Closes arduino#1036 Signed-off-by: Akos Kitta <[email protected]>
1 parent fd5154a commit 7c2843f

File tree

1 file changed

+83
-57
lines changed

1 file changed

+83
-57
lines changed

arduino-ide-extension/src/node/core-client-provider.ts

+83-57
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ import {
2121
import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
2222
import { NotificationServiceServer } from '../common/protocol';
2323
import { Deferred, retry } from '@theia/core/lib/common/promise-util';
24-
import { Status as RpcStatus } from './cli-protocol/google/rpc/status_pb';
24+
import {
25+
Status as RpcStatus,
26+
Status,
27+
} from './cli-protocol/google/rpc/status_pb';
2528

2629
@injectable()
2730
export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Client> {
@@ -90,10 +93,11 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
9093
this._initialized.resolve();
9194
this.updateIndex(this._client); // Update the indexes asynchronously
9295
} catch (error: unknown) {
93-
if (
94-
this.isPackageIndexMissingError(error) ||
95-
this.isDiscoveryNotFoundError(error)
96-
) {
96+
console.error(
97+
'Error occurred while initializing the core gRPC client provider',
98+
error
99+
);
100+
if (error instanceof IndexUpdateRequiredBeforeInitError) {
97101
// If it's a first start, IDE2 must run index update before the init request.
98102
await this.updateIndexes(this._client);
99103
await this.initInstance(this._client);
@@ -114,41 +118,6 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
114118
});
115119
}
116120

117-
private isPackageIndexMissingError(error: unknown): boolean {
118-
const assert = (message: string) =>
119-
message.includes('loading json index file');
120-
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
121-
return this.isRpcStatusError(error, assert);
122-
}
123-
124-
private isDiscoveryNotFoundError(error: unknown): boolean {
125-
const assert = (message: string) =>
126-
message.includes('discovery') &&
127-
(message.includes('not found') || message.includes('not installed'));
128-
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740
129-
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744
130-
return this.isRpcStatusError(error, assert);
131-
}
132-
133-
private isCancelError(error: unknown): boolean {
134-
return (
135-
error instanceof Error &&
136-
error.message.toLocaleLowerCase().includes('cancelled on client')
137-
);
138-
}
139-
140-
// Final error codes are not yet defined by the CLI. Hence, we do string matching in the message RPC status.
141-
private isRpcStatusError(
142-
error: unknown,
143-
assert: (message: string) => boolean
144-
) {
145-
if (error instanceof RpcStatus) {
146-
const { message } = RpcStatus.toObject(false, error);
147-
return assert(message.toLocaleLowerCase());
148-
}
149-
return false;
150-
}
151-
152121
protected async createClient(
153122
port: string | number
154123
): Promise<CoreClientProvider.Client> {
@@ -192,7 +161,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
192161
initReq.setInstance(instance);
193162
return new Promise<void>((resolve, reject) => {
194163
const stream = client.init(initReq);
195-
const errorStatus: RpcStatus[] = [];
164+
const errors: RpcStatus[] = [];
196165
stream.on('data', (res: InitResponse) => {
197166
const progress = res.getInitProgress();
198167
if (progress) {
@@ -210,28 +179,30 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
210179

211180
const error = res.getError();
212181
if (error) {
213-
console.error(error.getMessage());
214-
errorStatus.push(error);
215-
// Cancel the init request. No need to wait until the end of the event. The init has already failed.
216-
// Canceling the request will result in a cancel error, but we need to reject with the original error later.
217-
stream.cancel();
182+
const { code, message } = Status.toObject(false, error);
183+
console.error(
184+
`Detected an error response during the gRPC core client initialization: code: ${code}, message: ${message}`
185+
);
186+
errors.push(error);
218187
}
219188
});
220-
stream.on('error', (error) => {
221-
// On any error during the init request, the request is canceled.
222-
// On cancel, the IDE2 ignores the cancel error and rejects with the original one.
223-
reject(
224-
this.isCancelError(error) && errorStatus.length
225-
? errorStatus[0]
226-
: error
227-
);
189+
stream.on('error', reject);
190+
stream.on('end', () => {
191+
const error = this.evaluateErrorStatus(errors);
192+
if (error) {
193+
reject(error);
194+
return;
195+
}
196+
resolve();
228197
});
229-
stream.on('end', () =>
230-
errorStatus.length ? reject(errorStatus) : resolve()
231-
);
232198
});
233199
}
234200

201+
private evaluateErrorStatus(status: RpcStatus[]): Error | undefined {
202+
const error = isIndexUpdateRequiredBeforeInit(status); // put future error matching here
203+
return error;
204+
}
205+
235206
protected async updateIndexes(
236207
client: CoreClientProvider.Client
237208
): Promise<CoreClientProvider.Client> {
@@ -338,3 +309,58 @@ export abstract class CoreClientAware {
338309
);
339310
}
340311
}
312+
313+
class IndexUpdateRequiredBeforeInitError extends Error {
314+
constructor(causes: RpcStatus.AsObject[]) {
315+
super(`The index of the cores and libraries must be updated before initializing the core gRPC client.
316+
The following problems were detected during the gRPC client initialization:
317+
${causes
318+
.map(({ code, message }) => ` - code: ${code}, message: ${message}`)
319+
.join('\n')}
320+
`);
321+
Object.setPrototypeOf(this, IndexUpdateRequiredBeforeInitError.prototype);
322+
if (!causes.length) {
323+
throw new Error(`expected non-empty 'causes'`);
324+
}
325+
}
326+
}
327+
328+
function isIndexUpdateRequiredBeforeInit(
329+
status: RpcStatus[]
330+
): IndexUpdateRequiredBeforeInitError | undefined {
331+
const causes = status
332+
.filter((s) =>
333+
IndexUpdateRequiredBeforeInit.map((predicate) => predicate(s)).some(
334+
Boolean
335+
)
336+
)
337+
.map((s) => RpcStatus.toObject(false, s));
338+
return causes.length
339+
? new IndexUpdateRequiredBeforeInitError(causes)
340+
: undefined;
341+
}
342+
const IndexUpdateRequiredBeforeInit = [
343+
isPackageIndexMissingStatus,
344+
isDiscoveryNotFoundStatus,
345+
];
346+
function isPackageIndexMissingStatus(status: RpcStatus): boolean {
347+
const predicate = ({ message }: RpcStatus.AsObject) =>
348+
message.includes('loading json index file');
349+
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
350+
return evaluate(status, predicate);
351+
}
352+
function isDiscoveryNotFoundStatus(status: RpcStatus): boolean {
353+
const predicate = ({ message }: RpcStatus.AsObject) =>
354+
message.includes('discovery') &&
355+
(message.includes('not found') || message.includes('not installed'));
356+
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740
357+
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744
358+
return evaluate(status, predicate);
359+
}
360+
function evaluate(
361+
subject: RpcStatus,
362+
predicate: (error: RpcStatus.AsObject) => boolean
363+
): boolean {
364+
const status = RpcStatus.toObject(false, subject);
365+
return predicate(status);
366+
}

0 commit comments

Comments
 (0)