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

Commit 5ebdf21a authored by Kweku Adams's avatar Kweku Adams
Browse files

Put syncs in a dedicated job namespace.

Put all sync jobs in a dedicated SyncManager job namespace so that sync
jobs don't conflict with jobs scheduled by other parts of the system.

Bug: 238764917
Bug: 253649458
Test: atest CtsSyncManagerTestsCases
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/content
Change-Id: I7190d9d710f83e319bf922d2dfbfdb13b5e77310
parent 6cc3ef13
Loading
Loading
Loading
Loading
+0 −8
Original line number Diff line number Diff line
@@ -17,23 +17,15 @@
package com.android.server.job;

import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.util.proto.ProtoOutputStream;

import java.util.List;

/**
 * JobScheduler local system service interface.
 * {@hide} Only for use within the system server.
 */
public interface JobSchedulerInternal {

    /**
     * Returns a list of pending jobs scheduled by the system service.
     */
    List<JobInfo> getSystemScheduledPendingJobs();

    /**
     * Cancel the jobs for a given uid (e.g. when app data is cleared)
     *
+0 −17
Original line number Diff line number Diff line
@@ -3474,23 +3474,6 @@ public class JobSchedulerService extends com.android.server.SystemService

    final class LocalService implements JobSchedulerInternal {

        /**
         * Returns a list of all pending jobs. A running job is not considered pending. Periodic
         * jobs are always considered pending.
         */
        @Override
        public List<JobInfo> getSystemScheduledPendingJobs() {
            synchronized (mLock) {
                final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
                mJobs.forEachJob(Process.SYSTEM_UID, (job) -> {
                    if (job.getJob().isPeriodic() || !mConcurrencyManager.isJobRunningLocked(job)) {
                        pendingJobs.add(job.getJob());
                    }
                });
                return pendingJobs;
            }
        }

