From ab06997ed5d4599f7223f3033b5c3bc77ac7044e Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Mon, 30 Mar 2015 18:00:52 -0700 Subject: Fixes to full-backup scheduling edge cases If a scheduled full-data backup was attempted but the device had not yet run the primary key/value backup pass, the full-data backup scheduler would wind up in a bad state and potentially never retry until reboot. We now properly reschedule a future retry, using the key/value scheduling batch quantum as a backoff to be sure to give it a chance to run before the next time full data is attempted. Change-Id: Ic7eb7a7940fe6380f40d04813a46fc336e95815e --- .../server/backup/BackupManagerService.java | 116 ++++++++++++++------- .../android/server/backup/KeyValueBackupJob.java | 2 +- 2 files changed, 81 insertions(+), 37 deletions(-) (limited to 'services/backup') diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 45b0fb2..5cc59e5 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -3938,16 +3938,6 @@ public class BackupManagerService { return; } - // Don't proceed unless we have already established package metadata - // for the current dataset via a key/value backup pass. - File stateDir = new File(mBaseStateDir, transport.transportDirName()); - File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL); - if (pmState.length() <= 0) { - Slog.i(TAG, "Full backup requested but dataset not yet initialized " - + "via k/v backup pass; ignoring"); - return; - } - // Set up to send data to the transport final int N = mPackages.size(); for (int i = 0; i < N; i++) { @@ -4296,6 +4286,31 @@ public class BackupManagerService { writeFullBackupScheduleAsync(); } + private boolean fullBackupAllowable(IBackupTransport transport) { + if (transport == null) { + Slog.w(TAG, "Transport not present; full data backup not performed"); + return false; + } + + // Don't proceed unless we have already established package metadata + // for the current dataset via a key/value backup pass. + try { + File stateDir = new File(mBaseStateDir, transport.transportDirName()); + File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL); + if (pmState.length() <= 0) { + if (DEBUG) { + Slog.i(TAG, "Full backup requested but dataset not yet initialized"); + } + return false; + } + } catch (Exception e) { + Slog.w(TAG, "Unable to contact transport"); + return false; + } + + return true; + } + /** * Conditions are right for a full backup operation, so run one. The model we use is * to perform one app backup per scheduled job execution, and to reschedule the job @@ -4307,6 +4322,7 @@ public class BackupManagerService { boolean beginFullBackup(FullBackupJob scheduledJob) { long now = System.currentTimeMillis(); FullBackupEntry entry = null; + long latency = MIN_FULL_BACKUP_INTERVAL; if (!mEnabled || !mProvisioned) { // Backups are globally disabled, so don't proceed. We also don't reschedule @@ -4338,17 +4354,41 @@ public class BackupManagerService { return false; } - entry = mFullBackupQueue.get(0); - long timeSinceRun = now - entry.lastBackup; - if (timeSinceRun < MIN_FULL_BACKUP_INTERVAL) { - // It's too early to back up the next thing in the queue, so bow out + // At this point we know that we have work to do, just not right now. Any + // exit without actually running backups will also require that we + // reschedule the job. + boolean runBackup = true; + + if (!fullBackupAllowable(getTransport(mCurrentTransport))) { if (MORE_DEBUG) { - Slog.i(TAG, "Device ready but too early to back up next app"); + Slog.i(TAG, "Preconditions not met; not running full backup"); + } + runBackup = false; + // Typically this means we haven't run a key/value backup yet. Back off + // full-backup operations by the key/value job's run interval so that + // next time we run, we are likely to be able to make progress. + latency = KeyValueBackupJob.BATCH_INTERVAL; + } + + if (runBackup) { + entry = mFullBackupQueue.get(0); + long timeSinceRun = now - entry.lastBackup; + runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL); + if (!runBackup) { + // It's too early to back up the next thing in the queue, so bow out + if (MORE_DEBUG) { + Slog.i(TAG, "Device ready but too early to back up next app"); + } + // Wait until the next app in the queue falls due for a full data backup + latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun; } - final long latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun; + } + + if (!runBackup) { + final long deferTime = latency; // pin for the closure mBackupHandler.post(new Runnable() { @Override public void run() { - FullBackupJob.schedule(mContext, latency); + FullBackupJob.schedule(mContext, deferTime); } }); return false; @@ -8489,27 +8529,31 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF throw new IllegalStateException("Restore supported only for the device owner"); } - if (DEBUG) { - Slog.d(TAG, "fullTransportBackup()"); - } - - CountDownLatch latch = new CountDownLatch(1); - PerformFullTransportBackupTask task = - new PerformFullTransportBackupTask(null, pkgNames, false, null, latch); - (new Thread(task, "full-transport-master")).start(); - do { - try { - latch.await(); - break; - } catch (InterruptedException e) { - // Just go back to waiting for the latch to indicate completion + if (!fullBackupAllowable(getTransport(mCurrentTransport))) { + Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?"); + } else { + if (DEBUG) { + Slog.d(TAG, "fullTransportBackup()"); } - } while (true); - // We just ran a backup on these packages, so kick them to the end of the queue - final long now = System.currentTimeMillis(); - for (String pkg : pkgNames) { - enqueueFullBackup(pkg, now); + CountDownLatch latch = new CountDownLatch(1); + PerformFullTransportBackupTask task = + new PerformFullTransportBackupTask(null, pkgNames, false, null, latch); + (new Thread(task, "full-transport-master")).start(); + do { + try { + latch.await(); + break; + } catch (InterruptedException e) { + // Just go back to waiting for the latch to indicate completion + } + } while (true); + + // We just ran a backup on these packages, so kick them to the end of the queue + final long now = System.currentTimeMillis(); + for (String pkg : pkgNames) { + enqueueFullBackup(pkg, now); + } } if (DEBUG) { diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java index dc1c9d5..a4489c1 100644 --- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java +++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java @@ -41,7 +41,7 @@ public class KeyValueBackupJob extends JobService { // 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; + 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; -- cgit v1.1