summaryrefslogtreecommitdiffstats
path: root/services/backup
diff options
context:
space:
mode:
authorChristopher Tate <ctate@google.com>2015-02-26 19:32:55 -0800
committerChristopher Tate <ctate@google.com>2015-03-02 18:01:28 -0800
commit73570db59f9178cf3a8affe943e96808ac404049 (patch)
treefa500a58f47332bd66315f6c55c43eb85cdd3930 /services/backup
parent20d58e2af7738209b068038197db516d365d887a (diff)
downloadframeworks_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.java76
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueBackupJob.java121
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;
+ }
+
+}