Skip to content

Commit 2dab797

Browse files
fix: high cpu usage during livesync
During LiveSync operations (i.e. `tns run ...`), CLI may keep the CPU of the machine at a constantly high level even when it is not livesyncing. This is caused by the device detection changes that we are starting, which also checks for new emulator images. It turnes out the command `avdmanager list avds` takes really high usage of CPU on some machines when there are available AVD images. To fix this behavior split the current interval on two different ones - one will check for devices, which is run on 200ms and a second one which will check for emulator images, which will run once per minute. Fix the `startDeviceDetectionInterval` method - it should just start the device detection instead of waiting for the first execution to receive results. This breaks the current behavior, so execute a single synchronous device detection before starting the interval - this way we keep the current behavior.
1 parent d70fe8b commit 2dab797

File tree

2 files changed

+91
-62
lines changed

2 files changed

+91
-62
lines changed

lib/common/mobile/mobile-core/devices-service.ts

+48-33
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { performanceLog } from "../../decorators";
1313

1414
export class DevicesService extends EventEmitter implements Mobile.IDevicesService {
1515
private static DEVICE_LOOKING_INTERVAL = 200;
16+
private static EMULATOR_IMAGES_DETECTION_INTERVAL = 60 * 1000;
1617
private _devices: IDictionary<Mobile.IDevice> = {};
1718
private _availableEmulators: IDictionary<Mobile.IDeviceInfo> = {};
1819
private _platform: string;
@@ -21,8 +22,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
2122
private _data: Mobile.IDevicesServicesInitializationOptions;
2223
private _otherDeviceDiscoveries: Mobile.IDeviceDiscovery[] = [];
2324
private _allDeviceDiscoveries: Mobile.IDeviceDiscovery[] = [];
24-
private deviceDetectionInterval: any;
25-
private isDeviceDetectionIntervalInProgress: boolean;
25+
private deviceDetectionIntervals: NodeJS.Timer[] = [];
2626

2727
constructor(private $logger: ILogger,
2828
private $errors: IErrors,
@@ -287,43 +287,51 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
287287
}
288288
}
289289

