2021-11-03 14:26:26 +02:00
|
|
|
import LockHandler, { LockType, LockHandlerOptions, Lock, activeLock, LockClientType } from '../../services/synchronizer/LockHandler';
|
2021-08-12 17:54:10 +02:00
|
|
|
import { isNetworkSyncTarget, fileApi, setupDatabaseAndSynchronizer, synchronizer, switchClient, msleep, expectThrow, expectNotThrow } from '../../testing/test-utils';
|
2020-08-02 13:28:50 +02:00
|
|
|
|
|
|
|
// For tests with memory of file system we can use low intervals to make the tests faster.
|
|
|
|
// However if we use such low values with network sync targets, some calls might randomly fail with
|
|
|
|
// ECONNRESET and similar errors (Dropbox or OneDrive migth also throttle). Also we can't use a
|
|
|
|
// low lock TTL value because the lock might expire between the time it's written and the time it's checked.
|
|
|
|
// For that reason we add this multiplier for non-memory sync targets.
|
2024-02-26 12:16:23 +02:00
|
|
|
const timeoutMultiplier = isNetworkSyncTarget() ? 100 : 1;
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
let lockHandler_: LockHandler = null;
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function newLockHandler(options: LockHandlerOptions = null): LockHandler {
|
2020-08-02 13:28:50 +02:00
|
|
|
return new LockHandler(fileApi(), options);
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function lockHandler(): LockHandler {
|
2020-08-02 13:28:50 +02:00
|
|
|
if (lockHandler_) return lockHandler_;
|
|
|
|
lockHandler_ = new LockHandler(fileApi());
|
|
|
|
return lockHandler_;
|
|
|
|
}
|
|
|
|
|
2023-02-20 17:02:29 +02:00
|
|
|
describe('synchronizer_LockHandler', () => {
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2022-11-15 12:23:50 +02:00
|
|
|
beforeEach(async () => {
|
2020-08-02 13:28:50 +02:00
|
|
|
// logger.setLevel(Logger.LEVEL_WARN);
|
|
|
|
lockHandler_ = null;
|
|
|
|
await setupDatabaseAndSynchronizer(1);
|
|
|
|
await setupDatabaseAndSynchronizer(2);
|
|
|
|
await switchClient(1);
|
|
|
|
await synchronizer().start(); // Need to sync once to setup the sync target and allow locks to work
|
|
|
|
// logger.setLevel(Logger.LEVEL_DEBUG);
|
|
|
|
});
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should acquire and release a sync lock', (async () => {
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler().acquireLock(LockType.Sync, LockClientType.Mobile, '123456');
|
2020-08-02 13:28:50 +02:00
|
|
|
const locks = await lockHandler().locks(LockType.Sync);
|
|
|
|
expect(locks.length).toBe(1);
|
|
|
|
expect(locks[0].type).toBe(LockType.Sync);
|
|
|
|
expect(locks[0].clientId).toBe('123456');
|
2021-11-03 14:26:26 +02:00
|
|
|
expect(locks[0].clientType).toBe(LockClientType.Mobile);
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler().releaseLock(LockType.Sync, LockClientType.Mobile, '123456');
|
2020-08-02 13:28:50 +02:00
|
|
|
expect((await lockHandler().locks(LockType.Sync)).length).toBe(0);
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should not use files that are not locks', (async () => {
|
2021-11-03 14:26:26 +02:00
|
|
|
if (lockHandler().useBuiltInLocks) return; // Doesn't make sense with built-in locks
|
|
|
|
|
2024-05-07 18:29:23 +02:00
|
|
|
// Note: desktop.ini is blocked by Dropbox
|
|
|
|
await fileApi().put('locks/desktop.test.ini', 'a');
|
2020-08-02 13:28:50 +02:00
|
|
|
await fileApi().put('locks/exclusive.json', 'a');
|
|
|
|
await fileApi().put('locks/garbage.json', 'a');
|
2021-11-03 14:26:26 +02:00
|
|
|
await fileApi().put('locks/1_2_72c4d1b7253a4475bfb2f977117d26ed.json', 'a');
|
|
|
|
|
|
|
|
// Check that it doesn't cause an error if it fetches an old style lock
|
|
|
|
await fileApi().put('locks/sync_desktop_82c4d1b7253a4475bfb2f977117d26ed.json', 'a');
|
2020-08-02 13:28:50 +02:00
|
|
|
|
|
|
|
const locks = await lockHandler().locks(LockType.Sync);
|
|
|
|
expect(locks.length).toBe(1);
|
2021-11-03 14:26:26 +02:00
|
|
|
expect(locks[0].type).toBe(LockType.Sync);
|
|
|
|
expect(locks[0].clientType).toBe(LockClientType.Mobile);
|
|
|
|
expect(locks[0].clientId).toBe('72c4d1b7253a4475bfb2f977117d26ed');
|
2020-08-02 13:28:50 +02:00
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should allow multiple sync locks', (async () => {
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler().acquireLock(LockType.Sync, LockClientType.Mobile, '111');
|
2020-08-02 13:28:50 +02:00
|
|
|
|
|
|
|
await switchClient(2);
|
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler().acquireLock(LockType.Sync, LockClientType.Mobile, '222');
|
2020-08-02 13:28:50 +02:00
|
|
|
|
|
|
|
expect((await lockHandler().locks(LockType.Sync)).length).toBe(2);
|
|
|
|
|
|
|
|
{
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler().releaseLock(LockType.Sync, LockClientType.Mobile, '222');
|
2020-08-02 13:28:50 +02:00
|
|
|
const locks = await lockHandler().locks(LockType.Sync);
|
|
|
|
expect(locks.length).toBe(1);
|
|
|
|
expect(locks[0].clientId).toBe('111');
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should auto-refresh a lock', (async () => {
|
2024-02-26 12:16:23 +02:00
|
|
|
const handler = newLockHandler({ autoRefreshInterval: 100 * timeoutMultiplier });
|
2021-11-03 14:26:26 +02:00
|
|
|
const lock = await handler.acquireLock(LockType.Sync, LockClientType.Desktop, '111');
|
|
|
|
const lockBefore = activeLock(await handler.locks(), new Date(), handler.lockTtl, LockType.Sync, LockClientType.Desktop, '111');
|
2020-08-02 13:28:50 +02:00
|
|
|
handler.startAutoLockRefresh(lock, () => {});
|
2024-02-26 12:16:23 +02:00
|
|
|
await msleep(500 * timeoutMultiplier);
|
2021-11-03 14:26:26 +02:00
|
|
|
const lockAfter = activeLock(await handler.locks(), new Date(), handler.lockTtl, LockType.Sync, LockClientType.Desktop, '111');
|
2020-08-02 13:28:50 +02:00
|
|
|
expect(lockAfter.updatedTime).toBeGreaterThan(lockBefore.updatedTime);
|
|
|
|
handler.stopAutoLockRefresh(lock);
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should call the error handler when lock has expired while being auto-refreshed', (async () => {
|
2020-08-02 13:28:50 +02:00
|
|
|
const handler = newLockHandler({
|
2024-02-26 12:16:23 +02:00
|
|
|
lockTtl: 50 * timeoutMultiplier,
|
|
|
|
autoRefreshInterval: 200 * timeoutMultiplier,
|
2020-08-02 13:28:50 +02:00
|
|
|
});
|
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
const lock = await handler.acquireLock(LockType.Sync, LockClientType.Desktop, '111');
|
2024-04-05 13:16:49 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
2020-11-12 21:13:28 +02:00
|
|
|
let autoLockError: any = null;
|
2024-04-05 13:16:49 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
2020-11-12 21:13:28 +02:00
|
|
|
handler.startAutoLockRefresh(lock, (error: any) => {
|
2020-08-02 13:28:50 +02:00
|
|
|
autoLockError = error;
|
|
|
|
});
|
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
try {
|
2024-02-26 12:16:23 +02:00
|
|
|
await msleep(250 * timeoutMultiplier);
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
expect(autoLockError).toBeTruthy();
|
|
|
|
expect(autoLockError.code).toBe('lockExpired');
|
|
|
|
} finally {
|
|
|
|
handler.stopAutoLockRefresh(lock);
|
|
|
|
}
|
2020-08-02 13:28:50 +02:00
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should not allow sync locks if there is an exclusive lock', (async () => {
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler().acquireLock(LockType.Exclusive, LockClientType.Desktop, '111');
|
2020-08-02 13:28:50 +02:00
|
|
|
|
|
|
|
await expectThrow(async () => {
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler().acquireLock(LockType.Sync, LockClientType.Mobile, '222');
|
2020-08-02 13:28:50 +02:00
|
|
|
}, 'hasExclusiveLock');
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should not allow exclusive lock if there are sync locks', (async () => {
|
2020-08-02 13:28:50 +02:00
|
|
|
const lockHandler = newLockHandler({ lockTtl: 1000 * 60 * 60 });
|
2021-11-03 14:26:26 +02:00
|
|
|
if (lockHandler.useBuiltInLocks) return; // Tested server side
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler.acquireLock(LockType.Sync, LockClientType.Mobile, '111');
|
|
|
|
await lockHandler.acquireLock(LockType.Sync, LockClientType.Mobile, '222');
|
2020-08-02 13:28:50 +02:00
|
|
|
|
|
|
|
await expectThrow(async () => {
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler.acquireLock(LockType.Exclusive, LockClientType.Desktop, '333');
|
2020-08-02 13:28:50 +02:00
|
|
|
}, 'hasSyncLock');
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should allow exclusive lock if the sync locks have expired', (async () => {
|
2024-02-26 12:16:23 +02:00
|
|
|
const lockHandler = newLockHandler({ lockTtl: 500 * timeoutMultiplier });
|
2021-11-03 14:26:26 +02:00
|
|
|
if (lockHandler.useBuiltInLocks) return; // Tested server side
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler.acquireLock(LockType.Sync, LockClientType.Mobile, '111');
|
|
|
|
await lockHandler.acquireLock(LockType.Sync, LockClientType.Mobile, '222');
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2024-02-26 12:16:23 +02:00
|
|
|
await msleep(600 * timeoutMultiplier);
|
2020-08-02 13:28:50 +02:00
|
|
|
|
|
|
|
await expectNotThrow(async () => {
|
2021-11-03 14:26:26 +02:00
|
|
|
await lockHandler.acquireLock(LockType.Exclusive, LockClientType.Desktop, '333');
|
2020-08-02 13:28:50 +02:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should decide what is the active exclusive lock', (async () => {
|
2020-08-02 13:28:50 +02:00
|
|
|
const lockHandler = newLockHandler();
|
|
|
|
|
|
|
|
{
|
2021-11-03 14:26:26 +02:00
|
|
|
const locks: Lock[] = [
|
|
|
|
{
|
|
|
|
type: LockType.Exclusive,
|
|
|
|
clientId: '1',
|
|
|
|
clientType: LockClientType.Desktop,
|
|
|
|
updatedTime: Date.now(),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
2020-08-02 13:28:50 +02:00
|
|
|
await msleep(100);
|
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
locks.push({
|
|
|
|
type: LockType.Exclusive,
|
|
|
|
clientId: '2',
|
|
|
|
clientType: LockClientType.Desktop,
|
|
|
|
updatedTime: Date.now(),
|
|
|
|
});
|
|
|
|
|
|
|
|
const lock = activeLock(locks, new Date(), lockHandler.lockTtl, LockType.Exclusive);
|
|
|
|
expect(lock.clientId).toBe('1');
|
2020-08-02 13:28:50 +02:00
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
// it('should ignore locks by same client when trying to acquire exclusive lock', (async () => {
|
|
|
|
// const lockHandler = newLockHandler();
|
2021-08-18 16:49:43 +02:00
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
// await lockHandler.acquireLock(LockType.Sync, LockClientType.Desktop, '111');
|
2021-08-18 16:49:43 +02:00
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
// await expectThrow(async () => {
|
|
|
|
// await lockHandler.acquireLock(LockType.Exclusive, LockClientType.Desktop, '111', { clearExistingSyncLocksFromTheSameClient: false });
|
|
|
|
// }, 'hasSyncLock');
|
2021-08-18 16:49:43 +02:00
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
// await expectNotThrow(async () => {
|
|
|
|
// await lockHandler.acquireLock(LockType.Exclusive, LockClientType.Desktop, '111', { clearExistingSyncLocksFromTheSameClient: true });
|
|
|
|
// });
|
2020-08-02 13:28:50 +02:00
|
|
|
|
2021-11-03 14:26:26 +02:00
|
|
|
// const lock = activeLock(await lockHandler.locks(), new Date(), lockHandler.lockTtl, LockType.Exclusive);
|
|
|
|
// expect(lock.clientId).toBe('111');
|
2020-08-02 13:28:50 +02:00
|
|
|
// }));
|
|
|
|
|
|
|
|
});
|