Skip to content

applib/touch_service: don't re-init the event node on re-subscribe#1784

Open
tsutomu1984-hub wants to merge 1 commit into
coredevices:mainfrom
tsutomu1984-hub:fix/touch-service-relist
Open

applib/touch_service: don't re-init the event node on re-subscribe#1784
tsutomu1984-hub wants to merge 1 commit into
coredevices:mainfrom
tsutomu1984-hub:fix/touch-service-relist

Conversation

@tsutomu1984-hub

Copy link
Copy Markdown

What

touch_service_subscribe() re-initialized state->raw_event_info (the per-task EventServiceInfo) on every call, zeroing its embedded list_node. When a subscriber re-subscribes while already subscribed, this corrupts the per-task event-service subscriber list and leaks the kernel-side touch subscriber count. This moves the initialization inside the "first subscribe" guard.

Why it happens

The touch service keeps a single per-task subscription, guarded by state->raw_subscribed so it registers only once. But the raw_event_info assignment sat before that guard, so it ran on every call:

state->raw_event_info = (EventServiceInfo){ .type = PEBBLE_TOUCH_EVENT, .handler = ... }; // every call
if (!state->raw_subscribed) {
  event_service_client_subscribe(&state->raw_event_info);  // only first time
  state->raw_subscribed = true;
}

A ClickConfigProvider re-runs on every window appear (window_set_on_screenprv_call_click_provider, and the modal manager re-running it on focus). Widgets that subscribe to touch from their click config provider therefore call touch_service_subscribe() again on each appear. On the 2nd+ call:

  1. raw_event_info is re-initialized → its embedded list_node.next/prev are zeroed while the node is still linked in the task's event_service_client subscriber list.
  2. The raw_subscribed guard skips the real (re-)subscribe, so the corruption is silent.

Later, on unsubscribe:

  1. event_service_client_unsubscribe()sys_event_service_client_unsubscribe() calls list_remove(), but with zeroed next/prev the node isn't properly unlinked (a prior node still points to it).
  2. The follow-up list_find(..., handler->type) then finds the stale, still-linked node (its .type was re-set to PEBBLE_TOUCH_EVENT), concludes "another handler for this type still exists", and skips the kernel-side unsubscribe.
  3. The kernel's remove_subscriber_callback never fires → the touch subscriber count (services/touch/touch.c) leaks.

On KernelMain the leak is permanent (KernelMain is never torn down like an app, so event_service_clear_process_subscriptions never cleans it up). A leaked count keeps touch_has_app_subscribers() permanently true.

The fix

Initialize raw_event_info only on the first subscribe (inside the !raw_subscribed guard); later calls just refresh raw_handler/raw_context. The list_node stays intact while linked, so unsubscribe reaches the kernel and the count balances.

Relationship to #1773

touch_service currently has no widget-level callers on main (only the manufacturing touch test, which subscribes once), so this bug is latent on main today. It becomes reachable for any widget that subscribes from its click config provider — i.e. the ScrollLayer/MenuLayer touch support in #1773 and other in-flight touch work. Landing this fix first lets those PRs build on a correct touch_service.

Testing

Compiles into the obelix (Pebble Time 2) firmware. Verified on-device with a build combining this fix with ScrollLayer/MenuLayer touch, swap_layer touch, and a click-synthesis bridge: before the fix, visiting a touch-subscribing screen then a non-subscribing one left the count leaked (touch suppressed downstream); after, the count returns to baseline and behavior is correct.

🤖 Generated with Claude Code

touch_service_subscribe re-initialized state->raw_event_info on every
call, zeroing its embedded list_node. Because a click_config_provider
re-runs on each window appear (ScrollLayer/MenuLayer/swap_layer all
subscribe from there), the node's linkage was zeroed while it was still
linked in the per-task event subscriber list. A later unsubscribe then
found the stale node via list_find and skipped the kernel-side
unsubscribe, leaking the touch subscriber count. On KernelMain (which is
never torn down like an app) the leak is permanent, so
touch_has_app_subscribers() stayed true and suppressed the touch-to-click
synthesis bridge everywhere (e.g. the Music app) after visiting any
touch-subscribing screen.

Only initialize the node on the first subscribe; later calls just refresh
the handler/context.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: tsutomu sasaki <tsutomu1984@gmail.com>
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.

1 participant