Skip to content

Commit 24ebb95

Browse files
authored
Schedule active context jobs upon initialization(b/139435527). (#707)
* Schedule active context jobs upon initialization(b/139435527). * Fix tests * Address review comments + javadoc.
1 parent 6519064 commit 24ebb95

File tree

14 files changed

+272
-22
lines changed

14 files changed

+272
-22
lines changed

transport/transport-runtime/src/androidTest/java/com/google/android/datatransport/runtime/TestRuntimeComponent.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
import com.google.android.datatransport.runtime.scheduling.jobscheduling.SchedulerConfig;
2121
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
2222
import com.google.android.datatransport.runtime.scheduling.persistence.EventStoreModule;
23+
import com.google.android.datatransport.runtime.scheduling.persistence.SQLiteEventStore;
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;
2627
import dagger.BindsInstance;
2728
import dagger.Component;
29+
import java.io.IOException;
2830
import javax.inject.Singleton;
2931

3032
@Component(
@@ -38,6 +40,14 @@ abstract class TestRuntimeComponent extends TransportRuntimeComponent {
3840

3941
abstract TransportRuntime getTransportRuntime();
4042

43+
abstract SQLiteEventStore getEventStore();
44+
45+
@Override
46+
public void close() throws IOException {
47+
getEventStore().clearDb();
48+
super.close();
49+
}
50+
4151
@Component.Builder
4252
interface Builder {
4353
@BindsInstance

transport/transport-runtime/src/androidTest/java/com/google/android/datatransport/runtime/TestSchedulingModule.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,27 @@
1414

1515
package com.google.android.datatransport.runtime;
1616

17+
import static org.mockito.Mockito.spy;
18+
1719
import com.google.android.datatransport.runtime.scheduling.DefaultScheduler;
1820
import com.google.android.datatransport.runtime.scheduling.Scheduler;
21+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
22+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
1923
import dagger.Binds;
2024
import dagger.Module;
25+
import dagger.Provides;
26+
import javax.inject.Provider;
27+
import javax.inject.Singleton;
2128

2229
@Module
2330
abstract class TestSchedulingModule {
2431

2532
@Binds
2633
abstract Scheduler scheduler(DefaultScheduler scheduler);
34+
35+
@Provides
36+
@Singleton
37+
static WorkScheduler workScheduler(Provider<Uploader> uploader) {
38+
return spy(new TestWorkScheduler(uploader));
39+
}
2740
}

transport/transport-runtime/src/androidTest/java/com/google/android/datatransport/runtime/TestWorkScheduler.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@
1414

1515
package com.google.android.datatransport.runtime;
1616

17-
import android.content.Context;
17+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
1818
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
19+
import javax.inject.Provider;
1920

2021
public class TestWorkScheduler implements WorkScheduler {
2122

22-
private final Context context;
23+
private final Provider<Uploader> uploader;
2324

24-
public TestWorkScheduler(Context applicationContext) {
25-
this.context = applicationContext;
25+
TestWorkScheduler(Provider<Uploader> uploader) {
26+
this.uploader = uploader;
2627
}
2728

2829
@Override
2930
public void schedule(TransportContext transportContext, int attemptNumber) {
3031
if (attemptNumber > 2) {
3132
return;
3233
}
33-
TransportRuntime.initialize(context);
34-
TransportRuntime.getInstance().getUploader().upload(transportContext, attemptNumber, () -> {});
34+
uploader.get().upload(transportContext, attemptNumber, () -> {});
3535
}
3636
}

transport/transport-runtime/src/androidTest/java/com/google/android/datatransport/runtime/UploaderIntegrationTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static org.mockito.ArgumentMatchers.eq;
2020
import static org.mockito.Mockito.doThrow;
2121
import static org.mockito.Mockito.mock;
22-
import static org.mockito.Mockito.spy;
2322
import static org.mockito.Mockito.times;
2423
import static org.mockito.Mockito.verify;
2524
import static org.mockito.Mockito.when;
@@ -53,17 +52,17 @@ public class UploaderIntegrationTest {
5352
private final TransportBackend mockBackend = mock(TransportBackend.class);
5453
private final BackendRegistry mockRegistry = mock(BackendRegistry.class);
5554
private final Context context = InstrumentationRegistry.getInstrumentation().getContext();
56-
private final WorkScheduler spyScheduler = spy(new TestWorkScheduler(context));
5755

58-
private final TransportRuntimeComponent component =
56+
private final UploaderTestRuntimeComponent component =
5957
DaggerUploaderTestRuntimeComponent.builder()
6058
.setApplicationContext(context)
6159
.setBackendRegistry(mockRegistry)
62-
.setWorkScheduler(spyScheduler)
6360
.setEventClock(() -> 3)
6461
.setUptimeClock(() -> 1)
6562
.build();
6663

64+
private final WorkScheduler spyScheduler = component.getWorkScheduler();
65+
6766
@Rule public final TransportRuntimeRule runtimeRule = new TransportRuntimeRule(component);
6867

6968
@Before

transport/transport-runtime/src/androidTest/java/com/google/android/datatransport/runtime/UploaderTestRuntimeComponent.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
import android.content.Context;
1818
import com.google.android.datatransport.runtime.backends.BackendRegistry;
1919
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
20-
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
20+
import com.google.android.datatransport.runtime.scheduling.persistence.SQLiteEventStore;
2121
import com.google.android.datatransport.runtime.scheduling.persistence.SpyEventStoreModule;
2222
import com.google.android.datatransport.runtime.time.Clock;
2323
import com.google.android.datatransport.runtime.time.Monotonic;
2424
import com.google.android.datatransport.runtime.time.WallTime;
2525
import dagger.BindsInstance;
2626
import dagger.Component;
27+
import java.io.IOException;
2728
import javax.inject.Singleton;
2829

2930
@Component(
@@ -37,16 +38,21 @@ abstract class UploaderTestRuntimeComponent extends TransportRuntimeComponent {
3738

3839
abstract TransportRuntime getTransportRuntime();
3940

40-
abstract EventStore getEventStore();
41+
abstract SQLiteEventStore getEventStore();
42+
43+
abstract WorkScheduler getWorkScheduler();
44+
45+
@Override
46+
public void close() throws IOException {
47+
getEventStore().clearDb();
48+
super.close();
49+
}
4150

4251
@Component.Builder
4352
interface Builder {
4453
@BindsInstance
4554
Builder setApplicationContext(Context applicationContext);
4655

47-
@BindsInstance
48-
Builder setWorkScheduler(WorkScheduler workScheduler);
49-
5056
@BindsInstance
5157
Builder setBackendRegistry(BackendRegistry registry);
5258

transport/transport-runtime/src/androidTest/java/com/google/android/datatransport/runtime/scheduling/persistence/SpyEventStoreModule.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import static org.mockito.Mockito.spy;
1818

1919
import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard;
20+
import com.google.android.datatransport.runtime.time.Clock;
21+
import com.google.android.datatransport.runtime.time.Monotonic;
22+
import com.google.android.datatransport.runtime.time.WallTime;
2023
import dagger.Binds;
2124
import dagger.Module;
2225
import dagger.Provides;
@@ -32,10 +35,17 @@ static EventStoreConfig storeConfig() {
3235

3336
@Provides
3437
@Singleton
35-
static EventStore eventStore(SQLiteEventStore store) {
36-
return spy(store);
38+
static SQLiteEventStore sqliteEventStore(
39+
@WallTime Clock wallClock,
40+
@Monotonic Clock clock,
41+
EventStoreConfig config,
42+
SchemaManager schemaManager) {
43+
return spy(new SQLiteEventStore(wallClock, clock, config, schemaManager));
3744
}
3845

46+
@Binds
47+
abstract EventStore eventStore(SQLiteEventStore store);
48+
3949
@Binds
4050
abstract SynchronizationGuard synchronizationGuard(SQLiteEventStore store);
4151

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,62 @@
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+
/**
24+
* Re-schedules any contexts that have pending events and are not currently scheduled.
25+
*
26+
* <p>The reasons for them not to be scheduled include:
27+
*
28+
* <ul>
29+
* <li>Host application update to newer version
30+
* <li>Device restart
31+
* </ul>
32+
*
33+
* Note: there is no way for us to know how many attempts had been previously tried, so we
34+
* re-schedule from attempt 1.
35+
*/
36+
public class WorkInitializer {
37+
private final Executor executor;
38+
private final EventStore store;
39+
private final WorkScheduler scheduler;
40+
private final SynchronizationGuard guard;
41+
42+
@Inject
43+
WorkInitializer(
44+
Executor executor, EventStore store, WorkScheduler scheduler, SynchronizationGuard guard) {
45+
this.executor = executor;
46+
this.store = store;
47+
this.scheduler = scheduler;
48+
this.guard = guard;
49+
}
50+
51+
public void ensureContextsScheduled() {
52+
executor.execute(
53+
() ->
54+
guard.runCriticalSection(
55+
() -> {
56+
for (TransportContext context : store.loadActiveContexts()) {
57+
scheduler.schedule(context, 1);
58+
}
59+
return null;
60+
}));
61+
}
62+
}

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

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

53+
/** Load all {@link TransportContext}s that have pending events. */
54+
Iterable<TransportContext> loadActiveContexts();
55+
5356
/** Remove events that have been stored for more than 7 days. */
5457
int cleanUp();
5558
}

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import android.os.SystemClock;
2222
import android.util.Base64;
2323
import androidx.annotation.Nullable;
24+
import androidx.annotation.RestrictTo;
2425
import androidx.annotation.VisibleForTesting;
2526
import androidx.annotation.WorkerThread;
2627
import com.google.android.datatransport.runtime.EventInternal;
@@ -274,6 +275,28 @@ public Iterable<PersistedEvent> loadBatch(TransportContext transportContext) {
274275
});
275276
}
276277

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

312+
@RestrictTo(RestrictTo.Scope.TESTS)
313+
public void clearDb() {
314+
inTransaction(
315+
db -> {
316+
db.delete("events", null, new String[] {});
317+
db.delete("transport_contexts", null, new String[] {});
318+
return null;
319+
});
320+
}
321+
322+
private static byte[] maybeBase64Decode(@Nullable String value) {
323+
if (value == null) {
324+
return null;
325+
}
326+
return Base64.decode(value, Base64.DEFAULT);
327+
}
328+
289329
/** Loads all events for a backend. */
290330
private List<PersistedEvent> loadEvents(SQLiteDatabase db, TransportContext transportContext) {
291331
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(

0 commit comments

Comments
 (0)