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

Commit 73570db5 authored by Christopher Tate's avatar Christopher Tate
Browse files

Use scheduled job rather than periodic alarms for key/value backups

Instead of a runs-forever periodic alarm that drives key/value backup
passes, we instead schedule-on-demand a trigger job that will kick off
the pass after a batching interval.  The key semantic change is that
we now never wake for key/value backup work unless we've been explicitly
asked to do so.  We also use a rather longer batching interval than
was previously the case.

Bug 19536032

Change-Id: Ie377562b2812c9aeda0ee73770dfa94af6017778
parent 20d58e2a
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -3188,6 +3188,10 @@
                 android:permission="android.permission.BIND_JOB_SERVICE" >
        </service>

        <service android:name="com.android.server.backup.KeyValueBackupJob"
                 android:permission="android.permission.BIND_JOB_SERVICE" >
        </service>

        <service
            android:name="com.android.server.pm.BackgroundDexOptService"
            android:exported="true"
+27 −49
Original line number Diff line number Diff line
@@ -157,9 +157,9 @@ import libcore.io.IoUtils;
public class BackupManagerService {

    private static final String TAG = "BackupManagerService";
    private static final boolean DEBUG = true;
    private static final boolean MORE_DEBUG = false;
    private static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;
    static final boolean DEBUG = true;
    static final boolean MORE_DEBUG = false;
    static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;

    // System-private key used for backing up an app's widget state.  Must
    // begin with U+FFxx by convention (we reserve all keys starting
@@ -195,17 +195,6 @@ public class BackupManagerService {
    static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
    static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";

    // How often we perform a backup pass.  Privileged external callers can
    // trigger an immediate pass.
    private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR;

    // Random variation in backup scheduling time to avoid server load spikes
    private static final int FUZZ_MILLIS = 5 * 60 * 1000;

    // The amount of time between the initial provisioning of the device and
    // the first backup pass.
    private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR;

    // Retry interval for clear/init when the transport is unavailable
    private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;

@@ -299,7 +288,6 @@ public class BackupManagerService {
    volatile boolean mBackupRunning;
    volatile boolean mConnecting;
    volatile long mLastBackupPass;
    volatile long mNextBackupPass;

    // For debugging, we maintain a progress trace of operations during backup
    static final boolean DEBUG_BACKUP_TRACE = true;
@@ -381,7 +369,7 @@ public class BackupManagerService {
                if (mProvisioned && !wasProvisioned && mEnabled) {
                    // we're now good to go, so start the backup alarms
                    if (MORE_DEBUG) Slog.d(TAG, "Now provisioned, so starting backups");
                    startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL);
                    KeyValueBackupJob.schedule(mContext);
                    scheduleNextFullBackupJob();
                }
            }
@@ -657,7 +645,6 @@ public class BackupManagerService {
            case MSG_RUN_BACKUP:
            {
                mLastBackupPass = System.currentTimeMillis();
                mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL;

                IBackupTransport transport = getTransport(mCurrentTransport);
                if (transport == null) {
@@ -2939,12 +2926,22 @@ public class BackupManagerService {
        void revertAndEndBackup() {
            if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything");
            addBackupTrace("transport error; reverting");

            // We want to reset the backup schedule based on whatever the transport suggests
            // by way of retry/backoff time.
            long delay;
            try {
                delay = mTransport.requestBackupTime();
            } catch (Exception e) {
                Slog.w(TAG, "Unable to contact transport for recommended backoff");
                delay = 0;  // use the scheduler's default
            }
            KeyValueBackupJob.schedule(mContext, delay);

            for (BackupRequest request : mOriginalQueue) {
                dataChangedImpl(request.packageName);
            }
            // We also want to reset the backup schedule based on whatever
            // the transport suggests by way of retry/backoff time.
            restartBackupAlarm();

        }

        void agentErrorCleanup() {
@@ -2977,15 +2974,6 @@ public class BackupManagerService {
            }
        }

        void restartBackupAlarm() {
            addBackupTrace("setting backup trigger");
            synchronized (mQueueLock) {
                try {
                    startBackupAlarmsLocked(mTransport.requestBackupTime());
                } catch (RemoteException e) { /* cannot happen */ }
            }
        }

        void executeNextState(BackupState nextState) {
            if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
                    + this + " nextState=" + nextState);
@@ -8111,6 +8099,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
                }
            }
        }

        // ...and schedule a backup pass if necessary
        KeyValueBackupJob.schedule(mContext);
    }

    // Note: packageName is currently unused, but may be in the future
@@ -8245,16 +8236,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF

        if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
        synchronized (mQueueLock) {
            // Because the alarms we are using can jitter, and we want an *immediate*
            // backup pass to happen, we restart the timer beginning with "next time,"
            // then manually fire the backup trigger intent ourselves.
            startBackupAlarmsLocked(BACKUP_INTERVAL);
            // Fire the intent that kicks off the whole shebang...
            try {
                mRunBackupIntent.send();
            } catch (PendingIntent.CanceledException e) {
                // should never happen
                Slog.e(TAG, "run-backup intent cancelled!");
            }

            // ...and cancel any pending scheduled job, because we've just superseded it
            KeyValueBackupJob.cancel(mContext);
        }
    }

@@ -8530,13 +8521,13 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
            synchronized (mQueueLock) {
                if (enable && !wasEnabled && mProvisioned) {
                    // if we've just been enabled, start scheduling backup passes
                    startBackupAlarmsLocked(BACKUP_INTERVAL);
                    KeyValueBackupJob.schedule(mContext);
                    scheduleNextFullBackupJob();
                } else if (!enable) {
                    // No longer enabled, so stop running backups
                    if (DEBUG) Slog.i(TAG, "Opting out of backup");

                    mAlarmManager.cancel(mRunBackupIntent);
                    KeyValueBackupJob.cancel(mContext);

                    // This also constitutes an opt-out, so we wipe any data for
                    // this device from the backend.  We start that process with
@@ -8590,19 +8581,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
         */
    }

    private void startBackupAlarmsLocked(long delayBeforeFirstBackup) {
        // We used to use setInexactRepeating(), but that may be linked to
        // backups running at :00 more often than not, creating load spikes.
        // Schedule at an exact time for now, and also add a bit of "fuzz".

        Random random = new Random();
        long when = System.currentTimeMillis() + delayBeforeFirstBackup +
                random.nextInt(FUZZ_MILLIS);
        mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when,
                BACKUP_INTERVAL + random.nextInt(FUZZ_MILLIS), mRunBackupIntent);
        mNextBackupPass = when;
    }

