Skip to content

gmail sync-all: per-account summary line/progress bar can be silently dropped due to MultiProgress race #1652

Description

@newhoggy

Summary

omni-dev gmail sync-all can silently drop an account's per-account summary line (and its progress bars) from terminal output, even though that account's sync completed successfully and its counts are correctly folded into the final combined: total. This looks like data loss but is purely a terminal-rendering race — no actual sync/data issue.

Repro

Run gmail sync-all across 5+ configured accounts (concurrency > 1) where multiple accounts finish sync around the same time. Example observed output (account names anonymized to match a 5-account config):

jky.greens: 2 fetched, 0 errors
777.punchbowl: 1 fetched, 2 labels updated, 0 errors
chateau.de.ky: 3 fetched, 1 labels updated, 0 errors
newhoggy: 348 fetched, 3 vanished, 4 labels updated, 0 errors
newhoggy ######## 351/351 messages fetched
chateau.de.ky ######## 3/3 messages fetched
combined: 361 fetched, 3 vanished, 29 labels updated, 0 errors

Only 4 of the 5 configured accounts (jky.greens, 777.punchbowl, chateau.de.ky, newhoggy) got a printed summary line; the 5th (prosperacommercial) never printed a line or progress bar at all. However:

  • Sum of the 4 printed accounts: 354 fetched, 7 labels updated
  • combined: total: 361 fetched, 29 labels updated
  • Difference (361-354=7 fetched, 29-7=22 labels updated) exactly matches a plausible contribution from the missing 5th account

So prosperacommercial synced successfully and its counts reached combined, but its own terminal output was lost.

Root cause

Two independent tasks both redraw the same shared indicatif::MultiProgress for a given account, with no ordering guarantee between them:

  1. The main loop prints that account's summary line via multi.suspend(...) when its sync task completes (src/cli/gmail/sync_all.rs:316).
  2. A separate per-account render task (src/cli/gmail/sync_all.rs:282, render_tasks.push(tokio::spawn(bars.drain(rx)))) calls finish_and_clear() / finish() on that account's bars once its event channel closes (src/cli/gmail/sync/progress.rs:113-114).

Both are triggered by the same underlying event — run_one_account returning, which drops progress_tx and closes the channel (src/cli/gmail/sync_all.rs:294-296) — but they run as two independent Tokio tasks with no join point between them. MultiProgress::suspend clears all bars, runs its closure, then redraws, but has no way to coordinate with a concurrent finish()/finish_and_clear() call from the render task. Whichever redraw wins the race can clobber output the other just wrote.

The code only awaits all render_tasks (guaranteeing bars are settled) before printing the final combined: total (see the comment around src/cli/gmail/sync_all.rs:323-328) — there's no equivalent guarantee before each individual per-account summary print during the loop. That gap is what lets an account's own line/bars get silently dropped while its data still reaches combined correctly (the outcomes used for combined are populated independently of the printing).

Suggested fix directions

  • Await/join a given account's render task before (or as part of) printing its summary line, so the two redraws for that account can't race.
  • Alternatively, serialize all prints and bar-finish calls through a single coordinating point (e.g. have the render task itself perform or trigger the summary-line print for its account after finish_and_clear(), rather than having the main loop do it independently).

Impact

Cosmetic/observability only — no data loss. But it's misleading: a user watching the output can reasonably conclude an account was skipped entirely, when it actually synced fine.

Environment: omni-dev 0.41.0 (b37b0aed 2026-08-25), macOS, gmail-sync.yaml concurrency: 20, 5 configured accounts.

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