Skip to content

Sync loop pegs CPU at 99% / grows RAM unbounded on incremental sync #273

Description

@StarrWulfe

Summary

The incremental iCloud sync loop re-saves every existing message, chat, and attachment on every pass — even when the CloudKit record is byte-identical to the local row. On an account with a populated ObjectBox store, this pegs a CPU core at 99% and grows resident memory monotonically until the UI thread is starved or the system OOMs.

Reproduction

  1. Install OpenBubbles 1.15.x on Linux (Flatpak) or any desktop target.
  2. Sign in with an Apple ID that has at least ~6 months of iMessage history. (151 MB ObjectBox store observed on Toyoko; should reproduce at any nontrivial size.)
  3. Launch the app and let it sit for 5-15 minutes without interacting.
  4. Observe:
    • One CPU core pinned at 99-194% in top/btop
    • Resident memory grows from ~600 MB to >2.5 GB over ~15 minutes
    • journalctl --user shows a continuous stream of flutter: ... [INFO] [BlueBubblesApp] Syncing new message lines
    • System load average climbs steadily
  5. Killing the process is the only way to stop it; on next launch the loop restarts.

Root cause

Three sites in rustpush_service.dart always write existing rows during the incremental sync loop, instead of checking whether anything changed:

  1. existing.save() for messages (~line 2541): The existing.ckRecordId == item.key branch correctly classifies the row as localUnchanged++, but the code falls through to existing.save() instead of continue-ing.
  2. existing.save(null) for attachments (~line 2460): No equivalent guard at all; every attachment's ckRecordId is re-written on every sync pass.
  3. Database.chats.put(this) in Chat.applyFromCloud: Always writes the row, even when the cloud record matches local state.

Additionally, Message.applyFromCloud and Attachment.applyFromCloud always re-decode the proto / re-allocate attributedBody lists before the save, so every sync pass allocates fresh objects. That's the primary RAM-growth amplifier on top of the save storms.

Fix

PR forthcoming. The minimal fix is:

  • Add continue to the message sync loop's localUnchanged branch (so the save is skipped).
  • Add an equivalent guard to the attachment sync loop.
  • Change Chat.applyFromCloud, Message.applyFromCloud, and Attachment.applyFromCloud to return bool. Each early-returns false when the cloud record is identical to local state, so the caller's redundant save is skipped.

Verification

  • Manual: with the fix, a sync pass over an unchanged store hits the early-return on the first item and returns immediately. RAM stays flat, CPU stays idle, no log spam.
  • Static: dart analyze shows no new errors vs the unpatched branch (2074 baseline vs 2076 with the patch — the +2 are pre-existing dead-code warnings the analyzer reaches further into).
  • Dynamic: needs a Flutter toolchain to build the app and validate end-to-end. Patching this without Flutter installed is fine for review; the maintainers' CI will exercise the full build.

Environment

  • OpenBubbles 1.15.0.0 (Flatpak, stable channel)
  • Linux 7.2.5-3-omarchy (Arch-derived)
  • ObjectBox store: 151 MB (~6 months of iMessage history, 8 chats, mixed group + 1:1)
  • Hardware: Framework 16 laptop, AMD Radeon RX 7600, 60 GB RAM

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions