Skip to content

Commit

Permalink
fix(replay): Ensure we do not flush if flush took too long (#8784)
Browse files Browse the repository at this point in the history
Add another safeguard to avoid too-long replays, in the case where
flushing takes too long.
  • Loading branch information
mydea authored Aug 10, 2023
1 parent da1b592 commit 0bade5d
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 1 deletion.
11 changes: 10 additions & 1 deletion packages/replay/src/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,15 @@ export class ReplayContainer implements ReplayContainerInterface {
// Note this empties the event buffer regardless of outcome of sending replay
const recordingData = await this.eventBuffer.finish();

const timestamp = Date.now();

// Check total duration again, to avoid sending outdated stuff
// We leave 30s wiggle room to accomodate late flushing etc.
// This _could_ happen when the browser is suspended during flushing, in which case we just want to stop
if (timestamp - this._context.initialTimestamp > this.timeouts.maxSessionLife + 30_000) {
throw new Error('Session is too long, not sending replay');
}

// NOTE: Copy values from instance members, as it's possible they could
// change before the flush finishes.
const replayId = this.session.id;
Expand All @@ -1082,7 +1091,7 @@ export class ReplayContainer implements ReplayContainerInterface {
eventContext,
session: this.session,
options: this.getOptions(),
timestamp: Date.now(),
timestamp,
});
} catch (err) {
this._handleException(err);
Expand Down
42 changes: 42 additions & 0 deletions packages/replay/test/integration/flush.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,4 +442,46 @@ describe('Integration | flush', () => {

replay.getOptions()._experiments.traceInternals = false;
});

/**
* This tests the case where a flush happens in time,
* but something takes too long (e.g. because we are idle, ...)
* so by the time we actually send the replay it's too late.
* In this case, we want to stop the replay.
*/
it('stops if flushing after maxSessionLife', async () => {
replay.timeouts.maxSessionLife = 100_000;

sessionStorage.clear();
clearSession(replay);
replay['_loadAndCheckSession']();
await new Promise(process.nextTick);
jest.setSystemTime(BASE_TIMESTAMP);

replay.eventBuffer!.clear();

// We do not care about this warning here
replay.eventBuffer!.hasCheckout = true;

// We want to simulate that flushing happens _way_ late
replay['_addPerformanceEntries'] = () => {
return new Promise(resolve => setTimeout(resolve, 140_000));
};

// Add event inside of session life timespan
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP + 100, type: 2 };
mockRecord._emitter(TEST_EVENT);

await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
await advanceTimers(160_000);

expect(mockFlush).toHaveBeenCalledTimes(2);
expect(mockSendReplay).toHaveBeenCalledTimes(0);
expect(replay.isEnabled()).toBe(false);

replay.timeouts.maxSessionLife = MAX_SESSION_LIFE;

// Start again for following tests
await replay.start();
});
});

0 comments on commit 0bade5d

Please sign in to comment.