Skip to content

Schedule active context jobs upon initialization(b/139435527). #707

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import com.google.android.datatransport.runtime.scheduling.jobscheduling.SchedulerConfig;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
import com.google.android.datatransport.runtime.scheduling.persistence.EventStoreModule;
import com.google.android.datatransport.runtime.scheduling.persistence.SQLiteEventStore;
import com.google.android.datatransport.runtime.time.Clock;
import com.google.android.datatransport.runtime.time.Monotonic;
import com.google.android.datatransport.runtime.time.WallTime;
import dagger.BindsInstance;
import dagger.Component;
import java.io.IOException;
import javax.inject.Singleton;

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

abstract TransportRuntime getTransportRuntime();

abstract SQLiteEventStore getEventStore();

@Override
public void close() throws IOException {
getEventStore().clearDb();
super.close();
}

@Component.Builder
interface Builder {
@BindsInstance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,27 @@

package com.google.android.datatransport.runtime;

import static org.mockito.Mockito.spy;

import com.google.android.datatransport.runtime.scheduling.DefaultScheduler;
import com.google.android.datatransport.runtime.scheduling.Scheduler;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import javax.inject.Provider;
import javax.inject.Singleton;

@Module
abstract class TestSchedulingModule {

@Binds
abstract Scheduler scheduler(DefaultScheduler scheduler);

@Provides
@Singleton
static WorkScheduler workScheduler(Provider<Uploader> uploader) {
return spy(new TestWorkScheduler(uploader));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@

package com.google.android.datatransport.runtime;

import android.content.Context;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
import javax.inject.Provider;

public class TestWorkScheduler implements WorkScheduler {

private final Context context;
private final Provider<Uploader> uploader;

public TestWorkScheduler(Context applicationContext) {
this.context = applicationContext;
TestWorkScheduler(Provider<Uploader> uploader) {
this.uploader = uploader;
}

@Override
public void schedule(TransportContext transportContext, int attemptNumber) {
if (attemptNumber > 2) {
return;
}
TransportRuntime.initialize(context);
TransportRuntime.getInstance().getUploader().upload(transportContext, attemptNumber, () -> {});
uploader.get().upload(transportContext, attemptNumber, () -> {});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -53,17 +52,17 @@ public class UploaderIntegrationTest {
private final TransportBackend mockBackend = mock(TransportBackend.class);
private final BackendRegistry mockRegistry = mock(BackendRegistry.class);
private final Context context = InstrumentationRegistry.getInstrumentation().getContext();
private final WorkScheduler spyScheduler = spy(new TestWorkScheduler(context));

private final TransportRuntimeComponent component =
private final UploaderTestRuntimeComponent component =
DaggerUploaderTestRuntimeComponent.builder()
.setApplicationContext(context)
.setBackendRegistry(mockRegistry)
.setWorkScheduler(spyScheduler)
.setEventClock(() -> 3)
.setUptimeClock(() -> 1)
.build();

private final WorkScheduler spyScheduler = component.getWorkScheduler();

@Rule public final TransportRuntimeRule runtimeRule = new TransportRuntimeRule(component);

@Before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
import android.content.Context;
import com.google.android.datatransport.runtime.backends.BackendRegistry;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
import com.google.android.datatransport.runtime.scheduling.persistence.SQLiteEventStore;
import com.google.android.datatransport.runtime.scheduling.persistence.SpyEventStoreModule;
import com.google.android.datatransport.runtime.time.Clock;
import com.google.android.datatransport.runtime.time.Monotonic;
import com.google.android.datatransport.runtime.time.WallTime;
import dagger.BindsInstance;
import dagger.Component;
import java.io.IOException;
import javax.inject.Singleton;

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

abstract TransportRuntime getTransportRuntime();

abstract EventStore getEventStore();
abstract SQLiteEventStore getEventStore();

abstract WorkScheduler getWorkScheduler();

@Override
public void close() throws IOException {
getEventStore().clearDb();
super.close();
}

@Component.Builder
interface Builder {
@BindsInstance
Builder setApplicationContext(Context applicationContext);

@BindsInstance
Builder setWorkScheduler(WorkScheduler workScheduler);

@BindsInstance
Builder setBackendRegistry(BackendRegistry registry);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import static org.mockito.Mockito.spy;

import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard;
import com.google.android.datatransport.runtime.time.Clock;
import com.google.android.datatransport.runtime.time.Monotonic;
import com.google.android.datatransport.runtime.time.WallTime;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
Expand All @@ -32,10 +35,17 @@ static EventStoreConfig storeConfig() {

@Provides
@Singleton
static EventStore eventStore(SQLiteEventStore store) {
return spy(store);
static SQLiteEventStore sqliteEventStore(
@WallTime Clock wallClock,
@Monotonic Clock clock,
EventStoreConfig config,
SchemaManager schemaManager) {
return spy(new SQLiteEventStore(wallClock, clock, config, schemaManager));
}

@Binds
abstract EventStore eventStore(SQLiteEventStore store);

@Binds
abstract SynchronizationGuard synchronizationGuard(SQLiteEventStore store);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.android.datatransport.TransportFactory;
import com.google.android.datatransport.runtime.scheduling.Scheduler;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkInitializer;
import com.google.android.datatransport.runtime.time.Clock;
import com.google.android.datatransport.runtime.time.Monotonic;
import com.google.android.datatransport.runtime.time.WallTime;
Expand Down Expand Up @@ -48,11 +49,14 @@ public class TransportRuntime implements TransportInternal {
@WallTime Clock eventClock,
@Monotonic Clock uptimeClock,
Scheduler scheduler,
Uploader uploader) {
Uploader uploader,
WorkInitializer initializer) {
this.eventClock = eventClock;
this.uptimeClock = uptimeClock;
this.scheduler = scheduler;
this.uploader = uploader;

initializer.ensureContextsScheduled();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.android.datatransport.runtime.scheduling.jobscheduling;

import com.google.android.datatransport.runtime.TransportContext;
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard;
import java.util.concurrent.Executor;
import javax.inject.Inject;

/**
* Re-schedules any contexts that have pending events and are not currently scheduled.
*
* <p>The reasons for them not to be scheduled include:
*
* <ul>
* <li>Host application update to newer version
* <li>Device restart
* </ul>
*
* Note: there is no way for us to know how many attempts had been previously tried, so we
* re-schedule from attempt 1.
*/
public class WorkInitializer {
private final Executor executor;
private final EventStore store;
private final WorkScheduler scheduler;
private final SynchronizationGuard guard;

@Inject
WorkInitializer(
Executor executor, EventStore store, WorkScheduler scheduler, SynchronizationGuard guard) {
this.executor = executor;
this.store = store;
this.scheduler = scheduler;
this.guard = guard;
}

public void ensureContextsScheduled() {
executor.execute(
() ->
guard.runCriticalSection(
() -> {
for (TransportContext context : store.loadActiveContexts()) {
scheduler.schedule(context, 1);
}
return null;
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public interface EventStore extends Closeable {
/** Load all pending events for a given backend. */
Iterable<PersistedEvent> loadBatch(TransportContext transportContext);

/** Load all {@link TransportContext}s that have pending events. */
Iterable<TransportContext> loadActiveContexts();

/** Remove events that have been stored for more than 7 days. */
int cleanUp();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import android.os.SystemClock;
import android.util.Base64;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.google.android.datatransport.runtime.EventInternal;
Expand Down Expand Up @@ -274,6 +275,28 @@ public Iterable<PersistedEvent> loadBatch(TransportContext transportContext) {
});
}

@Override
public Iterable<TransportContext> loadActiveContexts() {
return inTransaction(
db ->
tryWithCursor(
db.rawQuery(
"SELECT t.backend_name, t.priority, t.extras FROM transport_contexts AS t, events AS e WHERE e.context_id = t._id",
new String[] {}),
cursor -> {
List<TransportContext> results = new ArrayList<>();
while (cursor.moveToNext()) {
results.add(
TransportContext.builder()
.setBackendName(cursor.getString(0))
.setPriority(cursor.getInt(1))
.setExtras(maybeBase64Decode(cursor.getString(2)))
.build());
}
return results;
}));
}

@Override
public int cleanUp() {
long oneWeekAgo = wallClock.getTime() - config.getEventCleanUpAge();
Expand All @@ -286,6 +309,23 @@ public void close() {
schemaManager.close();
}

@RestrictTo(RestrictTo.Scope.TESTS)
public void clearDb() {
inTransaction(
db -> {
db.delete("events", null, new String[] {});
db.delete("transport_contexts", null, new String[] {});
return null;
});
}

private static byte[] maybeBase64Decode(@Nullable String value) {
if (value == null) {
return null;
}
return Base64.decode(value, Base64.DEFAULT);
}

/** Loads all events for a backend. */
private List<PersistedEvent> loadEvents(SQLiteDatabase db, TransportContext transportContext) {
List<PersistedEvent> events = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.google.android.datatransport.runtime.backends.TransportBackend;
import com.google.android.datatransport.runtime.scheduling.ImmediateScheduler;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader;
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkInitializer;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -40,9 +41,11 @@
public class TransportRuntimeTest {
private static final String TEST_KEY = "test";
private static final String TEST_VALUE = "test-value";
private TransportInternal transportInternalMock = mock(TransportInternal.class);
private TransportBackend mockBackend = mock(TransportBackend.class);
private BackendRegistry mockRegistry = mock(BackendRegistry.class);

private final TransportInternal transportInternalMock = mock(TransportInternal.class);
private final TransportBackend mockBackend = mock(TransportBackend.class);
private final BackendRegistry mockRegistry = mock(BackendRegistry.class);
private final WorkInitializer mockInitializer = mock(WorkInitializer.class);

@Test
public void testTransportInternalSend() {
Expand Down Expand Up @@ -75,7 +78,10 @@ public void testTransportRuntimeBackendDiscovery() {
() -> eventMillis,
() -> uptimeMillis,
new ImmediateScheduler(Runnable::run, mockRegistry),
new Uploader(null, null, null, null, null, null, () -> 2));
new Uploader(null, null, null, null, null, null, () -> 2),
mockInitializer);
verify(mockInitializer, times(1)).ensureContextsScheduled();

when(mockRegistry.get(mockBackendName)).thenReturn(mockBackend);
when(mockBackend.decorate(any()))
.thenAnswer(
Expand Down
Loading