Force Firestore long-polling to fix 30-50s stalls in Safari - #1467
Draft
codementum wants to merge 1 commit into
Draft
codementum wants to merge 1 commit into
codementum wants to merge 1 commit into
Conversation
The WebChannel backchannel is held open by default in case the backend has more data to send. Safari buffers that response rather than delivering it incrementally, so listener data and write acknowledgements only reach the SDK when the request eventually times out. Measured on a deployed study in Safari 26.6: a cold load took 57s, of which ~50s was three gaps with zero network activity while a single backchannel sat open for 51.8s. All 37 static assets had finished in 250ms, and the first participant-visible asset was not requested until t+53.5s. The end-of-study upload shows the same shape -- the write dispatches in 44ms, its acknowledgement blocks for exactly 30.066s -- which participants see as "We could not confirm your upload after 3 attempts." The same study in Chrome is unaffected, consistent with Chrome delivering the stream incrementally. experimentalAutoDetectLongPolling is enabled by default in firebase v11 and does not prevent this. experimentalForceLongPolling closes the request as soon as the backend sends data, costing one extra round trip per message. The two settings cannot be combined. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lane here-- identified this issue and possible fix w/ Claude. Appears to be working well now.
Problem
Firestore's WebChannel transport stalls in Safari, adding 30-50s to study loads and hanging the end-of-study upload for exactly 30s. This affects any study using the Firebase storage engine.
By default the SDK holds the backchannel GET open in case the backend has more data to send. Safari buffers that response rather than delivering it incrementally, so listener data and write acknowledgements only reach the SDK when the request eventually times out.
Evidence
HAR capture from a deployed study. Safari 26.6 / macOS,
firebase11.10.0, study repo v2.4.3.Cold study load: 57s. All 37 static assets finished in 250ms — the app shell was on the wire almost immediately. The remaining ~56s was three gaps with zero network activity:
introduction.md— the first asset a participant actually sees — was not requested until t+53.5s. Throughout, exactly one request was slow, and it spanned the gaps:End-of-study upload. Fifteen requests, every one 40-300ms except:
A 44ms write whose acknowledgement takes 30.066s — a timeout, not transfer time. Participants hit the retry loop in
StudyEnd.utils.ts(3 attempts, 2s/5s/10s backoff) and can end up seeing "We could not confirm your upload after 3 attempts."The same study in Chrome is unaffected, consistent with Chrome delivering the stream incrementally.
Fix
experimentalForceLongPolling: truecloses the request as soon as the backend sends data. Cost is one extra round trip per server message.FirebaseStorageEnginecurrently callsinitializeFirestore(firebaseApp, {})with an empty settings object, so no transport options are set at all. NoteexperimentalAutoDetectLongPollingis enabled by default in firebase v11 and did not prevent this; the two settings cannot be combined.Alternative considered
Forcing long-polling only for WebKit would preserve streaming on Chromium. I didn't browser-sniff — for a platform where participants arrive on whatever browser they own, predictable seemed better than optimal. Happy to narrow it if you'd prefer.
Testing
Verified against a 36-step survey (markdown/image/form components, Firebase engine) in Safari: the 30-50s load stalls and the 30s end-screen hang are both gone.
eslintclean, andtscreports no errors in the changed file.I have not tested this against studies using realtime listeners heavily (e.g. the live monitor view), where the extra round trip per message may be more noticeable.