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

Commit efd01ba5 authored by Shreyas Basarge's avatar Shreyas Basarge Committed by Android (Google) Code Review
Browse files

Merge "SyncManager sync blow up fix"

parents 80abba71 1675400a
Loading
Loading
Loading
Loading
+114 −49
Original line number Diff line number Diff line
@@ -96,6 +96,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Random;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Collection;
import java.util.Collections;
@@ -177,18 +178,6 @@ public class SyncManager {
     */
    private static final int SYNC_MONITOR_PROGRESS_THRESHOLD_BYTES = 10; // 10 bytes

    /**
     * How long to delay each queued {@link SyncHandler} message that may have occurred before boot
     * or befor the device became provisioned.
     */
    private static final long PER_SYNC_BOOT_DELAY_MILLIS = 1000L;  // 1 second

    /**
     * The maximum amount of time we're willing to delay syncs out of boot, after device has been
     * provisioned, etc.
     */
    private static final long MAX_SYNC_BOOT_DELAY_MILLIS = 60000L;  // 1 minute

    /**
     * If a previously scheduled sync becomes ready and we are low on storage, it gets
     * pushed back for this amount of time.
@@ -242,12 +231,24 @@ public class SyncManager {
    private SparseArray<SyncOperation> mScheduledSyncs = new SparseArray<SyncOperation>(32);
    private final Random mRand;

    private int getUnusedJobId() {
    private boolean isJobIdInUseLockedH(int jobId) {
        if (mScheduledSyncs.indexOfKey(jobId) >= 0) {
            return true;
        }
        for (ActiveSyncContext asc: mActiveSyncContexts) {
            if (asc.mSyncOperation.jobId == jobId) {
                return true;
            }
        }
        return false;
    }

    private int getUnusedJobIdH() {
        synchronized (mScheduledSyncs) {
            int newJobId = mRand.nextInt(Integer.MAX_VALUE);
            while (mScheduledSyncs.indexOfKey(newJobId) >= 0) {
            int newJobId;
            do {
                newJobId = mRand.nextInt(Integer.MAX_VALUE);
            }
            } while (isJobIdInUseLockedH(newJobId));
            return newJobId;
        }
    }
@@ -425,6 +426,35 @@ public class SyncManager {
        }
    }

    /**
     * Cancel all unnecessary jobs. This function will be run once after every boot.
     */
    private void cleanupJobs() {
        // O(n^2) in number of jobs, so we run this on the background thread.
        mSyncHandler.postAtFrontOfQueue(new Runnable() {
            @Override
            public void run() {
                List<SyncOperation> ops = getAllPendingSyncsFromCache();
                Set<String> cleanedKeys = new HashSet<String>();
                for (SyncOperation opx: ops) {
                    if (cleanedKeys.contains(opx.key)) {
                        continue;
                    }
                    cleanedKeys.add(opx.key);
                    for (SyncOperation opy: ops) {
                        if (opx == opy) {
                            continue;
                        }
                        if (opx.key.equals(opy.key)) {
                            removeSyncOperationFromCache(opy.jobId);
                            mJobScheduler.cancel(opy.jobId);
                        }
                    }
                }
            }
        });
    }

    private synchronized void verifyJobScheduler() {
        if (mJobScheduler != null) {
            return;
@@ -449,6 +479,7 @@ public class SyncManager {
                }
            }
        }
        cleanupJobs();
    }

    private JobScheduler getJobScheduler() {
@@ -722,7 +753,6 @@ public class SyncManager {
                             String requestedAuthority, Bundle extras, long beforeRuntimeMillis,
                             long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) {
        final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
        EndPoint ep = new EndPoint(requestedAccount,requestedAuthority, userId);
        if (extras == null) {
            extras = new Bundle();
        }
@@ -1197,7 +1227,18 @@ public class SyncManager {
        }

        // Check if duplicate syncs are pending. If found, keep one with least expected run time.
        if (!syncOperation.isReasonPeriodic()) {
        if (!syncOperation.isPeriodic) {
            // Check currently running syncs
            for (ActiveSyncContext asc: mActiveSyncContexts) {
                if (asc.mSyncOperation.key.equals(syncOperation.key)) {
                    if (isLoggable) {
                        Log.v(TAG, "Duplicate sync is already running. Not scheduling "
                                + syncOperation);
                    }
                    return;
                }
            }

            int duplicatesCount = 0;
            long now = SystemClock.elapsedRealtime();
            syncOperation.expectedRuntime = now + minDelay;
@@ -1240,14 +1281,16 @@ public class SyncManager {
            }
        }

        syncOperation.jobId = getUnusedJobId();
        // Syncs that are re-scheduled shouldn't get a new job id.
        if (syncOperation.jobId == SyncOperation.NO_JOB_ID) {
            syncOperation.jobId = getUnusedJobIdH();
        }
        addSyncOperationToCache(syncOperation);

        if (isLoggable) {
            Slog.v(TAG, "scheduling sync operation " + syncOperation.target.toString());
            Slog.v(TAG, "scheduling sync operation " + syncOperation.toString());
        }

        // This is done to give preference to syncs that are not pushed back.
        int priority = syncOperation.findPriority();

        final int networkType = syncOperation.isNotAllowedOnMetered() ?
@@ -1343,7 +1386,7 @@ public class SyncManager {
                Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
                        + "encountered an error: " + operation);
            }
            scheduleSyncOperationH(operation, 0 /* immediately */);
            scheduleSyncOperationH(operation);
        } else if (syncResult.tooManyRetries) {
            // If this sync aborted because the internal sync loop retried too many times then
            //   don't reschedule. Otherwise we risk getting into a retry loop.
@@ -1357,7 +1400,7 @@ public class SyncManager {
                Log.d(TAG, "retrying sync operation because even though it had an error "
                        + "it achieved some success");
            }
            scheduleSyncOperationH(operation, 0 /* immediately */);
            scheduleSyncOperationH(operation);
        } else if (syncResult.syncAlreadyInProgress) {
            if (isLoggable) {
                Log.d(TAG, "retrying sync operation that failed because there was already a "
@@ -2207,7 +2250,8 @@ public class SyncManager {
            synchronized (this) {
                if (!mBootCompleted || !mProvisioned) {
                    if (msg.what == MESSAGE_START_SYNC) {
                        deferSyncH((SyncOperation) msg.obj, 60*1000 /* 1 minute */);
                        SyncOperation op = (SyncOperation) msg.obj;
                        addSyncOperationToCache(op);
                    }
                    // Need to copy the message bc looper will recycle it.
                    Message m = Message.obtain(msg);
@@ -2272,21 +2316,24 @@ public class SyncManager {

                    case MESSAGE_STOP_SYNC:
                        op = (SyncOperation) msg.obj;
                        if (isLoggable) {
                            Slog.v(TAG, "Stop sync received.");
                        }
                        ActiveSyncContext asc = findActiveSyncContextH(op.jobId);
                        if (asc != null) {
                            runSyncFinishedOrCanceledH(null /* no result */, asc);
                            boolean reschedule = msg.arg1 != 0;
                            boolean applyBackoff = msg.arg2 != 0;
                            if (isLoggable) {
                            Slog.v(TAG, "Stop sync received. Reschedule: " + reschedule
                                Slog.v(TAG, "Stopping sync. Reschedule: " + reschedule
                                        + "Backoff: " + applyBackoff);
                            }
                            if (applyBackoff) {
                                increaseBackoffSetting(op.target);
                            }
                            if (reschedule) {
                            scheduleSyncOperationH(op);
                                deferStoppedSyncH(op, 0);
                            }
                        ActiveSyncContext asc = findActiveSyncContextH(op.jobId);
                        if (asc != null) {
                            runSyncFinishedOrCanceledH(null /* no result */, asc);
                        }
                        break;

@@ -2414,7 +2461,8 @@ public class SyncManager {

        /**
         * Defer the specified SyncOperation by rescheduling it on the JobScheduler with some
         * delay.
         * delay. This is equivalent to a failure. If this is a periodic sync, a delayed one-off
         * sync will be scheduled.
         */
        private void deferSyncH(SyncOperation op, long delay) {
            mSyncJobService.callJobFinished(op.jobId, false);
@@ -2426,14 +2474,22 @@ public class SyncManager {
            }
        }

        /* Same as deferSyncH, but assumes that job is no longer running on JobScheduler. */
        private void deferStoppedSyncH(SyncOperation op, long delay) {
            if (op.isPeriodic) {
                scheduleSyncOperationH(op.createOneTimeSyncOperation(), delay);
            } else {
                removeSyncOperationFromCache(op.jobId);
                scheduleSyncOperationH(op, delay);
            }
        }

        /**
         * Cancel an active sync and reschedule it on the JobScheduler with some delay.
         */
        private void deferActiveSyncH(ActiveSyncContext asc) {
            SyncOperation op = asc.mSyncOperation;

            mSyncHandler.obtainMessage(MESSAGE_STOP_SYNC, 0 /* no reschedule */,
                    0 /* no backoff */, op).sendToTarget();
            runSyncFinishedOrCanceledH(null, asc);
            deferSyncH(op, SYNC_DELAY_ON_CONFLICT);
        }

@@ -2467,6 +2523,7 @@ public class SyncManager {
                // Check for adapter delays.
                if (isAdapterDelayed(op.target)) {
                    deferSyncH(op, 0 /* No minimum delay */);
                    return;
                }
            } else {
                // Remove SyncOperation entry from mScheduledSyncs cache for non periodic jobs.
@@ -2567,10 +2624,10 @@ public class SyncManager {
                    Slog.v(TAG, "updating period " + syncOperation + " to " + pollFrequencyMillis
                            + " and flex to " + flexMillis);
                }
                removePeriodicSyncInternalH(syncOperation);
                syncOperation.periodMillis = pollFrequencyMillis;
                syncOperation.flexMillis = flexMillis;
                scheduleSyncOperationH(syncOperation);
                SyncOperation newOp = new SyncOperation(syncOperation, pollFrequencyMillis,
                        flexMillis);
                newOp.jobId = syncOperation.jobId;
                scheduleSyncOperationH(newOp);
            }
        }

@@ -2614,9 +2671,8 @@ public class SyncManager {
            SyncOperation op = new SyncOperation(target, syncAdapterInfo.uid,
                    syncAdapterInfo.componentName.getPackageName(), SyncOperation.REASON_PERIODIC,
                    SyncStorageEngine.SOURCE_PERIODIC, extras,
                    syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID);
            op.periodMillis = pollFrequencyMillis;
            op.flexMillis = flexMillis;
                    syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID,
                    pollFrequencyMillis, flexMillis);
            scheduleSyncOperationH(op);
            mSyncStorageEngine.reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
        }
@@ -2816,10 +2872,19 @@ public class SyncManager {
         * Should be called when a one-off instance of a periodic sync completes successfully.
         */
        private void reschedulePeriodicSyncH(SyncOperation syncOperation) {
            removeSyncOperationFromCache(syncOperation.sourcePeriodicId);
            getJobScheduler().cancel(syncOperation.sourcePeriodicId);
            SyncOperation periodic = syncOperation.createPeriodicSyncOperation();
            scheduleSyncOperationH(periodic);
            // Ensure that the periodic sync wasn't removed.
            SyncOperation periodicSync = null;
            List<SyncOperation> ops = getAllPendingSyncsFromCache();
            for (SyncOperation op: ops) {
                if (op.isPeriodic && syncOperation.matchesPeriodicOperation(op)) {
                    periodicSync = op;
                    break;
                }
            }
            if (periodicSync == null) {
                return;
            }
            scheduleSyncOperationH(periodicSync);
        }

        private void runSyncFinishedOrCanceledH(SyncResult syncResult,
+43 −50
Original line number Diff line number Diff line
@@ -79,9 +79,9 @@ public class SyncOperation {
    public final String key;

    /** Poll frequency of periodic sync in milliseconds */
    public long periodMillis;
    public final long periodMillis;
    /** Flex time of periodic sync in milliseconds */
    public long flexMillis;
    public final long flexMillis;
    /** Descriptive string key for this operation */
    public String wakeLockName;
    /**
@@ -90,6 +90,9 @@ public class SyncOperation {
     */
    public long expectedRuntime;

    /** Stores the number of times this sync operation failed and had to be retried. */
    int retries;

    /** jobId of the JobScheduler job corresponding to this sync */
    public int jobId;

@@ -103,23 +106,32 @@ public class SyncOperation {
    private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
                          int reason, int source, Bundle extras, boolean allowParallelSyncs) {
        this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false,
                NO_JOB_ID);
                NO_JOB_ID, 0, 0);
    }

    public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) {
        this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource,
                new Bundle(op.extras), op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId,
                periodMillis, flexMillis);
    }

    public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
                         int reason, int source, Bundle extras, boolean allowParallelSyncs,
                         boolean isPeriodic, int sourcePeriodicId) {
                         boolean isPeriodic, int sourcePeriodicId, long periodMillis,
                         long flexMillis) {
        this.target = info;
        this.owningUid = owningUid;
        this.owningPackage = owningPackage;
        this.reason = reason;
        this.syncSource = source;
        this.extras = new Bundle(extras);
        cleanBundle(this.extras);
        this.allowParallelSyncs = allowParallelSyncs;
        this.isPeriodic = isPeriodic;
        this.sourcePeriodicId = sourcePeriodicId;
        this.key = toKey(target, extras);
        this.periodMillis = periodMillis;
        this.flexMillis = flexMillis;
        this.jobId = NO_JOB_ID;
        this.key = toKey();
    }

    /* Get a one off sync operation instance from a periodic sync. */
@@ -128,18 +140,8 @@ public class SyncOperation {
            return null;
        }
        SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource,
                new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */);
        // Copied to help us recreate the periodic sync from this one off sync.
        op.periodMillis = periodMillis;
        op.flexMillis = flexMillis;
        return op;
    }

    public SyncOperation createPeriodicSyncOperation() {
        SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource,
                new Bundle(extras), allowParallelSyncs, true, NO_JOB_ID);
        op.periodMillis = periodMillis;
        op.flexMillis = flexMillis;
                new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */,
                periodMillis, flexMillis);
        return op;
    }

@@ -224,6 +226,7 @@ public class SyncOperation {
        jobInfoExtras.putLong("periodMillis", periodMillis);
        jobInfoExtras.putLong("flexMillis", flexMillis);
        jobInfoExtras.putLong("expectedRuntime", expectedRuntime);
        jobInfoExtras.putInt("retries", retries);
        return jobInfoExtras;
    }

@@ -240,6 +243,7 @@ public class SyncOperation {
        int initiatedBy;
        Bundle extras;
        boolean allowParallelSyncs, isPeriodic;
        long periodMillis, flexMillis;

        if (!jobExtras.getBoolean("SyncManagerJob", false)) {
            return null;
@@ -256,6 +260,8 @@ public class SyncOperation {
        allowParallelSyncs = jobExtras.getBoolean("allowParallelSyncs", false);
        isPeriodic = jobExtras.getBoolean("isPeriodic", false);
        initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID);
        periodMillis = jobExtras.getLong("periodMillis");
        flexMillis = jobExtras.getLong("flexMillis");
        extras = new Bundle();

        PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras");
@@ -277,37 +283,13 @@ public class SyncOperation {
        SyncStorageEngine.EndPoint target =
                new SyncStorageEngine.EndPoint(account, provider, userId);
        SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source,
                extras, allowParallelSyncs, isPeriodic, initiatedBy);
                extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis);
        op.jobId = jobExtras.getInt("jobId");
        op.periodMillis = jobExtras.getLong("periodMillis");
        op.flexMillis = jobExtras.getLong("flexMillis");
        op.expectedRuntime = jobExtras.getLong("expectedRuntime");
        op.retries = jobExtras.getInt("retries");
        return op;
    }

    /**
     * Make sure the bundle attached to this SyncOperation doesn't have unnecessary
     * flags set.
     * @param bundle to clean.
     */
    private void cleanBundle(Bundle bundle) {
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD);
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL);
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED);
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED);
    }

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

    /**
     * Determine whether if this sync operation is running, the provided operation would conflict
     * with it.
@@ -326,6 +308,12 @@ public class SyncOperation {
        return reason == REASON_PERIODIC;
    }

    boolean matchesPeriodicOperation(SyncOperation other) {
        return target.matchesSpec(other.target)
                && SyncManager.syncExtrasEquals(extras, other.extras, true)
                && periodMillis == other.periodMillis && flexMillis == other.flexMillis;
    }

    boolean isDerivedFromFailedPeriodicSync() {
        return sourcePeriodicId != NO_JOB_ID;
    }
@@ -339,15 +327,18 @@ public class SyncOperation {
        return 0;
    }

    static String toKey(SyncStorageEngine.EndPoint info, Bundle extras) {
    private String toKey() {
        StringBuilder sb = new StringBuilder();
        sb.append("provider: ").append(info.provider);
        sb.append(" account {name=" + info.account.name
        sb.append("provider: ").append(target.provider);
        sb.append(" account {name=" + target.account.name
                + ", user="
                + info.userId
                + target.userId
                + ", type="
                + info.account.type
                + target.account.type
                + "}");
        sb.append(" isPeriodic: ").append(isPeriodic);
        sb.append(" period: ").append(periodMillis);
        sb.append(" flex: ").append(flexMillis);
        sb.append(" extras: ");
        extrasToStringBuilder(extras, sb);
        return sb.toString();
@@ -360,7 +351,9 @@ public class SyncOperation {

    String dump(PackageManager pm, boolean useOneLine) {
        StringBuilder sb = new StringBuilder();
        sb.append(target.account.name)
        sb.append("JobId: ").append(jobId)
                .append(", ")
                .append(target.account.name)
                .append(" u")
                .append(target.userId).append(" (")
                .append(target.account.type)
+3 −7
Original line number Diff line number Diff line
@@ -145,14 +145,10 @@ public class SyncOperationTest extends AndroidTestCase {
                "provider", 0);
        Bundle extras = new Bundle();
        SyncOperation periodic = new SyncOperation(ep, 0, "package", 0, 0, extras, false, true,
                SyncOperation.NO_JOB_ID);
        periodic.periodMillis = 60000;
        periodic.flexMillis = 10000;
                SyncOperation.NO_JOB_ID, 60000, 10000);
        SyncOperation oneoff = periodic.createOneTimeSyncOperation();
        assertFalse("Conversion to oneoff sync failed.", oneoff.isPeriodic);
        SyncOperation recreated = oneoff.createPeriodicSyncOperation();
        assertTrue("Conversion to periodic sync failed.", oneoff.isPeriodic);
        assertEquals("Period not restored", periodic.periodMillis, recreated.periodMillis);
        assertEquals("Flex not restored", periodic.flexMillis, recreated.flexMillis);
        assertEquals("Period not restored", periodic.periodMillis, oneoff.periodMillis);
        assertEquals("Flex not restored", periodic.flexMillis, oneoff.flexMillis);
    }
}