Fix render-thread crash when a bound ItemsSource is mutated (SkiaLayout snapshot)#301
Fix render-thread crash when a bound ItemsSource is mutated (SkiaLayout snapshot)#301LennoxP90 wants to merge 1 commit into
Conversation
…ut snapshot) ViewsAdapter stored the live, bound ItemsSource in _dataContexts and indexed it from the render thread (GetOrCreateViewForIndexInternal). A UI-thread Insert/RemoveAt on that collection — e.g. a windowed/virtualized list paging data in/out during scroll — races the render-thread read and tears the resizing backing array, crashing with SIGSEGV in the runtime (100% Mono backtrace, no GPU frames; reproduces on Accelerated and Default). Index an immutable snapshot of the data contexts from the render thread instead, rebuilt on the UI thread on each collection change (per change, not per rendered frame). Single change in ViewsAdapter.cs; no features removed, no public API added.
|
Hi @LennoxP90 thanks so much for this! I found that the render thread also indexes the live collection in three more spots beyond
So I created a derivative version of the fix, which snapshots all four read paths (each captures the snapshot ref once). I will look into the repro you provided to make this use-case work well, and make needed changes to the lib to handle this optional use-case. As i see it, it's something like a "two-directional LoadMore with optional removal of already loaded data". This is a nice use-case that could totally be handled internally to minimize the number of remeasurings and boilerplate coding. And by the way, the |
Minimal fork patches enabling WFMC's chat-detail virtualized list with RecyclingTemplate=Enabled (bounded-memory recycling) + MeasureVisible: 1. ViewsAdapter.InitializeSoft - refresh the immutable render snapshot on incremental collection change. It was only rebuilt on an ItemsSource REFERENCE change; appends to the same ObservableCollection left it frozen at the old length, so GetViewForIndex could not realise appended indices (the "only the first page renders" pagination bug). 2. SkiaLayout.ApplyAddChange (MeasureVisible, add-at-end) - re-kick background measurement from LastMeasuredIndex+1. The one-shot task idles after the initial set, so appended (LoadMore) rows were never measured. Cancel first so a stale still-flagged task does not dedup-skip the restart. 3. SkiaDrawnCell.ApplyBindingContext - re-measure on recycle (WasMeasured=false + InvalidateMeasureInternal). SetContent runs inside LockUpdate, suppressing its re-measure, so a recycled cell kept the donor message width (variable-width bubbles rendered wrong). Forces re-measure for the new content. Verified emulator-5554, 914-msg thread: correct render + bounded memory (~645MB@914, flat on re-scroll; vs ~1.3GB grow-only). Carries PRs taublast#301/taublast#303. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Indexes an immutable snapshot, rebuilt on the mutating thread (the one raising CollectionChanged) and swapped in atomically via a volatile reference. Covers all render reads: index, AttachView, iterator, count. Fixes taublast#300 Refs taublast#301 Co-authored-by: LennoxP90 <LennoxP90@users.noreply.github.com> (cherry picked from commit d4281b5)
Fixes #300.
Problem
ViewsAdapterstores the live, boundItemsSourcein_dataContextsand indexes it from the renderthread (
GetOrCreateViewForIndexInternal). When a consumer mutates that collection on the UI thread(
Insert/RemoveAt— e.g. a windowed/virtualized list that pages data in and out during scroll), therender thread tears a read of the resizing backing array →
SIGSEGVin the runtime (100% Mono backtrace,no GPU frames). Reproduces on
Accelerated(GL thread) andDefault(main thread).Fix
Index an immutable snapshot of the items from the render thread instead of the live collection:
_dataContextsbecomes a property whose setter refreshes avolatile object[] _renderSnapshot.GetOrCreateViewForIndexInternalreads_renderSnapshot(atomic reference read; out-of-range simplyyields no view that frame and self-heals).
so no per-frame allocations.
Single focused change in
src/Shared/Draw/Layout/SkiaLayout.ViewsAdapter.cs. No features removed, nopublic API added.
Testing
RemoveAt(0)) crashes reliably on the stock package and no longer crashes with this change; built intoand exercised via a MAUI app.
iOS/MacCatalyst matrix on a Windows-only host — happy for CI / a Mac to confirm the rest.
Notes / for discussion
CollectionChanged; please confirm this composes with the incremental-update paths(
_HandleSmartCollectionChangeetc.).scenario); it guards defensively if not.