1
1
import { ClientDuplexStream } from '@grpc/grpc-js' ;
2
- import { Disposable , Emitter , ILogger } from '@theia/core' ;
2
+ import {
3
+ ApplicationError ,
4
+ Disposable ,
5
+ Emitter ,
6
+ ILogger ,
7
+ nls ,
8
+ } from '@theia/core' ;
3
9
import { inject , named , postConstruct } from '@theia/core/shared/inversify' ;
4
10
import { diff , Operation } from 'just-diff' ;
5
- import { Board , Port , Status , Monitor } from '../common/protocol' ;
11
+ import {
12
+ Board ,
13
+ Port ,
14
+ Monitor ,
15
+ createAlreadyConnectedError ,
16
+ createMissingConfigurationError ,
17
+ createNotConnectedError ,
18
+ createConnectionFailedError ,
19
+ } from '../common/protocol' ;
6
20
import {
7
21
EnumerateMonitorPortSettingsRequest ,
8
22
EnumerateMonitorPortSettingsResponse ,
@@ -19,8 +33,13 @@ import {
19
33
PluggableMonitorSettings ,
20
34
MonitorSettingsProvider ,
21
35
} from './monitor-settings/monitor-settings-provider' ;
22
- import { Deferred } from '@theia/core/lib/common/promise-util' ;
36
+ import {
37
+ Deferred ,
38
+ retry ,
39
+ timeoutReject ,
40
+ } from '@theia/core/lib/common/promise-util' ;
23
41
import { MonitorServiceFactoryOptions } from './monitor-service-factory' ;
42
+ import { ServiceError } from './service-error' ;
24
43
25
44
export const MonitorServiceName = 'monitor-service' ;
26
45
type DuplexHandlerKeys =
@@ -76,7 +95,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
76
95
readonly onDispose = this . onDisposeEmitter . event ;
77
96
78
97
private _initialized = new Deferred < void > ( ) ;
79
- private creating : Deferred < Status > ;
98
+ private creating : Deferred < void > ;
80
99
private readonly board : Board ;
81
100
private readonly port : Port ;
82
101
private readonly monitorID : string ;
@@ -154,25 +173,24 @@ export class MonitorService extends CoreClientAware implements Disposable {
154
173
155
174
/**
156
175
* Start and connects a monitor using currently set board and port.
157
- * If a monitor is already started or board fqbn, port address and/or protocol
158
- * are missing nothing happens.
159
- * @returns a status to verify connection has been established.
176
+ * If a monitor is already started, the promise will reject with an `AlreadyConnectedError`.
177
+ * If the board fqbn, port address and/or protocol are missing, the promise rejects with a `MissingConfigurationError`.
160
178
*/
161
- async start ( ) : Promise < Status > {
179
+ async start ( ) : Promise < void > {
162
180
if ( this . creating ?. state === 'unresolved' ) return this . creating . promise ;
163
181
this . creating = new Deferred ( ) ;
164
182
if ( this . duplex ) {
165
183
this . updateClientsSettings ( {
166
184
monitorUISettings : { connected : true , serialPort : this . port . address } ,
167
185
} ) ;
168
- this . creating . resolve ( Status . ALREADY_CONNECTED ) ;
186
+ this . creating . reject ( createAlreadyConnectedError ( this . port ) ) ;
169
187
return this . creating . promise ;
170
188
}
171
189
172
190
if ( ! this . board ?. fqbn || ! this . port ?. address || ! this . port ?. protocol ) {
173
191
this . updateClientsSettings ( { monitorUISettings : { connected : false } } ) ;
174
192
175
- this . creating . resolve ( Status . CONFIG_MISSING ) ;
193
+ this . creating . reject ( createMissingConfigurationError ( this . port ) ) ;
176
194
return this . creating . promise ;
177
195
}
178
196
@@ -218,10 +236,8 @@ export class MonitorService extends CoreClientAware implements Disposable {
218
236
}
219
237
monitorRequest . setPortConfiguration ( config ) ;
220
238
221
- const wroteToStreamSuccessfully = await this . pollWriteToStream (
222
- monitorRequest
223
- ) ;
224
- if ( wroteToStreamSuccessfully ) {
239
+ try {
240
+ await this . pollWriteToStream ( monitorRequest ) ;
225
241
// Only store the config, if the monitor has successfully started.
226
242
this . currentPortConfigSnapshot = MonitorPortConfiguration . toObject (
227
243
false ,
@@ -239,13 +255,20 @@ export class MonitorService extends CoreClientAware implements Disposable {
239
255
this . updateClientsSettings ( {
240
256
monitorUISettings : { connected : true , serialPort : this . port . address } ,
241
257
} ) ;
242
- this . creating . resolve ( Status . OK ) ;
258
+ this . creating . resolve ( ) ;
243
259
return this . creating . promise ;
244
- } else {
260
+ } catch ( err ) {
245
261
this . logger . warn (
246
262
`failed starting monitor to ${ this . port ?. address } using ${ this . port ?. protocol } `
247
263
) ;
248
- this . creating . resolve ( Status . NOT_CONNECTED ) ;
264
+ this . creating . reject (
265
+ ApplicationError . is ( err )
266
+ ? err
267
+ : createConnectionFailedError (
268
+ this . port ,
269
+ err instanceof Error ? err . message : String ( err )
270
+ )
271
+ ) ;
249
272
return this . creating . promise ;
250
273
}
251
274
}
@@ -287,21 +310,17 @@ export class MonitorService extends CoreClientAware implements Disposable {
287
310
}
288
311
}
289
312
290
- pollWriteToStream ( request : MonitorRequest ) : Promise < boolean > {
291
- let attemptsRemaining = MAX_WRITE_TO_STREAM_TRIES ;
292
- const writeTimeoutMs = WRITE_TO_STREAM_TIMEOUT_MS ;
293
-
313
+ pollWriteToStream ( request : MonitorRequest ) : Promise < void > {
294
314
const createWriteToStreamExecutor =
295
315
( duplex : ClientDuplexStream < MonitorRequest , MonitorResponse > ) =>
296
- ( resolve : ( value : boolean ) => void , reject : ( ) => void ) => {
316
+ ( resolve : ( ) => void , reject : ( reason ?: unknown ) => void ) => {
297
317
const resolvingDuplexHandlers : DuplexHandler [ ] = [
298
318
{
299
319
key : 'error' ,
300
320
callback : async ( err : Error ) => {
301
321
this . logger . error ( err ) ;
302
- resolve ( false ) ;
303
- // TODO
304
- // this.theiaFEClient?.notifyError()
322
+ const details = ServiceError . is ( err ) ? err . details : err . message ;
323
+ reject ( createConnectionFailedError ( this . port , details ) ) ;
305
324
} ,
306
325
} ,
307
326
{
@@ -313,79 +332,47 @@ export class MonitorService extends CoreClientAware implements Disposable {
313
332
return ;
314
333
}
315
334
if ( monitorResponse . getSuccess ( ) ) {
316
- resolve ( true ) ;
335
+ resolve ( ) ;
317
336
return ;
318
337
}
319
338
const data = monitorResponse . getRxData ( ) ;
320
339
const message =
321
340
typeof data === 'string'
322
341
? data
323
- : this . streamingTextDecoder . decode ( data , { stream :true } ) ;
342
+ : this . streamingTextDecoder . decode ( data , { stream : true } ) ;
324
343
this . messages . push ( ...splitLines ( message ) ) ;
325
344
} ,
326
345
} ,
327
346
] ;
328
347
329
348
this . setDuplexHandlers ( duplex , resolvingDuplexHandlers ) ;
330
-
331
- setTimeout ( ( ) => {
332
- reject ( ) ;
333
- } , writeTimeoutMs ) ;
334
349
duplex . write ( request ) ;
335
350
} ;
336
351
337
- const pollWriteToStream = new Promise < boolean > ( ( resolve ) => {
338
- const startPolling = async ( ) => {
339
- // here we create a new duplex but we don't yet
340
- // set "this.duplex", nor do we use "this.duplex" in our poll
341
- // as duplex 'end' / 'close' events (which we do not "await")
342
- // will set "this.duplex" to null
343
- const createdDuplex = await this . createDuplex ( ) ;
344
-
345
- let pollingIsSuccessful ;
346
- // attempt a "writeToStream" and "await" CLI response: success (true) or error (false)
347
- // if we get neither within WRITE_TO_STREAM_TIMEOUT_MS or an error we get undefined
348
- try {
349
- const writeToStream = createWriteToStreamExecutor ( createdDuplex ) ;
350
- pollingIsSuccessful = await new Promise ( writeToStream ) ;
351
- } catch ( error ) {
352
- this . logger . error ( error ) ;
353
- }
354
-
355
- // CLI confirmed port opened successfully
356
- if ( pollingIsSuccessful ) {
357
- this . duplex = createdDuplex ;
358
- resolve ( true ) ;
359
- return ;
360
- }
361
-
362
- // if "pollingIsSuccessful" is false
363
- // the CLI gave us an error, lets try again
364
- // after waiting 2 seconds if we've not already
365
- // reached MAX_WRITE_TO_STREAM_TRIES
366
- if ( pollingIsSuccessful === false ) {
367
- attemptsRemaining -= 1 ;
368
- if ( attemptsRemaining > 0 ) {
369
- setTimeout ( startPolling , 2000 ) ;
370
- return ;
371
- } else {
372
- resolve ( false ) ;
373
- return ;
352
+ return Promise . race ( [
353
+ retry (
354
+ async ( ) => {
355
+ let createdDuplex = undefined ;
356
+ try {
357
+ createdDuplex = await this . createDuplex ( ) ;
358
+ await new Promise < void > ( createWriteToStreamExecutor ( createdDuplex ) ) ;
359
+ this . duplex = createdDuplex ;
360
+ } catch ( err ) {
361
+ createdDuplex ?. end ( ) ;
362
+ throw err ;
374
363
}
375
- }
376
-
377
- // "pollingIsSuccessful" remains undefined:
378
- // we got no response from the CLI within 30 seconds
379
- // resolve to false and end the duplex connection
380
- resolve ( false ) ;
381
- createdDuplex . end ( ) ;
382
- return ;
383
- } ;
384
-
385
- startPolling ( ) ;
386
- } ) ;
387
-
388
- return pollWriteToStream ;
364
+ } ,
365
+ 2_000 ,
366
+ MAX_WRITE_TO_STREAM_TRIES
367
+ ) ,
368
+ timeoutReject (
369
+ WRITE_TO_STREAM_TIMEOUT_MS ,
370
+ nls . localize (
371
+ 'arduino/monitor/connectionTimeout' ,
372
+ "Timeout. The IDE has not received the 'success' message from the monitor after successfully connecting to it."
373
+ )
374
+ ) ,
375
+ ] ) as Promise < unknown > as Promise < void > ;
389
376
}
390
377
391
378
/**
@@ -429,24 +416,22 @@ export class MonitorService extends CoreClientAware implements Disposable {
429
416
* @param message string sent to running monitor
430
417
* @returns a status to verify message has been sent.
431
418
*/
432
- async send ( message : string ) : Promise < Status > {
419
+ async send ( message : string ) : Promise < void > {
433
420
if ( ! this . duplex ) {
434
- return Status . NOT_CONNECTED ;
421
+ throw createNotConnectedError ( this . port ) ;
435
422
}
436
423
const coreClient = await this . coreClient ;
437
424
const { instance } = coreClient ;
438
425
439
426
const req = new MonitorRequest ( ) ;
440
427
req . setInstance ( instance ) ;
441
428
req . setTxData ( new TextEncoder ( ) . encode ( message ) ) ;
442
- return new Promise < Status > ( ( resolve ) => {
429
+ return new Promise < void > ( ( resolve , reject ) => {
443
430
if ( this . duplex ) {
444
- this . duplex ?. write ( req , ( ) => {
445
- resolve ( Status . OK ) ;
446
- } ) ;
431
+ this . duplex ?. write ( req , resolve ) ;
447
432
return ;
448
433
}
449
- this . stop ( ) . then ( ( ) => resolve ( Status . NOT_CONNECTED ) ) ;
434
+ this . stop ( ) . then ( ( ) => reject ( createNotConnectedError ( this . port ) ) ) ;
450
435
} ) ;
451
436
}
452
437
@@ -510,7 +495,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
510
495
* @param settings map of monitor settings to change
511
496
* @returns a status to verify settings have been sent.
512
497
*/
513
- async changeSettings ( settings : MonitorSettings ) : Promise < Status > {
498
+ async changeSettings ( settings : MonitorSettings ) : Promise < void > {
514
499
const config = new MonitorPortConfiguration ( ) ;
515
500
const { pluggableMonitorSettings } = settings ;
516
501
const reconciledSettings = await this . monitorSettingsProvider . setSettings (
@@ -537,15 +522,17 @@ export class MonitorService extends CoreClientAware implements Disposable {
537
522
} ) ;
538
523
539
524
if ( ! this . duplex ) {
540
- return Status . NOT_CONNECTED ;
525
+ // instead of throwing an error, return silently like the original logic
526
+ // https://github.com/arduino/arduino-ide/blob/9b49712669b06c97bda68a1e5f04eee4664c13f8/arduino-ide-extension/src/node/monitor-service.ts#L540
527
+ return ;
541
528
}
542
529
543
530
const diffConfig = this . maybeUpdatePortConfigSnapshot ( config ) ;
544
531
if ( ! diffConfig ) {
545
532
this . logger . info (
546
533
`No port configuration changes have been detected. No need to send configure commands to the running monitor ${ this . port . protocol } :${ this . port . address } .`
547
534
) ;
548
- return Status . OK ;
535
+ return ;
549
536
}
550
537
551
538
const coreClient = await this . coreClient ;
@@ -560,7 +547,6 @@ export class MonitorService extends CoreClientAware implements Disposable {
560
547
req . setInstance ( instance ) ;
561
548
req . setPortConfiguration ( diffConfig ) ;
562
549
this . duplex . write ( req ) ;
563
- return Status . OK ;
564
550
}
565
551
566
552
/**
0 commit comments