Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 307da1a4 authored by Fred Quintana's avatar Fred Quintana
Browse files

enhance the sync manager backoff logic and add support for retry-after

moved SyncQueue and SyncOperation into their own top-level classes
to ease maintainability and testing6

removed some dead code
parent e0560e48
Loading
Loading
Loading
Loading
+12 −2
Original line number Diff line number Diff line
@@ -40253,6 +40253,16 @@
 visibility="public"
>
</field>
<field name="delayUntil"
 type="long"
 transient="false"
 volatile="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</field>
<field name="fullSyncRequested"
 type="boolean"
 transient="false"
@@ -71769,7 +71779,7 @@
 type="float"
 transient="false"
 volatile="false"
 value="0.001f"
 value="0.0010f"
 static="true"
 final="true"
 deprecated="not deprecated"
@@ -208805,7 +208815,7 @@
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="t" type="T">
<parameter name="arg0" type="T">
</parameter>
</method>
</interface>
+9 −2
Original line number Diff line number Diff line
@@ -1660,9 +1660,16 @@ public class AccountManagerService
                }
            }
        } else {
            Account[] accounts = getAccounts(null /* type */);
            fout.println("Accounts: " + accounts.length);
            for (Account account : accounts) {
                fout.println("  " + account);
            }

            fout.println();
            synchronized (mSessions) {
                final long now = SystemClock.elapsedRealtime();
                fout.println("AccountManagerService: " + mSessions.size() + " sessions");
                fout.println("Active Sessions: " + mSessions.size());
                for (Session session : mSessions.values()) {
                    fout.println("  " + session.toDebugString(now));
                }
+135 −482

File changed.

Preview size limit exceeded, changes collapsed.

+95 −0
Original line number Diff line number Diff line
package android.content;

import android.accounts.Account;
import android.os.Bundle;
import android.os.SystemClock;

/**
 * Value type that represents a sync operation.
 * @hide
 */
public class SyncOperation implements Comparable {
    public final Account account;
    public int syncSource;
    public String authority;
    public Bundle extras;
    public final String key;
    public long earliestRunTime;
    public boolean expedited;
    public SyncStorageEngine.PendingOperation pendingOperation;

    public SyncOperation(Account account, int source, String authority, Bundle extras,
            long delay) {
        this.account = account;
        this.syncSource = source;
        this.authority = authority;
        this.extras = new Bundle(extras);
        removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
        removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
        removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
        removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
        removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
        final long now = SystemClock.elapsedRealtime();
        if (delay < 0) {
            this.expedited = true;
            this.earliestRunTime = now;
        } else {
            this.expedited = false;
            this.earliestRunTime = now + delay;
        }
        this.key = toKey();
    }

    private void removeFalseExtra(String extraName) {
        if (!extras.getBoolean(extraName, false)) {
            extras.remove(extraName);
        }
    }

    SyncOperation(SyncOperation other) {
        this.account = other.account;
        this.syncSource = other.syncSource;
        this.authority = other.authority;
        this.extras = new Bundle(other.extras);
        this.expedited = other.expedited;
        this.earliestRunTime = SystemClock.elapsedRealtime();
        this.key = toKey();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("authority: ").append(authority);
        sb.append(" account: ").append(account);
        sb.append(" extras: ");
        extrasToStringBuilder(extras, sb);
        sb.append(" syncSource: ").append(syncSource);
        sb.append(" when: ").append(earliestRunTime);
        sb.append(" expedited: ").append(expedited);
        return sb.toString();
    }

    private String toKey() {
        StringBuilder sb = new StringBuilder();
        sb.append("authority: ").append(authority);
        sb.append(" account: ").append(account);
        sb.append(" extras: ");
        extrasToStringBuilder(extras, sb);
        return sb.toString();
    }

    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
        sb.append("[");
        for (String key : bundle.keySet()) {
            sb.append(key).append("=").append(bundle.get(key)).append(" ");
        }
        sb.append("]");
    }

    public int compareTo(Object o) {
        SyncOperation other = (SyncOperation)o;
        if (earliestRunTime == other.earliestRunTime) {
            return 0;
        }
        return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
    }
}
+190 −0
Original line number Diff line number Diff line
package android.content;

import com.google.android.collect.Maps;

import android.os.Bundle;
import android.os.SystemClock;
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.Iterator;

/**
 *
 * @hide
 */
public class SyncQueue {
    private static final String TAG = "SyncManager";
    private SyncStorageEngine mSyncStorageEngine;

    // 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 SyncQueue(SyncStorageEngine syncStorageEngine) {
        mSyncStorageEngine = syncStorageEngine;
        ArrayList<SyncStorageEngine.PendingOperation> ops
                = mSyncStorageEngine.getPendingOperations();
        final int N = ops.size();
        for (int i=0; i<N; i++) {
            SyncStorageEngine.PendingOperation op = ops.get(i);
            // -1 is a special value that means expedited
            final int delay = op.expedited ? -1 : 0;
            SyncOperation syncOperation = new SyncOperation(
                    op.account, op.syncSource, op.authority, op.extras, delay);
            syncOperation.pendingOperation = op;
            add(syncOperation, op);
        }
    }

    public boolean add(SyncOperation operation) {
        return add(operation, null /* this is not coming from the database */);
    }

    private boolean add(SyncOperation operation,
            SyncStorageEngine.PendingOperation pop) {
        // - if an operation with the same key exists and this one should run earlier,
        //   update the earliestRunTime of the existing to the new time
        // - if an operation with the same key exists and if this one should run
        //   later, ignore it
        // - if no operation exists then add the new one
        final String operationKey = operation.key;
        final SyncOperation existingOperation = mOperationsMap.get(operationKey);

        if (existingOperation != null) {
            boolean changed = false;
            if (existingOperation.expedited == operation.expedited) {
                final long newRunTime =
                        Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
                if (existingOperation.earliestRunTime != newRunTime) {
                    existingOperation.earliestRunTime = newRunTime;
                    changed = true;
                }
            } else {
                if (operation.expedited) {
                    existingOperation.expedited = true;
                    changed = true;
                }
            }
            return changed;
        }

        operation.pendingOperation = pop;
        if (operation.pendingOperation == null) {
            pop = new SyncStorageEngine.PendingOperation(
                            operation.account, operation.syncSource,
                            operation.authority, operation.extras, operation.expedited);
            pop = mSyncStorageEngine.insertIntoPending(pop);
            if (pop == null) {
                throw new IllegalStateException("error adding pending sync operation "
                        + operation);
            }
            operation.pendingOperation = pop;
        }

        mOperationsMap.put(operationKey, operation);
        return true;
    }

    public void remove(SyncOperation operation) {
        SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
        if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
            final String errorMessage = "unable to find pending row for " + operationToRemove;
            Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
        }
    }

    /**
     * Find the operation that should run next. Operations are sorted by their earliestRunTime,
     * prioritizing expedited operations. The earliestRunTime is adjusted by the sync adapter's
     * backoff and delayUntil times, if any.
     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}
     * @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.
     */
    private Pair<SyncOperation, Long> nextOperation(long now) {
        SyncOperation lowestOp = null;
        long lowestOpRunTime = 0;
        for (SyncOperation op : mOperationsMap.values()) {
            // effectiveRunTime:
            //   - backoffTime > currentTime : backoffTime
            //   - backoffTime <= currentTime : op.runTime
            Pair<Long, Long> backoff = null;
            long delayUntilTime = 0;
            final boolean isManualSync =
                    op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
            if (!isManualSync) {
                backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
                delayUntilTime = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
            }
            long backoffTime = Math.max(backoff != null ? backoff.first : 0, delayUntilTime);
            long opRunTime = backoffTime > now ? backoffTime : op.earliestRunTime;
            if (lowestOp == null
                    || (lowestOp.expedited == op.expedited
                        ? opRunTime < lowestOpRunTime
                        : op.expedited)) {
                lowestOp = op;
                lowestOpRunTime = opRunTime;
            }
        }
        if (lowestOp == null) {
            return null;
        }
        return Pair.create(lowestOp, lowestOpRunTime);
    }

    /**
     * Return when the next SyncOperation will be ready to run or null if there are
     * none.
     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
     * decide if the sync operation is ready to run
     * @return when the next SyncOperation will be ready to run, expressed in elapsedRealtime()
     */
    public Long nextRunTime(long now) {
        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
        if (nextOpAndRunTime == null) {
            return null;
        }
        return nextOpAndRunTime.second;
    }

    /**
     * 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 SyncOperation nextReadyToRun(long now) {
        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
        if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
            return null;
        }
        return nextOpAndRunTime.first;
    }

    public void clear(Account account, String authority) {
        Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry<String, SyncOperation> entry = entries.next();
            SyncOperation syncOperation = entry.getValue();
            if (account != null && !syncOperation.account.equals(account)) continue;
            if (authority != null && !syncOperation.authority.equals(authority)) continue;
            entries.remove();
            if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
                final String errorMessage = "unable to find pending row for " + syncOperation;
                Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
            }
        }
    }

    public void dump(StringBuilder sb) {
        sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
        for (SyncOperation operation : mOperationsMap.values()) {
            sb.append(operation).append("\n");
        }
    }
}
Loading