Bug 1291821 - Keep track of sync deadline r=rnewman
authorGrisha Kruglov <gkruglov@mozilla.com>
Tue, 08 Nov 2016 12:38:00 -0800
changeset 344866 50608e0550054650b192bc451c3aeab6e6a0d5e6
parent 344865 63957df45bace6be17728d614625e6b358c0cc08
child 344867 3a43ecf3c62573024cd9b9d45d2edfc9c9e8d319
push id37970
push usergkruglov@mozilla.com
push dateSat, 25 Feb 2017 01:09:28 +0000
treeherderautoland@bd232d46a396 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1291821
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1291821 - Keep track of sync deadline r=rnewman MozReview-Commit-ID: Fvuc05K1arV
mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
@@ -55,30 +55,36 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
   private static final String LOG_TAG = FxAccountSyncAdapter.class.getSimpleName();
 
   public static final int NOTIFICATION_ID = LOG_TAG.hashCode();
 
   // Tracks the last seen storage hostname for backoff purposes.
   private static final String PREF_BACKOFF_STORAGE_HOST = "backoffStorageHost";
 
   // Used to do cheap in-memory rate limiting. Don't sync again if we
   // successfully synced within this duration.
   private static final int MINIMUM_SYNC_DELAY_MILLIS = 15 * 1000;        // 15 seconds.
   private volatile long lastSyncRealtimeMillis;
 
+  // Non-user initiated sync can't take longer than 30 minutes.
+  // To ensure we're not churning through device's battery/resources, we limit sync to 10 minutes,
+  // and request a re-sync if we hit that deadline.
+  private static final long SYNC_DEADLINE_DELTA_MILLIS = TimeUnit.MINUTES.toMillis(10);
+
   protected final ExecutorService executor;
   protected final FxAccountNotificationManager notificationManager;
 
   public FxAccountSyncAdapter(Context context, boolean autoInitialize) {
     super(context, autoInitialize);
     this.executor = Executors.newSingleThreadExecutor();
     this.notificationManager = new FxAccountNotificationManager(NOTIFICATION_ID);
   }
@@ -226,26 +232,26 @@ public class FxAccountSyncAdapter extend
     if (forced) {
       Logger.info(LOG_TAG, "Forced sync (" + kind + "): overruling remaining backoff of " + delay + "ms.");
     } else {
       Logger.info(LOG_TAG, "Not syncing (" + kind + "): must wait another " + delay + "ms.");
     }
     return forced;
   }
 
-  protected void syncWithAssertion(final String audience,
-                                   final String assertion,
+  protected void syncWithAssertion(final String assertion,
                                    final URI tokenServerEndpointURI,
                                    final BackoffHandler tokenBackoffHandler,
                                    final SharedPreferences sharedPrefs,
                                    final KeyBundle syncKeyBundle,
                                    final String clientState,
                                    final SessionCallback callback,
                                    final Bundle extras,
-                                   final AndroidFxAccount fxAccount) {
+                                   final AndroidFxAccount fxAccount,
+                                   final long syncDeadline) {
     final TokenServerClientDelegate delegate = new TokenServerClientDelegate() {
       private boolean didReceiveBackoff = false;
 
       @Override
       public String getUserAgent() {
         return FxAccountConstants.USER_AGENT;
       }
 
@@ -316,17 +322,17 @@ public class FxAccountSyncAdapter extend
           final Context context = getContext();
           final SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
 
           Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
           syncConfig.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
           syncConfig.setClusterURL(storageServerURI);
 
           globalSession = new GlobalSession(syncConfig, callback, context, clientsDataDelegate);
-          globalSession.start();
+          globalSession.start(syncDeadline);
         } catch (Exception e) {
           callback.handleError(globalSession, e);
           return;
         }
       }
 
       @Override
       public void handleFailure(TokenServerException e) {
@@ -381,16 +387,20 @@ public class FxAccountSyncAdapter extend
   @Override
   public void onPerformSync(final Account account, final Bundle extras, final String authority, ContentProviderClient provider, final SyncResult syncResult) {
     Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
     Logger.resetLogging();
 
     final Context context = getContext();
     final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
 
+    // NB: we use elapsedRealtime which is time since boot, to ensure our clock is monotonic and isn't
+    // paused while CPU is in the power-saving mode.
+    final long syncDeadline = SystemClock.elapsedRealtime() + SYNC_DEADLINE_DELTA_MILLIS;
+
     Logger.info(LOG_TAG, "Syncing FxAccount" +
         " account named like " + Utils.obfuscateEmail(account.name) +
         " for authority " + authority +
         " with instance " + this + ".");
 
     Logger.info(LOG_TAG, "Account last synced at: " + fxAccount.getLastSyncedTimestamp());
 
     if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
@@ -531,17 +541,19 @@ public class FxAccountSyncAdapter extend
               Logger.info(LOG_TAG, "Not syncing (token server).");
               syncDelegate.postponeSync(tokenBackoffHandler.delayMilliseconds());
               return;
             }
 
             final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy);
             final KeyBundle syncKeyBundle = married.getSyncKeyBundle();
             final String clientState = married.getClientState();
-            syncWithAssertion(audience, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, syncKeyBundle, clientState, sessionCallback, extras, fxAccount);
+            syncWithAssertion(
+                    assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs,
+                    syncKeyBundle, clientState, sessionCallback, extras, fxAccount, syncDeadline);
 
             // Register the device if necessary (asynchronous, in another thread)
             if (fxAccount.getDeviceRegistrationVersion() != FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION
                 || TextUtils.isEmpty(fxAccount.getDeviceId())) {
               FxAccountDeviceRegistrator.register(context);
             }
 
             // Force fetch the profile avatar information. (asynchronous, in another thread)
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
@@ -69,16 +69,18 @@ public class GlobalSession implements Ht
 
   protected Map<Stage, GlobalSyncStage> stages;
   public Stage currentState = Stage.idle;
 
   public final GlobalSessionCallback callback;
   protected final Context context;
   protected final ClientsDataDelegate clientsDelegate;
 
+  private long syncDeadline;
+
   /**
    * Map from engine name to new settings for an updated meta/global record.
    * Engines to remove will have <code>null</code> EngineSettings.
    */
   public final Map<String, EngineSettings> enginesToUpdate = new HashMap<String, EngineSettings>();
 
    /*
    * Key accessors.
@@ -229,16 +231,20 @@ public class GlobalSession implements Ht
         out.add(stage);
       } catch (NoSuchStageException e) {
         Logger.warn(LOG_TAG, "Unable to find stage with name " + name);
       }
     }
     return out;
   }
 
+  public long getSyncDeadline() {
+    return syncDeadline;
+  }
+
   /**
    * Advance and loop around the stages of a sync.
    * @param current
    * @return
    *        The next stage to execute.
    */
   public static Stage nextStage(Stage current) {
     int index = current.ordinal() + 1;
@@ -288,35 +294,40 @@ public class GlobalSession implements Ht
    * <ul>
    * <li>Verifying that any backoffs/minimum next sync requests are respected.</li>
    * <li>Ensuring that the device is online.</li>
    * <li>Ensuring that dependencies are ready.</li>
    * </ul>
    *
    * @throws AlreadySyncingException
    */
-  public void start() throws AlreadySyncingException {
+  public void start(final long syncDeadline) throws AlreadySyncingException {
     if (this.currentState != GlobalSyncStage.Stage.idle) {
       throw new AlreadySyncingException(this.currentState);
     }
+
+    // Make the deadline value available to stages via its getter.
+    this.syncDeadline = syncDeadline;
+
     installAsHttpResponseObserver(); // Uninstalled by completeSync or abort.
     this.advance();
   }
 
   /**
    * Stop this sync and start again.
    * @throws AlreadySyncingException
    */
   protected void restart() throws AlreadySyncingException {
     this.currentState = GlobalSyncStage.Stage.idle;
     if (callback.shouldBackOffStorage()) {
       this.callback.handleAborted(this, "Told to back off.");
       return;
     }
-    this.start();
+    // Restart with the same deadline as before.
+    this.start(syncDeadline);
   }
 
   /**
    * We're finished (aborted or succeeded): release resources.
    */
   protected void cleanUp() {
     uninstallAsHttpResponseObserver();
     this.stages = null;