mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
Merge pull request #36002 from overleaf/mj-save-backup-early
[web] Start saving offline backups after 10 seconds GitOrigin-RevId: 4101598f46b6f71dc8bf10a31cbec9110216ed7a
This commit is contained in:
committed by
Copybot
parent
ffeb3ef5de
commit
0c5e55a8be
@@ -8,6 +8,7 @@ import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
|
||||
const EVENT_NAMESPACE = 'offlineBackup'
|
||||
const OFFLINE_BACKUP_INTERVAL_MS = 2000
|
||||
const KEY_PREFIX = 'doc.offline-backup.'
|
||||
const STALL_AFTER_SECONDS = 10
|
||||
|
||||
type Baseline = {
|
||||
version: number
|
||||
@@ -101,6 +102,20 @@ export class OfflineDocBackup {
|
||||
return this.doc.connection.state !== 'ok'
|
||||
}
|
||||
|
||||
private isSavingStalled() {
|
||||
const oldestOpCreatedAt =
|
||||
this.doc.getInflightOpCreatedAt() ?? this.doc.getPendingOpCreatedAt()
|
||||
if (!oldestOpCreatedAt) {
|
||||
return false
|
||||
}
|
||||
const unsavedSeconds = (performance.now() - oldestOpCreatedAt) / 1000
|
||||
return unsavedSeconds > STALL_AFTER_SECONDS
|
||||
}
|
||||
|
||||
private shouldBackup() {
|
||||
return this.isOffline() || this.isSavingStalled()
|
||||
}
|
||||
|
||||
private refreshBaseline() {
|
||||
// baseline is the clean server state, buffered ops would
|
||||
// bake local edits into V_start and corrupt recovery
|
||||
@@ -144,7 +159,7 @@ export class OfflineDocBackup {
|
||||
// reflects remote ops applied before we go offline. refreshBaseline no-ops
|
||||
// once there are buffered ops, freezing the baseline at the point we start
|
||||
// diverging offline.
|
||||
if (!this.isOffline()) {
|
||||
if (!this.shouldBackup()) {
|
||||
this.refreshBaseline()
|
||||
return
|
||||
}
|
||||
@@ -156,7 +171,7 @@ export class OfflineDocBackup {
|
||||
}
|
||||
this.throttleTimer = window.setTimeout(() => {
|
||||
this.throttleTimer = null
|
||||
if (this.isOffline() && this.doc.hasBufferedOps()) {
|
||||
if (this.shouldBackup() && this.doc.hasBufferedOps()) {
|
||||
this.write()
|
||||
}
|
||||
}, OFFLINE_BACKUP_INTERVAL_MS)
|
||||
|
||||
@@ -15,6 +15,7 @@ const PROJECT_ID = 'project-456'
|
||||
const USER_ID = 'user-789'
|
||||
const KEY = `doc.offline-backup.${USER_ID}.${PROJECT_ID}.${DOC_ID}`
|
||||
const FLUSH_DELAY = 2000
|
||||
const STALL_AFTER_MS = 10000
|
||||
const SYNCED_EVENT = 'ide:offlineChangesSynced'
|
||||
|
||||
class FakeShareJsDoc extends EventEmitter {
|
||||
@@ -27,6 +28,8 @@ class FakeShareJsDoc extends EventEmitter {
|
||||
snapshot = 'server text'
|
||||
inflightOp: ShareJsOperation | null = null
|
||||
pendingOp: ShareJsOperation | null = null
|
||||
inflightOpCreatedAt: number | null = null
|
||||
pendingOpCreatedAt: number | null = null
|
||||
track_changes = false
|
||||
otType: OTType = 'sharejs-text-ot'
|
||||
_doc = { inflightSubmittedIds: new Set<string>() }
|
||||
@@ -60,6 +63,14 @@ class FakeShareJsDoc extends EventEmitter {
|
||||
return this.pendingOp
|
||||
}
|
||||
|
||||
getInflightOpCreatedAt() {
|
||||
return this.inflightOpCreatedAt
|
||||
}
|
||||
|
||||
getPendingOpCreatedAt() {
|
||||
return this.pendingOpCreatedAt
|
||||
}
|
||||
|
||||
getInflightSubmittedIds() {
|
||||
return this._doc.inflightSubmittedIds
|
||||
}
|
||||
@@ -151,6 +162,41 @@ describe('OfflineDocBackup', function () {
|
||||
expect(readRecord()).to.be.null
|
||||
})
|
||||
|
||||
it('backs up while still connected once saving stalls past the threshold', function () {
|
||||
create()
|
||||
doc.pendingOp = [{ i: 'x', p: 0 }]
|
||||
doc.pendingOpCreatedAt = performance.now() - (STALL_AFTER_MS + 1000)
|
||||
doc.trigger('change')
|
||||
clock.tick(FLUSH_DELAY)
|
||||
|
||||
expect(doc.connection.state).to.equal('ok')
|
||||
expect(readRecord()?.pendingOp).to.deep.equal([{ i: 'x', p: 0 }])
|
||||
})
|
||||
|
||||
it('does not back up while connected and within the stall threshold', function () {
|
||||
create()
|
||||
doc.pendingOp = [{ i: 'x', p: 0 }]
|
||||
doc.pendingOpCreatedAt = performance.now() - (STALL_AFTER_MS - 5000)
|
||||
doc.trigger('change')
|
||||
clock.tick(FLUSH_DELAY)
|
||||
|
||||
expect(readRecord()).to.be.null
|
||||
})
|
||||
|
||||
it('uses the inflight op age (the oldest) to detect a stall', function () {
|
||||
create()
|
||||
doc.inflightOp = [{ i: 'sent', p: 0 }]
|
||||
doc.inflightOpCreatedAt = performance.now() - (STALL_AFTER_MS + 1000)
|
||||
doc.pendingOp = [{ i: 'queued', p: 5 }]
|
||||
doc.pendingOpCreatedAt = performance.now()
|
||||
doc.trigger('change')
|
||||
clock.tick(FLUSH_DELAY)
|
||||
|
||||
const record = readRecord()
|
||||
expect(record?.inflightOp).to.deep.equal([{ i: 'sent', p: 0 }])
|
||||
expect(record?.pendingOp).to.deep.equal([{ i: 'queued', p: 5 }])
|
||||
})
|
||||
|
||||
it('writes the baseline plus live ops when offline with buffered ops', function () {
|
||||
create()
|
||||
goOffline()
|
||||
|
||||
Reference in New Issue
Block a user