Skip to content

Fix UI freeze on "mark all as read/unread" for forums, channels and boards#331

Open
jolavillette wants to merge 2 commits into
RetroShare:masterfrom
jolavillette:fix/gxs-mark-all-read-nofreeze
Open

Fix UI freeze on "mark all as read/unread" for forums, channels and boards#331
jolavillette wants to merge 2 commits into
RetroShare:masterfrom
jolavillette:fix/gxs-mark-all-read-nofreeze

Conversation

@jolavillette

@jolavillette jolavillette commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

works with retroshare-gui pr/3274

Fix UI freeze on "mark all as read/unread" for forums, channels and boards

Problem

Marking a large GXS group read — right-click → Mark all as read (or unread) — froze the UI for a very long time and could appear to hang indefinitely. On an 8000-post forum the hourglass stayed up for over an hour; force-quitting mid-way left thousands of posts still unread.

Root cause

Two independent amplifiers, both triggered per message:

  1. GUI side — the model spawned one detached std::thread per changed post (each blocking up to 5s waiting for its token to complete) and emitted one dataChanged() per post. Marking N posts created ~N threads and ~N signals, all driven from the GUI thread.
  2. Backend sideRsGenExchange::processMsgMetaChanges() wrote each status change to the GXS database in its own implicit transaction (one fsync per message). On the SQLCipher-encrypted store this meant thousands of serial encrypted commits.

Fix

Backend (libretroshare)

  • RsGeneralDataService: new batch updateMessageMetaData(std::vector<MsgLocMetaData>) overload (default loops over the single version). RsDataService overrides it to persist the whole batch in a single transaction, held under one DB mutex so nothing interleaves.
  • RsGenExchange::processMsgMetaChanges(): resolve all status masks first, then persist every queued change through that single transaction. This benefits every GXS service.
  • New bulk read-status APIs that queue all changes at once and emit a single event:
    • RsGxsForums::markRead(forumId, msgIds, read)
    • RsGxsChannels::setMessageReadStatus(channelId, msgIds, read)
    • RsPosted::setPostReadStatus(boardId, msgIds, read)

GUI (retroshare-gui)

  • GxsForumModel, GxsChannelPostsModel and PostedPostsModel: "mark all as read/unread" now collects the affected message ids, updates the in-memory model, issues one background batch call, and refreshes the view once — instead of one detached thread + one event + one dataChanged() per post.

Result

The GUI thread now does O(n) in-memory work plus one thread and one signal, regardless of how many posts are affected, so it stays responsive on arbitrarily large groups. Persistence happens in a single transaction (one fsync). Works symmetrically for read and unread, on forums, channels and boards.

Notes

  • Backwards compatible: the existing single-message markRead / setMessageReadStatus / setPostReadStatus are unchanged; the batch variants are additive.
  • This PR pairs with the companion in the other repository (retroshare-guilibretroshare): the GUI batch calls require the new backend overloads. Companion PR:

Testing

Open a forum/channel/board with thousands of posts, then right-click → Mark all as read, and again → Mark all as unread:

  • the UI stays responsive throughout (no hourglass hang);
  • unread counts update immediately;
  • the state persists across a restart.

@jolavillette
jolavillette force-pushed the fix/gxs-mark-all-read-nofreeze branch from 20e3bdc to a655d29 Compare July 18, 2026 13:33
jolavillette and others added 2 commits July 18, 2026 21:01
…s-read hang)

Marking a large forum (thousands of posts) "read" used to be catastrophic:
the GUI issued one blocking markRead() per post, and the GXS backend wrote
each status change to the SQLCipher DB in its own implicit transaction
(one fsync per message). On an 8000-post forum this took over an hour and,
if force-quit, left most posts still unread.

Backend changes:
- RsGeneralDataService: add a batch updateMessageMetaData(vector) overload.
  The default loops; RsDataService overrides it to persist the whole batch in
  a single transaction, held under one mDbMutex so nothing interleaves.
- RsGenExchange::processMsgMetaChanges(): resolve all status masks first, then
  persist every queued change through that single batched transaction.
- RsGxsForums / p3GxsForums: add a bulk markRead(forumId, msgIds, read) that
  queues all status changes at once and emits a single event, instead of one
  blocking request + one event per message.

"Mark all as read" now persists in a single transaction (one fsync) whatever
the number of posts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d fix)

Same treatment as the forum bulk markRead(), now for channels and boards, so
their "mark all as read/unread" also persists in a single transaction and emits
a single event instead of one blocking request + one event per post:

- RsGxsChannels / p3GxsChannels: add setMessageReadStatus(channelId, msgIds, read).
- RsPosted / p3Posted: add setPostReadStatus(boardId, msgIds, read).

Both reuse the batched, single-transaction processMsgMetaChanges() path added
for forums, which already benefits every GXS service.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jolavillette
jolavillette force-pushed the fix/gxs-mark-all-read-nofreeze branch from a655d29 to 7047cd0 Compare July 18, 2026 19:01
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