Sending: a send the server could not take (unreachable, timeout, 4xx) is no
longer failed at once. It waits in Scheduled ("retrying") and is tried again
after 1, 2, 5, 10, 30 min, then hourly, for 24 hours — on the server, so it
works with the browser closed. A permanent refusal (5xx, wrong password) is
"failed" at once, with the server's reason. GET /outbox lists both;
POST /outbox/{id}/retry (Retry now) and /cancel (Edit, Delete) act on them,
and they are left out of Drafts. Mail delivered but not filed in Sent now
counts as sent, and a conditional claim keeps two jobs from sending one
message twice. The app says when a send failed and when it finally went.
Actions: arq retries a job only on Retry, never on a plain exception, so the
"retry x5, then revert" for flags and moves never happened. Flag, move,
archive, restore, delete and empty-folder jobs now raise Retry while the
server is unreachable (~24 hours of growing gaps), skip a change a later one
replaced, stop at the first connection failure instead of 20 s per message,
and delete/empty keep the rows until the server has the messages gone.
Account health: 3 failed connections in a row mark an account unreachable
(amber in the sidebar and Settings, cleared on the next success); 3 refused
sign-ins stop its syncing as auth_failed until the password is entered
again, so the provider does not lock the account. Sync loops back off 30 s
doubling to 10 min and log one line per expected failure; a refused
connection fails at once with its reason (the connect task is kept), and a
silently dead IDLE connection is noticed within ~11 min instead of ~29.
Also: the empty reading pane is clipped to its width when dragged very
narrow; tests no longer leave live accounts and outbox rows in the database.
Migration 0021: outbox attempts / next_attempt_at / retry_until, accounts
sync_error / sync_error_since.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.0 KiB
Python
63 lines
2.0 KiB
Python
"""Worker job callables and queue function-sets (refactor §1, §6).
|
|
|
|
The process is launched via ``workers.run`` (``WORKER_ROLE`` decides which arq
|
|
workers run). This module only defines the job callables and the two queue
|
|
function-sets; the runner wires them onto the ``interactive`` / ``bulk`` queues.
|
|
The sync role runs the coordinator directly (see ``workers.run``), not via arq.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import uuid
|
|
from typing import Any
|
|
|
|
from arq import func
|
|
|
|
from app.outbox.draft_sync import push_draft_to_imap_job, remove_imap_draft_job
|
|
from app.outbox.jobs import send_job
|
|
from app.sync.actions import (
|
|
ACTION_MAX_TRIES,
|
|
delete_thread_job,
|
|
empty_folder_job,
|
|
move_job,
|
|
move_thread_to_folder_job,
|
|
push_flags_job,
|
|
restore_thread_job,
|
|
)
|
|
from app.sync.engine import bootstrap_and_backfill, refresh_account_job
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def sync_bootstrap_account(ctx: dict[str, Any], account_id: str) -> int:
|
|
"""Bootstrap + backfill an account (enqueued on account creation)."""
|
|
logger.info("sync.bootstrap_account start account=%s", account_id)
|
|
count = await bootstrap_and_backfill(uuid.UUID(account_id))
|
|
logger.info("sync.bootstrap_account done account=%s messages=%d", account_id, count)
|
|
return count
|
|
|
|
|
|
# Fast, interactive user actions (§6): short timeout, high concurrency. The ones
|
|
# pushing a change to the mail server retry (arq ``Retry``) for ~24 hours while
|
|
# the server is unreachable — more tries than arq's default of 5 allows.
|
|
_RETRYING_ACTIONS: list[Any] = [
|
|
push_flags_job,
|
|
move_job,
|
|
move_thread_to_folder_job,
|
|
restore_thread_job,
|
|
delete_thread_job,
|
|
empty_folder_job,
|
|
]
|
|
|
|
INTERACTIVE_FUNCTIONS: list[Any] = [
|
|
*(func(job, max_tries=ACTION_MAX_TRIES) for job in _RETRYING_ACTIONS),
|
|
send_job,
|
|
push_draft_to_imap_job,
|
|
remove_imap_draft_job,
|
|
refresh_account_job,
|
|
]
|
|
|
|
# Bulk work (§6): long timeout, low concurrency, isolated from interactive jobs.
|
|
BULK_FUNCTIONS = [sync_bootstrap_account]
|