Skip to content

Commit 975bb59

Browse files
authored
Log-adjusted backoff logic. (#1855)
The effect of the change is that initial attempts with small delay are spread out in time more, which increases the overall time that events are stored.
1 parent 07a75ed commit 975bb59

File tree

3 files changed

+112
-17
lines changed

3 files changed

+112
-17
lines changed

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/SchedulerConfig.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public abstract static class Builder {
6565

6666
private static final long ONE_SECOND = 1000;
6767

68+
private static final long BACKOFF_LOG_BASE = 10000;
69+
6870
public static SchedulerConfig getDefault(Clock clock) {
6971
return SchedulerConfig.builder()
7072
.addConfig(
@@ -135,10 +137,20 @@ public long getScheduleDelay(Priority priority, long minTimestamp, int attemptNu
135137
long timeDiff = minTimestamp - getClock().getTime();
136138
ConfigValue config = getValues().get(priority);
137139

138-
long delay = Math.max(((long) Math.pow(2, attemptNumber - 1)) * config.getDelta(), timeDiff);
140+
long delay = Math.max(adjustedExponentialBackoff(attemptNumber, config.getDelta()), timeDiff);
139141
return Math.min(delay, config.getMaxAllowedDelay());
140142
}
141143

144+
private long adjustedExponentialBackoff(int attemptNumber, long delta) {
145+
int attemptCoefficient = attemptNumber - 1;
146+
long deltaOr2 = delta > 1 ? delta : 2;
147+
148+
double logValue = Math.log(BACKOFF_LOG_BASE) / Math.log(deltaOr2 * attemptCoefficient);
149+
double logRegularized = Math.max(1, logValue);
150+
151+
return (long) (Math.pow(3, attemptCoefficient) * delta * logRegularized);
152+
}
153+
142154
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
143155
public JobInfo.Builder configureJob(
144156
JobInfo.Builder builder, Priority priority, long minimumTimestamp, int attemptNumber) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime.scheduling.jobscheduling;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
19+
import com.google.android.datatransport.Priority;
20+
import com.google.android.datatransport.runtime.time.Clock;
21+
import com.google.android.datatransport.runtime.time.TestClock;
22+
import edu.emory.mathcs.backport.java.util.Collections;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
import org.robolectric.RobolectricTestRunner;
26+
27+
@RunWith(RobolectricTestRunner.class)
28+
public class ScheduleDelayTest {
29+
private final Clock CLOCK = new TestClock(0);
30+
private static final int MAX_DELAY = 86400;
31+
32+
@Test
33+
public void getScheduleDelay_withDelta1_shouldBackoffAsExpected() {
34+
SchedulerConfig config = createConfig(1);
35+
long[] expectedDelays = new long[] {1, 39, 59, 138, 358, 972, 2702, 7632, 21795, 62721, 86400};
36+
for (int i = 0; i < expectedDelays.length; i++) {
37+
assertThat(config.getScheduleDelay(Priority.DEFAULT, 0, i + 1)).isEqualTo(expectedDelays[i]);
38+
}
39+
}
40+
41+
@Test
42+
public void getScheduleDelay_withDelta30_shouldBackoffAsExpected() {
43+
SchedulerConfig config = createConfig(30);
44+
long[] expectedDelays = new long[] {30, 243, 607, 1657, 4674, 13400, 38789, 86400};
45+
for (int i = 0; i < expectedDelays.length; i++) {
46+
assertThat(config.getScheduleDelay(Priority.DEFAULT, 0, i + 1)).isEqualTo(expectedDelays[i]);
47+
}
48+
}
49+
50+
@Test
51+
public void getScheduleDelay_withDelta24Hours_shouldBackoffAsExpected() {
52+
SchedulerConfig config = createConfig(MAX_DELAY);
53+
long[] expectedDelays = new long[] {86400, 86400, 86400};
54+
for (int i = 0; i < expectedDelays.length; i++) {
55+
assertThat(config.getScheduleDelay(Priority.DEFAULT, 0, i + 1)).isEqualTo(expectedDelays[i]);
56+
}
57+
}
58+
59+
private SchedulerConfig createConfig(int delta) {
60+
return SchedulerConfig.builder()
61+
.setClock(CLOCK)
62+
.addConfig(
63+
Priority.DEFAULT,
64+
SchedulerConfig.ConfigValue.builder()
65+
.setDelta(delta)
66+
.setMaxAllowedDelay(MAX_DELAY)
67+
.setFlags(Collections.emptySet())
68+
.build())
69+
.addConfig(
70+
Priority.VERY_LOW,
71+
SchedulerConfig.ConfigValue.builder().setDelta(0).setMaxAllowedDelay(0).build())
72+
.addConfig(
73+
Priority.HIGHEST,
74+
SchedulerConfig.ConfigValue.builder().setDelta(0).setMaxAllowedDelay(0).build())
75+
.build();
76+
}
77+
}

transport/transport-runtime/src/test/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/SchedulerConfigTest.java

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
public class SchedulerConfigTest {
3535

3636
private static final int DELTA = 10;
37-
private static final int MAX_DELAY = 100;
37+
private static final int MAX_DELAY = 86400;
3838
private final Clock CLOCK = new TestClock(0);
3939

4040
@Test
@@ -64,13 +64,13 @@ public void build_whenNotAllPrioritiesSet_shouldThrow() {
6464
assertThat(ex.getMessage()).contains("priorities");
6565
}
6666

67-
private SchedulerConfig createConfig(Set<SchedulerConfig.Flag> flags) {
67+
private SchedulerConfig createConfig(Set<SchedulerConfig.Flag> flags, int delta) {
6868
return SchedulerConfig.builder()
6969
.setClock(CLOCK)
7070
.addConfig(
7171
Priority.DEFAULT,
7272
SchedulerConfig.ConfigValue.builder()
73-
.setDelta(DELTA)
73+
.setDelta(delta)
7474
.setMaxAllowedDelay(MAX_DELAY)
7575
.setFlags(flags)
7676
.build())
@@ -85,44 +85,49 @@ private SchedulerConfig createConfig(Set<SchedulerConfig.Flag> flags) {
8585

8686
@Test
8787
public void getScheduleDelay_whenNoMinTimestampAndFirstAttempt_shouldReturnExpectedValue() {
88-
long delay = createConfig(Collections.emptySet()).getScheduleDelay(Priority.DEFAULT, 0, 1);
88+
long delay =
89+
createConfig(Collections.emptySet(), DELTA).getScheduleDelay(Priority.DEFAULT, 0, 1);
8990

9091
assertThat(delay).isEqualTo(10);
9192
}
9293

9394
@Test
9495
public void getScheduleDelay_whenNoMinTimestampAndSecondAttempt_shouldReturnExpectedValue() {
95-
long delay = createConfig(Collections.emptySet()).getScheduleDelay(Priority.DEFAULT, 0, 2);
96+
long delay =
97+
createConfig(Collections.emptySet(), DELTA).getScheduleDelay(Priority.DEFAULT, 0, 2);
9698

97-
assertThat(delay).isEqualTo(20);
99+
assertThat(delay).isEqualTo(120);
98100
}
99101

100102
@Test
101103
public void getScheduleDelay_withMinTimestampAndFirstAttempt_shouldReturnExpectedValue() {
102-
long delay = createConfig(Collections.emptySet()).getScheduleDelay(Priority.DEFAULT, 50, 1);
104+
long delay =
105+
createConfig(Collections.emptySet(), DELTA).getScheduleDelay(Priority.DEFAULT, 50, 1);
103106

104107
assertThat(delay).isEqualTo(50);
105108
}
106109

107110
@Test
108111
public void getScheduleDelay_withMinTimestampAndFourthAttempt_shouldReturnExpectedValue() {
109-
long delay = createConfig(Collections.emptySet()).getScheduleDelay(Priority.DEFAULT, 50, 4);
112+
long delay =
113+
createConfig(Collections.emptySet(), DELTA).getScheduleDelay(Priority.DEFAULT, 50, 4);
110114

111-
assertThat(delay).isEqualTo(80);
115+
assertThat(delay).isEqualTo(731);
112116
}
113117

114118
@Test
115119
public void getScheduleDelay_withMinTimestampAndFifthAttempt_shouldReturnMaxDelay() {
116-
long delay = createConfig(Collections.emptySet()).getScheduleDelay(Priority.DEFAULT, 50, 5);
120+
long delay =
121+
createConfig(Collections.emptySet(), DELTA).getScheduleDelay(Priority.DEFAULT, 50, 5);
117122

118-
assertThat(delay).isEqualTo(100);
123+
assertThat(delay).isEqualTo(2022);
119124
}
120125

121126
@Test
122127
public void configureJob_shouldSetCorrectDelay() {
123128
ComponentName serviceComponent =
124129
new ComponentName(RuntimeEnvironment.application, JobInfoSchedulerService.class);
125-
SchedulerConfig config = createConfig(Collections.emptySet());
130+
SchedulerConfig config = createConfig(Collections.emptySet(), DELTA);
126131

127132
JobInfo job =
128133
config
@@ -137,7 +142,7 @@ public void configureJob_shouldSetCorrectDelay() {
137142
public void configureJob_withDefaults_shouldSetCorrectFlags() {
138143
ComponentName serviceComponent =
139144
new ComponentName(RuntimeEnvironment.application, JobInfoSchedulerService.class);
140-
SchedulerConfig config = createConfig(Collections.emptySet());
145+
SchedulerConfig config = createConfig(Collections.emptySet(), DELTA);
141146

142147
JobInfo job =
143148
config
@@ -153,7 +158,8 @@ public void configureJob_withDefaults_shouldSetCorrectFlags() {
153158
public void configureJob_whenUnmetered_shouldSetCorrectFlags() {
154159
ComponentName serviceComponent =
155160
new ComponentName(RuntimeEnvironment.application, JobInfoSchedulerService.class);
156-
SchedulerConfig config = createConfig(EnumSet.of(SchedulerConfig.Flag.NETWORK_UNMETERED));
161+
SchedulerConfig config =
162+
createConfig(EnumSet.of(SchedulerConfig.Flag.NETWORK_UNMETERED), DELTA);
157163

158164
JobInfo job =
159165
config
@@ -169,7 +175,7 @@ public void configureJob_whenUnmetered_shouldSetCorrectFlags() {
169175
public void configureJob_whenIdle_shouldSetCorrectFlags() {
170176
ComponentName serviceComponent =
171177
new ComponentName(RuntimeEnvironment.application, JobInfoSchedulerService.class);
172-
SchedulerConfig config = createConfig(EnumSet.of(SchedulerConfig.Flag.DEVICE_IDLE));
178+
SchedulerConfig config = createConfig(EnumSet.of(SchedulerConfig.Flag.DEVICE_IDLE), DELTA);
173179

174180
JobInfo job =
175181
config
@@ -185,7 +191,7 @@ public void configureJob_whenIdle_shouldSetCorrectFlags() {
185191
public void configureJob_whenCharging_shouldSetCorrectFlags() {
186192
ComponentName serviceComponent =
187193
new ComponentName(RuntimeEnvironment.application, JobInfoSchedulerService.class);
188-
SchedulerConfig config = createConfig(EnumSet.of(SchedulerConfig.Flag.DEVICE_CHARGING));
194+
SchedulerConfig config = createConfig(EnumSet.of(SchedulerConfig.Flag.DEVICE_CHARGING), DELTA);
189195

190196
JobInfo job =
191197
config

0 commit comments

Comments
 (0)