diff options
author | Christopher Tate <ctate@google.com> | 2015-02-26 19:32:55 -0800 |
---|---|---|
committer | Christopher Tate <ctate@google.com> | 2015-03-02 18:01:28 -0800 |
commit | 73570db59f9178cf3a8affe943e96808ac404049 (patch) | |
tree | fa500a58f47332bd66315f6c55c43eb85cdd3930 /services/backup | |
parent | 20d58e2af7738209b068038197db516d365d887a (diff) | |
download | frameworks_base-73570db59f9178cf3a8affe943e96808ac404049.zip frameworks_base-73570db59f9178cf3a8affe943e96808ac404049.tar.gz frameworks_base-73570db59f9178cf3a8affe943e96808ac404049.tar.bz2 |
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
Diffstat (limited to 'services/backup')
-rw-r--r-- | services/backup/java/com/android/server/backup/BackupManagerService.java | 76 | ||||
-rw-r--r-- | services/backup/java/com/android/server/backup/KeyValueBackupJob.java | 121 |
2 files changed, 148 insertions, 49 deletions
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index acf4d39..94d400a 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -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(); diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java new file mode 100644 index 0000000..dc1c9d5 --- /dev/null +++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java @@ -0,0 +1,121 @@ +/* + * 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; + } + +} |