1- import * as lockfile from "lockfile" ;
1+ import * as lockfile from "proper- lockfile" ;
22import * as path from "path" ;
33import { cache } from "../decorators" ;
4+ import { getHash } from "../helpers" ;
45
56export class LockService implements ILockService {
67 private currentlyLockedFiles : string [ ] = [ ] ;
@@ -15,12 +16,11 @@ export class LockService implements ILockService {
1516 }
1617
1718 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.
2019 const lockParams : ILockOptions = {
21- retryWait : 100 ,
22- retries : 100 ,
23- stale : 30 * 1000 ,
20+ // https://www.npmjs.com/package/retry#retrytimeoutsoptions
21+ retriesObj : { retries : 13 , minTimeout : 100 , maxTimeout : 1000 , factor : 2 } ,
22+ stale : 10 * 1000 ,
23+ realpath : false
2424 } ;
2525
2626 return lockParams ;
@@ -31,41 +31,49 @@ export class LockService implements ILockService {
3131 private $processService : IProcessService ) {
3232 this . $processService . attachToProcessExitSignals ( this , ( ) => {
3333 const locksToRemove = _ . clone ( this . currentlyLockedFiles ) ;
34- _ . each ( locksToRemove , lock => {
35- this . unlock ( lock ) ;
36- } ) ;
34+ for ( const lockToRemove of locksToRemove ) {
35+ lockfile . unlockSync ( lockToRemove ) ;
36+ this . cleanLock ( lockToRemove ) ;
37+ }
3738 } ) ;
3839 }
3940
4041 public async executeActionWithLock < T > ( action : ( ) => Promise < T > , lockFilePath ?: string , lockOpts ?: ILockOptions ) : Promise < T > {
41- const resolvedLockFilePath = await this . lock ( lockFilePath , lockOpts ) ;
42+ const releaseFunc = await this . lock ( lockFilePath , lockOpts ) ;
4243
4344 try {
4445 const result = await action ( ) ;
4546 return result ;
4647 } finally {
47- this . unlock ( resolvedLockFilePath ) ;
48+ releaseFunc ( ) ;
4849 }
4950 }
5051
51- public lock ( lockFilePath ?: string , lockOpts ?: ILockOptions ) : Promise < string > {
52+ public async lock ( lockFilePath ?: string , lockOpts ?: ILockOptions ) : Promise < ( ) => void > {
5253 const { filePath, fileOpts } = this . getLockFileSettings ( lockFilePath , lockOpts ) ;
5354 this . currentlyLockedFiles . push ( filePath ) ;
55+ this . $fs . writeFile ( filePath , "" ) ;
5456
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- } ) ;
57+ try {
58+ const releaseFunc = await lockfile . lock ( filePath , fileOpts ) ;
59+ return async ( ) => {
60+ await releaseFunc ( ) ;
61+ this . cleanLock ( filePath ) ;
62+ } ;
63+ } catch ( err ) {
64+ throw new Error ( `Timeout while waiting for lock "${ filePath } "` ) ;
65+ }
6366 }
6467
6568 public unlock ( lockFilePath ?: string ) : void {
6669 const { filePath } = this . getLockFileSettings ( lockFilePath ) ;
67- _ . remove ( this . currentlyLockedFiles , e => e === lockFilePath ) ;
6870 lockfile . unlockSync ( filePath ) ;
71+ this . cleanLock ( filePath ) ;
72+ }
73+
74+ private cleanLock ( lockPath : string ) : void {
75+ _ . remove ( this . currentlyLockedFiles , e => e === lockPath ) ;
76+ this . $fs . deleteFile ( lockPath ) ;
6977 }
7078
7179 private getLockFileSettings ( filePath ?: string , fileOpts ?: ILockOptions ) : { filePath : string , fileOpts : ILockOptions } {
@@ -76,11 +84,31 @@ export class LockService implements ILockService {
7684 filePath = filePath || this . defaultLockFilePath ;
7785 fileOpts = fileOpts ? _ . assign ( { } , this . defaultLockParams , fileOpts ) : this . defaultLockParams ;
7886
87+ fileOpts . retriesObj = fileOpts . retriesObj || { } ;
88+ if ( fileOpts . retries ) {
89+ fileOpts . retriesObj . retries = fileOpts . retries ;
90+ }
91+
92+ if ( fileOpts . retryWait ) {
93+ // backwards compatibility
94+ fileOpts . retriesObj . minTimeout = fileOpts . retriesObj . maxTimeout = fileOpts . retryWait ;
95+ }
96+
97+ ( < any > fileOpts . retries ) = fileOpts . retriesObj ;
98+
7999 return {
80- filePath,
100+ filePath : this . getShortFileLock ( filePath ) ,
81101 fileOpts
82102 } ;
83103 }
104+
105+ private getShortFileLock ( filePath : string ) {
106+ const dirPath = path . dirname ( filePath ) ;
107+ const fileName = path . basename ( filePath ) ;
108+ const hashedFileName = getHash ( fileName , { algorithm : "MD5" } ) ;
109+ filePath = path . join ( dirPath , hashedFileName ) ;
110+ return filePath ;
111+ }
84112}
85113
86114$injector . register ( "lockService" , LockService ) ;
0 commit comments