Prevent backups from being scheduled twice within the jitter window.
Fixes #13559.
This commit is contained in:
parent
b113eec940
commit
aec0a9951a
4 changed files with 43 additions and 9 deletions
|
@ -47,7 +47,7 @@ public class LocalBackupListener extends PersistentAlarmManagerListener {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
int hour = SignalStore.settings().getBackupHour();
|
int hour = SignalStore.settings().getBackupHour();
|
||||||
int minute = SignalStore.settings().getBackupMinute();
|
int minute = SignalStore.settings().getBackupMinute();
|
||||||
LocalDateTime next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS);
|
LocalDateTime next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS, new Random());
|
||||||
|
|
||||||
long nextTime = JavaTimeExtensionsKt.toMillis(next);
|
long nextTime = JavaTimeExtensionsKt.toMillis(next);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ import org.thoughtcrime.securesms.util.RemoteConfig
|
||||||
import org.thoughtcrime.securesms.util.toMillis
|
import org.thoughtcrime.securesms.util.toMillis
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.Random
|
import java.util.Random
|
||||||
import java.util.concurrent.TimeUnit
|
import kotlin.time.Duration.Companion.days
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
class MessageBackupListener : PersistentAlarmManagerListener() {
|
class MessageBackupListener : PersistentAlarmManagerListener() {
|
||||||
override fun shouldScheduleExact(): Boolean {
|
override fun shouldScheduleExact(): Boolean {
|
||||||
|
@ -33,8 +34,8 @@ class MessageBackupListener : PersistentAlarmManagerListener() {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val BACKUP_JITTER_WINDOW_SECONDS = Math.toIntExact(TimeUnit.MINUTES.toSeconds(10))
|
private val BACKUP_JITTER_WINDOW_SECONDS = 10.minutes.inWholeSeconds.toInt()
|
||||||
private val BACKUP_MEDIA_SYNC_INTERVAL = TimeUnit.DAYS.toMillis(7)
|
private val BACKUP_MEDIA_SYNC_INTERVAL = 7.days.inWholeMilliseconds
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun schedule(context: Context?) {
|
fun schedule(context: Context?) {
|
||||||
|
@ -44,22 +45,23 @@ class MessageBackupListener : PersistentAlarmManagerListener() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getNextDailyBackupTimeFromNowWithJitter(now: LocalDateTime, hour: Int, minute: Int, maxJitterSeconds: Int): LocalDateTime {
|
fun getNextDailyBackupTimeFromNowWithJitter(now: LocalDateTime, hour: Int, minute: Int, maxJitterSeconds: Int, randomSource: Random = Random()): LocalDateTime {
|
||||||
var next = now.withHour(hour).withMinute(minute).withSecond(0)
|
var next = now.withHour(hour).withMinute(minute).withSecond(0)
|
||||||
|
|
||||||
if (!now.plusSeconds(maxJitterSeconds.toLong() / 2).isBefore(next)) {
|
val endOfJitterWindowForNow = now.plusSeconds(maxJitterSeconds.toLong() / 2)
|
||||||
|
while (!endOfJitterWindowForNow.isBefore(next)) {
|
||||||
next = next.plusDays(1)
|
next = next.plusDays(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val jitter = Random().nextInt(BACKUP_JITTER_WINDOW_SECONDS) - BACKUP_JITTER_WINDOW_SECONDS / 2
|
val jitter = randomSource.nextInt(maxJitterSeconds) - maxJitterSeconds / 2
|
||||||
return next.plusSeconds(jitter.toLong())
|
return next.plusSeconds(jitter.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNextBackupTimeToIntervalFromNow(): Long {
|
fun setNextBackupTimeToIntervalFromNow(maxJitterSeconds: Int = BACKUP_JITTER_WINDOW_SECONDS): Long {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val hour = SignalStore.settings.backupHour
|
val hour = SignalStore.settings.backupHour
|
||||||
val minute = SignalStore.settings.backupMinute
|
val minute = SignalStore.settings.backupMinute
|
||||||
var next = getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, BACKUP_JITTER_WINDOW_SECONDS)
|
var next = getNextDailyBackupTimeFromNowWithJitter(now, hour, minute, maxJitterSeconds)
|
||||||
next = when (SignalStore.backup.backupFrequency) {
|
next = when (SignalStore.backup.backupFrequency) {
|
||||||
BackupFrequency.MANUAL -> next.plusDays(364)
|
BackupFrequency.MANUAL -> next.plusDays(364)
|
||||||
BackupFrequency.MONTHLY -> next.plusDays(29)
|
BackupFrequency.MONTHLY -> next.plusDays(29)
|
||||||
|
|
|
@ -8,8 +8,12 @@ package org.thoughtcrime.securesms.service
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.thoughtcrime.securesms.BaseUnitTest
|
import org.thoughtcrime.securesms.BaseUnitTest
|
||||||
|
import org.thoughtcrime.securesms.testutil.MockRandom
|
||||||
|
import java.time.Duration
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
class BackListenerTest : BaseUnitTest() {
|
class BackListenerTest : BaseUnitTest() {
|
||||||
|
|
||||||
|
@ -44,4 +48,14 @@ class BackListenerTest : BaseUnitTest() {
|
||||||
val next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, 3, 0, jitterWindowSeconds)
|
val next = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, 3, 0, jitterWindowSeconds)
|
||||||
Assert.assertEquals(8, next.dayOfMonth)
|
Assert.assertEquals(8, next.dayOfMonth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBackupJitterWhenScheduledForMidnightButJitterMakesItRunJustBefore() {
|
||||||
|
val mockRandom = MockRandom(listOf(1.minutes.inWholeSeconds.toInt()))
|
||||||
|
val jitterWindowSeconds = 10.minutes.inWholeSeconds.toInt()
|
||||||
|
val now: LocalDateTime = LocalDateTime.of(2024, 6, 27, 23, 57, 0)
|
||||||
|
val next: LocalDateTime = MessageBackupListener.getNextDailyBackupTimeFromNowWithJitter(now, 0, 0, jitterWindowSeconds, mockRandom)
|
||||||
|
|
||||||
|
Assert.assertTrue(Duration.between(now, next).toSeconds() > (1.days.inWholeSeconds - jitterWindowSeconds))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.testutil
|
||||||
|
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.Random
|
||||||
|
|
||||||
|
class MockRandom(initialInts: List<Int>) : Random() {
|
||||||
|
|
||||||
|
val nextInts = LinkedList(initialInts)
|
||||||
|
|
||||||
|
override fun nextInt(): Int {
|
||||||
|
return nextInts.remove()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue