@@ -14,13 +14,14 @@ import {
1414 AvailablePorts ,
1515 AttachedBoardsChangeEvent ,
1616} from '../common/protocol' ;
17- import { Emitter } from '@theia/core/lib/common/event' ;
17+ import { Emitter , Event } from '@theia/core/lib/common/event' ;
1818import { DisposableCollection } from '@theia/core/lib/common/disposable' ;
1919import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol' ;
2020import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb' ;
2121import { v4 } from 'uuid' ;
2222import { ServiceError } from './service-error' ;
2323import { BackendApplicationContribution } from '@theia/core/lib/node' ;
24+ import { Deferred } from '@theia/core/lib/common/promise-util' ;
2425
2526type Duplex = ClientDuplexStream < BoardListWatchRequest , BoardListWatchResponse > ;
2627interface StreamWrapper extends Disposable {
@@ -30,7 +31,8 @@ interface StreamWrapper extends Disposable {
3031
3132/**
3233 * Singleton service for tracking the available ports and board and broadcasting the
33- * changes to all connected frontend instances. \
34+ * changes to all connected frontend instances.
35+ *
3436 * Unlike other services, this is not connection scoped.
3537 */
3638@injectable ( )
@@ -45,16 +47,16 @@ export class BoardDiscovery
4547 @inject ( NotificationServiceServer )
4648 private readonly notificationService : NotificationServiceServer ;
4749
48- // Used to know if the board watch process is already running to avoid
49- // starting it multiple times
50- private watching : boolean ;
50+ private watching : Deferred < void > | undefined ;
51+ private stopping : Deferred < void > | undefined ;
5152 private wrapper : StreamWrapper | undefined ;
5253 private readonly onStreamDidEndEmitter = new Emitter < void > ( ) ; // sent from the CLI when the discovery process is killed for example after the indexes update and the core client re-initialization.
5354 private readonly onStreamDidCancelEmitter = new Emitter < void > ( ) ; // when the watcher is canceled by the IDE2
5455 private readonly toDisposeOnStopWatch = new DisposableCollection ( ) ;
5556
5657 /**
5758 * Keys are the `address` of the ports.
59+ *
5860 * The `protocol` is ignored because the board detach event does not carry the protocol information,
5961 * just the address.
6062 * ```json
@@ -64,46 +66,57 @@ export class BoardDiscovery
6466 * }
6567 * ```
6668 */
67- private _state : AvailablePorts = { } ;
68- get state ( ) : AvailablePorts {
69- return this . _state ;
69+ private _availablePorts : AvailablePorts = { } ;
70+ get availablePorts ( ) : AvailablePorts {
71+ return this . _availablePorts ;
7072 }
7173
7274 onStart ( ) : void {
7375 this . start ( ) ;
74- this . onClientDidRefresh ( ( ) => this . start ( ) ) ;
76+ this . onClientDidRefresh ( ( ) => this . restart ( ) ) ;
77+ }
78+
79+ private async restart ( ) : Promise < void > {
80+ this . logger . info ( 'restarting before stop' ) ;
81+ await this . stop ( ) ;
82+ this . logger . info ( 'restarting after stop' ) ;
83+ return this . start ( ) ;
7584 }
7685
7786 onStop ( ) : void {
7887 this . stop ( ) ;
7988 }
8089
81- stop ( ) : Promise < void > {
90+ async stop ( restart = false ) : Promise < void > {
91+ this . logger . info ( 'stop' ) ;
92+ if ( this . stopping ) {
93+ this . logger . info ( 'stop already stopping' ) ;
94+ return this . stopping . promise ;
95+ }
96+ if ( ! this . watching ) {
97+ return ;
98+ }
99+ this . stopping = new Deferred ( ) ;
82100 this . logger . info ( '>>> Stopping boards watcher...' ) ;
83101 return new Promise < void > ( ( resolve , reject ) => {
84- const timeout = this . createTimeout (
85- BoardDiscovery . StopWatchTimeout ,
86- reject
87- ) ;
102+ const timeout = this . createTimeout ( 10_000 , reject ) ;
88103 const toDispose = new DisposableCollection ( ) ;
89- toDispose . pushAll ( [
90- timeout ,
91- this . onStreamDidEndEmitter . event ( ( ) => {
92- this . logger . info (
93- `<<< Received the end event from the stream. Boards watcher has been successfully stopped.`
94- ) ;
95- this . watching = false ;
104+ const waitForEvent = ( event : Event < unknown > ) =>
105+ event ( ( ) => {
106+ this . logger . info ( 'stop received event: either end or cancel' ) ;
96107 toDispose . dispose ( ) ;
108+ this . stopping ?. resolve ( ) ;
109+ this . stopping = undefined ;
110+ this . logger . info ( 'stop stopped' ) ;
97111 resolve ( ) ;
98- } ) ,
99- this . onStreamDidCancelEmitter . event ( ( ) => {
100- this . logger . info (
101- `<<< Received the cancel event from the stream. Boards watcher has been successfully stopped.`
102- ) ;
103- this . watching = false ;
104- toDispose . dispose ( ) ;
105- resolve ( ) ;
106- } ) ,
112+ if ( restart ) {
113+ this . start ( ) ;
114+ }
115+ } ) ;
116+ toDispose . pushAll ( [
117+ timeout ,
118+ waitForEvent ( this . onStreamDidEndEmitter . event ) ,
119+ waitForEvent ( this . onStreamDidCancelEmitter . event ) ,
107120 ] ) ;
108121 this . logger . info ( 'Canceling boards watcher...' ) ;
109122 this . toDisposeOnStopWatch . dispose ( ) ;
@@ -149,9 +162,14 @@ export class BoardDiscovery
149162 }
150163 const stream = client
151164 . boardListWatch ( )
152- . on ( 'end' , ( ) => this . onStreamDidEndEmitter . fire ( ) )
165+ . on ( 'end' , ( ) => {
166+ this . logger . info ( 'received end' ) ;
167+ this . onStreamDidEndEmitter . fire ( ) ;
168+ } )
153169 . on ( 'error' , ( error ) => {
170+ this . logger . info ( 'error received' ) ;
154171 if ( ServiceError . isCancel ( error ) ) {
172+ this . logger . info ( 'cancel error received!' ) ;
155173 this . onStreamDidCancelEmitter . fire ( ) ;
156174 } else {
157175 this . logger . error (
@@ -165,13 +183,21 @@ export class BoardDiscovery
165183 stream,
166184 uuid : v4 ( ) ,
167185 dispose : ( ) => {
186+ this . logger . info ( 'disposing requesting cancel' ) ;
168187 // Cancelling the stream will kill the discovery `builtin:mdns-discovery process`.
169188 // The client (this class) will receive a `{"eventType":"quit","error":""}` response from the CLI.
170189 stream . cancel ( ) ;
190+ this . logger . info ( 'disposing canceled' ) ;
171191 this . wrapper = undefined ;
172192 } ,
173193 } ;
174- this . toDisposeOnStopWatch . pushAll ( [ wrapper ] ) ;
194+ this . toDisposeOnStopWatch . pushAll ( [
195+ wrapper ,
196+ Disposable . create ( ( ) => {
197+ this . watching ?. reject ( new Error ( `Stopping watcher.` ) ) ;
198+ this . watching = undefined ;
199+ } ) ,
200+ ] ) ;
175201 return wrapper ;
176202 }
177203
@@ -188,17 +214,25 @@ export class BoardDiscovery
188214 }
189215
190216 async start ( ) : Promise < void > {
217+ this . logger . info ( 'start' ) ;
218+ if ( this . stopping ) {
219+ this . logger . info ( 'start is stopping wait' ) ;
220+ await this . stopping . promise ;
221+ this . logger . info ( 'start stopped' ) ;
222+ }
191223 if ( this . watching ) {
192- // We want to avoid starting the board list watch process multiple
193- // times to meet unforeseen consequences
194- return ;
224+ this . logger . info ( 'start already watching' ) ;
225+ return this . watching . promise ;
195226 }
227+ this . watching = new Deferred ( ) ;
228+ this . logger . info ( 'start new deferred' ) ;
196229 const { client, instance } = await this . coreClient ;
197230 const wrapper = await this . createWrapper ( client ) ;
198231 wrapper . stream . on ( 'data' , async ( resp : BoardListWatchResponse ) => {
199232 this . logger . info ( 'onData' , this . toJson ( resp ) ) ;
200233 if ( resp . getEventType ( ) === 'quit' ) {
201- await this . stop ( ) ;
234+ this . logger . info ( 'quit received' ) ;
235+ this . stop ( ) ;
202236 return ;
203237 }
204238
@@ -217,8 +251,8 @@ export class BoardDiscovery
217251 throw new Error ( `Unexpected event type: '${ resp . getEventType ( ) } '` ) ;
218252 }
219253
220- const oldState = deepClone ( this . _state ) ;
221- const newState = deepClone ( this . _state ) ;
254+ const oldState = deepClone ( this . _availablePorts ) ;
255+ const newState = deepClone ( this . _availablePorts ) ;
222256
223257 const address = ( detectedPort as any ) . getPort ( ) . getAddress ( ) ;
224258 const protocol = ( detectedPort as any ) . getPort ( ) . getProtocol ( ) ;
@@ -286,18 +320,21 @@ export class BoardDiscovery
286320 } ,
287321 } ;
288322
289- this . _state = newState ;
323+ this . _availablePorts = newState ;
290324 this . notificationService . notifyAttachedBoardsDidChange ( event ) ;
291325 }
292326 } ) ;
327+ this . logger . info ( 'start request start watch' ) ;
293328 await this . requestStartWatch (
294329 new BoardListWatchRequest ( ) . setInstance ( instance ) ,
295330 wrapper . stream
296331 ) ;
297- this . watching = true ;
332+ this . logger . info ( 'start requested start watch' ) ;
333+ this . watching . resolve ( ) ;
334+ this . logger . info ( 'start resolved watching' ) ;
298335 }
299336
300- getAttachedBoards ( state : AvailablePorts = this . state ) : Board [ ] {
337+ getAttachedBoards ( state : AvailablePorts = this . availablePorts ) : Board [ ] {
301338 const attachedBoards : Board [ ] = [ ] ;
302339 for ( const portID of Object . keys ( state ) ) {
303340 const [ , boards ] = state [ portID ] ;
@@ -306,7 +343,7 @@ export class BoardDiscovery
306343 return attachedBoards ;
307344 }
308345
309- getAvailablePorts ( state : AvailablePorts = this . state ) : Port [ ] {
346+ getAvailablePorts ( state : AvailablePorts = this . availablePorts ) : Port [ ] {
310347 const availablePorts : Port [ ] = [ ] ;
311348 for ( const portID of Object . keys ( state ) ) {
312349 const [ port ] = state [ portID ] ;
@@ -315,6 +352,3 @@ export class BoardDiscovery
315352 return availablePorts ;
316353 }
317354}
318- export namespace BoardDiscovery {
319- export const StopWatchTimeout = 10_000 ;
320- }
0 commit comments