Skip to content

fix(bluetooth): stop leaking 20Hz input poll tasks (#229) - #230

Open
OGKevin wants to merge 1 commit into
mainfrom
cursor-cloud-agent/fix-bt-poll-leak-3f09
Open

fix(bluetooth): stop leaking 20Hz input poll tasks (#229)#230
OGKevin wants to merge 1 commit into
mainfrom
cursor-cloud-agent/fix-bt-poll-leak-3f09

Conversation

@OGKevin

@OGKevin OGKevin commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

Triage of #229: the report has strong merit. The crashlog analysis is right about the KOReader scheduler API, and that bug can keep the UI loop waking at 20 Hz (and multiply those loops on reconnect).

What #229 claimed

  1. BluetoothKeyBindings:startPolling() stores self.poll_task = UIManager:scheduleIn(...).
  2. KOReader’s UIManager:scheduleIn returns nothing, so poll_task stays nil.
  3. stopPolling() cannot unschedule the callback, so loops leak across reconnect / re-init.
  4. onSuspend() only tears down Bluetooth when the radio is still reported on, so leftover tasks can keep running.

KOReader validation

From frontend/ui/uimanager.lua:

  • scheduleIn() calls schedule() and does not return a value.
  • unschedule(action) removes queue entries by function identity (_task_queue[i].action == action).
  • tickAfterNext() is the API that does return a wrapper specifically so callers can unschedule it.
  • The event loop sets its wait deadline from the next scheduled task, so a 50 ms (poll_interval = 0.05) task prevents long idle / standby while KOReader is awake.

DbusMonitor already used the correct pattern (store the callback, unschedule that function). The test mock of scheduleIn returned a numeric id, which is why unit tests never caught this.

Fix

  • Store the poll callback itself and pass that same function to scheduleIn / unschedule.
  • Always _cleanup() on suspend, even if the radio is already off.
  • Tests that mock KOReader’s void scheduleIn and the suspend cleanup path.

Test plan

  • busted spec/bluetooth_keybindings_spec.lua spec/kobo_bluetooth_spec.lua
  • luacheck / stylua on touched files
  • Full busted spec/ in CI

Fixes #229

Open in Web Open in Cursor 

UIManager:scheduleIn does not return a handle in KOReader, so assigning
its result to poll_task left stopPolling unable to unschedule the
callback. Each reconnect started another 50ms loop.

Track the callback function itself (as DbusMonitor already does) and
always clean up scheduled Bluetooth work on suspend even if the radio
is already off.

Co-authored-by: Kevin <OGKevin@users.noreply.github.com>
@cursor

cursor Bot commented Aug 20, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e7993d5c-a743-4a78-82ab-277bc7019db3

📥 Commits

Reviewing files that changed from the base of the PR and between 3b14de1 and e08aacc.

📒 Files selected for processing (5)
  • spec/bluetooth_keybindings_spec.lua
  • spec/helper.lua
  • spec/kobo_bluetooth_spec.lua
  • src/bluetooth_keybindings.lua
  • src/kobo_bluetooth.lua

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.

📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.lua

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.lua: Lua code must follow the detailed standards in .github/instructions/lua.md.
Write clear, maintainable Lua; prefer simplicity, document complex logic, keep functions focused and single-purpose, and use descriptive names.
Aim for at least 70% code coverage.
Mock external dependencies, but do not mock the code under test.
Validate function inputs, return early on errors, provide meaningful error messages, and handle edge cases explicitly.
Avoid unnecessary file I/O, cache computed values when appropriate, consider embedded-device memory usage, and profile performance-critical code.
Use four spaces for indentation and sort requires according to the project's Stylua configuration.
Use early returns instead of deeply nested conditional logic and avoid else statements where early returns are appropriate.
Avoid magic numbers by replacing them with named constants.
Avoid global state; pass dependencies explicitly.
Structure modules with a local module table, underscore-prefixed private helpers, public methods on the module table, and return Module.

Files:

  • spec/kobo_bluetooth_spec.lua
  • spec/bluetooth_keybindings_spec.lua
  • src/kobo_bluetooth.lua
  • spec/helper.lua
  • src/bluetooth_keybindings.lua
spec/**/*_spec.lua

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

spec/**/*_spec.lua: All new features must include Busted tests placed in spec/ with the _spec.lua suffix.
Use the Busted testing framework DSL for tests.
Organize Busted tests with describe, before_each, nested describe blocks, and focused it cases covering valid and invalid input.

Files:

  • spec/kobo_bluetooth_spec.lua
  • spec/bluetooth_keybindings_spec.lua
**/*.{lua,sh,md}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Run the appropriate project validation tools: Luacheck and Stylua for Lua, ShellCheck for shell scripts, Prettier for Markdown, and Busted for tests.

Files:

  • spec/kobo_bluetooth_spec.lua
  • spec/bluetooth_keybindings_spec.lua
  • src/kobo_bluetooth.lua
  • spec/helper.lua
  • src/bluetooth_keybindings.lua
**/*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow conventional commits: feat causes a minor release, fix a patch release, documentation and chore commits no release, and BREAKING CHANGE: a major release.

Files:

  • spec/kobo_bluetooth_spec.lua
  • spec/bluetooth_keybindings_spec.lua
  • src/kobo_bluetooth.lua
  • spec/helper.lua
  • src/bluetooth_keybindings.lua
🔇 Additional comments (5)
src/bluetooth_keybindings.lua (1)

174-218: LGTM!

spec/helper.lua (1)

1477-1485: LGTM!

spec/bluetooth_keybindings_spec.lua (1)

1052-1124: LGTM!

src/kobo_bluetooth.lua (1)

993-1008: LGTM!

spec/kobo_bluetooth_spec.lua (1)

283-297: LGTM!


Walkthrough

The Bluetooth polling loop now stores its callback instead of a scheduler handle. It prevents duplicate starts and avoids rescheduling after stopPolling. The scheduler mock removes callbacks by identity and handles missing task state. onSuspend now cleans up supported devices before checking Bluetooth state, and tests verify polling stops when Bluetooth is disabled.

Sequence Diagram(s)

sequenceDiagram
  participant BluetoothKeybindings
  participant UIManager
  participant IsolatedReaders
  BluetoothKeybindings->>UIManager: scheduleIn polling callback
  UIManager->>BluetoothKeybindings: invoke callback
  BluetoothKeybindings->>IsolatedReaders: poll readers
  BluetoothKeybindings->>UIManager: reschedule while polling is active
  BluetoothKeybindings->>UIManager: unschedule callback when polling stops
Loading

Poem

A rabbit taps the polling bell,
The callback knows when not to dwell.
It hops through readers, then retreats,
While suspend tucks away Bluetooth beats.
No duplicate loops cross the track—
Cleanly stopped, they do not come back.

Merge Risk: ⚪ Minimal · up to e08aa

This PR fixes Bluetooth polling-task cleanup and suspend handling. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits syntax and clearly describes the Bluetooth polling task leak fix.
Linked Issues check ✅ Passed The changes address issue #229 by preventing persistent Bluetooth polling tasks and cleaning up Bluetooth work during suspend.
Out of Scope Changes check ✅ Passed All code changes support Bluetooth polling lifecycle management, suspend cleanup, or tests for the reported battery-use issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@OGKevin
OGKevin marked this pull request as ready for review August 20, 2026 13:03
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.

Battery usage

2 participants