290-
protected async startDeviceDetectionInterval(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise<void> {
290+
protected async startDeviceDetectionIntervals(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise<void> {
291291
this.$processService.attachToProcessExitSignals(this, this.clearDeviceDetectionInterval);
292292

293-
if (this.deviceDetectionInterval) {
294-
this.$logger.trace("Device detection interval is already started. New Interval will not be started.");
293+
if (this.deviceDetectionIntervals.length) {
294+
this.$logger.trace("Device detection intervals are already started. New intervals will not be started.");
295295
return;
296296
}
297-
let isFirstExecution = true;
298297

299-
return new Promise<void>((resolve, reject) => {
300-
this.deviceDetectionInterval = setInterval(async () => {
301-
if (this.isDeviceDetectionIntervalInProgress) {
302-
return;
303-
}
298+
let isDeviceDetectionIntervalInProgress = false;
299+
const deviceDetectionInterval = setInterval(async () => {
300+
if (isDeviceDetectionIntervalInProgress) {
301+
return;
302+
}
304303

305-
this.isDeviceDetectionIntervalInProgress = true;
304+
isDeviceDetectionIntervalInProgress = true;
306305

307-
await this.detectCurrentlyAttachedDevices(deviceInitOpts);
308-
await this.detectCurrentlyAvailableEmulators();
306+
await this.detectCurrentlyAttachedDevices(deviceInitOpts);
309307

310-
try {
311-
const trustedDevices = _.filter(this._devices, device => device.deviceInfo.status === constants.CONNECTED_STATUS);
312-
await settlePromises(_.map(trustedDevices, device => device.applicationManager.checkForApplicationUpdates()));
313-
} catch (err) {
314-
this.$logger.trace("Error checking for application updates on devices.", err);
315-
}
308+
try {
309+
const trustedDevices = _.filter(this._devices, device => device.deviceInfo.status === constants.CONNECTED_STATUS);
310+
await settlePromises(_.map(trustedDevices, device => device.applicationManager.checkForApplicationUpdates()));
311+
} catch (err) {
312+
this.$logger.trace("Error checking for application updates on devices.", err);
313+
}
316314

317-
if (isFirstExecution) {
318-
isFirstExecution = false;
319-
resolve();
320-
this.deviceDetectionInterval.unref();
321-
}
315+
isDeviceDetectionIntervalInProgress = false;
322316

323-
this.isDeviceDetectionIntervalInProgress = false;
317+
}, DevicesService.DEVICE_LOOKING_INTERVAL);
324318

325-
}, DevicesService.DEVICE_LOOKING_INTERVAL);
326-
});
319+
deviceDetectionInterval.unref();
320+
this.deviceDetectionIntervals.push(deviceDetectionInterval);
321+
322+
let isEmulatorDetectionIntervalRunning = false;
323+
const emulatorDetectionInterval = setInterval(async () => {
324+
if (isEmulatorDetectionIntervalRunning) {
325+
return;
326+
}
327+
328+
isEmulatorDetectionIntervalRunning = true;
329+
await this.detectCurrentlyAvailableEmulators();
330+
isEmulatorDetectionIntervalRunning = false;
331+
}, DevicesService.EMULATOR_IMAGES_DETECTION_INTERVAL);
332+
333+
emulatorDetectionInterval.unref();
334+
this.deviceDetectionIntervals.push(emulatorDetectionInterval);
327335
}
328336

329337
/**
@@ -348,14 +356,17 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
348356
/**
349357
* Starts looking for running devices. All found devices are pushed to _devices variable.
350358
*/
351-
private startLookingForDevices(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
359+
private async startLookingForDevices(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
352360
this.$logger.trace("startLookingForDevices; platform is %s", this._platform);
353361

354362
if (this._platform) {
355363
return this.detectCurrentlyAttachedDevices(deviceInitOpts);
356364
}
357365

358-
return this.startDeviceDetectionInterval(deviceInitOpts);
366+
await this.detectCurrentlyAttachedDevices(deviceInitOpts);
367+
await this.detectCurrentlyAvailableEmulators();
368+
369+
await this.startDeviceDetectionIntervals(deviceInitOpts);
359370
}
360371

361372
/**
@@ -690,10 +701,14 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
690701
}
691702

692703
private clearDeviceDetectionInterval(): void {
693-
if (this.deviceDetectionInterval) {
694-
clearInterval(this.deviceDetectionInterval);
704+
if (this.deviceDetectionIntervals.length) {
705+
for (const interval of this.deviceDetectionIntervals) {
706+
clearInterval(interval);
707+
}
708+
709+
this.deviceDetectionIntervals.splice(0, this.deviceDetectionIntervals.length);
695710
} else {
696-
this.$logger.trace("Device detection interval is not started, so it cannot be stopped.");
711+
this.$logger.trace("Device detection intervals are not started, so it cannot be stopped.");
697712
}
698713
}
699714

lib/common/test/unit-tests/mobile/devices-service.ts

+43-29
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class DevicesServiceInheritor extends DevicesService {
2828
return super.startEmulatorIfNecessary(data);
2929
}
3030

31-
public startDeviceDetectionInterval(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise<void> {
32-
return super.startDeviceDetectionInterval(deviceInitOpts);
31+
public startDeviceDetectionIntervals(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise<void> {
32+
return super.startDeviceDetectionIntervals(deviceInitOpts);
3333
}
3434

3535
public detectCurrentlyAttachedDevices(options?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
@@ -236,6 +236,15 @@ function resetDefaultSetInterval(): void {
236236
global.setInterval = originalSetInterval;
237237
}
238238

239+
async function assertOnNextTick(assertionFunction: Function): Promise<void> {
240+
await new Promise(resolve => {
241+
process.nextTick(() => {
242+
assertionFunction();
243+
resolve();
244+
});
245+
});
246+
}
247+
239248
describe("devicesService", () => {
240249
let counter = 0;
241250
const iOSDevice = {
@@ -1184,7 +1193,7 @@ describe("devicesService", () => {
11841193
});
11851194
});
11861195

1187-
describe("startDeviceDetectionInterval", () => {
1196+
describe("startDeviceDetectionIntervals", () => {
11881197
let setIntervalsCalledCount: number;
11891198

11901199
beforeEach(() => {
@@ -1203,7 +1212,7 @@ describe("devicesService", () => {
12031212
hasStartedDeviceDetectionInterval = true;
12041213
});
12051214

1206-
await devicesService.startDeviceDetectionInterval();
1215+
await devicesService.startDeviceDetectionIntervals();
12071216

12081217
assert.isTrue(hasStartedDeviceDetectionInterval);
12091218
});
@@ -1227,11 +1236,11 @@ describe("devicesService", () => {
12271236
};
12281237
};
12291238

1230-
await devicesService.startDeviceDetectionInterval();
1231-
await devicesService.startDeviceDetectionInterval();
1232-
await devicesService.startDeviceDetectionInterval();
1239+
await devicesService.startDeviceDetectionIntervals();
1240+
await devicesService.startDeviceDetectionIntervals();
1241+
await devicesService.startDeviceDetectionIntervals();
12331242

1234-
assert.deepEqual(setIntervalsCalledCount, 1);
1243+
assert.deepEqual(setIntervalsCalledCount, 2);
12351244
});
12361245

12371246
describe("ios devices check", () => {
@@ -1248,15 +1257,15 @@ describe("devicesService", () => {
12481257
hasCheckedForIosDevices = true;
12491258
};
12501259

1251-
await devicesService.startDeviceDetectionInterval();
1260+
await devicesService.startDeviceDetectionIntervals();
12521261

12531262
assert.isTrue(hasCheckedForIosDevices);
12541263
});
12551264

12561265
it("should not throw if ios device check fails throws an exception.", async () => {
12571266
(<any>$iOSDeviceDiscovery).checkForDevices = throwErrorFunction;
12581267

1259-
await assert.isFulfilled(devicesService.startDeviceDetectionInterval());
1268+
await assert.isFulfilled(devicesService.startDeviceDetectionIntervals());
12601269
});
12611270
});
12621271

@@ -1267,22 +1276,22 @@ describe("devicesService", () => {
12671276
$androidDeviceDiscovery = testInjector.resolve("androidDeviceDiscovery");
12681277
});
12691278

1270-
it("should check for android devices.", async () => {
1279+
it("should start interval that will check for android devices.", async () => {
12711280
let hasCheckedForAndroidDevices = false;
12721281

12731282
$androidDeviceDiscovery.startLookingForDevices = async (): Promise<void> => {
12741283
hasCheckedForAndroidDevices = true;
12751284
};
12761285

1277-
await devicesService.startDeviceDetectionInterval();
1278-
1279-
assert.isTrue(hasCheckedForAndroidDevices);
1286+
mockSetInterval();
1287+
await devicesService.startDeviceDetectionIntervals();
1288+
await assertOnNextTick(() => assert.isTrue(hasCheckedForAndroidDevices));
12801289
});
12811290

12821291
it("should not throw if android device check fails throws an exception.", async () => {
12831292
$androidDeviceDiscovery.startLookingForDevices = throwErrorFunction;
12841293

1285-
await assert.isFulfilled(devicesService.startDeviceDetectionInterval());
1294+
await assert.isFulfilled(devicesService.startDeviceDetectionIntervals());
12861295
});
12871296
});
12881297

@@ -1305,7 +1314,7 @@ describe("devicesService", () => {
13051314
it("should not throw if ios simulator check fails throws an exception.", async () => {
13061315
(<any>$iOSSimulatorDiscovery).checkForDevices = throwErrorFunction;
13071316

1308-
await assert.isFulfilled(devicesService.startDeviceDetectionInterval());
1317+
await assert.isFulfilled(devicesService.startDeviceDetectionIntervals());
13091318
});
13101319
});
13111320

@@ -1317,22 +1326,23 @@ describe("devicesService", () => {
13171326
devicesService.addDeviceDiscovery(customDeviceDiscovery);
13181327
});
13191328

1320-
it("should check for devices.", async () => {
1329+
it("should check for devices in interval", async () => {
13211330
let hasCheckedForDevices = false;
13221331

13231332
customDeviceDiscovery.startLookingForDevices = async (): Promise<void> => {
13241333
hasCheckedForDevices = true;
13251334
};
13261335

1327-
await devicesService.startDeviceDetectionInterval();
1336+
mockSetInterval();
1337+
await devicesService.startDeviceDetectionIntervals();
13281338

1329-
assert.isTrue(hasCheckedForDevices);
1339+
await assertOnNextTick(() => assert.isTrue(hasCheckedForDevices));
13301340
});
13311341

13321342
it("should not throw if device check fails throws an exception.", async () => {
13331343
customDeviceDiscovery.startLookingForDevices = throwErrorFunction;
13341344

1335-
await assert.isFulfilled(devicesService.startDeviceDetectionInterval());
1345+
await assert.isFulfilled(devicesService.startDeviceDetectionIntervals());
13361346
});
13371347
});
13381348

@@ -1361,34 +1371,38 @@ describe("devicesService", () => {
13611371
});
13621372

13631373
it("should check for application updates for all connected devices.", async () => {
1364-
await devicesService.startDeviceDetectionInterval();
1374+
await devicesService.startDeviceDetectionIntervals();
13651375

1366-
assert.isTrue(hasCheckedForAndroidAppUpdates);
1367-
assert.isTrue(hasCheckedForIosAppUpdates);
1376+
await assertOnNextTick(() => {
1377+
assert.isTrue(hasCheckedForAndroidAppUpdates);
1378+
assert.isTrue(hasCheckedForIosAppUpdates);
1379+
});
13681380
});
13691381

13701382
it("should check for application updates if the check on one device throws an exception.", async () => {
13711383
iOSDevice.applicationManager.checkForApplicationUpdates = throwErrorFunction;
13721384

1373-
await devicesService.startDeviceDetectionInterval();
1385+
await devicesService.startDeviceDetectionIntervals();
13741386

1375-
assert.isTrue(hasCheckedForAndroidAppUpdates);
1387+
await assertOnNextTick(() => assert.isTrue(hasCheckedForAndroidAppUpdates));
13761388
});
13771389

13781390
it("should check for application updates only on devices with status Connected", async () => {
13791391
androidDevice.deviceInfo.status = constants.UNREACHABLE_STATUS;
1380-
await devicesService.startDeviceDetectionInterval();
1392+
await devicesService.startDeviceDetectionIntervals();
13811393

1382-
assert.isFalse(hasCheckedForAndroidAppUpdates);
1383-
assert.isTrue(hasCheckedForIosAppUpdates);
1394+
await assertOnNextTick(() => {
1395+
assert.isFalse(hasCheckedForAndroidAppUpdates);
1396+
assert.isTrue(hasCheckedForIosAppUpdates);
1397+
});
13841398
});
13851399

13861400
it("should not throw if all checks for application updates on all devices throw exceptions.", () => {
13871401
iOSDevice.applicationManager.checkForApplicationUpdates = throwErrorFunction;
13881402
androidDevice.applicationManager.checkForApplicationUpdates = throwErrorFunction;
13891403

13901404
const callback = () => {
1391-
devicesService.startDeviceDetectionInterval.call(devicesService);
1405+
devicesService.startDeviceDetectionIntervals.call(devicesService);
13921406
};
13931407

13941408
assert.doesNotThrow(callback);

0 commit comments

Comments
 (0)