        @Override
        public void cancelJobsForUid(int uid, boolean includeProxiedJobs,
                @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
+2 −0
Original line number Diff line number Diff line
@@ -83,4 +83,6 @@ message SyncStatusProto {
  }

  repeated StatusInfo status = 1;

  optional bool is_job_namespace_migrated = 2;
}
+52 −32
Original line number Diff line number Diff line
@@ -206,13 +206,6 @@ public class SyncManager {
     */
    private static final long SYNC_DELAY_ON_CONFLICT = 10*1000; // 10 seconds

    /**
     * Generate job ids in the range [MIN_SYNC_JOB_ID, MAX_SYNC_JOB_ID) to avoid conflicts with
     * other jobs scheduled by the system process.
     */
    private static final int MIN_SYNC_JOB_ID = 100000;
    private static final int MAX_SYNC_JOB_ID = 110000;

    private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*/";
    private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
    private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
@@ -243,12 +236,11 @@ public class SyncManager {

    volatile private PowerManager.WakeLock mSyncManagerWakeLock;
    volatile private boolean mDataConnectionIsConnected = false;
    private volatile int mNextJobIdOffset = 0;
    private volatile int mNextJobId = 0;

    private final NotificationManager mNotificationMgr;
    private final IBatteryStats mBatteryStats;
    private JobScheduler mJobScheduler;
    private JobSchedulerInternal mJobSchedulerInternal;

    private SyncStorageEngine mSyncStorageEngine;

@@ -284,24 +276,19 @@ public class SyncManager {
    }

    private int getUnusedJobIdH() {
        final int maxNumSyncJobIds = MAX_SYNC_JOB_ID - MIN_SYNC_JOB_ID;
        final List<JobInfo> pendingJobs = mJobSchedulerInternal.getSystemScheduledPendingJobs();
        for (int i = 0; i < maxNumSyncJobIds; ++i) {
            int newJobId = MIN_SYNC_JOB_ID + ((mNextJobIdOffset + i) % maxNumSyncJobIds);
            if (!isJobIdInUseLockedH(newJobId, pendingJobs)) {
                mNextJobIdOffset = (mNextJobIdOffset + i + 1) % maxNumSyncJobIds;
                return newJobId;
        final List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();
        while (isJobIdInUseLockedH(mNextJobId, pendingJobs)) {
            // SyncManager jobs are placed in their own namespace. Since there's no chance of
            // conflicting with other parts of the system, we can just keep incrementing until
            // we find an unused ID.
            mNextJobId++;
        }
        }
        // We've used all 10,000 intended job IDs.... We're probably in a world of pain right now :/
        Slog.wtf(TAG, "All " + maxNumSyncJobIds + " possible sync job IDs are taken :/");
        mNextJobIdOffset = (mNextJobIdOffset + 1) % maxNumSyncJobIds;
        return MIN_SYNC_JOB_ID + mNextJobIdOffset;
        return mNextJobId;
    }

    private List<SyncOperation> getAllPendingSyncs() {
        verifyJobScheduler();
        List<JobInfo> pendingJobs = mJobSchedulerInternal.getSystemScheduledPendingJobs();
        List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();
        final int numJobs = pendingJobs.size();
        final List<SyncOperation> pendingSyncs = new ArrayList<>(numJobs);
        for (int i = 0; i < numJobs; ++i) {
@@ -309,6 +296,8 @@ public class SyncManager {
            SyncOperation op = SyncOperation.maybeCreateFromJobExtras(job.getExtras());
            if (op != null) {
                pendingSyncs.add(op);
            } else {
                Slog.wtf(TAG, "Non-sync job inside of SyncManager's namespace");
            }
        }
        return pendingSyncs;
@@ -494,6 +483,38 @@ public class SyncManager {
        });
    }

    /**
     * Migrate syncs from the default job namespace to SyncManager's namespace if they haven't been
     * migrated already.
     */
    private void migrateSyncJobNamespaceIfNeeded() {
        if (mSyncStorageEngine.isJobNamespaceMigrated()) {
            return;
        }
        final JobScheduler jobSchedulerDefaultNamespace =
                mContext.getSystemService(JobScheduler.class);
        final List<JobInfo> pendingJobs = jobSchedulerDefaultNamespace.getAllPendingJobs();
        // Wait until we've confirmed that all syncs have been migrated to the new namespace
        // before we persist successful migration to our status file. This is done to avoid
        // internal consistency issues if the devices reboots right after SyncManager has
        // done the migration on its side but before JobScheduler has finished persisting
        // the updated jobs to disk. If JobScheduler hasn't persisted the update to disk,
        // then nothing that happened afterwards should have been persisted either, so there's
        // no concern over activity happening after the migration causing issues.
        boolean allSyncsMigrated = true;
        for (int i = pendingJobs.size() - 1; i >= 0; --i) {
            final JobInfo job = pendingJobs.get(i);
            final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(job.getExtras());
            if (op != null) {
                // This is a sync. Move it over to SyncManager's namespace.
                mJobScheduler.schedule(job);
                jobSchedulerDefaultNamespace.cancel(job.getId());
                allSyncsMigrated = false;
            }
        }
        mSyncStorageEngine.setJobNamespaceMigrated(allSyncsMigrated);
    }

    private synchronized void verifyJobScheduler() {
        if (mJobScheduler != null) {
            return;
@@ -503,10 +524,12 @@ public class SyncManager {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.d(TAG, "initializing JobScheduler object.");
            }
            mJobScheduler = (JobScheduler) mContext.getSystemService(
                    Context.JOB_SCHEDULER_SERVICE);
            mJobSchedulerInternal = getJobSchedulerInternal();
            // Get all persisted syncs from JobScheduler
            // Use a dedicated namespace to avoid conflicts with other jobs
            // scheduled by the system process.
            mJobScheduler = mContext.getSystemService(JobScheduler.class)
                    .forNamespace("SyncManager");
            migrateSyncJobNamespaceIfNeeded();
            // Get all persisted syncs from JobScheduler in the SyncManager namespace.
            List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();

            int numPersistedPeriodicSyncs = 0;
@@ -522,6 +545,8 @@ public class SyncManager {
                        // shown on the settings activity.
                        mSyncStorageEngine.markPending(op.target, true);
                    }
                } else {
                    Slog.wtf(TAG, "Non-sync job inside of SyncManager namespace");
                }
            }
            final String summary = "Loaded persisted syncs: "
@@ -543,11 +568,6 @@ public class SyncManager {
        }
    }

    @VisibleForTesting
    protected JobSchedulerInternal getJobSchedulerInternal() {
        return LocalServices.getService(JobSchedulerInternal.class);
    }

    /**
     * @return whether the device most likely has some periodic syncs.
     */
+23 −1
Original line number Diff line number Diff line
@@ -172,6 +172,8 @@ public class SyncStorageEngine {

    private volatile boolean mIsClockValid;

    private volatile boolean mIsJobNamespaceMigrated;

    static {
        sAuthorityRenames = new HashMap<String, String>();
        sAuthorityRenames.put("contacts", "com.android.contacts");
@@ -836,6 +838,20 @@ public class SyncStorageEngine {
        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, target);
    }

    void setJobNamespaceMigrated(boolean migrated) {
        if (mIsJobNamespaceMigrated == migrated) {
            return;
        }
        mIsJobNamespaceMigrated = migrated;
        // This isn't urgent enough to write synchronously. Post it to the handler thread so
        // SyncManager can move on with whatever it was doing.
        mHandler.sendEmptyMessageDelayed(MSG_WRITE_STATUS, WRITE_STATUS_DELAY);
    }

    boolean isJobNamespaceMigrated() {
        return mIsJobNamespaceMigrated;
    }

    public Pair<Long, Long> getBackoff(EndPoint info) {
        synchronized (mAuthorities) {
            AuthorityInfo authority = getAuthorityLocked(info, "getBackoff");
@@ -1585,7 +1601,6 @@ public class SyncStorageEngine {
        }
    }


    /**
     * Remove an authority associated with a provider. Needs to be a standalone function for
     * backward compatibility.
@@ -2101,6 +2116,10 @@ public class SyncStorageEngine {
                        mSyncStatus.put(status.authorityId, status);
                    }
                    break;
                case (int) SyncStatusProto.IS_JOB_NAMESPACE_MIGRATED:
                    mIsJobNamespaceMigrated =
                            proto.readBoolean(SyncStatusProto.IS_JOB_NAMESPACE_MIGRATED);
                    break;
                case ProtoInputStream.NO_MORE_FIELDS:
                    return;
            }
@@ -2368,6 +2387,9 @@ public class SyncStorageEngine {
            }
            proto.end(token);
        }

        proto.write(SyncStatusProto.IS_JOB_NAMESPACE_MIGRATED, mIsJobNamespaceMigrated);

        proto.flush();
    }

Loading