Skip to content

fix: handle screenshot memory leak at low throttle intervals(<=1s)#532

Merged
ioannisj merged 7 commits intoPostHog:mainfrom
KristijanKocev:feature/replay-memmory-pressure
Apr 2, 2026
Merged

fix: handle screenshot memory leak at low throttle intervals(<=1s)#532
ioannisj merged 7 commits intoPostHog:mainfrom
KristijanKocev:feature/replay-memmory-pressure

Conversation

@KristijanKocev
Copy link
Copy Markdown
Contributor

@KristijanKocev KristijanKocev commented Mar 24, 2026

💡 Motivation and Context

We started using posthog-react-native-session-replay since its release and it worked well until after a react-native version upgrade in october last year rendered the session replay unusable for us and I had to disable it.
Today I wanted to see if it has been fixed and the same issue appeared.
What would happen is that with a throttle delay of 1000 ms or less, the screenshot based session replay caused the memory usage to keep climbing, without ever releasing memory. Eventually this causes a crash as iOS kills the app for using too much memory.
This was observed on an iphone 13, 16 and 17 pro.
With a higher interval such as 5 seconds, the memory usage increased by ~25mb every 5 seconds and dropped shortly after, which is the expected behaviour.

This told me that the screenshot pipeline was not able to complete a cycle and release memory before it had to start all over again.

This change reduces that buildup in three places:

  • skips the extra full-size masked image render when there is nothing to redact
  • wraps screenshot capture and base64 encoding in autoreleasepool
  • binds replay capture to one in-flight snapshot plus one pending latest snapshot

The result was that I could set the throttle delay to 1 second again for good quality recordings while memory usage remained stable.

💚 How did you test it?

  • Reproduced the issue in a React Native app using iOS session replay with screenshotMode enabled(which is always enabled in the react native session replay wrapper library)
  • Observed persistent memory growth with a 1s throttle interval before the fix. Using both the performance monitor from react native and a trace with xcode instruments(allocations)
  • I replaced the posthog-ios pod that posthog-react-native-session-replay uses with a vendored version of it with these changes and the memory leak was gone
  • Compiled without errors

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file
  • Added the release label to the PR

@KristijanKocev KristijanKocev requested a review from a team as a code owner March 24, 2026 23:49
Comment on lines +42 to +50
private var flushThreshold: Int {
endpoint == .snapshot ? 1 : config.flushAt
}
private var flushBatchSize: Int {
endpoint == .snapshot ? 1 : config.maxBatchSize
}
private var flushTimerInterval: TimeInterval {
endpoint == .snapshot ? 1 : config.flushIntervalSeconds
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @KristijanKocev thank you for this! Can we check if this is indeed needed? My hunch says the autoreleasepool changes should already be enough.

I’m a bit wary of aggressive disk flushing. Even if that path is gated, it still means more frequent network sends, which could keep the radio active more often and increase overhead. This may improve one symptom while quietly making battery or network behavior worse.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be right, I will do some testing today and report back.

Copy link
Copy Markdown
Contributor Author

@KristijanKocev KristijanKocev Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just removed the queue flushing and it didn't have much of an effect since the memory leak was still gone, so you were right.

Comment on lines +315 to +348
private func startSnapshotIfPossible() -> Bool {
snapshotStateLock.withLock {
if snapshotInFlight {
snapshotPending = true
return false
}

snapshotInFlight = true
return true
}
}

private func finishSnapshot() {
let shouldScheduleNext = snapshotStateLock.withLock {
snapshotInFlight = false
let shouldScheduleNext = snapshotPending
snapshotPending = false
return shouldScheduleNext
}

if shouldScheduleNext {
DispatchQueue.main.async { [weak self] in
self?.snapshot()
}
}
}

private func resetSnapshotState() {
snapshotStateLock.withLock {
snapshotInFlight = false
snapshotPending = false
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies @KristijanKocev, can we try without these changes as well and see how the improvements of autoreleasepool perform alone?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have scoped the pr a bit better, that's my bad.
I removed them and yea just the autoreleasepool takes care of the leak.

Btw i am not sure if it's a known issue but on older iphones like 13 and older, every time a screenshot gets taken there is a mini freeze which can be easily noticed if you're scrolling.
These were some of my attempts which i've been using in prod to some effect, together with this pr #536
I'm not yet fully satisfied however which is why I will keep the changes to myself until am I sure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's been reported again and the PR you linked attempts to improve on that. If there are any ideas from what you've tried so far please do share. You can email me directly at yiannis at posthog dot com if you'd prefer.

Moving the whole screenshot capture to a bg thread helps but I'd like to avoid that for as long as possible cause it can have unpredictable side effects.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will keep toying with it, nothing so far has made much of a difference.
I tried switching to wireframe mode but that also requires a bit of work so that it can function for react native apps.
It might also have something to do with me still being on the old architecture but I haven't yet migrated to test that.

Copy link
Copy Markdown
Contributor

@ioannisj ioannisj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lg, just pushed a minor fix

@ioannisj ioannisj enabled auto-merge (squash) April 2, 2026 17:47
@ioannisj ioannisj merged commit 8bdd623 into PostHog:main Apr 2, 2026
30 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants