Skip to content

Commit 8d462eb

Browse files
committed
Schedule active context jobs upon initialization(b/139435527).
1 parent 137fb97 commit 8d462eb

File tree

8 files changed

+192
-5
lines changed

8 files changed

+192
-5
lines changed

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/TransportRuntime.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.android.datatransport.TransportFactory;
2121
import com.google.android.datatransport.runtime.scheduling.Scheduler;
2222
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
23+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkInitializer;
2324
import com.google.android.datatransport.runtime.time.Clock;
2425
import com.google.android.datatransport.runtime.time.Monotonic;
2526
import com.google.android.datatransport.runtime.time.WallTime;
@@ -48,11 +49,14 @@ public class TransportRuntime implements TransportInternal {
4849
@WallTime Clock eventClock,
4950
@Monotonic Clock uptimeClock,
5051
Scheduler scheduler,
51-
Uploader uploader) {
52+
Uploader uploader,
53+
WorkInitializer initializer) {
5254
this.eventClock = eventClock;
5355
this.uptimeClock = uptimeClock;
5456
this.scheduler = scheduler;
5557
this.uploader = uploader;
58+
59+
initializer.ensureContextsScheduled();
5660
}
5761

5862
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2019 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 com.google.android.datatransport.runtime.TransportContext;
18+
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
19+
import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard;
20+
import java.util.concurrent.Executor;
21+
import javax.inject.Inject;
22+
23+
public class WorkInitializer {
24+
private final Executor executor;
25+
private final EventStore store;
26+
private final WorkScheduler scheduler;
27+
private final SynchronizationGuard guard;
28+
29+
@Inject
30+
WorkInitializer(
31+
Executor executor, EventStore store, WorkScheduler scheduler, SynchronizationGuard guard) {
32+
this.executor = executor;
33+
this.store = store;
34+
this.scheduler = scheduler;
35+
this.guard = guard;
36+
}
37+
38+
public void ensureContextsScheduled() {
39+
executor.execute(
40+
() ->
41+
guard.runCriticalSection(
42+
() -> {
43+
for (TransportContext context : store.loadActiveContexts()) {
44+
scheduler.schedule(context, 1);
45+
}
46+
return null;
47+
}));
48+
}
49+
}

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/persistence/EventStore.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public interface EventStore extends Closeable {
5050
/** Load all pending events for a given backend. */
5151
Iterable<PersistedEvent> loadBatch(TransportContext transportContext);
5252

53+
Iterable<TransportContext> loadActiveContexts();
54+
5355
/** Remove events that have been stored for more than 7 days. */
5456
int cleanUp();
5557
}

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/persistence/SQLiteEventStore.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,28 @@ public Iterable<PersistedEvent> loadBatch(TransportContext transportContext) {
274274
});
275275
}
276276

277+
@Override
278+
public Iterable<TransportContext> loadActiveContexts() {
279+
return inTransaction(
280+
db ->
281+
tryWithCursor(
282+
db.rawQuery(
283+
"SELECT t.backend_name, t.priority, t.extras FROM transport_contexts AS t, events AS e WHERE e.context_id = t._id",
284+
new String[] {}),
285+
cursor -> {
286+
List<TransportContext> results = new ArrayList<>();
287+
while (cursor.moveToNext()) {
288+
results.add(
289+
TransportContext.builder()
290+
.setBackendName(cursor.getString(0))
291+
.setPriority(cursor.getInt(1))
292+
.setExtras(maybeBase64Decode(cursor.getString(2)))
293+
.build());
294+
}
295+
return results;
296+
}));
297+
}
298+
277299
@Override
278300
public int cleanUp() {
279301
long oneWeekAgo = wallClock.getTime() - config.getEventCleanUpAge();
@@ -286,6 +308,13 @@ public void close() {
286308
schemaManager.close();
287309
}
288310

311+
private static byte[] maybeBase64Decode(@Nullable String value) {
312+
if (value == null) {
313+
return null;
314+
}
315+
return Base64.decode(value, Base64.DEFAULT);
316+
}
317+
289318
/** Loads all events for a backend. */
290319
private List<PersistedEvent> loadEvents(SQLiteDatabase db, TransportContext transportContext) {
291320
List<PersistedEvent> events = new ArrayList<>();

transport/transport-runtime/src/test/java/com/google/android/datatransport/runtime/TransportRuntimeTest.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.google.android.datatransport.runtime.backends.TransportBackend;
3131
import com.google.android.datatransport.runtime.scheduling.ImmediateScheduler;
3232
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
33+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkInitializer;
3334
import java.util.Collections;
3435
import org.junit.Test;
3536
import org.junit.runner.RunWith;
@@ -40,9 +41,11 @@
4041
public class TransportRuntimeTest {
4142
private static final String TEST_KEY = "test";
4243
private static final String TEST_VALUE = "test-value";
43-
private TransportInternal transportInternalMock = mock(TransportInternal.class);
44-
private TransportBackend mockBackend = mock(TransportBackend.class);
45-
private BackendRegistry mockRegistry = mock(BackendRegistry.class);
44+
45+
private final TransportInternal transportInternalMock = mock(TransportInternal.class);
46+
private final TransportBackend mockBackend = mock(TransportBackend.class);
47+
private final BackendRegistry mockRegistry = mock(BackendRegistry.class);
48+
private final WorkInitializer mockInitializer = mock(WorkInitializer.class);
4649

4750
@Test
4851
public void testTransportInternalSend() {
@@ -75,7 +78,10 @@ public void testTransportRuntimeBackendDiscovery() {
7578
() -> eventMillis,
7679
() -> uptimeMillis,
7780
new ImmediateScheduler(Runnable::run, mockRegistry),
78-
new Uploader(null, null, null, null, null, null, () -> 2));
81+
new Uploader(null, null, null, null, null, null, () -> 2),
82+
mockInitializer);
83+
verify(mockInitializer, times(1)).ensureContextsScheduled();
84+
7985
when(mockRegistry.get(mockBackendName)).thenReturn(mockBackend);
8086
when(mockBackend.decorate(any()))
8187
.thenAnswer(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2019 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 org.mockito.Mockito.mock;
18+
import static org.mockito.Mockito.times;
19+
import static org.mockito.Mockito.verify;
20+
import static org.mockito.Mockito.when;
21+
22+
import com.google.android.datatransport.runtime.TransportContext;
23+
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
24+
import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard.CriticalSection;
25+
import java.util.Arrays;
26+
import org.junit.Test;
27+
import org.junit.runner.RunWith;
28+
import org.junit.runners.JUnit4;
29+
30+
@RunWith(JUnit4.class)
31+
public class WorkInitializerTest {
32+
33+
private final EventStore mockStore = mock(EventStore.class);
34+
private final WorkScheduler mockScheduler = mock(WorkScheduler.class);
35+
36+
private final WorkInitializer initializer =
37+
new WorkInitializer(Runnable::run, mockStore, mockScheduler, CriticalSection::execute);
38+
39+
@Test
40+
public void test() {
41+
TransportContext ctx1 =
42+
TransportContext.builder().setBackendName("backend1").setExtras(null).build();
43+
TransportContext ctx2 =
44+
TransportContext.builder().setBackendName("backend1").setExtras("e1".getBytes()).build();
45+
46+
when(mockStore.loadActiveContexts()).thenReturn(Arrays.asList(ctx1, ctx2));
47+
48+
initializer.ensureContextsScheduled();
49+
50+
verify(mockScheduler, times(1)).schedule(ctx1, 1);
51+
verify(mockScheduler, times(1)).schedule(ctx2, 1);
52+
}
53+
}

transport/transport-runtime/src/test/java/com/google/android/datatransport/runtime/scheduling/persistence/InMemoryEventStore.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ public synchronized Iterable<PersistedEvent> loadBatch(TransportContext transpor
102102
return events;
103103
}
104104

105+
@Override
106+
public synchronized Iterable<TransportContext> loadActiveContexts() {
107+
List<TransportContext> results = new ArrayList<>();
108+
for (Map.Entry<TransportContext, Map<Long, EventInternal>> entry : store.entrySet()) {
109+
if (entry.getValue().isEmpty()) {
110+
continue;
111+
}
112+
results.add(entry.getKey());
113+
}
114+
return results;
115+
}
116+
105117
@Override
106118
public int cleanUp() {
107119
return 0;

transport/transport-runtime/src/test/java/com/google/android/datatransport/runtime/scheduling/persistence/SQLiteEventStoreTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,36 @@ public void cleanUp_whenEventIsOld_shouldDeleteIt() {
268268
assertThat(store.cleanUp()).isEqualTo(1);
269269
assertThat(store.loadBatch(TRANSPORT_CONTEXT)).isEmpty();
270270
}
271+
272+
@Test
273+
public void loadActiveContexts_whenNoContextsAvailable_shouldReturnEmptyList() {
274+
assertThat(store.loadActiveContexts()).isEmpty();
275+
}
276+
277+
@Test
278+
public void loadActiveContexts_whenTwoContextsAvailable_shouldReturnThem() {
279+
TransportContext ctx1 =
280+
TransportContext.builder().setBackendName("backend1").setExtras(null).build();
281+
TransportContext ctx2 =
282+
TransportContext.builder().setBackendName("backend1").setExtras("e1".getBytes()).build();
283+
284+
store.persist(ctx1, EVENT);
285+
store.persist(ctx2, EVENT);
286+
287+
assertThat(store.loadActiveContexts()).containsExactly(ctx1, ctx2);
288+
}
289+
290+
@Test
291+
public void loadActiveContexts_whenTwoContextsWithOneAvailable_shouldReturnIt() {
292+
TransportContext ctx1 =
293+
TransportContext.builder().setBackendName("backend1").setExtras(null).build();
294+
TransportContext ctx2 =
295+
TransportContext.builder().setBackendName("backend1").setExtras("e1".getBytes()).build();
296+
297+
store.persist(ctx1, EVENT);
298+
PersistedEvent persistedEvent2 = store.persist(ctx2, EVENT);
299+
store.recordSuccess(Collections.singleton(persistedEvent2));
300+
301+
assertThat(store.loadActiveContexts()).containsExactly(ctx1);
302+
}
271303
}

0 commit comments

Comments
 (0)