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

Commit 9f5ae0f8 authored by Kweku Adams's avatar Kweku Adams
Browse files

Make the run shell command more reliable for connectivity jobs.

Connectivity signals are asynchronous. It might take a few seconds after
the job is evaluated before the signal comes in, so wait a few seconds
if it makes sense before completing the run command so that tests can
have more reliable and consistent behavior.

Bug: 316435418
Test: atest CtsJobSchedulerTestCases:FlexibilityConstraintTest
Change-Id: I8072c86228e1a6112638d003c6d11a50dfc39010
parent 1c668cf2
Loading
Loading
Loading
Loading
+66 −8
Original line number Diff line number Diff line
@@ -159,6 +159,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;

@@ -513,6 +514,10 @@ public class JobSchedulerService extends com.android.server.SystemService
                    if (name == null) {
                        continue;
                    }
                    if (DEBUG) {
                        Slog.d(TAG, "DeviceConfig " + name
                                + " changed to " + properties.getString(name, null));
                    }
                    switch (name) {
                        case Constants.KEY_ENABLE_API_QUOTAS:
                        case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
@@ -3497,7 +3502,10 @@ public class JobSchedulerService extends com.android.server.SystemService
                }

                final boolean shouldForceBatchJob;
                if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
                if (job.overrideState > JobStatus.OVERRIDE_NONE) {
                    // The job should run for some test. Don't force batch it.
                    shouldForceBatchJob = false;
                } else if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
                    // Never batch expedited or user-initiated jobs, even for RESTRICTED apps.
                    shouldForceBatchJob = false;
                } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
@@ -4950,6 +4958,8 @@ public class JobSchedulerService extends com.android.server.SystemService
        Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + namespace + "/" + userId
                + " " + jobId + " s=" + satisfied + " f=" + force);

        final CountDownLatch delayLatch = new CountDownLatch(1);
        final JobStatus js;
        try {
            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
                    userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4958,7 +4968,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            }

            synchronized (mLock) {
                final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
                js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
                if (js == null) {
                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
                }
@@ -4969,23 +4979,71 @@ public class JobSchedulerService extends com.android.server.SystemService
                // Re-evaluate constraints after the override is set in case one of the overridden
                // constraints was preventing another constraint from thinking it needed to update.
                for (int c = mControllers.size() - 1; c >= 0; --c) {
                    mControllers.get(c).reevaluateStateLocked(uid);
                    mControllers.get(c).evaluateStateLocked(js);
                }

                if (!js.isConstraintsSatisfied()) {
                    if (js.hasConnectivityConstraint()
                            && !js.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)
                            && js.wouldBeReadyWithConstraint(JobStatus.CONSTRAINT_CONNECTIVITY)) {
                        // Because of how asynchronous the connectivity signals are, JobScheduler
                        // may not get the connectivity satisfaction signal immediately. In this
                        // case, wait a few seconds to see if it comes in before saying the
                        // connectivity constraint isn't satisfied.
                        mHandler.postDelayed(
                                checkConstraintRunnableForTesting(
                                        mHandler, js, delayLatch, 5, 1000),
                                1000);
                    } else {
                        // There's no asynchronous signal to wait for. We can immediately say the
                        // job's constraints aren't satisfied and return.
                        js.overrideState = JobStatus.OVERRIDE_NONE;
                        return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
                    }

                queueReadyJobsForExecutionLocked();
                maybeRunPendingJobsLocked();
                } else {
                    delayLatch.countDown();
                }
            }
        } catch (RemoteException e) {
            // can't happen
            return 0;
        }

        // Choose to block the return until we're sure about the state of the connectivity job
        // so that tests can expect a reliable state after calling the run command.
        try {
            delayLatch.await(7L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Slog.e(TAG, "Couldn't wait for asynchronous constraint change", e);
        }

        synchronized (mLock) {
            if (!js.isConstraintsSatisfied()) {
                js.overrideState = JobStatus.OVERRIDE_NONE;
                return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
            }

            queueReadyJobsForExecutionLocked();
            maybeRunPendingJobsLocked();
        }
        return 0;
    }

    private static Runnable checkConstraintRunnableForTesting(@NonNull final Handler handler,
            @NonNull final JobStatus js, @NonNull final CountDownLatch latch,
            final int remainingAttempts, final long delayMs) {
        return () -> {
            if (remainingAttempts <= 0 || js.isConstraintsSatisfied()) {
                latch.countDown();
                return;
            }
            handler.postDelayed(
                    checkConstraintRunnableForTesting(
                            handler, js, latch, remainingAttempts - 1, delayMs),
                    delayMs);
        };
    }

    // Shell command infrastructure: immediately timeout currently executing jobs
    int executeStopCommand(PrintWriter pw, String pkgName, int userId,
            @Nullable String namespace, boolean hasJobId, int jobId,
+1 −1
Original line number Diff line number Diff line
@@ -2194,7 +2194,7 @@ public final class JobStatus {
     * @return Whether or not this job would be ready to run if it had the specified constraint
     * granted, based on its requirements.
     */
    boolean wouldBeReadyWithConstraint(int constraint) {
    public boolean wouldBeReadyWithConstraint(int constraint) {
        return readinessStatusWithConstraint(constraint, true);
    }