A change to the feed list can silently undo another made at the same time #153

Closed
opened 2026-10-08 18:36:21 -07:00 by rays · 1 comment
Owner

Found in a code audit on 2026-10-09.

The catalogue and the server settings are written whole by Ctx::store_cfg (src/main.rs:183), from a copy each writer took earlier. Nothing stops two writers overlapping, and the longest window is web::add_feed (src/web.rs:1460-1483): it copies the config, then runs find_feed (up to two fetches) and add_one (another fetch, for the title), up to a minute or more, and then stores its copy.

Anything stored meanwhile is written over: another person's new feed (it leaves their sidebar and is never scanned, though their subscription row stays), an unsubscribe that took a feed out, an admin's change to the settings, the scan's follow_move (a feed's new address) or its category from classify, clean_directory. No error anywhere.

The other writers: add, rm, subscribe_opml, clean_directory, follow_move, the classify step in scan_one, patch_feed, remove_feed, patch_settings, and reload, which replaces the live copy from the database.

Fix: one async lock in Ctx held from the copy to the store by every writer, with add_feed taking its copy after the fetches that find the feed.

Found in a code audit on 2026-10-09. The catalogue and the server settings are written whole by `Ctx::store_cfg` (`src/main.rs:183`), from a copy each writer took earlier. Nothing stops two writers overlapping, and the longest window is `web::add_feed` (`src/web.rs:1460-1483`): it copies the config, then runs `find_feed` (up to two fetches) and `add_one` (another fetch, for the title), up to a minute or more, and then stores its copy. Anything stored meanwhile is written over: another person's new feed (it leaves their sidebar and is never scanned, though their subscription row stays), an unsubscribe that took a feed out, an admin's change to the settings, the scan's `follow_move` (a feed's new address) or its category from `classify`, `clean_directory`. No error anywhere. The other writers: `add`, `rm`, `subscribe_opml`, `clean_directory`, `follow_move`, the classify step in `scan_one`, `patch_feed`, `remove_feed`, `patch_settings`, and `reload`, which replaces the live copy from the database. Fix: one async lock in `Ctx` held from the copy to the store by every writer, with `add_feed` taking its copy after the fetches that find the feed.
rays added the bug label 2026-10-08 18:36:21 -07:00
Author
Owner

Fixed in c426ab6: every change takes its copy through Ctx::edit_cfg, which holds a lock until store_cfg keeps it, and store_cfg accepts nothing else, so no writer can skip it. add_feed takes its copy after finding the feed. Test: two_changes_to_the_catalogue_at_once_both_keep. Left as a ponytail comment on the lock: ipx add, rm and import write from a process of their own and are outside it. Deployed as 0.10.3-dev on 2026-10-09.

Fixed in c426ab6: every change takes its copy through Ctx::edit_cfg, which holds a lock until store_cfg keeps it, and store_cfg accepts nothing else, so no writer can skip it. add_feed takes its copy after finding the feed. Test: two_changes_to_the_catalogue_at_once_both_keep. Left as a ponytail comment on the lock: `ipx add`, `rm` and `import` write from a process of their own and are outside it. Deployed as 0.10.3-dev on 2026-10-09.
rays closed this issue 2026-10-08 18:58:41 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rays/ipx#153