Bug 1910953 - Use a Web Lock to sequence deletions and creations of backups. r=backup-reviewers,fchasen

This allows us to avoid creating a backup while we're in the middle of deleting
one, and deleting a backup when we're in the middle of creating one.

An AbortController is used to clear the lock's request queue on shutdown
in the (unlikely) event that a whole slew of backup creation and deletion
requests have queued up.

Differential Revision: https://phabricator.services.mozilla.com/D218240
This commit is contained in:
Mike Conley
2024-08-06 13:54:35 +00:00
parent b93742c566
commit acf9e6ac1f

View File

@@ -648,6 +648,14 @@ export class BackupService extends EventTarget {
*/
#encState = undefined;
/**
* The AbortController used to abort any queued requests to create or delete
* backups that might be waiting on the WRITE_BACKUP_LOCK_NAME lock.
*
* @type {AbortController}
*/
#backupWriteAbortController = null;
/**
* The path of the default parent directory for saving backups.
* The current default is the Documents directory.
@@ -840,6 +848,16 @@ export class BackupService extends EventTarget {
return AppConstants.MOZ_APP_BASENAME + " Backup Recovery Storage";
}
/**
* The name of the exclusive Web Lock that will be requested and held when
* creating or deleting a backup.
*
* @type {string}
*/
static get WRITE_BACKUP_LOCK_NAME() {
return "write-backup";
}
/**
* Returns a reference to a BackupService singleton. If this is the first time
* that this getter is accessed, this causes the BackupService singleton to be
@@ -897,6 +915,7 @@ export class BackupService extends EventTarget {
let { promise, resolve } = Promise.withResolvers();
this.#postRecoveryPromise = promise;
this.#postRecoveryResolver = resolve;
this.#backupWriteAbortController = new AbortController();
}
/**
@@ -1069,11 +1088,17 @@ export class BackupService extends EventTarget {
return null;
}
return locks.request(
BackupService.WRITE_BACKUP_LOCK_NAME,
{ signal: this.#backupWriteAbortController.signal },
async () => {
this.#backupInProgress = true;
const backupTimer = Glean.browserBackup.totalBackupTime.start();
try {
lazy.logConsole.debug(`Creating backup for profile at ${profilePath}`);
lazy.logConsole.debug(
`Creating backup for profile at ${profilePath}`
);
let archiveDestFolderPath = await this.resolveArchiveDestFolderPath(
lazy.backupDirPref
@@ -1189,7 +1214,9 @@ export class BackupService extends EventTarget {
);
await IOUtils.writeJSON(manifestPath, manifest);
let renamedStagingPath = await this.#finalizeStagingFolder(stagingPath);
let renamedStagingPath = await this.#finalizeStagingFolder(
stagingPath
);
lazy.logConsole.log(
"Wrote backup to staging directory at ",
renamedStagingPath
@@ -1223,7 +1250,10 @@ export class BackupService extends EventTarget {
// backups folder while it gets written. Once that's done, we'll attempt
// to move it to the user's configured backup path.
let archiveTmpPath = PathUtils.join(backupDirPath, "archive.html");
lazy.logConsole.log("Exporting single-file archive to ", archiveTmpPath);
lazy.logConsole.log(
"Exporting single-file archive to ",
archiveTmpPath
);
await this.createArchive(
archiveTmpPath,
BackupService.ARCHIVE_TEMPLATE,
@@ -1257,7 +1287,10 @@ export class BackupService extends EventTarget {
);
let nowSeconds = Math.floor(Date.now() / 1000);
Services.prefs.setIntPref(LAST_BACKUP_TIMESTAMP_PREF_NAME, nowSeconds);
Services.prefs.setIntPref(
LAST_BACKUP_TIMESTAMP_PREF_NAME,
nowSeconds
);
this.#_state.lastBackupDate = nowSeconds;
Glean.browserBackup.totalBackupTime.stopAndAccumulate(backupTimer);
@@ -1269,6 +1302,8 @@ export class BackupService extends EventTarget {
this.#backupInProgress = false;
}
}
);
}
/**
* Generates a string from a Date in the form of:
@@ -3121,6 +3156,7 @@ export class BackupService extends EventTarget {
}
case "quit-application-granted": {
this.uninitBackupScheduler();
this.#backupWriteAbortController.abort();
break;
}
}
@@ -3279,6 +3315,10 @@ export class BackupService extends EventTarget {
* @returns {Promise<undefined>}
*/
async deleteLastBackup() {
return locks.request(
BackupService.WRITE_BACKUP_LOCK_NAME,
{ signal: this.#backupWriteAbortController.signal },
async () => {
if (this.#_state.lastBackupFileName) {
let backupFilePath = PathUtils.join(
lazy.backupDirPref,
@@ -3313,4 +3353,6 @@ export class BackupService extends EventTarget {
}
}
}
);
}
}