Skip to content

Commit 0bade5d

Browse files
authored
fix(replay): Ensure we do not flush if flush took too long (#8784)
Add another safeguard to avoid too-long replays, in the case where flushing takes too long.
1 parent da1b592 commit 0bade5d

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

packages/replay/src/replay.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,15 @@ export class ReplayContainer implements ReplayContainerInterface {
10671067
// Note this empties the event buffer regardless of outcome of sending replay
10681068
const recordingData = await this.eventBuffer.finish();
10691069

1070+
const timestamp = Date.now();
1071+
1072+
// Check total duration again, to avoid sending outdated stuff
1073+
// We leave 30s wiggle room to accomodate late flushing etc.
1074+
// This _could_ happen when the browser is suspended during flushing, in which case we just want to stop
1075+
if (timestamp - this._context.initialTimestamp > this.timeouts.maxSessionLife + 30_000) {
1076+
throw new Error('Session is too long, not sending replay');
1077+
}
1078+
10701079
// NOTE: Copy values from instance members, as it's possible they could
10711080
// change before the flush finishes.
10721081
const replayId = this.session.id;
@@ -1082,7 +1091,7 @@ export class ReplayContainer implements ReplayContainerInterface {
10821091
eventContext,
10831092
session: this.session,
10841093
options: this.getOptions(),
1085-
timestamp: Date.now(),
1094+
timestamp,
10861095
});
10871096
} catch (err) {
10881097
this._handleException(err);

packages/replay/test/integration/flush.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,4 +442,46 @@ describe('Integration | flush', () => {
442442

443443
replay.getOptions()._experiments.traceInternals = false;
444444
});
445+
446+
/**
447+
* This tests the case where a flush happens in time,
448+
* but something takes too long (e.g. because we are idle, ...)
449+
* so by the time we actually send the replay it's too late.
450+
* In this case, we want to stop the replay.
451+
*/
452+
it('stops if flushing after maxSessionLife', async () => {
453+
replay.timeouts.maxSessionLife = 100_000;
454+
455+
sessionStorage.clear();
456+
clearSession(replay);
457+
replay['_loadAndCheckSession']();
458+
await new Promise(process.nextTick);
459+
jest.setSystemTime(BASE_TIMESTAMP);
460+
461+
replay.eventBuffer!.clear();
462+
463+
// We do not care about this warning here
464+
replay.eventBuffer!.hasCheckout = true;
465+
466+
// We want to simulate that flushing happens _way_ late
467+
replay['_addPerformanceEntries'] = () => {
468+
return new Promise(resolve => setTimeout(resolve, 140_000));
469+
};
470+
471+
// Add event inside of session life timespan
472+
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP + 100, type: 2 };
473+
mockRecord._emitter(TEST_EVENT);
474+
475+
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
476+
await advanceTimers(160_000);
477+
478+
expect(mockFlush).toHaveBeenCalledTimes(2);
479+
expect(mockSendReplay).toHaveBeenCalledTimes(0);
480+
expect(replay.isEnabled()).toBe(false);
481+
482+
replay.timeouts.maxSessionLife = MAX_SESSION_LIFE;
483+
484+
// Start again for following tests
485+
await replay.start();
486+
});
445487
});

0 commit comments

Comments
 (0)