Propagate Sync Cancellation and Exceptions Correctly - #2813
Draft
sunkup wants to merge 1 commit into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Corrects sync cancellation propagation and exception classification across syncers and WorkManager workers.
Changes:
- Re-throws cancellation instead of recording it as a sync error.
- Classifies provider death and unexpected exceptions correctly.
- Adds regression tests for contacts and shared sync behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
BaseSyncWorker.kt |
Documents result mapping and propagates cancellation. |
Syncer.kt |
Handles cancellation and exception classification. |
AddressBookSyncer.kt |
Removes exception swallowing. |
SyncerTest.kt |
Tests exception classification. |
AddressBookSyncerTest.kt |
Tests contacts exception propagation. |
Suppressed comments (1)
core/src/main/kotlin/at/bitfire/davdroid/sync/worker/BaseSyncWorker.kt:68
- issue (non-blocking): Not every hard error has already produced a user notification.
The generic branch in Syncer.invoke() only logs and sets hardError, and exceptions from handleGroupMethodChange() now reach that branch without passing through SyncManager.
* - Hard error (the user has to take action, for instance fix their credentials): [Result.failure].
* [at.bitfire.davdroid.sync.SyncManager] has already notified the user about the details.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+64
to
+65
| * - Soft error (temporary problem, like a network error) with attempts left: [Result.retry], so that | ||
| * WorkManager runs the work again later (at most [MAX_RUN_ATTEMPTS] attempts). |
| /** | ||
| * Acquires the content provider, runs the sync, and handles exceptions that are not handled by the SyncManager itself. | ||
| * | ||
| * Handled exceptions are recorded in [syncResult] as soft or hard error. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
It's true, like stated in the issue, that
Syncer.invoke()'scatch (e: Exception)has noCancellationExceptioncase, so a cancellation falls into the genericelsebranch, is logged atSEVEREand turned intosyncResult.hardError = trueinstead of being re-thrown. This affects events/tasks/jtx today (contacts never get there because of the extra catch described below).The message
Couldn't sync contacts, as mentioned in the issuecomes fromAddressBookSyncer, not fromSyncer.invoke()(whose message isCouldn't sync com.android.contacts), and the loggedSyncResult` has no error set at all.So the "swallow point" is
AddressBookSyncer.syncAddressBook()(core/src/main/kotlin/at/bitfire/davdroid/sync/AddressBookSyncer.kt:113):This full on catch "swallows" everything without recording an error in
syncResult, so the worker reportsResult.success().The other syncers (
CalendarSyncer,JtxSyncer,TaskSyncer) do not have such an exception catch, they letexceptions reach
Syncer.invoke()instead.Short description
AddressBookSyncer.kt: Remove the full oncatch (e: Exception)fromsyncAddressBook()soperformSync()re-thrown exceptions (including cancellation andDeadObjectException) andhandleGroupMethodChange()exceptions reachSyncer.invoke(). Per-collection error handling already happens inSyncManager/SyncExceptionHandler.Syncer.kt: Ininvoke(), re-throwCancellationException. KeepDeadObjectExceptionas a soft error,InvalidAccountExceptionas a warning, and all other exceptions as hard errors.BaseSyncWorker.kt: IndoIoWork(), explicitly logCancellationExceptionat INFO and re-throw it. UpdatedoSyncWork()KDoc to document the intended result/error mapping.Tests: Update
SyncerTest.ktto keepSyncResultand test cancellation propagation,DeadObjectExceptionassoftError, and unknown exception ashardError. AddAddressBookSyncerTest.ktto verify aCancellationExceptionfromContactsSyncManager.performSync()travels throughsyncCollection().Checklist