fix(nif): double-buffer the tap-handle registry (Android + iOS)#45
Merged
Conversation
clear_taps reset the handle count to 0 then re-registered every handler in tree order, so a high-frequency event (drag/scroll) firing from the UI thread *during* a render saw a transiently-small count and a half-rebuilt table, and got dropped — worse the later a widget registered (e.g. a canvas after a row of buttons). Double-buffer the registry: register_tap builds into the INACTIVE table while readers keep resolving the last committed (ACTIVE) table; set_root swaps them atomically under tap_mutex. A concurrent send now always sees a complete table on either side of the swap. - Android (mob_nif.zig): tap_tables[2] + tap_active/tap_active_count/ tap_build_count; snapTap/throttleCheck/set_throttle_config read the active table. - iOS (mob_nif.m): tap_handles/tap_handle_next become a pointer+count to the active table (readers unchanged); register/clear use the building table; the pointer is repointed at set_root. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CHANGELOG [Unreleased]: the double-buffer fix (no API change), so users can track it landing in the next release. - troubleshooting.md: path-dep mob 'mob_nif:log undef' at boot → recompile the path-dep as its own step before deploy (and match the elixir_lib toolchain). Saves the next person the same multi-hour misdiagnosis. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GenericJam
added a commit
that referenced
this pull request
Jun 20, 2026
Co-Authored-By: Claude Opus 4.8 <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
clear_taps(called every render) reset the tap-handle count to 0 and thenre-registered every handler in tree order. A high-frequency event — a finger
drag or scroll dispatched from the UI thread during a render — could
observe a transiently-small count and a half-rebuilt table, and get dropped.
The hazard grew with registration order: a widget registered late in the tree
(e.g. a
Canvasafter a row ofButtons) was the most likely to be missed.This surfaced while wiring finger-drawing on a real device: drags onto a canvas
intermittently vanished depending on what else was on screen.
Fix
Double-buffer the tap-handle registry.
register_tapbuilds into the inactivetable while readers keep resolving the last committed (active) table;
set_rootswaps them atomically undertap_mutex. A concurrent event now alwaysresolves against a complete table on either side of the swap — never a torn,
mid-rebuild one.
mob_nif.zig):tap_tables[2]+tap_active/tap_active_count/
tap_build_count;snapTap/throttleCheck/set_throttle_configreadthe active table;
register_tap/clear_tapswrite the building table;set_rootswaps under the mutex.mob_nif.m):tap_handles/tap_handle_nextbecome a pointer+countto the active table (readers unchanged); register/clear use the building table;
the pointer is repointed at
set_root.No API change — purely an internal correctness fix to event delivery.
Verification
mob_nifexports unchanged: 95 on iOS).mix deps.compile mob --force): a finger-drag canvas routed 74 drag samplesthrough the swapped table and rendered the stroke correctly — drags resolve
through the active table across renders with no drops.
Docs (so users can track this)
CHANGELOG.md[Unreleased] → Fixed.guides/troubleshooting.md: a path-depmob_nif:log undef-at-boot entry —recompile the path-dep as its own step before deploy (and match the
elixir_libtoolchain). Encountered while verifying this change on-device.🤖 Generated with Claude Code