Instant: stop stale background sync when embedded views are disposed (workaround + stress example) - #12
Draft
mutumbakato wants to merge 1 commit into
Draft
Instant: stop stale background sync when embedded views are disposed (workaround + stress example)#12mutumbakato wants to merge 1 commit into
mutumbakato wants to merge 1 commit into
Conversation
Closing a NutrientInstantView on Android does not stop the document's listen-for-server-changes long-poll: the InstantPdfDocument survives in the descriptor cache with its sync coordinator still listening, so every document ever viewed keeps a background /sync connection open. After viewing enough distinct documents these stale long-polls exhaust the per-host connection pool and the next document's auth/download queues behind them, surfacing as 40-60 s document loads (and, behind a reverse proxy with a read timeout, as endless empty-body 502s for previously viewed documents). iOS already stops sync on view teardown. The new "Instant Switch Stress" example demonstrates both the issue and the workaround, using only public APIs and the stock pub.dev packages: - InstantSyncLifecycleAdapter (an AndroidAdapter) prevents the listen-for-changes long-poll from starting at onPdfFragmentReady and stops listening on the cached document at onFragmentDetached. Timing matters: an already-running long-poll holds its pool slot until the server releases it (~47-60 s measured), so stopping at dispose alone is not enough. - syncNow() pulls server changes on demand, replacing the disabled real-time listener (local edits still push automatically). - The example automates document-switch / close-reopen cycles against a Document Engine and reports per-cycle load latency, with a UI toggle to compare baseline vs workaround. Measured on a 12-distinct-document sweep: baseline median 746 ms with 47.6 s stalls; with the adapter enabled median ~250 ms and no stalls. Verified against a licensed Document Engine that adapter registration does not affect licensing. Also removes the example's dependency_overrides that pointed outside the repository (a monorepo sync artifact that broke pub get on clean checkouts); all platform packages now resolve from pub.dev. Repro/verification for https://pspdfkit.zendesk.com/agent/tickets/134164 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.
Problem
Closing a
NutrientInstantViewon Android does not stop that document's listen-for-server-changes long-poll. TheInstantPdfDocumentsurvives in the descriptor cache with its sync coordinator still listening, so every document ever viewed keeps a background/syncconnection open. After viewing enough distinct documents, these stale long-polls exhaust the per-host connection pool and the next document's auth/download queues behind them:What this PR contains
1.
InstantSyncLifecycleAdapter(in the new example) — a workaround using only public APIs and the stock pub.dev packages:onPdfFragmentReady, disableslistenToServerChangesWhenVisiblebefore the document loads. Timing matters: an already-running long-poll holds its pool slot until the server releases it (~47–60 s measured), so stopping at dispose alone is not enough (we measured that variant: stalls unchanged).onFragmentDetached, stops listening on the cached document (belt and braces).syncNow()pulls server changes on demand, replacing the disabled real-time listener. Local edits still upload automatically, and every push also applies server changes.Registered once via
platform.Nutrient.initialize(androidAdapter: ...). Verified against a licensed Document Engine that adapter registration does not affect licensing.2. "Instant Switch Stress" example — automates document-switch / close-reopen cycles against a Document Engine and reports per-cycle load latency, with a UI toggle to compare baseline vs workaround.
3. Fixes
example/pubspec.yaml— removesdependency_overridespointing outside the repository (monorepo sync artifact that brokepub geton clean checkouts).Measured results (12 distinct fresh documents per run)
Server-side confirmation: without the workaround, Document Engine logs show all previously viewed documents still re-polling
/syncminutes after their views were disposed; with it, only the mounted document syncs.Trade-off
While a document is passively viewed it no longer receives other users' changes in real time; changes arrive via
syncNow(), on any local edit, or the periodic background sync. For single-user review workflows this is typically acceptable. The proper SDK fix (full sync cleanup on view teardown, matching iOS) is tracked internally and lands in an upcoming release.Try it
The example defaults to a local Document Engine on
:5001with documentsfresh-doc-01..30andclean-doc-auploaded (see the header ofinstant_switch_stress_example.dartfor setup). To adopt the workaround in an app, copyInstantSyncLifecycleAdapterfrom the example and register it at startup — license initialization viaNutrient.initialize(...)is unaffected.