chore: add sync backoff mechanism checks (#2786)

This commit is contained in:
Karol Sójko
2024-01-23 09:04:55 +01:00
committed by GitHub
parent 2757884d10
commit ba236a2f2b
7 changed files with 115 additions and 1 deletions

View File

@@ -0,0 +1,55 @@
import { AnyItemInterface } from '@standardnotes/models'
import { SyncBackoffService } from './SyncBackoffService'
describe('SyncBackoffService', () => {
const createService = () => new SyncBackoffService()
it('should not be in backoff if no backoff was set', () => {
const service = createService()
expect(service.isItemInBackoff({ uuid: '123' } as jest.Mocked<AnyItemInterface>)).toBe(false)
})
it('should be in backoff if backoff was set', () => {
const service = createService()
service.backoffItem({ uuid: '123' } as jest.Mocked<AnyItemInterface>)
expect(service.isItemInBackoff({ uuid: '123' } as jest.Mocked<AnyItemInterface>)).toBe(true)
})
it('should not be in backoff if backoff expired', () => {
const service = createService()
jest.spyOn(Date, 'now').mockReturnValueOnce(1_000_000)
service.backoffItem({ uuid: '123' } as jest.Mocked<AnyItemInterface>)
jest.spyOn(Date, 'now').mockReturnValueOnce(2_000_000)
expect(service.isItemInBackoff({ uuid: '123' } as jest.Mocked<AnyItemInterface>)).toBe(false)
})
it('should double backoff penalty on each backoff', () => {
const service = createService()
jest.spyOn(Date, 'now').mockReturnValueOnce(1_000_000)
service.backoffItem({ uuid: '123' } as jest.Mocked<AnyItemInterface>)
jest.spyOn(Date, 'now').mockReturnValueOnce(1_000_000)
expect(service.isItemInBackoff({ uuid: '123' } as jest.Mocked<AnyItemInterface>)).toBe(true)
jest.spyOn(Date, 'now').mockReturnValueOnce(1_000_000)
service.backoffItem({ uuid: '123' } as jest.Mocked<AnyItemInterface>)
jest.spyOn(Date, 'now').mockReturnValueOnce(1_001_000)
expect(service.isItemInBackoff({ uuid: '123' } as jest.Mocked<AnyItemInterface>)).toBe(true)
jest.spyOn(Date, 'now').mockReturnValueOnce(1_000_000)
service.backoffItem({ uuid: '123' } as jest.Mocked<AnyItemInterface>)
jest.spyOn(Date, 'now').mockReturnValueOnce(1_003_000)
expect(service.isItemInBackoff({ uuid: '123' } as jest.Mocked<AnyItemInterface>)).toBe(true)
})
})

View File

@@ -0,0 +1,37 @@
import { AnyItemInterface } from '@standardnotes/models'
import { SyncBackoffServiceInterface } from './SyncBackoffServiceInterface'
export class SyncBackoffService implements SyncBackoffServiceInterface {
private backoffPenalties: Map<string, number>
private backoffStartTimestamps: Map<string, number>
constructor() {
this.backoffPenalties = new Map<string, number>()
this.backoffStartTimestamps = new Map<string, number>()
}
isItemInBackoff(item: AnyItemInterface): boolean {
const backoffStartingTimestamp = this.backoffStartTimestamps.get(item.uuid)
if (!backoffStartingTimestamp) {
return false
}
const backoffPenalty = this.backoffPenalties.get(item.uuid)
if (!backoffPenalty) {
return false
}
const backoffEndTimestamp = backoffStartingTimestamp + backoffPenalty
return backoffEndTimestamp > Date.now()
}
backoffItem(item: AnyItemInterface): void {
const backoffPenalty = this.backoffPenalties.get(item.uuid) || 0
const newBackoffPenalty = backoffPenalty === 0 ? 1_000 : backoffPenalty * 2
this.backoffPenalties.set(item.uuid, newBackoffPenalty)
this.backoffStartTimestamps.set(item.uuid, Date.now())
}
}

View File

@@ -0,0 +1,6 @@
import { AnyItemInterface } from '@standardnotes/models'
export interface SyncBackoffServiceInterface {
isItemInBackoff(item: AnyItemInterface): boolean
backoffItem(item: AnyItemInterface): void
}

View File

@@ -181,6 +181,8 @@ export * from './Subscription/AppleIAPReceipt'
export * from './Subscription/SubscriptionManager'
export * from './Subscription/SubscriptionManagerEvent'
export * from './Subscription/SubscriptionManagerInterface'
export * from './Sync/SyncBackoffService'
export * from './Sync/SyncBackoffServiceInterface'
export * from './Sync/SyncMode'
export * from './Sync/SyncOpStatus'
export * from './Sync/SyncOptions'

View File

@@ -145,6 +145,8 @@ import {
IsVaultAdmin,
IsReadonlyVaultMember,
DesignateSurvivor,
SyncBackoffService,
SyncBackoffServiceInterface,
} from '@standardnotes/services'
import { ItemManager } from '../../Services/Items/ItemManager'
import { PayloadManager } from '../../Services/Payloads/PayloadManager'
@@ -1351,6 +1353,10 @@ export class Dependencies {
)
})
this.factory.set(TYPES.SyncBackoffService, () => {
return new SyncBackoffService()
})
this.factory.set(TYPES.SyncService, () => {
return new SyncService(
this.get<ItemManager>(TYPES.ItemManager),
@@ -1369,6 +1375,7 @@ export class Dependencies {
this.get<Logger>(TYPES.Logger),
this.get<WebSocketsService>(TYPES.WebSocketsService),
this.get<SyncFrequencyGuardInterface>(TYPES.SyncFrequencyGuard),
this.get<SyncBackoffServiceInterface>(TYPES.SyncBackoffService),
this.get<InternalEventBus>(TYPES.InternalEventBus),
)
})

View File

@@ -30,6 +30,7 @@ export const TYPES = {
SubscriptionManager: Symbol.for('SubscriptionManager'),
HistoryManager: Symbol.for('HistoryManager'),
SyncFrequencyGuard: Symbol.for('SyncFrequencyGuard'),
SyncBackoffService: Symbol.for('SyncBackoffService'),
SyncService: Symbol.for('SyncService'),
ProtectionService: Symbol.for('ProtectionService'),
UserService: Symbol.for('UserService'),

View File

@@ -86,6 +86,7 @@ import {
ApplicationSyncOptions,
WebSocketsServiceEvent,
WebSocketsService,
SyncBackoffServiceInterface,
} from '@standardnotes/services'
import { OfflineSyncResponse } from './Offline/Response'
import {
@@ -171,6 +172,7 @@ export class SyncService
private logger: LoggerInterface,
private sockets: WebSocketsService,
private syncFrequencyGuard: SyncFrequencyGuardInterface,
private syncBackoffService: SyncBackoffServiceInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -452,7 +454,11 @@ export class SyncService
}
private itemsNeedingSync() {
return this.itemManager.getDirtyItems()
const dirtyItems = this.itemManager.getDirtyItems()
const itemsWithoutBackoffPenalty = dirtyItems.filter((item) => !this.syncBackoffService.isItemInBackoff(item))
return itemsWithoutBackoffPenalty
}
public async markAllItemsAsNeedingSyncAndPersist(): Promise<void> {