Skip to content

Commit a003831

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
fixup.
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
1 parent aea550f commit a003831

File tree

2 files changed

+80
-46
lines changed

2 files changed

+80
-46
lines changed

Diff for: arduino-ide-extension/src/node/board-discovery.ts

+79-45
Original file line numberDiff line numberDiff line change
@@ -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';
1818
import { DisposableCollection } from '@theia/core/lib/common/disposable';
1919
import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
2020
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
2121
import { v4 } from 'uuid';
2222
import { ServiceError } from './service-error';
2323
import { BackendApplicationContribution } from '@theia/core/lib/node';
24+
import { Deferred } from '@theia/core/lib/common/promise-util';
2425

2526
type Duplex = ClientDuplexStream<BoardListWatchRequest, BoardListWatchResponse>;
2627
interface 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-
}

Diff for: arduino-ide-extension/src/node/boards-service-impl.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class BoardsServiceImpl
6060
protected readonly boardDiscovery: BoardDiscovery;
6161

6262
async getState(): Promise<AvailablePorts> {
63-
return this.boardDiscovery.state;
63+
return this.boardDiscovery.availablePorts;
6464
}
6565

6666
async getAttachedBoards(): Promise<Board[]> {

0 commit comments

Comments
 (0)