diff options
author | Dipankar Bhardwaj <dipankarb@google.com> | 2023-02-16 11:24:00 +0000 |
---|---|---|
committer | Dipankar Bhardwaj <dipankarb@google.com> | 2023-02-28 11:43:50 +0000 |
commit | 3579ba628ab50265578cf1ff50398f0a49184659 (patch) | |
tree | fb24bd50878aba92b89540947cd5cb48b23a479e | |
parent | ded1883f0c2c1b7aa7fab1fdd4f66b41d1c8504e (diff) | |
download | MediaProvider-3579ba628ab50265578cf1ff50398f0a49184659.tar.gz |
Refactor database recovery code
Moved recovery code from DatabaseHelper to DatabaseBackupAndRecovery
class.
Test: atest StableUriIdleMaintenanceServiceTest.java
Bug: 259259604
Change-Id: I1405aaccc124783349050b0ffd293aac2ed8e910
3 files changed, 93 insertions, 95 deletions
diff --git a/src/com/android/providers/media/DatabaseBackupAndRecovery.java b/src/com/android/providers/media/DatabaseBackupAndRecovery.java index b7afca866..d861e5b88 100644 --- a/src/com/android/providers/media/DatabaseBackupAndRecovery.java +++ b/src/com/android/providers/media/DatabaseBackupAndRecovery.java @@ -17,6 +17,9 @@ package com.android.providers.media; import static com.android.providers.media.DatabaseHelper.INTERNAL_DATABASE_NAME; +import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY; +import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__INTERNAL; +import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC; import static com.android.providers.media.util.Logging.TAG; import android.content.ContentValues; @@ -24,6 +27,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.MediaStore; @@ -31,6 +35,7 @@ import android.system.Os; import android.util.Log; import android.util.Pair; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import com.android.providers.media.dao.FileRow; @@ -88,6 +93,11 @@ public class DatabaseBackupAndRecovery { "/storage/emulated/" + UserHandle.myUserId(); /** + * Number of records to read from leveldb in a JNI call. + */ + protected static final int LEVEL_DB_READ_LIMIT = 1000; + + /** * Stores cached value of next owner id. This helps in improving performance by backing up next * row id less frequently in the external storage. */ @@ -605,6 +615,82 @@ public class DatabaseBackupAndRecovery { } } + protected void recoverData(SQLiteDatabase db, String volumeName) { + final long startTime = SystemClock.elapsedRealtime(); + int i = 0; + final String fuseFilePath = getFuseFilePathFromVolumeName(volumeName); + // Wait for external primary to be attached as we use same thread for internal volume. + // Maximum wait for 10s + while (!isFuseDaemonReadyForFilePath(fuseFilePath) && i < 1000) { + Log.d(TAG, "Waiting for fuse daemon to be ready."); + // Poll after every 10ms + SystemClock.sleep(10); + i++; + } + if (!isFuseDaemonReadyForFilePath(fuseFilePath)) { + Log.e(TAG, "Could not recover data as fuse daemon could not serve requests."); + return; + } + + long rowsRecovered = 0; + long dirtyRowsCount = 0; + String[] backedUpFilePaths; + String lastReadValue = ""; + + while (true) { + backedUpFilePaths = readBackedUpFilePaths(volumeName, lastReadValue, + LEVEL_DB_READ_LIMIT); + if (backedUpFilePaths.length <= 0) { + break; + } + + for (String filePath : backedUpFilePaths) { + Optional<BackupIdRow> fileRow = readDataFromBackup(volumeName, filePath); + if (fileRow.isPresent()) { + if (fileRow.get().getIsDirty()) { + dirtyRowsCount++; + continue; + } + + insertDataInDatabase(db, fileRow.get(), filePath, volumeName); + rowsRecovered++; + } + } + + // Read less rows than expected + if (backedUpFilePaths.length < LEVEL_DB_READ_LIMIT) { + break; + } + lastReadValue = backedUpFilePaths[backedUpFilePaths.length - 1]; + } + long recoveryTime = SystemClock.elapsedRealtime() - startTime; + MediaProviderStatsLog.write(MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED, + getVolumeName(volumeName), recoveryTime, rowsRecovered, dirtyRowsCount); + Log.i(TAG, String.format(Locale.ROOT, "%d rows recovered for volume:%s.", rowsRecovered, + volumeName)); + Log.i(TAG, String.format(Locale.ROOT, "Recovery time: %d ms", recoveryTime)); + } + + private int getVolumeName(String volumeName) { + if (volumeName.equalsIgnoreCase(MediaStore.VOLUME_INTERNAL)) { + return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__INTERNAL; + } else if (volumeName.equalsIgnoreCase(MediaStore.VOLUME_EXTERNAL_PRIMARY)) { + return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY; + } + + return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC; + } + + private static String getFuseFilePathFromVolumeName(String volumeName) { + switch (volumeName) { + case MediaStore.VOLUME_INTERNAL: + case MediaStore.VOLUME_EXTERNAL_PRIMARY: + return EXTERNAL_PRIMARY_ROOT_PATH; + default: + return "/storage/" + volumeName; + } + } + /** * Returns list of backed up files from external storage. */ diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java index afec29d2d..aac3fcbfa 100644 --- a/src/com/android/providers/media/DatabaseHelper.java +++ b/src/com/android/providers/media/DatabaseHelper.java @@ -18,9 +18,6 @@ package com.android.providers.media; import static com.android.providers.media.DatabaseBackupAndRecovery.getXattr; import static com.android.providers.media.DatabaseBackupAndRecovery.setXattr; -import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY; -import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__INTERNAL; -import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC; import static com.android.providers.media.util.DatabaseUtils.bindList; import static com.android.providers.media.util.Logging.LOGV; import static com.android.providers.media.util.Logging.TAG; @@ -71,7 +68,6 @@ import androidx.annotation.VisibleForTesting; import com.android.modules.utils.BackgroundThread; import com.android.providers.media.dao.FileRow; import com.android.providers.media.playlist.Playlist; -import com.android.providers.media.stableuris.dao.BackupIdRow; import com.android.providers.media.util.DatabaseUtils; import com.android.providers.media.util.FileUtils; import com.android.providers.media.util.ForegroundThread; @@ -154,8 +150,6 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { */ private static final String DATA_MEDIA_XATTR_DIRECTORY_PATH = "/data/media/0"; - protected static final int LEVEL_DB_READ_LIMIT = 1000; - static final String INTERNAL_DATABASE_NAME = "internal.db"; static final String EXTERNAL_DATABASE_NAME = "external.db"; @@ -557,10 +551,11 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { } catch (Exception e) { throw new RuntimeException("Could not retrieve local content provider", e); } + DatabaseBackupAndRecovery databaseBackupAndRecovery = + mediaProvider.getDatabaseBackupAndRecovery(); String volumeName = isInternal() ? MediaStore.VOLUME_INTERNAL : MediaStore.VOLUME_EXTERNAL; - if (!isInternal() - || !mediaProvider.getDatabaseBackupAndRecovery().isStableUrisEnabled(volumeName)) { + if (!isInternal() || !databaseBackupAndRecovery.isStableUrisEnabled(volumeName)) { return; } @@ -574,9 +569,8 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { // StableUrisIdleMaintenanceService will be attempted to run only once in 7days. // Any rollback before that will not recover DB rows. BackgroundThread.getExecutor().execute( - () -> mediaProvider.getDatabaseBackupAndRecovery() - .backupInternalDatabase(null)); - // Set next row id in External Storage to handle rollback in future. + () -> databaseBackupAndRecovery.backupInternalDatabase(null)); + // Set next row id in External Storage to handle rollback in the future. backupNextRowId(NEXT_ROW_ID_DEFAULT_BILLION_VALUE); updateSessionIdInDatabaseAndExternalStorage(db); return; @@ -599,7 +593,7 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { // Recover data from backup // Ensure we do not back up in case of recovery. mIsRecovering.set(true); - recoverData(mediaProvider, db, volumeName); + databaseBackupAndRecovery.recoverData(db, volumeName); updateNextRowIdInDatabaseAndExternalStorage(db); mIsRecovering.set(false); updateSessionIdInDatabaseAndExternalStorage(db); @@ -621,88 +615,6 @@ public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable { } } - @GuardedBy("sRecoveryLock") - private void recoverData(MediaProvider mediaProvider, SQLiteDatabase db, String volumeName) { - final long startTime = SystemClock.elapsedRealtime(); - int i = 0; - final String fuseFilePath = getFuseFilePathFromVolumeName(volumeName); - // Wait for external primary to be attached as we use same thread for internal volume. - // Maximum wait for 10s - DatabaseBackupAndRecovery dbBackupAndRecovery = - mediaProvider.getDatabaseBackupAndRecovery(); - while (!dbBackupAndRecovery.isFuseDaemonReadyForFilePath(fuseFilePath) - && i < 1000) { - Log.d(TAG, "Waiting for fuse daemon to be ready."); - // Poll after every 10ms - SystemClock.sleep(10); - i++; - } - if (!dbBackupAndRecovery.isFuseDaemonReadyForFilePath(fuseFilePath)) { - Log.e(TAG, "Could not recover data as fuse daemon could not serve requests."); - return; - } - - long rowsRecovered = 0; - long dirtyRowsCount = 0; - String[] backedUpFilePaths; - String lastReadValue = ""; - - while (true) { - backedUpFilePaths = mediaProvider.getDatabaseBackupAndRecovery() - .readBackedUpFilePaths(volumeName, lastReadValue, LEVEL_DB_READ_LIMIT); - if (backedUpFilePaths.length <= 0) { - break; - } - - for (String filePath : backedUpFilePaths) { - Optional<BackupIdRow> fileRow = mediaProvider.getDatabaseBackupAndRecovery() - .readDataFromBackup(volumeName, filePath); - if (fileRow.isPresent()) { - if (fileRow.get().getIsDirty()) { - dirtyRowsCount++; - continue; - } - - dbBackupAndRecovery.insertDataInDatabase(db, fileRow.get(), filePath, - volumeName); - rowsRecovered++; - } - } - - // Read less rows than expected - if (backedUpFilePaths.length < LEVEL_DB_READ_LIMIT) { - break; - } - lastReadValue = backedUpFilePaths[backedUpFilePaths.length - 1]; - } - long recoveryTime = SystemClock.elapsedRealtime() - startTime; - MediaProviderStatsLog.write(MediaProviderStatsLog.MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED, - getVolumeName(volumeName), recoveryTime, rowsRecovered, dirtyRowsCount); - Log.i(TAG, String.format(Locale.ROOT, "%d rows recovered for volume:%s.", rowsRecovered, - volumeName)); - Log.i(TAG, String.format(Locale.ROOT, "Recovery time: %d ms", recoveryTime)); - } - - private int getVolumeName(String volumeName) { - if (volumeName.equalsIgnoreCase(MediaStore.VOLUME_INTERNAL)) { - return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__INTERNAL; - } else if (volumeName.equalsIgnoreCase(MediaStore.VOLUME_EXTERNAL_PRIMARY)) { - return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY; - } - - return MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC; - } - - private static String getFuseFilePathFromVolumeName(String volumeName) { - switch (volumeName) { - case MediaStore.VOLUME_INTERNAL: - case MediaStore.VOLUME_EXTERNAL_PRIMARY: - return "/storage/emulated/" + UserHandle.myUserId(); - default: - return "/storage/" + volumeName; - } - } - private void tryRecoverRowIdSequence(SQLiteDatabase db) { if (isInternal()) { // Database row id recovery for internal is handled in tryRecoverDatabase() diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java index 402dba1c6..ee8275260 100644 --- a/src/com/android/providers/media/MediaProvider.java +++ b/src/com/android/providers/media/MediaProvider.java @@ -54,9 +54,9 @@ import static com.android.providers.media.AccessChecker.getWhereForOwnerPackageM import static com.android.providers.media.AccessChecker.getWhereForUserSelectedAccess; import static com.android.providers.media.AccessChecker.hasAccessToCollection; import static com.android.providers.media.AccessChecker.hasUserSelectedAccess; +import static com.android.providers.media.DatabaseBackupAndRecovery.LEVEL_DB_READ_LIMIT; import static com.android.providers.media.DatabaseHelper.EXTERNAL_DATABASE_NAME; import static com.android.providers.media.DatabaseHelper.INTERNAL_DATABASE_NAME; -import static com.android.providers.media.DatabaseHelper.LEVEL_DB_READ_LIMIT; import static com.android.providers.media.LocalCallingIdentity.APPOP_REQUEST_INSTALL_PACKAGES_FOR_SHARED_UID; import static com.android.providers.media.LocalCallingIdentity.PERMISSION_ACCESS_MTP; import static com.android.providers.media.LocalCallingIdentity.PERMISSION_INSTALL_PACKAGES; |