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 ;
@@ -156,23 +175,22 @@ export class MonitorService extends CoreClientAware implements Disposable {
156
175
* Start and connects a monitor using currently set board and port.
157
176
* If a monitor is already started or board fqbn, port address and/or protocol
158
177
* are missing nothing happens.
159
- * @returns a status to verify connection has been established.
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,20 @@ 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
+ (
317
+ resolve : ( value ?: unknown ) => void ,
318
+ reject : ( reason ?: unknown ) => void
319
+ ) => {
297
320
const resolvingDuplexHandlers : DuplexHandler [ ] = [
298
321
{
299
322
key : 'error' ,
300
323
callback : async ( err : Error ) => {
301
324
this . logger . error ( err ) ;
302
- resolve ( false ) ;
303
- // TODO
304
- // this.theiaFEClient?.notifyError()
325
+ const details = ServiceError . is ( err ) ? err . details : err . message ;
326
+ reject ( createConnectionFailedError ( this . port , details ) ) ;
305
327
} ,
306
328
} ,
307
329
{
@@ -313,79 +335,47 @@ export class MonitorService extends CoreClientAware implements Disposable {
313
335
return ;
314
336
}
315
337
if ( monitorResponse . getSuccess ( ) ) {
316
- resolve ( true ) ;
338
+ resolve ( ) ;
317
339
return ;
318
340
}
319
341
const data = monitorResponse . getRxData ( ) ;
320
342
const message =
321
343
typeof data === 'string'
322
344
? data
323
- : this . streamingTextDecoder . decode ( data , { stream :true } ) ;
345
+ : this . streamingTextDecoder . decode ( data , { stream : true } ) ;
324
346
this . messages . push ( ...splitLines ( message ) ) ;
325
347
} ,
326
348
} ,
327
349
] ;
328
350
329
351
this . setDuplexHandlers ( duplex , resolvingDuplexHandlers ) ;
330
-
331
- setTimeout ( ( ) => {
332
- reject ( ) ;
333
- } , writeTimeoutMs ) ;
334
352
duplex . write ( request ) ;
335
353
} ;
336
354
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 ;
355
+ return Promise . race ( [
356
+ retry (
357
+ async ( ) => {
358
+ let createdDuplex = undefined ;
359
+ try {
360
+ createdDuplex = await this . createDuplex ( ) ;
361
+ await new Promise ( createWriteToStreamExecutor ( createdDuplex ) ) ;
362
+ this . duplex = createdDuplex ;
363
+ } catch ( err ) {
364
+ createdDuplex ?. end ( ) ;
365
+ throw err ;
374
366
}
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 ;
367
+ } ,
368
+ 2_000 ,
369
+ MAX_WRITE_TO_STREAM_TRIES
370
+ ) ,
371
+ timeoutReject (
372
+ WRITE_TO_STREAM_TIMEOUT_MS ,
373
+ nls . localize (
374
+ 'arduino/monitor/connectionTimeout' ,
375
+ "Timeout. The IDE has not received the 'success' message from the monitor after successfully connecting to it."
376
+ )
377
+ ) ,
378
+ ] ) as Promise < unknown > as Promise < void > ;
389
379
}
390
380
391
381
/**
@@ -429,24 +419,22 @@ export class MonitorService extends CoreClientAware implements Disposable {
429
419
* @param message string sent to running monitor
430
420
* @returns a status to verify message has been sent.
431
421
*/
432
- async send ( message : string ) : Promise < Status > {
422
+ async send ( message : string ) : Promise < void > {
433
423
if ( ! this . duplex ) {
434
- return Status . NOT_CONNECTED ;
424
+ throw createNotConnectedError ( this . port ) ;
435
425
}
436
426
const coreClient = await this . coreClient ;
437
427
const { instance } = coreClient ;
438
428
439
429
const req = new MonitorRequest ( ) ;
440
430
req . setInstance ( instance ) ;
441
431
req . setTxData ( new TextEncoder ( ) . encode ( message ) ) ;
442
- return new Promise < Status > ( ( resolve ) => {
432
+ return new Promise < void > ( ( resolve , reject ) => {
443
433
if ( this . duplex ) {
444
- this . duplex ?. write ( req , ( ) => {
445
- resolve ( Status . OK ) ;
446
- } ) ;
434
+ this . duplex ?. write ( req , resolve ) ;
447
435
return ;
448
436
}
449
- this . stop ( ) . then ( ( ) => resolve ( Status . NOT_CONNECTED ) ) ;
437
+ this . stop ( ) . then ( ( ) => reject ( createNotConnectedError ( this . port ) ) ) ;
450
438
} ) ;
451
439
}
452
440
@@ -510,7 +498,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
510
498
* @param settings map of monitor settings to change
511
499
* @returns a status to verify settings have been sent.
512
500
*/
513
- async changeSettings ( settings : MonitorSettings ) : Promise < Status > {
501
+ async changeSettings ( settings : MonitorSettings ) : Promise < void > {
514
502
const config = new MonitorPortConfiguration ( ) ;
515
503
const { pluggableMonitorSettings } = settings ;
516
504
const reconciledSettings = await this . monitorSettingsProvider . setSettings (
@@ -537,15 +525,15 @@ export class MonitorService extends CoreClientAware implements Disposable {
537
525
} ) ;
538
526
539
527
if ( ! this . duplex ) {
540
- return Status . NOT_CONNECTED ;
528
+ throw createNotConnectedError ( this . port ) ;
541
529
}
542
530
543
531
const diffConfig = this . maybeUpdatePortConfigSnapshot ( config ) ;
544
532
if ( ! diffConfig ) {
545
533
this . logger . info (
546
534
`No port configuration changes have been detected. No need to send configure commands to the running monitor ${ this . port . protocol } :${ this . port . address } .`
547
535
) ;
548
- return Status . OK ;
536
+ return ;
549
537
}
550
538
551
539
const coreClient = await this . coreClient ;
@@ -560,7 +548,6 @@ export class MonitorService extends CoreClientAware implements Disposable {
560
548
req . setInstance ( instance ) ;
561
549
req . setPortConfiguration ( diffConfig ) ;
562
550
this . duplex . write ( req ) ;
563
- return Status . OK ;
564
551
}
565
552
566
553
/**
0 commit comments