Merge branch 'fix/sync-scheduling-watches' into fix/never-synced-label

# Conflicts:
#	docs/BUGS.md
This commit is contained in:
nvrl committed 2026-10-31 19:26:28 +01:00
commit ab8fe0e61d
10 files changed
+661 -104

No files matched your search

+58 -21
View File
@@ -69,7 +69,6 @@ original wording.
| Entry | Title | What is left |
|---|---|---|
| [B273](#b273--a-restore-proposes-another-computers-paths-when-the-two-share-a-hostname) | A restore proposes another computer's paths when the two share a hostname | Not fixed |
| [B272](#b272--after-a-folder-is-removed-and-added-again-its-file-events-go-to-the-old-id) | After a folder is removed and added again, its file events go to the old id | Not fixed |
| [B271](#b271--one-failed-listing-makes-a-large-first-sync-start-over-from-nothing) | One failed listing makes a large first sync start over from nothing | Not fixed |
| [B270](#b270--one-file-left-in-a-mirror-folder-clears-the-wipe-guard-for-all-the-others) | One file left in a mirror folder clears the wipe guard for all the others | Not fixed |
| [B269](#b269--a-restore-that-skipped-every-folder-reports-success) | A restore that skipped every folder reports success | Not fixed |
@@ -79,10 +78,6 @@ original wording.
| [B265](#b265--address-keys-whose-token-is-for-an-inactive-account-key-never-open) | Address keys whose token is for an inactive account key never open | Needs an SDK change; not reproduced |
| [B264](#b264--a-mount-that-fails-after-starting-its-workers-leaves-them-running) | A mount that fails after starting its workers leaves them running | Not fixed |
| [B263](#b263--a-mount-that-can-never-succeed-retries-every-5-s-and-tells-only-the-journal) | A mount that can never succeed retries every 5 s and tells only the journal | Not fixed |
| [B262](#b262--watching-a-large-synced-folder-holds-up-the-sync-engine-and-a-failed-watch-is-never-retried) | Watching a large synced folder holds up the sync engine, and a failed watch is never retried | Not fixed |
| [B260](#b260--the-poll-pushes-a-new-folders-first-sync-behind-every-other-folder) | The poll pushes a new folder's first sync behind every other folder | Not fixed |
| [B259](#b259--the-sync-engine-can-die-or-hang-and-nothing-shows-it-until-a-restart) | The sync engine can die or hang, and nothing shows it until a restart | Not fixed; the trigger in #53 is not known |
| [B258](#b258--journalctl--p-warning-shows-nothing-whatever-the-daemon-logged) | `journalctl -p warning` shows nothing, whatever the daemon logged | Not fixed |
| [B134](#b134--a-file-deleted-online-while-the-drain-checks-its-write-is-kept-as-a-conflict-copy) | A file deleted online while the drain checks its write is kept as a conflict copy | Only with `local_first` off |
| [B91](#b91--the-daemon-hung-overnight-and-nothing-could-say-what-it-was-waiting-on) | The daemon hung overnight, and nothing could say what it was waiting on | The cause is not diagnosed; the daemon now reports where it is stuck and is restarted |
| [B67](#b67--fresh-state-restore-can-silently-select-a-new-device-high-22) | Fresh-state restore can silently select a new device (HIGH-22) | An ambiguous device after a loss of local state is not asked about |
@@ -95,6 +90,8 @@ original wording.
| Entry | Title | What is left |
|---|---|---|
| [B262](#b262--watching-a-large-synced-folder-holds-up-the-sync-engine-and-a-failed-watch-is-never-retried) | Watching a large synced folder holds up the sync engine, and a failed watch is never retried | The first pass still waits for the watch; a failed watch is not shown in the app |
| [B259](#b259--the-sync-engine-can-die-or-hang-and-nothing-shows-it-until-a-restart) | The sync engine can die or hang, and nothing shows it until a restart | No pass deadline; an ended engine is reported, not restarted; the trigger in #53 is not known |
| [B199](#b199--a-stopped-share-stays-on-the-shared-by-me-page-for-minutes) | A stopped share stays on the Shared by me page for minutes | The share itself is not deleted; the SDK has no call for it |
| [B88](#b88--pdfs-pin-rename-move-delete-and-sharing-still-cannot-name-a-path-under-a-secondary-mount) | `pdfs pin`, `rename`, `move`, `delete` and sharing still cannot name a path under a secondary mount | `pin`, `unpin`, opening a file, thumbnails, uploads, sharing and versions |
| [B72](#b72--pdfs-trash-never-returns) | `pdfs trash` never returns | The slowness of the first refresh is unmeasured |
@@ -116,6 +113,9 @@ a live account since.
| Entry | Title |
|---|---|
| [B261](#b261--a-folder-that-has-never-synced-reads-up-to-date) | A folder that has never synced reads "up to date" |
| [B272](#b272--after-a-folder-is-removed-and-added-again-its-file-events-go-to-the-old-id) | After a folder is removed and added again, its file events go to the old id |
| [B260](#b260--the-poll-pushes-a-new-folders-first-sync-behind-every-other-folder) | The poll pushes a new folder's first sync behind every other folder |
| [B258](#b258--journalctl--p-warning-shows-nothing-whatever-the-daemon-logged) | `journalctl -p warning` shows nothing, whatever the daemon logged |
| [B256](#b256--pdfs-copy-right-after-a-save-copies-the-old-version) | `pdfs copy` right after a save copies the old version |
| [B255](#b255--a-synced-folder-whose-files-are-all-gone-here-is-stuck-for-good) | A synced folder whose files are all gone here is stuck for good |
| [B254](#b254--an-edit-in-the-same-second-as-a-new-upload-never-arrives) | An edit in the same second as a new upload never arrives |
@@ -395,7 +395,7 @@ written before it existed.
### B272 — After a folder is removed and added again, its file events go to the old id
**Status:** Open
**Status:** Fixed, not verified live
**Found:** 2026-10-31, code audit after issue #53.
**Where:** `rewatch` in `crates/pdfs-fuse/src/sync/engine.rs`
@@ -407,8 +407,10 @@ two-minute poll, and the same goes for every later edit until the daemon restart
path is still wanted, so the old entry stays with the removed folder's id. Its events become
`Reconcile(old id)`, `sync_folder_get` finds no row, and the request is dropped.
**Fix.** Not done. Compare `(path, id)` pairs, and update the id of a path that is still
watched.
**Fix.** `keep_wanted` keeps a watch whose path is still wanted and gives it the id the
folder list has now.
**Test:** `a_re_added_folder_keeps_its_watch_under_the_new_id` in `pdfs-fuse`.
---
@@ -613,7 +615,8 @@ show it (a state file, and `STATUS=` to systemd).
### B262 — Watching a large synced folder holds up the sync engine, and a failed watch is never retried
**Status:** Open
**Status:** Partly fixed. A watch is still set up before the first pass, and a failed watch
is not shown in the folder's state.
**Found:** 2026-10-31, code audit after issue #53.
**Where:** `rewatch` and `engine_loop` in `crates/pdfs-fuse/src/sync/engine.rs`
@@ -631,8 +634,16 @@ line, the folder is never watched, and later changes are found only by the two-m
and they use up watches the next folder needs.
- Errors the watcher reports later are dropped in its callback.
**Fix.** Not done. Reconcile first, unwatch after a partial failure and retry with a back-off,
turn off symlink following, and show a watch failure in the folder's state.
**Fix.**
- A failed watch is unwatched, which removes the watches it had added under the folder, and
is tried again every 10 minutes. The warning names `fs.inotify.max_user_watches`.
- The watcher no longer follows symlinks.
- Errors the watcher reports are logged.
- Not done: the watch still runs before the folder's first pass. Watching first is what keeps
a change made during the pass from being missed, so the order stays; the cost is one walk
of a new folder's tree. A failed watch is not shown in the folder's state yet.
**Test:** none for the watch failure itself; it needs the inotify limit reached.
---
@@ -667,7 +678,7 @@ expects the new label.
### B260 — The poll pushes a new folder's first sync behind every other folder
**Status:** Open
**Status:** Fixed, not verified live
**Found:** 2026-10-31, code audit after issue #53.
**Where:** `engine_loop` in `crates/pdfs-fuse/src/sync/engine.rs`
@@ -680,14 +691,18 @@ running. When a burst holds both a `ReconcileAll` and a folder's `Reconcile(id)`
only `reconcile_all`, which goes in id order. A new folder has the highest id, so it always
goes last.
**Fix.** Not done. Run the folders asked for by id before the rest, and don't queue a poll
while a pass runs.
**Fix.** `run_burst` runs the folders a burst names by id first, lowest id first, and then
every other folder when the burst also holds a `ReconcileAll`. A named folder is read again
before its pass. A pause that lands between passes now stops the rest of the burst.
**Test:** `named_folders_run_before_the_rest_of_a_burst` in `pdfs-fuse`.
---
### B259 — The sync engine can die or hang, and nothing shows it until a restart
**Status:** Open
**Status:** Partly fixed. A hung pass is reported but not ended, and a panic outside a pass
still stops the engine; it is reported, not restarted.
**Found:** 2026-10-31, issue #53: a newly added folder did not sync until the reporter restarted
the app. All FUSE workers were idle, nothing was queued, and the warning-level journal was
empty (B258).
@@ -711,15 +726,30 @@ is why it helps.
nothing about sync.
- A database error when the engine reads a folder drops that folder's request without a log.
**Fix.** Not done. Catch a panic per pass, end its progress and set the folder to `error`. Have
the supervisor restart a finished engine thread. Log passes at info, and report the folder,
the pass age and the queue in `Diagnostics`.
**Fix.**
- Each pass runs under `catch_unwind` (`EngineHealth::contain` in
`crates/pdfs-fuse/src/sync/engine.rs`). A panic ends that pass only: its progress is cleared,
the folder is set to `error`, the activity feed says so, and the next poll tries again.
- `Diagnostics` carries the engine's state: running or not, the folder of the running pass, how
long it has run and how long since it last reported progress, and how many passes panicked.
`pdfs diagnostics` prints it as `sync engine: …`.
- The supervisor warns when the engine thread has ended, and when a pass has reported no
progress for 15 minutes.
- A first pass logs its start and end at info, as does any pass that changed something or
took 30 s or more. A request for a folder the database cannot read is logged.
- Not done: a deadline for a pass, and restarting an engine thread that has ended. The
channel's receiver ends with the thread, so a restart needs a new channel in every `Core`
clone.
**Test:** `a_panicking_pass_is_contained_and_counted`, `the_running_pass_is_reported` and
`the_engine_reads_as_stopped_once_its_thread_ends` in `pdfs-fuse`;
`a_stopped_sync_engine_is_a_stall` and `only_a_quiet_sync_pass_is_a_stall` in the supervisor.
---
### B258 — `journalctl -p warning` shows nothing, whatever the daemon logged
**Status:** Open
**Status:** Fixed, not verified live
**Found:** 2026-10-31, issue #53: the reporter ran `journalctl --user -u proton-drive.service -b
-p warning`, as `docs/TROUBLESHOOTING.md` tells them to, and got `-- No entries --`.
**Where:** `main` in `crates/pdfs-cli/src/main.rs`; `packaging/proton-drive.service`
@@ -732,8 +762,15 @@ stores every line of a service's output at the unit's default priority, info, un
starts with a `<N>` prefix. So every `WARN` and `ERROR` line is stored as info, and the
troubleshooting guide's filter hides all of them.
**Fix.** Not done. When `JOURNAL_STREAM` says the output goes to journald, start each line with
the `<N>` prefix for its level.
**Fix.** When standard output is the journal stream (`JOURNAL_STREAM` names the same
device and inode as fd 1), each line starts with the `<N>` prefix for its level, and ANSI colours
are off. A terminal that merely inherited `JOURNAL_STREAM` keeps the plain format
(`crates/pdfs-cli/src/journal.rs`).
**Test:** `each_line_opens_with_the_priority_of_its_level` and
`journal_stream_must_name_the_same_file` in `pdfs-cli`. On 2026-10-31, `pdfs diagnose` run with
`RUST_LOG=debug` under `systemd-run --user -p StandardOutput=journal` stored its debug lines at
`PRIORITY=7` and its report at 6.
---