Merge pull request #2659 from vector-im/feature/bma/fix_init_sync

Fix issue with delay set to 0
This commit is contained in:
Benoit Marty 2021-01-14 10:41:10 +01:00 committed by GitHub
commit 69efb45fb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 164 additions and 79 deletions

View file

@ -50,8 +50,9 @@ abstract class SyncService : Service() {
private var sessionId: String? = null
private var mIsSelfDestroyed: Boolean = false
private var syncTimeoutSeconds: Int = 6
private var syncDelaySeconds: Int = 60
private var syncTimeoutSeconds: Int = getDefaultSyncTimeoutSeconds()
private var syncDelaySeconds: Int = getDefaultSyncDelaySeconds()
private var periodic: Boolean = false
private var preventReschedule: Boolean = false
@ -119,7 +120,11 @@ abstract class SyncService : Service() {
serviceScope.coroutineContext.cancelChildren()
if (!preventReschedule && periodic && sessionId != null && backgroundDetectionObserver.isInBackground) {
Timber.d("## Sync: Reschedule service in $syncDelaySeconds sec")
onRescheduleAsked(sessionId ?: "", false, syncTimeoutSeconds, syncDelaySeconds)
onRescheduleAsked(
sessionId = sessionId ?: "",
syncTimeoutSeconds = syncTimeoutSeconds,
syncDelaySeconds = syncDelaySeconds
)
}
super.onDestroy()
}
@ -166,15 +171,22 @@ abstract class SyncService : Service() {
}
if (throwable is Failure.NetworkConnection) {
// Timeout is not critical, so retry as soon as possible.
val retryDelay = if (isInitialSync || throwable.cause is SocketTimeoutException) {
0
} else {
syncDelaySeconds
if (throwable.cause is SocketTimeoutException) {
// For big accounts, computing sync response can take time, but Synapse will cache the
// result for the next request. So keep retrying in loop
Timber.w("Timeout during sync, retry in loop")
doSync()
return
}
// Network might be off, no need to reschedule endless alarms :/
preventReschedule = true
// Instead start a work to restart background sync when network is on
onNetworkError(sessionId ?: "", isInitialSync, syncTimeoutSeconds, retryDelay)
onNetworkError(
sessionId = sessionId ?: "",
syncTimeoutSeconds = syncTimeoutSeconds,
syncDelaySeconds = syncDelaySeconds,
isPeriodic = periodic
)
}
// JobCancellation could be caught here when onDestroy cancels the coroutine context
if (isRunning.get()) stopMe()
@ -188,8 +200,8 @@ abstract class SyncService : Service() {
}
val matrix = Matrix.getInstance(applicationContext)
val safeSessionId = intent.getStringExtra(EXTRA_SESSION_ID) ?: return false
syncTimeoutSeconds = intent.getIntExtra(EXTRA_TIMEOUT_SECONDS, 6)
syncDelaySeconds = intent.getIntExtra(EXTRA_DELAY_SECONDS, 60)
syncTimeoutSeconds = intent.getIntExtra(EXTRA_TIMEOUT_SECONDS, getDefaultSyncTimeoutSeconds())
syncDelaySeconds = intent.getIntExtra(EXTRA_DELAY_SECONDS, getDefaultSyncDelaySeconds())
try {
val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId)
?: throw IllegalStateException("## Sync: You should have a session to make it work")
@ -208,11 +220,15 @@ abstract class SyncService : Service() {
}
}
abstract fun getDefaultSyncTimeoutSeconds(): Int
abstract fun getDefaultSyncDelaySeconds(): Int
abstract fun onStart(isInitialSync: Boolean)
abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int)
abstract fun onRescheduleAsked(sessionId: String, syncTimeoutSeconds: Int, syncDelaySeconds: Int)
abstract fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int)
abstract fun onNetworkError(sessionId: String, syncTimeoutSeconds: Int, syncDelaySeconds: Int, isPeriodic: Boolean)
override fun onBind(intent: Intent?): IBinder? {
return null

View file

@ -20,6 +20,7 @@ import android.content.Context
import androidx.work.ListenableWorker
import androidx.work.WorkerFactory
import androidx.work.WorkerParameters
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider
@ -32,6 +33,8 @@ class MatrixWorkerFactory @Inject constructor(
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
Timber.d("MatrixWorkerFactory.createWorker for $workerClassName")
val foundEntry =
workerFactories.entries.find { Class.forName(workerClassName).isAssignableFrom(it.key) }
val factoryProvider = foundEntry?.value

View file

@ -26,37 +26,36 @@ import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import im.vector.app.core.di.HasVectorInjector
import im.vector.app.core.services.VectorSyncService
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.internal.session.sync.job.SyncService
import timber.log.Timber
class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
lateinit var vectorPreferences: VectorPreferences
override fun onReceive(context: Context, intent: Intent) {
val appContext = context.applicationContext
if (appContext is HasVectorInjector) {
val activeSession = appContext.injector().activeSessionHolder().getSafeActiveSession()
if (activeSession == null) {
Timber.v("No active session don't launch sync service.")
return
}
vectorPreferences = appContext.injector().vectorPreferences()
}
Timber.d("## Sync: AlarmSyncBroadcastReceiver received intent")
val vectorPreferences = (context.applicationContext as? HasVectorInjector)
?.injector()
?.takeIf { it.activeSessionHolder().getSafeActiveSession() != null }
?.vectorPreferences()
?: return Unit.also { Timber.v("No active session, so don't launch sync service.") }
val sessionId = intent.getStringExtra(SyncService.EXTRA_SESSION_ID) ?: return
// This method is called when the BroadcastReceiver is receiving an Intent broadcast.
Timber.d("RestartBroadcastReceiver received intent")
VectorSyncService.newPeriodicIntent(context, sessionId, vectorPreferences.backgroundSyncTimeOut(), vectorPreferences.backgroundSyncDelay()).let {
try {
ContextCompat.startForegroundService(context, it)
} catch (ex: Throwable) {
Timber.i("## Sync: Failed to start service, Alarm scheduled to restart service")
scheduleAlarm(context, sessionId, vectorPreferences.backgroundSyncDelay())
Timber.e(ex)
}
}
VectorSyncService.newPeriodicIntent(
context = context,
sessionId = sessionId,
syncTimeoutSeconds = vectorPreferences.backgroundSyncTimeOut(),
syncDelaySeconds = vectorPreferences.backgroundSyncDelay(),
isNetworkBack = false
)
.let {
try {
ContextCompat.startForegroundService(context, it)
} catch (ex: Throwable) {
Timber.i("## Sync: Failed to start service, Alarm scheduled to restart service")
scheduleAlarm(context, sessionId, vectorPreferences.backgroundSyncDelay())
Timber.e(ex)
}
}
}
companion object {

View file

@ -38,14 +38,18 @@ fun Session.startSyncing(context: Context) {
val applicationContext = context.applicationContext
if (!hasAlreadySynced()) {
// initial sync is done as a service so it can continue below app lifecycle
VectorSyncService.newOneShotIntent(applicationContext, sessionId, 0).also {
try {
ContextCompat.startForegroundService(applicationContext, it)
} catch (ex: Throwable) {
// TODO
Timber.e(ex)
}
}
VectorSyncService.newOneShotIntent(
context = applicationContext,
sessionId = sessionId
)
.let {
try {
ContextCompat.startForegroundService(applicationContext, it)
} catch (ex: Throwable) {
// TODO
Timber.e(ex)
}
}
} else {
val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
Timber.v("--> is at least started? $isAtLeastStarted")

View file

@ -33,6 +33,7 @@ import androidx.work.WorkerParameters
import im.vector.app.R
import im.vector.app.core.extensions.vectorComponent
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.BackgroundSyncMode
import org.matrix.android.sdk.internal.session.sync.job.SyncService
import timber.log.Timber
@ -40,27 +41,26 @@ class VectorSyncService : SyncService() {
companion object {
fun newOneShotIntent(context: Context, sessionId: String, timeoutSeconds: Int): Intent {
fun newOneShotIntent(context: Context,
sessionId: String): Intent {
return Intent(context, VectorSyncService::class.java).also {
it.putExtra(EXTRA_SESSION_ID, sessionId)
it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds)
it.putExtra(EXTRA_TIMEOUT_SECONDS, 0)
it.putExtra(EXTRA_PERIODIC, false)
}
}
fun newPeriodicIntent(
context: Context,
sessionId: String,
timeoutSeconds: Int,
delayInSeconds: Int,
networkBack: Boolean = false
): Intent {
fun newPeriodicIntent(context: Context,
sessionId: String,
syncTimeoutSeconds: Int,
syncDelaySeconds: Int,
isNetworkBack: Boolean): Intent {
return Intent(context, VectorSyncService::class.java).also {
it.putExtra(EXTRA_SESSION_ID, sessionId)
it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds)
it.putExtra(EXTRA_TIMEOUT_SECONDS, syncTimeoutSeconds)
it.putExtra(EXTRA_PERIODIC, true)
it.putExtra(EXTRA_DELAY_SECONDS, delayInSeconds)
it.putExtra(EXTRA_NETWORK_BACK_RESTART, networkBack)
it.putExtra(EXTRA_DELAY_SECONDS, syncDelaySeconds)
it.putExtra(EXTRA_NETWORK_BACK_RESTART, isNetworkBack)
}
}
@ -78,6 +78,10 @@ class VectorSyncService : SyncService() {
notificationUtils = vectorComponent().notificationUtils()
}
override fun getDefaultSyncDelaySeconds() = BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
override fun getDefaultSyncTimeoutSeconds() = BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
override fun onStart(isInitialSync: Boolean) {
val notificationSubtitleRes = if (isInitialSync) {
R.string.notification_initial_sync
@ -88,20 +92,26 @@ class VectorSyncService : SyncService() {
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
}
override fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) {
rescheduleSyncService(sessionId, timeout, delay)
override fun onRescheduleAsked(sessionId: String,
syncTimeoutSeconds: Int,
syncDelaySeconds: Int) {
rescheduleSyncService(
sessionId = sessionId,
syncTimeoutSeconds = syncTimeoutSeconds,
syncDelaySeconds = syncDelaySeconds,
isPeriodic = true,
isNetworkBack = false
)
}
override fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) {
Timber.d("## Sync: A network error occured during sync")
override fun onNetworkError(sessionId: String,
syncTimeoutSeconds: Int,
syncDelaySeconds: Int,
isPeriodic: Boolean) {
Timber.d("## Sync: A network error occurred during sync")
val rescheduleSyncWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<RestartWhenNetworkOn>()
.setInputData(Data.Builder()
.putString("sessionId", sessionId)
.putInt("timeout", timeout)
.putInt("delay", delay)
.build()
)
.setInputData(RestartWhenNetworkOn.createInputData(sessionId, syncTimeoutSeconds, syncDelaySeconds, isPeriodic))
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
@ -124,31 +134,84 @@ class VectorSyncService : SyncService() {
notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE)
}
// I do not move or rename this class, since I'm not sure about the side effect regarding the WorkManager
class RestartWhenNetworkOn(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
val sessionId = inputData.getString("sessionId") ?: return Result.failure()
val timeout = inputData.getInt("timeout", 6)
val delay = inputData.getInt("delay", 60)
applicationContext.rescheduleSyncService(sessionId, timeout, delay, true)
Timber.d("## Sync: RestartWhenNetworkOn.doWork()")
val sessionId = inputData.getString(KEY_SESSION_ID) ?: return Result.failure()
val syncTimeoutSeconds = inputData.getInt(KEY_SYNC_TIMEOUT_SECONDS, BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS)
val syncDelaySeconds = inputData.getInt(KEY_SYNC_DELAY_SECONDS, BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS)
val isPeriodic = inputData.getBoolean(KEY_IS_PERIODIC, false)
applicationContext.rescheduleSyncService(
sessionId = sessionId,
syncTimeoutSeconds = syncTimeoutSeconds,
syncDelaySeconds = syncDelaySeconds,
isPeriodic = isPeriodic,
isNetworkBack = true
)
// Indicate whether the work finished successfully with the Result
return Result.success()
}
companion object {
fun createInputData(sessionId: String,
syncTimeoutSeconds: Int,
syncDelaySeconds: Int,
isPeriodic: Boolean
): Data {
return Data.Builder()
.putString(KEY_SESSION_ID, sessionId)
.putInt(KEY_SYNC_TIMEOUT_SECONDS, syncTimeoutSeconds)
.putInt(KEY_SYNC_DELAY_SECONDS, syncDelaySeconds)
.putBoolean(KEY_IS_PERIODIC, isPeriodic)
.build()
}
private const val KEY_SESSION_ID = "sessionId"
private const val KEY_SYNC_TIMEOUT_SECONDS = "timeout"
private const val KEY_SYNC_DELAY_SECONDS = "delay"
private const val KEY_IS_PERIODIC = "isPeriodic"
}
}
}
private fun Context.rescheduleSyncService(sessionId: String, timeout: Int, delay: Int, networkBack: Boolean = false) {
val periodicIntent = VectorSyncService.newPeriodicIntent(this, sessionId, timeout, delay, networkBack)
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PendingIntent.getForegroundService(this, 0, periodicIntent, 0)
private fun Context.rescheduleSyncService(sessionId: String,
syncTimeoutSeconds: Int,
syncDelaySeconds: Int,
isPeriodic: Boolean,
isNetworkBack: Boolean) {
Timber.d("## Sync: rescheduleSyncService")
val intent = if (isPeriodic) {
VectorSyncService.newPeriodicIntent(
context = this,
sessionId = sessionId,
syncTimeoutSeconds = syncTimeoutSeconds,
syncDelaySeconds = syncDelaySeconds,
isNetworkBack = isNetworkBack
)
} else {
PendingIntent.getService(this, 0, periodicIntent, 0)
VectorSyncService.newOneShotIntent(
context = this,
sessionId = sessionId
)
}
val firstMillis = System.currentTimeMillis() + delay * 1000L
val alarmMgr = getSystemService<AlarmManager>()!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
if (isNetworkBack || syncDelaySeconds == 0) {
// Do not wait, do the sync now (more reactivity if network back is due to user action)
startService(intent)
} else {
alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PendingIntent.getForegroundService(this, 0, intent, 0)
} else {
PendingIntent.getService(this, 0, intent, 0)
}
val firstMillis = System.currentTimeMillis() + syncDelaySeconds * 1000L
val alarmMgr = getSystemService<AlarmManager>()!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
} else {
alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
}
}
}