@@ -14,13 +14,14 @@ import {
14
14
AvailablePorts ,
15
15
AttachedBoardsChangeEvent ,
16
16
} from '../common/protocol' ;
17
- import { Emitter } from '@theia/core/lib/common/event' ;
17
+ import { Emitter , Event } from '@theia/core/lib/common/event' ;
18
18
import { DisposableCollection } from '@theia/core/lib/common/disposable' ;
19
19
import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol' ;
20
20
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb' ;
21
21
import { v4 } from 'uuid' ;
22
22
import { ServiceError } from './service-error' ;
23
23
import { BackendApplicationContribution } from '@theia/core/lib/node' ;
24
+ import { Deferred } from '@theia/core/lib/common/promise-util' ;
24
25
25
26
type Duplex = ClientDuplexStream < BoardListWatchRequest , BoardListWatchResponse > ;
26
27
interface StreamWrapper extends Disposable {
@@ -30,7 +31,8 @@ interface StreamWrapper extends Disposable {
30
31
31
32
/**
32
33
* 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
+ *
34
36
* Unlike other services, this is not connection scoped.
35
37
*/
36
38
@injectable ( )
@@ -45,16 +47,16 @@ export class BoardDiscovery
45
47
@inject ( NotificationServiceServer )
46
48
private readonly notificationService : NotificationServiceServer ;
47
49
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 ;
51
52
private wrapper : StreamWrapper | undefined ;
52
53
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.
53
54
private readonly onStreamDidCancelEmitter = new Emitter < void > ( ) ; // when the watcher is canceled by the IDE2
54
55
private readonly toDisposeOnStopWatch = new DisposableCollection ( ) ;
55
56
56
57
/**
57
58
* Keys are the `address` of the ports.
59
+ *
58
60
* The `protocol` is ignored because the board detach event does not carry the protocol information,
59
61
* just the address.
60
62
* ```json
@@ -64,46 +66,57 @@ export class BoardDiscovery
64
66
* }
65
67
* ```
66
68
*/
67
- private _state : AvailablePorts = { } ;
68
- get state ( ) : AvailablePorts {
69
- return this . _state ;
69
+ private _availablePorts : AvailablePorts = { } ;
70
+ get availablePorts ( ) : AvailablePorts {
71
+ return this . _availablePorts ;
70
72
}
71
73
72
74
onStart ( ) : void {
73
75
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 ( ) ;
75
84
}
76
85
77
86
onStop ( ) : void {
78
87
this . stop ( ) ;
79
88
}
80
89
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 ( ) ;
82
100
this . logger . info ( '>>> Stopping boards watcher...' ) ;
83
101
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 ) ;
88
103
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' ) ;
96
107
toDispose . dispose ( ) ;
108
+ this . stopping ?. resolve ( ) ;
109
+ this . stopping = undefined ;
110
+ this . logger . info ( 'stop stopped' ) ;
97
111
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 ) ,
107
120
] ) ;
108
121
this . logger . info ( 'Canceling boards watcher...' ) ;
109
122
this . toDisposeOnStopWatch . dispose ( ) ;
@@ -149,9 +162,14 @@ export class BoardDiscovery
149
162
}
150
163
const stream = client
151
164
. boardListWatch ( )
152
- . on ( 'end' , ( ) => this . onStreamDidEndEmitter . fire ( ) )
165
+ . on ( 'end' , ( ) => {
166
+ this . logger . info ( 'received end' ) ;
167
+ this . onStreamDidEndEmitter . fire ( ) ;
168
+ } )
153
169
. on ( 'error' , ( error ) => {
170
+ this . logger . info ( 'error received' ) ;
154
171
if ( ServiceError . isCancel ( error ) ) {
172
+ this . logger . info ( 'cancel error received!' ) ;
155
173
this . onStreamDidCancelEmitter . fire ( ) ;
156
174
} else {
157
175
this . logger . error (
@@ -165,13 +183,21 @@ export class BoardDiscovery
165
183
stream,
166
184
uuid : v4 ( ) ,
167
185
dispose : ( ) => {
186
+ this . logger . info ( 'disposing requesting cancel' ) ;
168
187
// Cancelling the stream will kill the discovery `builtin:mdns-discovery process`.
169
188
// The client (this class) will receive a `{"eventType":"quit","error":""}` response from the CLI.
170
189
stream . cancel ( ) ;
190
+ this . logger . info ( 'disposing canceled' ) ;
171
191
this . wrapper = undefined ;
172
192
} ,
173
193
} ;
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
+ ] ) ;
175
201
return wrapper ;
176
202
}
177
203
@@ -188,17 +214,25 @@ export class BoardDiscovery
188
214
}
189
215
190
216
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
+ }
191
223
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 ;
195
226
}
227
+ this . watching = new Deferred ( ) ;
228
+ this . logger . info ( 'start new deferred' ) ;
196
229
const { client, instance } = await this . coreClient ;
197
230
const wrapper = await this . createWrapper ( client ) ;
198
231
wrapper . stream . on ( 'data' , async ( resp : BoardListWatchResponse ) => {
199
232
this . logger . info ( 'onData' , this . toJson ( resp ) ) ;
200
233
if ( resp . getEventType ( ) === 'quit' ) {
201
- await this . stop ( ) ;
234
+ this . logger . info ( 'quit received' ) ;
235
+ this . stop ( ) ;
202
236
return ;
203
237
}
204
238
@@ -217,8 +251,8 @@ export class BoardDiscovery
217
251
throw new Error ( `Unexpected event type: '${ resp . getEventType ( ) } '` ) ;
218
252
}
219
253
220
- const oldState = deepClone ( this . _state ) ;
221
- const newState = deepClone ( this . _state ) ;
254
+ const oldState = deepClone ( this . _availablePorts ) ;
255
+ const newState = deepClone ( this . _availablePorts ) ;
222
256
223
257
const address = ( detectedPort as any ) . getPort ( ) . getAddress ( ) ;
224
258
const protocol = ( detectedPort as any ) . getPort ( ) . getProtocol ( ) ;
@@ -286,18 +320,21 @@ export class BoardDiscovery
286
320
} ,
287
321
} ;
288
322
289
- this . _state = newState ;
323
+ this . _availablePorts = newState ;
290
324
this . notificationService . notifyAttachedBoardsDidChange ( event ) ;
291
325
}
292
326
} ) ;
327
+ this . logger . info ( 'start request start watch' ) ;
293
328
await this . requestStartWatch (
294
329
new BoardListWatchRequest ( ) . setInstance ( instance ) ,
295
330
wrapper . stream
296
331
) ;
297
- this . watching = true ;
332
+ this . logger . info ( 'start requested start watch' ) ;
333
+ this . watching . resolve ( ) ;
334
+ this . logger . info ( 'start resolved watching' ) ;
298
335
}
299
336
300
- getAttachedBoards ( state : AvailablePorts = this . state ) : Board [ ] {
337
+ getAttachedBoards ( state : AvailablePorts = this . availablePorts ) : Board [ ] {
301
338
const attachedBoards : Board [ ] = [ ] ;
302
339
for ( const portID of Object . keys ( state ) ) {
303
340
const [ , boards ] = state [ portID ] ;
@@ -306,7 +343,7 @@ export class BoardDiscovery
306
343
return attachedBoards ;
307
344
}
308
345
309
- getAvailablePorts ( state : AvailablePorts = this . state ) : Port [ ] {
346
+ getAvailablePorts ( state : AvailablePorts = this . availablePorts ) : Port [ ] {
310
347
const availablePorts : Port [ ] = [ ] ;
311
348
for ( const portID of Object . keys ( state ) ) {
312
349
const [ port ] = state [ portID ] ;
@@ -315,6 +352,3 @@ export class BoardDiscovery
315
352
return availablePorts ;
316
353
}
317
354
}
318
- export namespace BoardDiscovery {
319
- export const StopWatchTimeout = 10_000 ;
320
- }
0 commit comments