Merge "Make a separate active sync queue for initialization and regular syncs."

This commit is contained in:
Fred Quintana
2010-10-06 10:03:19 -07:00
committed by Android (Google) Code Review
5 changed files with 638 additions and 728 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -33,9 +33,12 @@ public class SyncOperation implements Comparable {
public long earliestRunTime;
public boolean expedited;
public SyncStorageEngine.PendingOperation pendingOperation;
public Long backoff;
public long delayUntil;
public long effectiveRunTime;
public SyncOperation(Account account, int source, String authority, Bundle extras,
long delayInMs) {
long delayInMs, long backoff, long delayUntil) {
this.account = account;
this.syncSource = source;
this.authority = authority;
@@ -48,6 +51,8 @@ public class SyncOperation implements Comparable {
removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
this.delayUntil = delayUntil;
this.backoff = backoff;
final long now = SystemClock.elapsedRealtime();
if (delayInMs < 0) {
this.expedited = true;
@@ -56,6 +61,7 @@ public class SyncOperation implements Comparable {
this.expedited = false;
this.earliestRunTime = now + delayInMs;
}
updateEffectiveRunTime();
this.key = toKey();
}
@@ -72,49 +78,78 @@ public class SyncOperation implements Comparable {
this.extras = new Bundle(other.extras);
this.expedited = other.expedited;
this.earliestRunTime = SystemClock.elapsedRealtime();
this.backoff = other.backoff;
this.delayUntil = other.delayUntil;
this.updateEffectiveRunTime();
this.key = toKey();
}
public String toString() {
return dump(true);
}
public String dump(boolean useOneLine) {
StringBuilder sb = new StringBuilder();
sb.append("authority: ").append(authority);
sb.append(" account: ").append(account);
sb.append(" extras: ");
extrasToStringBuilder(extras, sb, false /* asKey */);
sb.append(" syncSource: ").append(syncSource);
sb.append(" when: ").append(earliestRunTime);
sb.append(" expedited: ").append(expedited);
sb.append(account.name);
sb.append(" (" + account.type + ")");
sb.append(", " + authority);
sb.append(", ");
sb.append(SyncStorageEngine.SOURCES[syncSource]);
sb.append(", earliestRunTime " + earliestRunTime);
if (expedited) {
sb.append(", EXPEDITED");
}
if (!useOneLine && !extras.keySet().isEmpty()) {
sb.append("\n ");
extrasToStringBuilder(extras, sb);
}
return sb.toString();
}
public boolean isInitialization() {
return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
}
public boolean ignoreBackoff() {
return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
}
private String toKey() {
StringBuilder sb = new StringBuilder();
sb.append("authority: ").append(authority);
sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
sb.append(" extras: ");
extrasToStringBuilder(extras, sb, true /* asKey */);
extrasToStringBuilder(extras, sb);
return sb.toString();
}
public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) {
public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
sb.append("[");
for (String key : bundle.keySet()) {
// if we are writing this as a key don't consider whether this
// is an initialization sync or not when computing the key since
// we set this flag appropriately when dispatching the sync request.
if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) {
continue;
}
sb.append(key).append("=").append(bundle.get(key)).append(" ");
}
sb.append("]");
}
public void updateEffectiveRunTime() {
effectiveRunTime = ignoreBackoff()
? earliestRunTime
: Math.max(
Math.max(earliestRunTime, delayUntil),
backoff);
}
public int compareTo(Object o) {
SyncOperation other = (SyncOperation)o;
if (earliestRunTime == other.earliestRunTime) {
if (expedited != other.expedited) {
return expedited ? -1 : 1;
}
if (effectiveRunTime == other.effectiveRunTime) {
return 0;
}
return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
}
}

View File

@@ -16,17 +16,18 @@
package android.content;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.Pair;
import android.util.Log;
import android.accounts.Account;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
*
@@ -38,7 +39,7 @@ public class SyncQueue {
// A Map of SyncOperations operationKey -> SyncOperation that is designed for
// quick lookup of an enqueued SyncOperation.
private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
public final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
public SyncQueue(SyncStorageEngine syncStorageEngine) {
mSyncStorageEngine = syncStorageEngine;
@@ -47,8 +48,11 @@ public class SyncQueue {
final int N = ops.size();
for (int i=0; i<N; i++) {
SyncStorageEngine.PendingOperation op = ops.get(i);
final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority);
SyncOperation syncOperation = new SyncOperation(
op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
op.account, op.syncSource, op.authority, op.extras, 0 /* delay */,
backoff != null ? backoff.first : 0,
syncStorageEngine.getDelayUntilTime(op.account, op.authority));
syncOperation.expedited = op.expedited;
syncOperation.pendingOperation = op;
add(syncOperation, op);
@@ -119,65 +123,26 @@ public class SyncQueue {
}
}
/**
* Find the operation that should run next. Operations are sorted by their earliestRunTime,
* prioritizing first those with a syncable state of "unknown" that aren't retries then
* expedited operations.
* The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any.
* @return the operation that should run next and when it should run. The time may be in
* the future. It is expressed in milliseconds since boot.
*/
public Pair<SyncOperation, Long> nextOperation() {
SyncOperation best = null;
long bestRunTime = 0;
boolean bestSyncableIsUnknownAndNotARetry = false;
public void onBackoffChanged(Account account, String providerName, long backoff) {
// for each op that matches the account and provider update its
// backoff and effectiveStartTime
for (SyncOperation op : mOperationsMap.values()) {
long opRunTime = op.earliestRunTime;
if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
opRunTime = Math.max(
Math.max(opRunTime, delayUntil),
backoff != null ? backoff.first : 0);
}
// we know a sync is a retry if the intialization flag is set, since that will only
// be set by the sync dispatching code, thus if it is set it must have already been
// dispatched
final boolean syncableIsUnknownAndNotARetry =
!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
&& mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0;
// if the unsyncable state differs, make the current the best if it is unsyncable
// else, if the expedited state differs, make the current the best if it is expedited
// else, make the current the best if it is earlier than the best
if (best == null
|| ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry)
? (best.expedited == op.expedited
? opRunTime < bestRunTime
: op.expedited)
: syncableIsUnknownAndNotARetry)) {
best = op;
bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry;
bestRunTime = opRunTime;
if (op.account.equals(account) && op.authority.equals(providerName)) {
op.backoff = backoff;
op.updateEffectiveRunTime();
}
}
if (best == null) {
return null;
}
return Pair.create(best, bestRunTime);
}
/**
* Find and return the SyncOperation that should be run next and is ready to run.
* @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
* decide if the sync operation is ready to run
* @return the SyncOperation that should be run next and is ready to run.
*/
public Pair<SyncOperation, Long> nextReadyToRun(long now) {
Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
return null;
public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
// for each op that matches the account and provider update its
// delayUntilTime and effectiveStartTime
for (SyncOperation op : mOperationsMap.values()) {
if (op.account.equals(account) && op.authority.equals(providerName)) {
op.delayUntil = delayUntil;
op.updateEffectiveRunTime();
}
}
return nextOpAndRunTime;
}
public void remove(Account account, String authority) {
@@ -200,9 +165,17 @@ public class SyncQueue {
}
public void dump(StringBuilder sb) {
final long now = SystemClock.elapsedRealtime();
sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
for (SyncOperation operation : mOperationsMap.values()) {
sb.append(operation).append("\n");
sb.append(" ");
if (operation.effectiveRunTime <= now) {
sb.append("READY");
} else {
sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
}
sb.append(" - ");
sb.append(operation.dump(false)).append("\n");
}
}
}

View File

@@ -229,7 +229,7 @@ public class SyncStorageEngine extends Handler {
private final ArrayList<PendingOperation> mPendingOperations =
new ArrayList<PendingOperation>();
private SyncInfo mCurrentSync;
private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>();
private final SparseArray<SyncStatusInfo> mSyncStatus =
new SparseArray<SyncStatusInfo>();
@@ -690,23 +690,12 @@ public class SyncStorageEngine extends Handler {
/**
* Returns true if there is currently a sync operation for the given
* account or authority in the pending list, or actively being processed.
* account or authority actively being processed.
*/
public boolean isSyncActive(Account account, String authority) {
synchronized (mAuthorities) {
int i = mPendingOperations.size();
while (i > 0) {
i--;
// TODO(fredq): this probably shouldn't be considering
// pending operations.
PendingOperation op = mPendingOperations.get(i);
if (op.account.equals(account) && op.authority.equals(authority)) {
return true;
}
}
if (mCurrentSync != null) {
AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId);
for (SyncInfo syncInfo : mCurrentSyncs) {
AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
if (ainfo != null && ainfo.account.equals(account)
&& ainfo.authority.equals(authority)) {
return true;
@@ -887,40 +876,47 @@ public class SyncStorageEngine extends Handler {
}
/**
* Called when the currently active sync is changing (there can only be
* one at a time). Either supply a valid ActiveSyncContext with information
* about the sync, or null to stop the currently active sync.
* Called when a sync is starting. Supply a valid ActiveSyncContext with information
* about the sync.
*/
public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
final SyncInfo syncInfo;
synchronized (mAuthorities) {
if (activeSyncContext != null) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setActiveSync: account="
+ activeSyncContext.mSyncOperation.account
+ " auth=" + activeSyncContext.mSyncOperation.authority
+ " src=" + activeSyncContext.mSyncOperation.syncSource
+ " extras=" + activeSyncContext.mSyncOperation.extras);
}
if (mCurrentSync != null) {
Log.w(TAG, "setActiveSync called with existing active sync!");
}
AuthorityInfo authority = getAuthorityLocked(
activeSyncContext.mSyncOperation.account,
activeSyncContext.mSyncOperation.authority,
"setActiveSync");
if (authority == null) {
return;
}
mCurrentSync = new SyncInfo(authority.ident,
authority.account, authority.authority,
activeSyncContext.mStartTime);
} else {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
mCurrentSync = null;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setActiveSync: account="
+ activeSyncContext.mSyncOperation.account
+ " auth=" + activeSyncContext.mSyncOperation.authority
+ " src=" + activeSyncContext.mSyncOperation.syncSource
+ " extras=" + activeSyncContext.mSyncOperation.extras);
}
AuthorityInfo authority = getOrCreateAuthorityLocked(
activeSyncContext.mSyncOperation.account,
activeSyncContext.mSyncOperation.authority,
-1 /* assign a new identifier if creating a new authority */,
true /* write to storage if this results in a change */);
syncInfo = new SyncInfo(authority.ident,
authority.account, authority.authority,
activeSyncContext.mStartTime);
mCurrentSyncs.add(syncInfo);
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
reportActiveChange();
return syncInfo;
}
/**
* Called to indicate that a previously active sync is no longer active.
*/
public void removeActiveSync(SyncInfo syncInfo) {
synchronized (mAuthorities) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "removeActiveSync: account="
+ syncInfo.account + " auth=" + syncInfo.authority);
}
mCurrentSyncs.remove(syncInfo);
}
reportActiveChange();
}
/**
@@ -1095,10 +1091,26 @@ public class SyncStorageEngine extends Handler {
* Return the currently active sync information, or null if there is no
* active sync. Note that the returned object is the real, live active
* sync object, so be careful what you do with it.
* <p>
* Since multiple concurrent syncs are now supported you should use
* {@link #getCurrentSyncs()} to get the accurate list of current syncs.
* This method returns the first item from the list of current syncs
* or null if there are none.
* @deprecated use {@link #getCurrentSyncs()}
*/
public SyncInfo getCurrentSync() {
synchronized (mAuthorities) {
return mCurrentSync;
return !mCurrentSyncs.isEmpty() ? mCurrentSyncs.get(0) : null;
}
}
/**
* Return a list of the currently active syncs. Note that the returned items are the
* real, live active sync objects, so be careful what you do with it.
*/
public List<SyncInfo> getCurrentSyncs() {
synchronized (mAuthorities) {
return new ArrayList<SyncInfo>(mCurrentSyncs);
}
}

View File

@@ -1,164 +0,0 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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 android.content;
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContext;
import android.test.mock.MockContentResolver;
import android.accounts.Account;
import android.os.Bundle;
import android.os.SystemClock;
public class SyncQueueTest extends AndroidTestCase {
private static final Account ACCOUNT1 = new Account("test.account1", "test.type1");
private static final Account ACCOUNT2 = new Account("test.account2", "test.type2");
private static final String AUTHORITY1 = "test.authority1";
private static final String AUTHORITY2 = "test.authority2";
private static final String AUTHORITY3 = "test.authority3";
private SyncStorageEngine mSettings;
private SyncQueue mSyncQueue;
@Override
protected void setUp() throws Exception {
super.setUp();
MockContentResolver mockResolver = new MockContentResolver();
mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext()));
mSyncQueue = new SyncQueue(mSettings);
}
public void testSyncQueueOrder() throws Exception {
final SyncOperation op1 = new SyncOperation(
ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
final SyncOperation op2 = new SyncOperation(
ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
final SyncOperation op3 = new SyncOperation(
ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
final SyncOperation op4 = new SyncOperation(
ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60);
final SyncOperation op5 = new SyncOperation(
ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
final SyncOperation op6 = new SyncOperation(
ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
op6.expedited = true;
mSyncQueue.add(op1);
mSyncQueue.add(op2);
mSyncQueue.add(op3);
mSyncQueue.add(op4);
mSyncQueue.add(op5);
mSyncQueue.add(op6);
long now = SystemClock.elapsedRealtime() + 200;
assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op6);
assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op1);
assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op4);
assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op5);
assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op2);
assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op3);
}
public void testOrderWithBackoff() throws Exception {
final SyncOperation op1 = new SyncOperation(
ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
final SyncOperation op2 = new SyncOperation(
ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
final SyncOperation op3 = new SyncOperation(
ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
final SyncOperation op4 = new SyncOperation(
ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60);
final SyncOperation op5 = new SyncOperation(
ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
final SyncOperation op6 = new SyncOperation(
ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
op6.expedited = true;
mSyncQueue.add(op1);
mSyncQueue.add(op2);
mSyncQueue.add(op3);
mSyncQueue.add(op4);
mSyncQueue.add(op5);
mSyncQueue.add(op6);
long now = SystemClock.elapsedRealtime() + 200;
assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op6);
assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op1);
mSettings.setBackoff(ACCOUNT2, AUTHORITY3, now + 200, 5);
assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
mSettings.setBackoff(ACCOUNT2, AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, now + 200);
assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, 0);
assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op4);
assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op5);
assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op2);
assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
mSyncQueue.remove(op3);
}
Bundle newTestBundle(String val) {
Bundle bundle = new Bundle();
bundle.putString("test", val);
return bundle;
}
static class TestContext extends ContextWrapper {
ContentResolver mResolver;
public TestContext(ContentResolver resolver, Context realContext) {
super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
mResolver = resolver;
}
@Override
public void enforceCallingOrSelfPermission(String permission, String message) {
}
@Override
public ContentResolver getContentResolver() {
return mResolver;
}
}
}