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:
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user