forked from mirrors/gecko-dev
Bug 1891141 - Add configuration parameters for pages per step and step delay to mozIStorageAsyncConnection.backupToFileAsync. r=mak
This also fixes a bug in the test_connection_online_backup.js test which wasn't properly evaluating that the page_size PRAGMA was being copied properly. Differential Revision: https://phabricator.services.mozilla.com/D207471
This commit is contained in:
parent
50a66bfbd8
commit
8b8218c610
5 changed files with 127 additions and 29 deletions
3
dom/cache/Connection.cpp
vendored
3
dom/cache/Connection.cpp
vendored
|
|
@ -288,7 +288,8 @@ uint32_t Connection::DecreaseTransactionNestingLevel(
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
Connection::BackupToFileAsync(nsIFile* aDestinationFile,
|
Connection::BackupToFileAsync(nsIFile* aDestinationFile,
|
||||||
mozIStorageCompletionCallback* aCallback) {
|
mozIStorageCompletionCallback* aCallback,
|
||||||
|
uint32_t aPagesPerStep, uint32_t aStepDelayMs) {
|
||||||
// async methods are not supported
|
// async methods are not supported
|
||||||
return NS_ERROR_NOT_IMPLEMENTED;
|
return NS_ERROR_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -395,6 +395,12 @@ interface mozIStorageAsyncConnection : nsISupports {
|
||||||
* - status: the status of the operation, use this to check if making
|
* - status: the status of the operation, use this to check if making
|
||||||
* the copy was successful.
|
* the copy was successful.
|
||||||
* - value: unused.
|
* - value: unused.
|
||||||
|
* @param [aPagesPerStep=5]
|
||||||
|
* The number of pages in the database to copy per step. Defaults to 5
|
||||||
|
* if omitted or set to 0.
|
||||||
|
* @param [aStepDelayMs=250]
|
||||||
|
* The number of milliseconds to wait between each step. Defaults to
|
||||||
|
* 250 if omitted or set to 0.
|
||||||
* @throws NS_ERROR_ABORT
|
* @throws NS_ERROR_ABORT
|
||||||
* If the application has begun the process of shutting down already.
|
* If the application has begun the process of shutting down already.
|
||||||
* @throws NS_ERROR_NOT_INITIALIZED
|
* @throws NS_ERROR_NOT_INITIALIZED
|
||||||
|
|
@ -405,5 +411,7 @@ interface mozIStorageAsyncConnection : nsISupports {
|
||||||
* If the execution thread cannot be acquired.
|
* If the execution thread cannot be acquired.
|
||||||
*/
|
*/
|
||||||
void backupToFileAsync(in nsIFile aDestinationFile,
|
void backupToFileAsync(in nsIFile aDestinationFile,
|
||||||
in mozIStorageCompletionCallback aCallback);
|
in mozIStorageCompletionCallback aCallback,
|
||||||
|
[optional] in unsigned long aPagesPerStep,
|
||||||
|
[optional] in unsigned long aStepDelayMs);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -606,12 +606,15 @@ class AsyncBackupDatabaseFile final : public Runnable, public nsITimerCallback {
|
||||||
*/
|
*/
|
||||||
AsyncBackupDatabaseFile(Connection* aConnection, sqlite3* aNativeConnection,
|
AsyncBackupDatabaseFile(Connection* aConnection, sqlite3* aNativeConnection,
|
||||||
nsIFile* aDestinationFile,
|
nsIFile* aDestinationFile,
|
||||||
mozIStorageCompletionCallback* aCallback)
|
mozIStorageCompletionCallback* aCallback,
|
||||||
|
int32_t aPagesPerStep, uint32_t aStepDelayMs)
|
||||||
: Runnable("storage::AsyncBackupDatabaseFile"),
|
: Runnable("storage::AsyncBackupDatabaseFile"),
|
||||||
mConnection(aConnection),
|
mConnection(aConnection),
|
||||||
mNativeConnection(aNativeConnection),
|
mNativeConnection(aNativeConnection),
|
||||||
mDestinationFile(aDestinationFile),
|
mDestinationFile(aDestinationFile),
|
||||||
mCallback(aCallback),
|
mCallback(aCallback),
|
||||||
|
mPagesPerStep(aPagesPerStep),
|
||||||
|
mStepDelayMs(aStepDelayMs),
|
||||||
mBackupFile(nullptr),
|
mBackupFile(nullptr),
|
||||||
mBackupHandle(nullptr) {
|
mBackupHandle(nullptr) {
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
@ -681,19 +684,14 @@ class AsyncBackupDatabaseFile final : public Runnable, public nsITimerCallback {
|
||||||
rv = file->InitWithPath(tempPath);
|
rv = file->InitWithPath(tempPath);
|
||||||
DISPATCH_AND_RETURN_IF_FAILED(rv);
|
DISPATCH_AND_RETURN_IF_FAILED(rv);
|
||||||
|
|
||||||
// The number of milliseconds to wait between each batch of copies.
|
int srv = ::sqlite3_backup_step(mBackupHandle, mPagesPerStep);
|
||||||
static constexpr uint32_t STEP_DELAY_MS = 250;
|
|
||||||
// The number of pages to copy per step
|
|
||||||
static constexpr int COPY_PAGES = 5;
|
|
||||||
|
|
||||||
int srv = ::sqlite3_backup_step(mBackupHandle, COPY_PAGES);
|
|
||||||
if (srv == SQLITE_OK || srv == SQLITE_BUSY || srv == SQLITE_LOCKED) {
|
if (srv == SQLITE_OK || srv == SQLITE_BUSY || srv == SQLITE_LOCKED) {
|
||||||
// We're continuing the backup later. Release the guard to avoid closing
|
// We're continuing the backup later. Release the guard to avoid closing
|
||||||
// the database.
|
// the database.
|
||||||
guard.release();
|
guard.release();
|
||||||
// Queue up the next step
|
// Queue up the next step
|
||||||
return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this,
|
return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mStepDelayMs,
|
||||||
STEP_DELAY_MS, nsITimer::TYPE_ONE_SHOT,
|
nsITimer::TYPE_ONE_SHOT,
|
||||||
GetCurrentSerialEventTarget());
|
GetCurrentSerialEventTarget());
|
||||||
}
|
}
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
|
|
@ -771,6 +769,8 @@ class AsyncBackupDatabaseFile final : public Runnable, public nsITimerCallback {
|
||||||
nsCOMPtr<nsITimer> mTimer;
|
nsCOMPtr<nsITimer> mTimer;
|
||||||
nsCOMPtr<nsIFile> mDestinationFile;
|
nsCOMPtr<nsIFile> mDestinationFile;
|
||||||
nsCOMPtr<mozIStorageCompletionCallback> mCallback;
|
nsCOMPtr<mozIStorageCompletionCallback> mCallback;
|
||||||
|
int32_t mPagesPerStep;
|
||||||
|
uint32_t mStepDelayMs;
|
||||||
sqlite3* mBackupFile;
|
sqlite3* mBackupFile;
|
||||||
sqlite3_backup* mBackupHandle;
|
sqlite3_backup* mBackupHandle;
|
||||||
};
|
};
|
||||||
|
|
@ -2962,7 +2962,8 @@ uint32_t Connection::DecreaseTransactionNestingLevel(
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
Connection::BackupToFileAsync(nsIFile* aDestinationFile,
|
Connection::BackupToFileAsync(nsIFile* aDestinationFile,
|
||||||
mozIStorageCompletionCallback* aCallback) {
|
mozIStorageCompletionCallback* aCallback,
|
||||||
|
uint32_t aPagesPerStep, uint32_t aStepDelayMs) {
|
||||||
NS_ENSURE_ARG(aDestinationFile);
|
NS_ENSURE_ARG(aDestinationFile);
|
||||||
NS_ENSURE_ARG(aCallback);
|
NS_ENSURE_ARG(aCallback);
|
||||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
|
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
|
||||||
|
|
@ -2984,9 +2985,28 @@ Connection::BackupToFileAsync(nsIFile* aDestinationFile,
|
||||||
return NS_ERROR_NOT_INITIALIZED;
|
return NS_ERROR_NOT_INITIALIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The number of pages of the database to copy per step
|
||||||
|
static constexpr int32_t DEFAULT_PAGES_PER_STEP = 5;
|
||||||
|
// The number of milliseconds to wait between each step.
|
||||||
|
static constexpr uint32_t DEFAULT_STEP_DELAY_MS = 250;
|
||||||
|
|
||||||
|
CheckedInt<int32_t> pagesPerStep(aPagesPerStep);
|
||||||
|
if (!pagesPerStep.isValid()) {
|
||||||
|
return NS_ERROR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pagesPerStep.value()) {
|
||||||
|
pagesPerStep = DEFAULT_PAGES_PER_STEP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!aStepDelayMs) {
|
||||||
|
aStepDelayMs = DEFAULT_STEP_DELAY_MS;
|
||||||
|
}
|
||||||
|
|
||||||
// Create and dispatch our backup event to the execution thread.
|
// Create and dispatch our backup event to the execution thread.
|
||||||
nsCOMPtr<nsIRunnable> backupEvent =
|
nsCOMPtr<nsIRunnable> backupEvent =
|
||||||
new AsyncBackupDatabaseFile(this, mDBConn, aDestinationFile, aCallback);
|
new AsyncBackupDatabaseFile(this, mDBConn, aDestinationFile, aCallback,
|
||||||
|
pagesPerStep.value(), aStepDelayMs);
|
||||||
rv = asyncThread->Dispatch(backupEvent, NS_DISPATCH_NORMAL);
|
rv = asyncThread->Dispatch(backupEvent, NS_DISPATCH_NORMAL);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,15 @@ async function getPreparedAsyncDatabase() {
|
||||||
*
|
*
|
||||||
* @param {mozIStorageAsyncConnection} connection
|
* @param {mozIStorageAsyncConnection} connection
|
||||||
* A connection to a database that should be copied.
|
* A connection to a database that should be copied.
|
||||||
|
* @param {number} [pagesPerStep]
|
||||||
|
* The number of pages to copy per step. If not supplied or is 0, falls back
|
||||||
|
* to the platform default which is currently 5.
|
||||||
|
* @param {number} [stepDelayMs]
|
||||||
|
* The number of milliseconds to wait between copying step. If not supplied
|
||||||
|
* or is 0, falls back to the platform default which is currently 250.
|
||||||
* @returns {Promise<nsIFile>}
|
* @returns {Promise<nsIFile>}
|
||||||
*/
|
*/
|
||||||
async function createCopy(connection) {
|
async function createCopy(connection, pagesPerStep, stepDelayMs) {
|
||||||
let destFilePath = PathUtils.join(PathUtils.profileDir, BACKUP_FILE_NAME);
|
let destFilePath = PathUtils.join(PathUtils.profileDir, BACKUP_FILE_NAME);
|
||||||
let destFile = await IOUtils.getFile(destFilePath);
|
let destFile = await IOUtils.getFile(destFilePath);
|
||||||
Assert.ok(
|
Assert.ok(
|
||||||
|
|
@ -96,10 +102,15 @@ async function createCopy(connection) {
|
||||||
);
|
);
|
||||||
|
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
connection.backupToFileAsync(destFile, result => {
|
connection.backupToFileAsync(
|
||||||
|
destFile,
|
||||||
|
result => {
|
||||||
Assert.ok(Components.isSuccessCode(result));
|
Assert.ok(Components.isSuccessCode(result));
|
||||||
resolve(result);
|
resolve(result);
|
||||||
});
|
},
|
||||||
|
pagesPerStep,
|
||||||
|
stepDelayMs
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return destFile;
|
return destFile;
|
||||||
|
|
@ -121,7 +132,7 @@ async function assertSuccessfulCopy(file, expectedEntries = TEST_ROWS) {
|
||||||
|
|
||||||
await executeSimpleSQLAsync(conn, "PRAGMA page_size", resultSet => {
|
await executeSimpleSQLAsync(conn, "PRAGMA page_size", resultSet => {
|
||||||
let result = resultSet.getNextRow();
|
let result = resultSet.getNextRow();
|
||||||
Assert.equal(TEST_PAGE_SIZE, result.getResultByIndex(0).getAsUint32());
|
Assert.equal(TEST_PAGE_SIZE, result.getResultByIndex(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
let stmt = conn.createAsyncStatement("SELECT COUNT(*) FROM test");
|
let stmt = conn.createAsyncStatement("SELECT COUNT(*) FROM test");
|
||||||
|
|
@ -190,6 +201,36 @@ add_task(async function test_backupToFileAsync_during_insert() {
|
||||||
await asyncClose(newConnection);
|
await asyncClose(newConnection);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that alternative pages-per-step and step delay values can be set when
|
||||||
|
* calling backupToFileAsync.
|
||||||
|
*/
|
||||||
|
add_task(async function test_backupToFileAsync_during_insert() {
|
||||||
|
let newConnection = await getPreparedAsyncDatabase();
|
||||||
|
|
||||||
|
// Let's try some higher values...
|
||||||
|
let copyFile = await createCopy(newConnection, 15, 500);
|
||||||
|
Assert.ok(
|
||||||
|
await IOUtils.exists(copyFile.path),
|
||||||
|
"A new file was created by backupToFileAsync"
|
||||||
|
);
|
||||||
|
|
||||||
|
await assertSuccessfulCopy(copyFile);
|
||||||
|
await IOUtils.remove(copyFile.path);
|
||||||
|
|
||||||
|
// And now we'll try some lower values...
|
||||||
|
copyFile = await createCopy(newConnection, 1, 25);
|
||||||
|
Assert.ok(
|
||||||
|
await IOUtils.exists(copyFile.path),
|
||||||
|
"A new file was created by backupToFileAsync"
|
||||||
|
);
|
||||||
|
|
||||||
|
await assertSuccessfulCopy(copyFile);
|
||||||
|
await IOUtils.remove(copyFile.path);
|
||||||
|
|
||||||
|
await asyncClose(newConnection);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the behaviour of backupToFileAsync as exposed through Sqlite.sys.mjs.
|
* Tests the behaviour of backupToFileAsync as exposed through Sqlite.sys.mjs.
|
||||||
*/
|
*/
|
||||||
|
|
@ -206,6 +247,13 @@ add_task(async function test_backupToFileAsync_via_Sqlite_module() {
|
||||||
|
|
||||||
await assertSuccessfulCopy(copyFile);
|
await assertSuccessfulCopy(copyFile);
|
||||||
await IOUtils.remove(copyFile.path);
|
await IOUtils.remove(copyFile.path);
|
||||||
|
|
||||||
|
// Also check that we can plumb through pagesPerStep and stepDelayMs.
|
||||||
|
await moduleConnection.backup(copyFilePath, 15, 500);
|
||||||
|
Assert.ok(await IOUtils.exists(copyFilePath), "A new file was created");
|
||||||
|
await assertSuccessfulCopy(copyFile);
|
||||||
|
await IOUtils.remove(copyFile.path);
|
||||||
|
|
||||||
await moduleConnection.close();
|
await moduleConnection.close();
|
||||||
await asyncClose(xpcomConnection);
|
await asyncClose(xpcomConnection);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1184,9 +1184,15 @@ ConnectionData.prototype = Object.freeze({
|
||||||
* @param {string} destFilePath
|
* @param {string} destFilePath
|
||||||
* The path on the local filesystem to write the database copy. Any existing
|
* The path on the local filesystem to write the database copy. Any existing
|
||||||
* file at this path will be overwritten.
|
* file at this path will be overwritten.
|
||||||
|
* @param {number} [pagesPerStep=0]
|
||||||
|
* The number of pages to copy per step. If not supplied or is 0, falls back
|
||||||
|
* to the platform default which is currently 5.
|
||||||
|
* @param {number} [stepDelayMs=0]
|
||||||
|
* The number of milliseconds to wait between copying step. If not supplied
|
||||||
|
* or is 0, falls back to the platform default which is currently 250.
|
||||||
* @return Promise<undefined, nsresult>
|
* @return Promise<undefined, nsresult>
|
||||||
*/
|
*/
|
||||||
async backupToFile(destFilePath) {
|
async backupToFile(destFilePath, pagesPerStep = 0, stepDelayMs = 0) {
|
||||||
if (!this._dbConn) {
|
if (!this._dbConn) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error("No opened database connection to create a backup from.")
|
new Error("No opened database connection to create a backup from.")
|
||||||
|
|
@ -1194,13 +1200,18 @@ ConnectionData.prototype = Object.freeze({
|
||||||
}
|
}
|
||||||
let destFile = await IOUtils.getFile(destFilePath);
|
let destFile = await IOUtils.getFile(destFilePath);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this._dbConn.backupToFileAsync(destFile, result => {
|
this._dbConn.backupToFileAsync(
|
||||||
|
destFile,
|
||||||
|
result => {
|
||||||
if (Components.isSuccessCode(result)) {
|
if (Components.isSuccessCode(result)) {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
reject(result);
|
reject(result);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
pagesPerStep,
|
||||||
|
stepDelayMs
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -2002,10 +2013,20 @@ OpenedConnection.prototype = Object.freeze({
|
||||||
* @param {string} destFilePath
|
* @param {string} destFilePath
|
||||||
* The path on the local filesystem to write the database copy. Any existing
|
* The path on the local filesystem to write the database copy. Any existing
|
||||||
* file at this path will be overwritten.
|
* file at this path will be overwritten.
|
||||||
|
* @param {number} [pagesPerStep=0]
|
||||||
|
* The number of pages to copy per step. If not supplied or is 0, falls back
|
||||||
|
* to the platform default which is currently 5.
|
||||||
|
* @param {number} [stepDelayMs=0]
|
||||||
|
* The number of milliseconds to wait between copying step. If not supplied
|
||||||
|
* or is 0, falls back to the platform default which is currently 250.
|
||||||
* @return Promise<undefined, nsresult>
|
* @return Promise<undefined, nsresult>
|
||||||
*/
|
*/
|
||||||
backup(destFilePath) {
|
backup(destFilePath, pagesPerStep = 0, stepDelayMs = 0) {
|
||||||
return this._connectionData.backupToFile(destFilePath);
|
return this._connectionData.backupToFile(
|
||||||
|
destFilePath,
|
||||||
|
pagesPerStep,
|
||||||
|
stepDelayMs
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue