Skip to content

Commit f4768e0

Browse files
author
Dimitar Tachev
authored
Merge pull request #4281 from NativeScript/tachev/merge-release-into-master
chore: merge release into master
2 parents ba861d8 + 8b686c9 commit f4768e0

21 files changed

+146
-275
lines changed

lib/common/bootstrap.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,4 @@ $injector.require("qr", "./services/qr");
122122
$injector.require("printPluginsService", "./services/plugins/print-plugins-service");
123123
$injector.require("npmPluginsService", "./services/plugins/npm-plugins-service");
124124

125-
$injector.require("lockfile", "./services/lockfile");
125+
$injector.require(["lockfile", "lockService"], "./services/lock-service");

lib/common/declarations.d.ts

-61
Original file line numberDiff line numberDiff line change
@@ -1965,67 +1965,6 @@ interface IiOSNotificationService {
19651965
postNotification(deviceIdentifier: string, notification: string, commandType?: string): Promise<number>;
19661966
}
19671967

1968-
/**
1969-
* Copied from @types/lockfile
1970-
* Describes the options that can be passed to lockfile methods.
1971-
*/
1972-
interface ILockFileOptions {
1973-
/**
1974-
* A number of milliseconds to wait for locks to expire before giving up.
1975-
* Only used by lockFile.lock. Poll for opts.wait ms.
1976-
* If the lock is not cleared by the time the wait expires, then it returns with the original error.
1977-
*/
1978-
wait?: number;
1979-
1980-
/**
1981-
* When using opts.wait, this is the period in ms in which it polls to check if the lock has expired. Defaults to 100.
1982-
*/
1983-
pollPeriod?: number;
1984-
1985-
/**
1986-
* A number of milliseconds before locks are considered to have expired.
1987-
*/
1988-
stale?: number;
1989-
1990-
/**
1991-
* Used by lock and lockSync. Retry n number of times before giving up.
1992-
*/
1993-
retries?: number;
1994-
1995-
/**
1996-
* Used by lock. Wait n milliseconds before retrying.
1997-
*/
1998-
retryWait?: number;
1999-
}
2000-
2001-
/**
2002-
* Describes methods that can be used to use file locking.
2003-
*/
2004-
interface ILockFile {
2005-
/**
2006-
* Acquire a file lock on the specified path.
2007-
* @param {string} lockFilePath Path to lockfile that has to be created. Defaults to `<profile dir>/lockfile.lock`
2008-
* @param {ILockFileOptions} lockFileOpts Options used for creating the lockfile.
2009-
* @returns {Promise<void>}
2010-
*/
2011-
lock(lockFilePath?: string, lockFileOpts?: ILockFileOptions): Promise<void>;
2012-
2013-
/**
2014-
* Close and unlink the lockfile.
2015-
* @param {string} lockFilePath Path to lockfile that has to be created. Defaults to `<profile dir>/lockfile.lock`
2016-
* @returns {void}
2017-
*/
2018-
unlock(lockFilePath?: string): void;
2019-
2020-
/**
2021-
* Check if the lockfile is locked and not stale.
2022-
* @param {string} lockFilePath Path to lockfile that has to be created. Defaults to `<profile dir>/lockfile.lock`
2023-
* @param {ILockFileOptions} lockFileOpts Options used for creating the lockfile.
2024-
* @returns {boolean} true in case file is locked, false otherwise
2025-
*/
2026-
check(lockFilePath?: string, lockFileOpts?: ILockFileOptions): boolean;
2027-
}
2028-
20291968
declare module "stringify-package" {
20301969
function stringifyPackage(data: any, indent: any, newline: string): string
20311970
export = stringifyPackage

lib/common/helpers.ts

+1
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ export async function connectEventuallyUntilTimeout(factory: () => Promise<net.S
495495
socket.on("error", tryConnectAfterTimeout);
496496
} catch (e) {
497497
lastKnownError = e;
498+
tryConnectAfterTimeout(e);
498499
}
499500
}
500501

lib/common/mobile/ios/device/ios-device.ts

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class IOSDevice extends IOSDeviceBase {
1717
protected $errors: IErrors,
1818
private $injector: IInjector,
1919
protected $iOSDebuggerPortService: IIOSDebuggerPortService,
20+
protected $lockService: ILockService,
2021
private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor,
2122
protected $processService: IProcessService,
2223
private $deviceLogProvider: Mobile.IDeviceLogProvider,
@@ -63,6 +64,7 @@ export class IOSDevice extends IOSDeviceBase {
6364
await this.$iOSSocketRequestExecutor.executeAttachRequest(this, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId);
6465
const port = await this.getDebuggerPort(appId);
6566
const deviceId = this.deviceInfo.identifier;
67+
6668
const socket = await helpers.connectEventuallyUntilTimeout(
6769
async () => {
6870
const deviceResponse = _.first((await this.$iosDeviceOperations.connectToPort([{ deviceId: deviceId, port: port }]))[deviceId]);

lib/common/mobile/ios/ios-device-base.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export abstract class IOSDeviceBase implements Mobile.IiOSDevice {
55
protected abstract $errors: IErrors;
66
protected abstract $iOSDebuggerPortService: IIOSDebuggerPortService;
77
protected abstract $processService: IProcessService;
8+
protected abstract $lockService: ILockService;
89
abstract deviceInfo: Mobile.IDeviceInfo;
910
abstract applicationManager: Mobile.IDeviceApplicationManager;
1011
abstract fileSystem: Mobile.IDeviceFileSystem;
@@ -24,21 +25,23 @@ export abstract class IOSDeviceBase implements Mobile.IiOSDevice {
2425
}
2526

2627
public async getSocket(appId: string): Promise<net.Socket> {
27-
if (this.cachedSockets[appId]) {
28-
return this.cachedSockets[appId];
29-
}
28+
return this.$lockService.executeActionWithLock(async () => {
29+
if (this.cachedSockets[appId]) {
30+
return this.cachedSockets[appId];
31+
}
3032

31-
this.cachedSockets[appId] = await this.getSocketCore(appId);
33+
this.cachedSockets[appId] = await this.getSocketCore(appId);
3234

33-
if (this.cachedSockets[appId]) {
34-
this.cachedSockets[appId].on("close", () => {
35-
this.destroySocket(appId);
36-
});
35+
if (this.cachedSockets[appId]) {
36+
this.cachedSockets[appId].on("close", () => {
37+
this.destroySocket(appId);
38+
});
3739

38-
this.$processService.attachToProcessExitSignals(this, () => this.destroySocket(appId));
39-
}
40+
this.$processService.attachToProcessExitSignals(this, () => this.destroySocket(appId));
41+
}
4042

41-
return this.cachedSockets[appId];
43+
return this.cachedSockets[appId];
44+
}, "ios-debug-socket.lock");
4245
}
4346

4447
public destroyLiveSyncSocket(appId: string) {

lib/common/mobile/ios/simulator/ios-simulator-device.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class IOSSimulator extends IOSDeviceBase implements Mobile.IiOSDevice {
1414
constructor(private simulator: Mobile.IiSimDevice,
1515
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
1616
protected $errors: IErrors,
17+
protected $lockService: ILockService,
1718
private $injector: IInjector,
1819
protected $iOSDebuggerPortService: IIOSDebuggerPortService,
1920
private $iOSSimResolver: Mobile.IiOSSimResolver,

lib/common/services/lock-service.ts

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import * as lockfile from "lockfile";
2+
import * as path from "path";
3+
import { cache } from "../decorators";
4+
5+
export class LockService implements ILockService {
6+
private currentlyLockedFiles: string[] = [];
7+
8+
@cache()
9+
private get defaultLockFilePath(): string {
10+
return this.getAbsoluteLockFilePath("lockfile.lock");
11+
}
12+
13+
private getAbsoluteLockFilePath(relativeLockFilePath: string) {
14+
return path.join(this.$settingsService.getProfileDir(), relativeLockFilePath);
15+
}
16+
17+
private get defaultLockParams(): ILockOptions {
18+
// We'll retry 100 times and time between retries is 100ms, i.e. full wait in case we are unable to get lock will be 10 seconds.
19+
// In case lock is older than the `stale` value, consider it stale and try to get a new lock.
20+
const lockParams: ILockOptions = {
21+
retryWait: 100,
22+
retries: 100,
23+
stale: 30 * 1000,
24+
};
25+
26+
return lockParams;
27+
}
28+
29+
constructor(private $fs: IFileSystem,
30+
private $settingsService: ISettingsService,
31+
private $processService: IProcessService) {
32+
this.$processService.attachToProcessExitSignals(this, () => {
33+
const locksToRemove = _.clone(this.currentlyLockedFiles);
34+
_.each(locksToRemove, lock => {
35+
this.unlock(lock);
36+
});
37+
});
38+
}
39+
40+
public async executeActionWithLock<T>(action: () => Promise<T>, lockFilePath?: string, lockOpts?: ILockOptions): Promise<T> {
41+
const resolvedLockFilePath = await this.lock(lockFilePath, lockOpts);
42+
43+
try {
44+
const result = await action();
45+
return result;
46+
} finally {
47+
this.unlock(resolvedLockFilePath);
48+
}
49+
}
50+
51+
private lock(lockFilePath?: string, lockOpts?: ILockOptions): Promise<string> {
52+
const { filePath, fileOpts } = this.getLockFileSettings(lockFilePath, lockOpts);
53+
this.currentlyLockedFiles.push(filePath);
54+
55+
// Prevent ENOENT error when the dir, where lock should be created, does not exist.
56+
this.$fs.ensureDirectoryExists(path.dirname(filePath));
57+
58+
return new Promise<string>((resolve, reject) => {
59+
lockfile.lock(filePath, fileOpts, (err: Error) => {
60+
err ? reject(new Error(`Timeout while waiting for lock "${filePath}"`)) : resolve(filePath);
61+
});
62+
});
63+
}
64+
65+
private unlock(lockFilePath?: string): void {
66+
const { filePath } = this.getLockFileSettings(lockFilePath);
67+
_.remove(this.currentlyLockedFiles, e => e === lockFilePath);
68+
lockfile.unlockSync(filePath);
69+
}
70+
71+
private getLockFileSettings(filePath?: string, fileOpts?: ILockOptions): { filePath: string, fileOpts: ILockOptions } {
72+
if (filePath && !path.isAbsolute(filePath)) {
73+
filePath = this.getAbsoluteLockFilePath(filePath);
74+
}
75+
76+
filePath = filePath || this.defaultLockFilePath;
77+
fileOpts = fileOpts || this.defaultLockParams;
78+
79+
return {
80+
filePath,
81+
fileOpts
82+
};
83+
}
84+
}
85+
86+
$injector.register("lockService", LockService);
87+
// backwards compatibility
88+
$injector.register("lockfile", LockService);

lib/common/services/lockfile.ts

-63
This file was deleted.

lib/common/services/user-settings-service.ts

+5-15
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ export class UserSettingsServiceBase implements IUserSettingsService {
55
private userSettingsFilePath: string = null;
66
protected userSettingsData: any = null;
77
private get lockFilePath(): string {
8-
return `${this.userSettingsFilePath}.lock`;
8+
return `user-settings.lock`;
99
}
1010

1111
constructor(userSettingsFilePath: string,
1212
protected $fs: IFileSystem,
13-
protected $lockfile: ILockFile,
13+
protected $lockService: ILockService,
1414
private $logger: ILogger) {
1515
this.userSettingsFilePath = userSettingsFilePath;
1616
}
@@ -21,7 +21,7 @@ export class UserSettingsServiceBase implements IUserSettingsService {
2121
return this.userSettingsData ? this.userSettingsData[settingName] : null;
2222
};
2323

24-
return this.executeActionWithLock<T>(action);
24+
return this.$lockService.executeActionWithLock<T>(action, this.lockFilePath);
2525
}
2626

2727
public async saveSetting<T>(key: string, value: T): Promise<void> {
@@ -39,17 +39,7 @@ export class UserSettingsServiceBase implements IUserSettingsService {
3939
await this.saveSettings();
4040
};
4141

42-
return this.executeActionWithLock<void>(action);
43-
}
44-
45-
private async executeActionWithLock<T>(action: () => Promise<T>): Promise<T> {
46-
try {
47-
await this.$lockfile.lock(this.lockFilePath);
48-
const result = await action();
49-
return result;
50-
} finally {
51-
this.$lockfile.unlock(this.lockFilePath);
52-
}
42+
return this.$lockService.executeActionWithLock<void>(action, this.lockFilePath);
5343
}
5444

5545
public saveSettings(data?: any): Promise<void> {
@@ -66,7 +56,7 @@ export class UserSettingsServiceBase implements IUserSettingsService {
6656
this.$fs.writeJson(this.userSettingsFilePath, this.userSettingsData);
6757
};
6858

69-
return this.executeActionWithLock<void>(action);
59+
return this.$lockService.executeActionWithLock<void>(action, this.lockFilePath);
7060
}
7161

7262
// TODO: Remove Promise, reason: writeFile - blocked as other implementation of the interface has async operation.

lib/common/test/unit-tests/mobile/ios-simulator-discovery.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Yok } from "../../../yok";
44
import { assert } from "chai";
55
import { DeviceDiscoveryEventNames, CONNECTED_STATUS } from "../../../constants";
66
import { DevicePlatformsConstants } from "../../../mobile/device-platforms-constants";
7-
import { ErrorsStub, CommonLoggerStub, HooksServiceStub } from "../stubs";
7+
import { ErrorsStub, CommonLoggerStub, HooksServiceStub, LockServiceStub } from "../stubs";
88
import { FileSystemStub } from "../../../../../test/stubs";
99

1010
let currentlyRunningSimulators: Mobile.IiSimDevice[];
@@ -16,6 +16,7 @@ function createTestInjector(): IInjector {
1616
injector.register("injector", injector);
1717
injector.register("errors", ErrorsStub);
1818
injector.register("iOSDebuggerPortService", {});
19+
injector.register("lockService", LockServiceStub);
1920
injector.register("iOSSimResolver", {
2021
iOSSim: {
2122
getRunningSimulators: async () => currentlyRunningSimulators

lib/common/test/unit-tests/stubs.ts

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
import * as util from "util";
44
import { EventEmitter } from "events";
55

6+
export class LockServiceStub implements ILockService {
7+
public async executeActionWithLock<T>(action: () => Promise<T>, lockFilePath?: string, lockOpts?: ILockOptions): Promise<T> {
8+
const result = await action();
9+
return result;
10+
}
11+
}
12+
613
export class CommonLoggerStub implements ILogger {
714
setLevel(level: string): void { }
815
getLevel(): string { return undefined; }

0 commit comments

Comments
 (0)