| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | import {getFileSystem} from '../../../src/ngtsc/file_system'; | 
					
						
							|  |  |  | import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; | 
					
						
							| 
									
										
										
										
											2020-05-14 20:06:12 +01:00
										 |  |  | import {MockLogger} from '../../../src/ngtsc/logging/testing'; | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  | import {AsyncLocker} from '../../src/locking/async_locker'; | 
					
						
							|  |  |  | import {MockLockFile} from '../helpers/mock_lock_file'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | runInEachFileSystem(() => { | 
					
						
							|  |  |  |   describe('AsyncLocker', () => { | 
					
						
							|  |  |  |     describe('lock()', () => { | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |       it('should guard the `fn()` with calls to `write()` and `remove()`', async () => { | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |         const fs = getFileSystem(); | 
					
						
							|  |  |  |         const log: string[] = []; | 
					
						
							|  |  |  |         const lockFile = new MockLockFile(fs, log); | 
					
						
							|  |  |  |         const locker = new AsyncLocker(lockFile, new MockLogger(), 100, 10); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |         await locker.lock(async () => { | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |           log.push('fn() - before'); | 
					
						
							|  |  |  |           // This promise forces node to do a tick in this function, ensuring that we are truly
 | 
					
						
							|  |  |  |           // testing an async scenario.
 | 
					
						
							|  |  |  |           await Promise.resolve(); | 
					
						
							|  |  |  |           log.push('fn() - after'); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         expect(log).toEqual(['write()', 'fn() - before', 'fn() - after', 'remove()']); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should guard the `fn()` with calls to `write()` and `remove()`, even if it throws', | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |          async () => { | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |            let error: string = ''; | 
					
						
							|  |  |  |            const fs = getFileSystem(); | 
					
						
							|  |  |  |            const log: string[] = []; | 
					
						
							|  |  |  |            const lockFile = new MockLockFile(fs, log); | 
					
						
							|  |  |  |            const locker = new AsyncLocker(lockFile, new MockLogger(), 100, 10); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            try { | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |              await locker.lock(async () => { | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |                log.push('fn()'); | 
					
						
							|  |  |  |                throw new Error('ERROR'); | 
					
						
							|  |  |  |              }); | 
					
						
							|  |  |  |            } catch (e) { | 
					
						
							|  |  |  |              error = e.message; | 
					
						
							|  |  |  |            } | 
					
						
							|  |  |  |            expect(error).toEqual('ERROR'); | 
					
						
							|  |  |  |            expect(log).toEqual(['write()', 'fn()', 'remove()']); | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |       it('should retry if another process is locking', async () => { | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |         const fs = getFileSystem(); | 
					
						
							|  |  |  |         const log: string[] = []; | 
					
						
							|  |  |  |         const lockFile = new MockLockFile(fs, log); | 
					
						
							|  |  |  |         const logger = new MockLogger(); | 
					
						
							|  |  |  |         const locker = new AsyncLocker(lockFile, logger, 100, 10); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let lockFileContents: string|null = '188'; | 
					
						
							|  |  |  |         spyOn(lockFile, 'write').and.callFake(() => { | 
					
						
							|  |  |  |           log.push('write()'); | 
					
						
							|  |  |  |           if (lockFileContents) { | 
					
						
							|  |  |  |             throw {code: 'EEXIST'}; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         spyOn(lockFile, 'read').and.callFake(() => { | 
					
						
							|  |  |  |           log.push('read() => ' + lockFileContents); | 
					
						
							| 
									
										
										
										
											2020-01-03 14:28:06 +09:00
										 |  |  |           if (lockFileContents === null) { | 
					
						
							|  |  |  |             throw {code: 'ENOENT'}; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |           return lockFileContents; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |         const promise = locker.lock(async () => log.push('fn()')); | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |         // The lock is now waiting on the lock-file becoming free, so no `fn()` in the log.
 | 
					
						
							|  |  |  |         expect(log).toEqual(['write()', 'read() => 188']); | 
					
						
							|  |  |  |         expect(logger.logs.info).toEqual([[ | 
					
						
							| 
									
										
										
										
											2020-04-28 15:30:45 +01:00
										 |  |  |           'Another process, with id 188, is currently running ngcc.\nWaiting up to 1s for it to finish.\n' + | 
					
						
							|  |  |  |           `(If you are sure no ngcc process is running then you should delete the lock-file at ${ | 
					
						
							|  |  |  |               lockFile.path}.)`
 | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |         ]]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         lockFileContents = null; | 
					
						
							|  |  |  |         // The lock-file has been removed, so we can create our own lock-file, call `fn()` and then
 | 
					
						
							|  |  |  |         // remove the lock-file.
 | 
					
						
							|  |  |  |         await promise; | 
					
						
							|  |  |  |         expect(log).toEqual(['write()', 'read() => 188', 'write()', 'fn()', 'remove()']); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |       it('should extend the retry timeout if the other process locking the file changes', async () => { | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |         const fs = getFileSystem(); | 
					
						
							|  |  |  |         const log: string[] = []; | 
					
						
							|  |  |  |         const lockFile = new MockLockFile(fs, log); | 
					
						
							|  |  |  |         const logger = new MockLogger(); | 
					
						
							|  |  |  |         const locker = new AsyncLocker(lockFile, logger, 200, 5); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let lockFileContents: string|null = '188'; | 
					
						
							|  |  |  |         spyOn(lockFile, 'write').and.callFake(() => { | 
					
						
							|  |  |  |           log.push('write()'); | 
					
						
							|  |  |  |           if (lockFileContents) { | 
					
						
							|  |  |  |             throw {code: 'EEXIST'}; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         spyOn(lockFile, 'read').and.callFake(() => { | 
					
						
							|  |  |  |           log.push('read() => ' + lockFileContents); | 
					
						
							| 
									
										
										
										
											2020-01-03 14:28:06 +09:00
										 |  |  |           if (lockFileContents === null) { | 
					
						
							|  |  |  |             throw {code: 'ENOENT'}; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |           return lockFileContents; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |         const promise = locker.lock(async () => log.push('fn()')); | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |         // The lock is now waiting on the lock-file becoming free, so no `fn()` in the log.
 | 
					
						
							|  |  |  |         expect(log).toEqual(['write()', 'read() => 188']); | 
					
						
							|  |  |  |         expect(logger.logs.info).toEqual([[ | 
					
						
							| 
									
										
										
										
											2020-04-28 15:30:45 +01:00
										 |  |  |           'Another process, with id 188, is currently running ngcc.\nWaiting up to 1s for it to finish.\n' + | 
					
						
							|  |  |  |           `(If you are sure no ngcc process is running then you should delete the lock-file at ${ | 
					
						
							|  |  |  |               lockFile.path}.)`
 | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |         ]]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         lockFileContents = '444'; | 
					
						
							|  |  |  |         // The lock-file has been taken over by another process - wait for the next attempt
 | 
					
						
							|  |  |  |         await new Promise(resolve => setTimeout(resolve, 250)); | 
					
						
							|  |  |  |         expect(log).toEqual(['write()', 'read() => 188', 'write()', 'read() => 444']); | 
					
						
							|  |  |  |         expect(logger.logs.info).toEqual([ | 
					
						
							| 
									
										
										
										
											2020-04-28 15:30:45 +01:00
										 |  |  |           ['Another process, with id 188, is currently running ngcc.\nWaiting up to 1s for it to finish.\n' + | 
					
						
							|  |  |  |            `(If you are sure no ngcc process is running then you should delete the lock-file at ${ | 
					
						
							|  |  |  |                lockFile.path}.)`],
 | 
					
						
							|  |  |  |           ['Another process, with id 444, is currently running ngcc.\nWaiting up to 1s for it to finish.\n' + | 
					
						
							|  |  |  |            `(If you are sure no ngcc process is running then you should delete the lock-file at ${ | 
					
						
							|  |  |  |                lockFile.path}.)`]
 | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         lockFileContents = null; | 
					
						
							|  |  |  |         // The lock-file has been removed, so we can create our own lock-file, call `fn()` and
 | 
					
						
							|  |  |  |         // then remove the lock-file.
 | 
					
						
							|  |  |  |         await promise; | 
					
						
							|  |  |  |         expect(log).toEqual([ | 
					
						
							|  |  |  |           'write()', 'read() => 188', 'write()', 'read() => 444', 'write()', 'fn()', 'remove()' | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should error if another process does not release the lock-file before this times out', | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |          async () => { | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |            const fs = getFileSystem(); | 
					
						
							|  |  |  |            const log: string[] = []; | 
					
						
							|  |  |  |            const lockFile = new MockLockFile(fs, log); | 
					
						
							|  |  |  |            const logger = new MockLogger(); | 
					
						
							|  |  |  |            const locker = new AsyncLocker(lockFile, logger, 100, 2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            let lockFileContents: string|null = '188'; | 
					
						
							|  |  |  |            spyOn(lockFile, 'write').and.callFake(() => { | 
					
						
							|  |  |  |              log.push('write()'); | 
					
						
							|  |  |  |              if (lockFileContents) { | 
					
						
							|  |  |  |                throw {code: 'EEXIST'}; | 
					
						
							|  |  |  |              } | 
					
						
							|  |  |  |            }); | 
					
						
							|  |  |  |            spyOn(lockFile, 'read').and.callFake(() => { | 
					
						
							|  |  |  |              log.push('read() => ' + lockFileContents); | 
					
						
							| 
									
										
										
										
											2020-01-03 14:28:06 +09:00
										 |  |  |              if (lockFileContents === null) { | 
					
						
							|  |  |  |                throw {code: 'ENOENT'}; | 
					
						
							|  |  |  |              } | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |              return lockFileContents; | 
					
						
							|  |  |  |            }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |            const promise = locker.lock(async () => log.push('fn()')); | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |            // The lock is now waiting on the lock-file becoming free, so no `fn()` in the log.
 | 
					
						
							|  |  |  |            expect(log).toEqual(['write()', 'read() => 188']); | 
					
						
							|  |  |  |            // Do not remove the lock-file and let the call to `lock()` timeout.
 | 
					
						
							|  |  |  |            let error: Error; | 
					
						
							|  |  |  |            await promise.catch(e => error = e); | 
					
						
							|  |  |  |            expect(log).toEqual(['write()', 'read() => 188', 'write()', 'read() => 188']); | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |            expect(error!.message) | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |                .toEqual( | 
					
						
							|  |  |  |                    `Timed out waiting 0.2s for another ngcc process, with id 188, to complete.\n` + | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |                    `(If you are sure no ngcc process is running then you should delete the lock-file at ${ | 
					
						
							|  |  |  |                        lockFile.path}.)`);
 | 
					
						
							| 
									
										
										
										
											2020-03-04 12:49:36 +00:00
										 |  |  |          }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2020-01-03 14:28:06 +09:00
										 |  |  | }); |