Skip to content

Commit

Permalink
Correctly handle DeadObjectException (bitfireAT/davx5#578, #591)
Browse files Browse the repository at this point in the history
* Soft fail sync on DeadObjectException so that it is retried without immediate error message

* Handle DeadObjectException (→ retries sync); Syncer: generalize all-catch
  • Loading branch information
rfc2822 committed May 9, 2024
1 parent ad24dd5 commit 857309c
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,21 @@ class AddressBookSyncer(
provider: ContentProviderClient, // for noop address book provider (not for contacts provider)
syncResult: SyncResult
) {
try {
if (updateLocalAddressBooks(account, syncResult)) {
context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.use { contactsProvider ->
for (addressBookAccount in LocalAddressBook.findAll(context, null, account).map { it.account }) {
Logger.log.info("Synchronizing address book $addressBookAccount")
syncAddresBook(
addressBookAccount,
extras,
ContactsContract.AUTHORITY,
httpClient,
contactsProvider,
syncResult
)
}
if (updateLocalAddressBooks(account, syncResult)) {
context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)?.use { contactsProvider ->
for (addressBookAccount in LocalAddressBook.findAll(context, null, account).map { it.account }) {
Logger.log.info("Synchronizing address book $addressBookAccount")
syncAddresBook(
addressBookAccount,
extras,
ContactsContract.AUTHORITY,
httpClient,
contactsProvider,
syncResult
)
}
}
} catch (e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't sync address books", e)
}

Logger.log.info("Address book sync complete")
}

private fun updateLocalAddressBooks(account: Account, syncResult: SyncResult): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,21 @@ class CalendarSyncer(context: Context): Syncer(context) {
provider: ContentProviderClient,
syncResult: SyncResult
) {
try {
val accountSettings = AccountSettings(context, account)
val accountSettings = AccountSettings(context, account)

if (accountSettings.getEventColors())
AndroidCalendar.insertColors(provider, account)
else
AndroidCalendar.removeColors(provider, account)
if (accountSettings.getEventColors())
AndroidCalendar.insertColors(provider, account)
else
AndroidCalendar.removeColors(provider, account)

updateLocalCalendars(provider, account, accountSettings)
updateLocalCalendars(provider, account, accountSettings)

val calendars = AndroidCalendar
.find(account, provider, LocalCalendar.Factory, "${CalendarContract.Calendars.SYNC_EVENTS}!=0", null)
for (calendar in calendars) {
Logger.log.info("Synchronizing calendar #${calendar.id}, URL: ${calendar.name}")
CalendarSyncManager(context, account, accountSettings, extras, httpClient.value, authority, syncResult, calendar).performSync()
}
} catch(e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't sync calendars", e)
val calendars = AndroidCalendar
.find(account, provider, LocalCalendar.Factory, "${CalendarContract.Calendars.SYNC_EVENTS}!=0", null)
for (calendar in calendars) {
Logger.log.info("Synchronizing calendar #${calendar.id}, URL: ${calendar.name}")
CalendarSyncManager(context, account, accountSettings, extras, httpClient.value, authority, syncResult, calendar).performSync()
}
Logger.log.info("Calendar sync complete")
}

private fun updateLocalCalendars(provider: ContentProviderClient, account: Account, settings: AccountSettings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,7 @@ class JtxSyncer(context: Context): Syncer(context) {

} catch (e: TaskProvider.ProviderTooOldException) {
TaskUtils.notifyProviderTooOld(context, e)
} catch (e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't sync jtx collections", e)
}
Logger.log.info("jtx sync complete")
}

private fun updateLocalCollections(account: Account, client: ContentProviderClient, settings: AccountSettings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.Context
import android.content.Intent
import android.content.SyncResult
import android.net.Uri
import android.os.DeadObjectException
import android.os.RemoteException
import android.provider.CalendarContract
import android.provider.ContactsContract
Expand Down Expand Up @@ -296,7 +297,12 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L

}, { e, local, remote ->
when (e) {
// sync was cancelled or account has been removed: re-throw to SyncAdapterService (now BaseSyncer)
// DeadObjectException (may occur when syncing takes too long and process is demoted to cached):
// re-throw to base Syncer → will cause soft error and restart the sync process
is DeadObjectException ->
throw e

// sync was cancelled or account has been removed: re-throw to BaseSyncer
is InterruptedException,
is InterruptedIOException,
is InvalidAccountException ->
Expand Down
20 changes: 17 additions & 3 deletions app/src/main/kotlin/at/bitfire/davdroid/syncadapter/Syncer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.accounts.Account
import android.content.ContentProviderClient
import android.content.Context
import android.content.SyncResult
import android.os.DeadObjectException
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.log.Logger
Expand Down Expand Up @@ -54,11 +55,11 @@ abstract class Syncer(val context: Context) {

@EntryPoint
@InstallIn(SingletonComponent::class)
interface SyncAdapterEntryPoint {
interface SyncerEntryPoint {
fun appDatabase(): AppDatabase
}

private val syncAdapterEntryPoint = EntryPointAccessors.fromApplication(context, SyncAdapterEntryPoint::class.java)
private val syncAdapterEntryPoint = EntryPointAccessors.fromApplication(context, SyncerEntryPoint::class.java)
internal val db = syncAdapterEntryPoint.appDatabase()


Expand All @@ -77,11 +78,24 @@ abstract class Syncer(val context: Context) {
val httpClient = lazy { HttpClient.Builder(context, accountSettings).build() }

try {
val runSync = true /* ose */
val runSync = /* ose */ true

if (runSync)
sync(account, extras, authority, httpClient, provider, syncResult)

} catch (e: DeadObjectException) {
/* May happen when the remote process dies or (since Android 14) when IPC (for instance with the calendar provider)
is suddenly forbidden because our sync process was demoted from a "service process" to a "cached process". */
Logger.log.log(Level.WARNING, "Received DeadObjectException, treating as soft error", e)
syncResult.stats.numIoExceptions++

} catch (e: InvalidAccountException) {
Logger.log.log(Level.WARNING, "Account was removed during synchronization", e)

} catch (e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't sync $authority", e)
syncResult.stats.numParseExceptions++

} finally {
if (httpClient.isInitialized())
httpClient.value.close()
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ androidx-work = "2.9.0"
appIntro = "7.0.0-beta02"
bitfire-cert4android = "f1cc9b9ca3"
bitfire-dav4jvm = "b87d772e44"
bitfire-ical4android = "f10bd57dac"
bitfire-vcard4android = "adf00bd98a"
bitfire-ical4android = "a3e886c738"
bitfire-vcard4android = "03a37a8284"
commons-collections = "4.4"
commons-lang = "3.14.0"
# don't update until API level 26 (Android 8) is the minimum API [https://github.com/bitfireAT/davx5/issues/130]
Expand Down

0 comments on commit 857309c

Please sign in to comment.