    // Report whether the backup mechanism is currently enabled
    public boolean isBackupEnabled() {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled");
@@ -9308,7 +9286,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
            if (mBackupRunning) pw.println("Backup currently running");
            pw.println("Last backup pass started: " + mLastBackupPass
                    + " (now = " + System.currentTimeMillis() + ')');
            pw.println("  next scheduled: " + mNextBackupPass);
            pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled());

            pw.println("Available transports:");
            final String[] transports = listAllTransports();
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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 com.android.server.backup;

import android.app.AlarmManager;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
import android.util.Slog;

import java.util.Random;

/**
 * Job for scheduling key/value backup work.  This module encapsulates all
 * of the policy around when those backup passes are executed.
 */
public class KeyValueBackupJob extends JobService {
    private static final String TAG = "KeyValueBackupJob";
    private static ComponentName sKeyValueJobService =
            new ComponentName("android", KeyValueBackupJob.class.getName());
    private static final int JOB_ID = 0x5039;

    // Once someone asks for a backup, this is how long we hold off, batching
    // up additional requests, before running the actual backup pass.  Privileged
    // callers can always trigger an immediate pass via BackupManager.backupNow().
    private static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR;

    // Random variation in next-backup scheduling time to avoid server load spikes
    private static final int FUZZ_MILLIS = 10 * 60 * 1000;

    // Don't let the job scheduler defer forever; give it a (lenient) deadline
    private static final long MAX_DEFERRAL = 1 * AlarmManager.INTERVAL_HOUR;

    private static boolean sScheduled = false;
    private static long sNextScheduled = 0;

    public static void schedule(Context ctx) {
        schedule(ctx, 0);
    }

    public static void schedule(Context ctx, long delay) {
        synchronized (KeyValueBackupJob.class) {
            if (!sScheduled) {
                if (delay <= 0) {
                    delay = BATCH_INTERVAL + new Random().nextInt(FUZZ_MILLIS);
                }
                if (BackupManagerService.DEBUG_SCHEDULING) {
                    Slog.v(TAG, "Scheduling k/v pass in "
                            + (delay / 1000 / 60) + " minutes");
                }
                JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
                JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
                        .setMinimumLatency(delay)
                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                        .setOverrideDeadline(delay + MAX_DEFERRAL);
                js.schedule(builder.build());

                sNextScheduled = System.currentTimeMillis() + delay;
                sScheduled = true;
            }
        }
    }

    public static void cancel(Context ctx) {
        synchronized (KeyValueBackupJob.class) {
            JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            js.cancel(JOB_ID);
            sNextScheduled = 0;
            sScheduled = false;
        }
    }

    public static long nextScheduled() {
        synchronized (KeyValueBackupJob.class) {
            return sNextScheduled;
        }
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        synchronized (KeyValueBackupJob.class) {
            sNextScheduled = 0;
            sScheduled = false;
        }

        // Time to run a key/value backup!
        Trampoline service = BackupManagerService.getInstance();
        try {
            service.backupNow();
        } catch (RemoteException e) {}

        // This was just a trigger; ongoing wakelock management is done by the
        // rest of the backup system.
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // Intentionally empty; the job starting was just a trigger
        return false;
    }

}