/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Maximum size of the pages cache per connection. #define MAX_CACHE_SIZE_KIBIBYTES 2048 // 2 MiB
mozilla::LazyLogModule gStorageLog("mozStorage");
// Checks that the protected code is running on the main-thread only if the // connection was also opened on it. #ifdef DEBUG # define CHECK_MAINTHREAD_ABUSE() \ do { \
NS_WARNING_ASSERTION( \
eventTargetOpenedOn == GetMainThreadSerialEventTarget() || \
!NS_IsMainThread(), \ "Using Storage synchronous API on main-thread, but " \ "the connection was opened on another thread."); \
} while (0) #else # define CHECK_MAINTHREAD_ABUSE() \ do { /* Nothing */ \
} while (0) #endif
namespace mozilla::storage {
using mozilla::dom::quota::QuotaObject; using mozilla::Telemetry::AccumulateCategoricalKeyed; using mozilla::Telemetry::LABELS_SQLITE_STORE_OPEN; using mozilla::Telemetry::LABELS_SQLITE_STORE_QUERY;
namespace {
int nsresultToSQLiteResult(nsresult aXPCOMResultCode) { if (NS_SUCCEEDED(aXPCOMResultCode)) { return SQLITE_OK;
}
switch (aXPCOMResultCode) { case NS_ERROR_FILE_CORRUPTED: return SQLITE_CORRUPT; case NS_ERROR_FILE_ACCESS_DENIED: return SQLITE_CANTOPEN; case NS_ERROR_STORAGE_BUSY: return SQLITE_BUSY; case NS_ERROR_FILE_IS_LOCKED: return SQLITE_LOCKED; case NS_ERROR_FILE_READ_ONLY: return SQLITE_READONLY; case NS_ERROR_STORAGE_IOERR: return SQLITE_IOERR; case NS_ERROR_FILE_NO_DEVICE_SPACE: return SQLITE_FULL; case NS_ERROR_OUT_OF_MEMORY: return SQLITE_NOMEM; case NS_ERROR_UNEXPECTED: return SQLITE_MISUSE; case NS_ERROR_ABORT: return SQLITE_ABORT; case NS_ERROR_STORAGE_CONSTRAINT: return SQLITE_CONSTRAINT; default: return SQLITE_ERROR;
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Must return in switch above!");
}
//////////////////////////////////////////////////////////////////////////////// //// Local Functions
int tracefunc(unsigned aReason, void* aClosure, void* aP, void* aX) { switch (aReason) { case SQLITE_TRACE_STMT: { // aP is a pointer to the prepared statement.
sqlite3_stmt* stmt = static_cast<sqlite3_stmt*>(aP); // aX is a pointer to a string containing the unexpanded SQL or a comment, // starting with "--"" in case of a trigger. char* expanded = static_cast<char*>(aX); // Simulate what sqlite_trace was doing. if (!::strncmp(expanded, "--", 2)) {
MOZ_LOG(gStorageLog, LogLevel::Debug,
("TRACE_STMT on %p: '%s'", aClosure, expanded));
} else { char* sql = ::sqlite3_expanded_sql(stmt);
MOZ_LOG(gStorageLog, LogLevel::Debug,
("TRACE_STMT on %p: '%s'", aClosure, sql));
::sqlite3_free(sql);
} break;
} case SQLITE_TRACE_PROFILE: { // aX is pointer to a 64bit integer containing nanoseconds it took to // execute the last command.
sqlite_int64 time = *(static_cast<sqlite_int64*>(aX)) / 1000000; if (time > 0) {
MOZ_LOG(gStorageLog, LogLevel::Debug,
("TRACE_TIME on %p: %lldms", aClosure, time));
} break;
}
} return 0;
}
RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv)); if (!arguments) return;
nsCOMPtr<nsIVariant> result;
nsresult rv = func->OnFunctionCall(arguments, getter_AddRefs(result)); if (NS_FAILED(rv)) {
nsAutoCString errorMessage;
GetErrorName(rv, errorMessage);
errorMessage.InsertLiteral("User function returned ", 0);
errorMessage.Append('!');
NS_WARNING(errorMessage.get());
::sqlite3_result_error(aCtx, errorMessage.get(), -1);
::sqlite3_result_error_code(aCtx, nsresultToSQLiteResult(rv)); return;
} int retcode = variantToSQLiteT(aCtx, result); if (retcode != SQLITE_OK) {
NS_WARNING("User function returned invalid data type!");
::sqlite3_result_error(aCtx, "User function returned invalid data type",
-1);
}
}
/** * This code is heavily based on the sample at: * http://www.sqlite.org/unlock_notify.html
*/ class UnlockNotification { public:
UnlockNotification()
: mMutex("UnlockNotification mMutex"),
mCondVar(mMutex, "UnlockNotification condVar"),
mSignaled(false) {}
void UnlockNotifyCallback(void** aArgs, int aArgsSize) { for (int i = 0; i < aArgsSize; i++) {
UnlockNotification* notification = static_cast<UnlockNotification*>(aArgs[i]);
notification->Signal();
}
}
int WaitForUnlockNotify(sqlite3* aDatabase) {
UnlockNotification notification; int srv =
::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback, ¬ification);
MOZ_ASSERT(srv == SQLITE_LOCKED || srv == SQLITE_OK); if (srv == SQLITE_OK) {
notification.Wait();
}
return srv;
}
//////////////////////////////////////////////////////////////////////////////// //// Local Classes
class AsyncCloseConnection final : public Runnable { public:
AsyncCloseConnection(Connection* aConnection, sqlite3* aNativeConnection,
nsIRunnable* aCallbackEvent)
: Runnable("storage::AsyncCloseConnection"),
mConnection(aConnection),
mNativeConnection(aNativeConnection),
mCallbackEvent(aCallbackEvent) {}
NS_IMETHOD Run() override { // Make sure we don't dispatch to the current thread.
MOZ_ASSERT(!IsOnCurrentSerialEventTarget(mConnection->eventTargetOpenedOn));
/** * An event used to initialize the clone of a connection. * * Must be executed on the clone's async execution thread.
*/ class AsyncInitializeClone final : public Runnable { public: /** * @param aConnection The connection being cloned. * @param aClone The clone. * @param aReadOnly If |true|, the clone is read only. * @param aCallback A callback to trigger once initialization * is complete. This event will be called on * aClone->eventTargetOpenedOn.
*/
AsyncInitializeClone(Connection* aConnection, Connection* aClone, constbool aReadOnly,
mozIStorageCompletionCallback* aCallback)
: Runnable("storage::AsyncInitializeClone"),
mConnection(aConnection),
mClone(aClone),
mReadOnly(aReadOnly),
mCallback(aCallback) {
MOZ_ASSERT(NS_IsMainThread());
}
// Generally, the callback will be released by CallbackComplete. // However, if for some reason Run() is not executed, we still // need to ensure that it is released here.
NS_ProxyRelease("AsyncInitializeClone::mCallback", thread,
mCallback.forget());
}
/** * A listener for async connection closing.
*/ class CloseListener final : public mozIStorageCompletionCallback { public:
NS_DECL_ISUPPORTS
CloseListener() : mClosed(false) {}
class AsyncVacuumEvent final : public Runnable { public:
AsyncVacuumEvent(Connection* aConnection,
mozIStorageCompletionCallback* aCallback, bool aUseIncremental, int32_t aSetPageSize)
: Runnable("storage::AsyncVacuum"),
mConnection(aConnection),
mCallback(aCallback),
mUseIncremental(aUseIncremental),
mSetPageSize(aSetPageSize),
mStatus(NS_ERROR_UNEXPECTED) {}
NS_IMETHOD Run() override { // This is initially dispatched to the helper thread, then re-dispatched // to the opener thread, where it will callback. if (IsOnCurrentSerialEventTarget(mConnection->eventTargetOpenedOn)) { // Send the completion event. if (mCallback) {
mozilla::Unused << mCallback->Complete(mStatus, nullptr);
} return NS_OK;
}
// Ensure to invoke the callback regardless of errors. auto guard = MakeScopeExit([&]() {
mConnection->mIsStatementOnHelperThreadInterruptible = false;
mozilla::Unused << mConnection->eventTargetOpenedOn->Dispatch( this, NS_DISPATCH_NORMAL);
});
// Get list of attached databases.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mConnection->CreateStatement(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA database_list"_ns,
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv); // We must accumulate names and loop through them later, otherwise VACUUM // will see an ongoing statement and bail out.
nsTArray<nsCString> schemaNames; bool hasResult = false; while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
nsAutoCString name;
rv = stmt->GetUTF8String(1, name); if (NS_SUCCEEDED(rv) && !name.EqualsLiteral("temp")) {
schemaNames.AppendElement(name);
}
}
mStatus = NS_OK; // Mark this vacuum as an interruptible operation, so it can be interrupted // if the connection closes during shutdown.
mConnection->mIsStatementOnHelperThreadInterruptible = true; for (const nsCString& schemaName : schemaNames) {
rv = this->Vacuum(schemaName); if (NS_FAILED(rv)) { // This is sub-optimal since it's only keeping the last error reason, // but it will do for now.
mStatus = rv;
}
} return mStatus;
}
nsresult Vacuum(const nsACString& aSchemaName) { // Abort if we're in shutdown. if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { return NS_ERROR_ABORT;
}
int32_t removablePages = mConnection->RemovablePagesInFreeList(aSchemaName); if (!removablePages) { // There's no empty pages to remove, so skip this vacuum for now. return NS_OK;
}
nsresult rv; bool needsFullVacuum = true;
/** * A runnable to perform an SQLite database backup when there may be one or more * open connections on that database.
*/ class AsyncBackupDatabaseFile final : public Runnable, public nsITimerCallback { public:
NS_DECL_ISUPPORTS_INHERITED
/** * @param aConnection The connection to the database being backed up. * @param aNativeConnection The native connection to the database being backed * up. * @param aDestinationFile The destination file for the created backup. * @param aCallback A callback to trigger once the backup process has * completed. The callback will be supplied with an nsresult * indicating whether or not the backup was successfully * created. This callback will be called on the * mConnection->eventTargetOpenedOn thread. * @throws
*/
AsyncBackupDatabaseFile(Connection* aConnection, sqlite3* aNativeConnection,
nsIFile* aDestinationFile,
mozIStorageCompletionCallback* aCallback,
int32_t aPagesPerStep, uint32_t aStepDelayMs)
: Runnable("storage::AsyncBackupDatabaseFile"),
mConnection(aConnection),
mNativeConnection(aNativeConnection),
mDestinationFile(aDestinationFile),
mCallback(aCallback),
mPagesPerStep(aPagesPerStep),
mStepDelayMs(aStepDelayMs),
mBackupFile(nullptr),
mBackupHandle(nullptr) {
MOZ_ASSERT(NS_IsMainThread());
}
nsAutoString path;
nsresult rv = mDestinationFile->GetPath(path); if (NS_FAILED(rv)) { return Dispatch(rv, nullptr);
} // Put a .tmp on the end of the destination while the backup is underway. // This extension will be stripped off after the backup successfully // completes.
path.AppendLiteral(".tmp");
int srv = ::sqlite3_open(NS_ConvertUTF16toUTF8(path).get(), &mBackupFile); if (srv != SQLITE_OK) {
::sqlite3_close(mBackupFile);
mBackupFile = nullptr; return Dispatch(NS_ERROR_FAILURE, nullptr);
}
// This guard is used to close the backup database in the event of // some failure throughout this process. We release the exit guard // only if we complete the backup successfully, or defer to another // later call to DoStep. auto guard = MakeScopeExit([&]() {
MOZ_ALWAYS_TRUE(::sqlite3_close(mBackupFile) == SQLITE_OK);
mBackupFile = nullptr;
});
int srv = ::sqlite3_backup_step(mBackupHandle, mPagesPerStep); if (srv == SQLITE_OK || srv == SQLITE_BUSY || srv == SQLITE_LOCKED) { // We're continuing the backup later. Release the guard to avoid closing // the database.
guard.release(); // Queue up the next step return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mStepDelayMs,
nsITimer::TYPE_ONE_SHOT,
GetCurrentSerialEventTarget());
} #ifdef DEBUG if (srv != SQLITE_DONE) {
nsCString warnMsg;
warnMsg.AppendLiteral( "The SQLite database copy could not be completed due to an error: ");
warnMsg.Append(::sqlite3_errmsg(mBackupFile));
NS_WARNING(warnMsg.get());
} #endif
// The database is already closed, so we can release this guard now.
guard.release();
if (srv != SQLITE_DONE) {
NS_WARNING("Failed to create database copy.");
// The partially created database file is not useful. Let's remove it.
rv = file->Remove(false); if (NS_FAILED(rv)) {
NS_WARNING( "Removing a partially backed up SQLite database file failed.");
}
// Generally, the callback will be released by CallbackComplete. // However, if for some reason Run() is not executed, we still // need to ensure that it is released here.
NS_ProxyRelease("AsyncInitializeClone::mCallback", thread,
mCallback.forget());
}
Connection::Connection(Service* aService, int aFlags,
ConnectionOperation aSupportedOperations, const nsCString& aTelemetryFilename, bool aInterruptible, bool aIgnoreLockingMode, bool aOpenNotExclusive)
: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex"),
sharedDBMutex("Connection::sharedDBMutex"),
eventTargetOpenedOn(WrapNotNull(GetCurrentSerialEventTarget())),
mIsStatementOnHelperThreadInterruptible(false),
mDBConn(nullptr),
mDefaultTransactionType(mozIStorageConnection::TRANSACTION_DEFERRED),
mDestroying(false),
mProgressHandler(nullptr),
mStorageService(aService),
mFlags(aFlags),
mTransactionNestingLevel(0),
mSupportedOperations(aSupportedOperations),
mInterruptible(aSupportedOperations == Connection::ASYNCHRONOUS ||
aInterruptible),
mIgnoreLockingMode(aIgnoreLockingMode),
mOpenNotExclusive(aOpenNotExclusive),
mAsyncExecutionThreadShuttingDown(false),
mConnectionClosed(false),
mGrowthChunkSize(0) {
MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY, "Can't ignore locking for a non-readonly connection!");
mStorageService->registerConnection(this);
MOZ_ASSERT(!aTelemetryFilename.IsEmpty(), "A telemetry filename should have been passed-in.");
mTelemetryFilename.Assign(aTelemetryFilename);
}
Connection::~Connection() { // Failsafe Close() occurs in our custom Release method because of // complications related to Close() potentially invoking AsyncClose() which // will increment our refcount.
MOZ_ASSERT(!mAsyncExecutionThread, "The async thread has not been shutdown properly!");
}
// This is identical to what NS_IMPL_RELEASE provides, but with the // extra |1 == count| case.
NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void) {
MOZ_ASSERT(0 != mRefCnt, "dup release");
nsrefcnt count = --mRefCnt;
NS_LOG_RELEASE(this, count, "Connection"); if (1 == count) { // If the refcount went to 1, the single reference must be from // gService->mConnections (in class |Service|). And the code calling // Release is either: // - The "user" code that had created the connection, releasing on any // thread. // - One of Service's getConnections() callers had acquired a strong // reference to the Connection that out-lived the last "user" reference, // and now that just got dropped. Note that this reference could be // getting dropped on the main thread or Connection->eventTargetOpenedOn // (because of the NewRunnableMethod used by minimizeMemory). // // Either way, we should now perform our failsafe Close() and unregister. // However, we only want to do this once, and the reality is that our // refcount could go back up above 1 and down again at any time if we are // off the main thread and getConnections() gets called on the main thread, // so we use an atomic here to do this exactly once. if (mDestroying.compareExchange(false, true)) { // Close the connection, dispatching to the opening event target if we're // not on that event target already and that event target is still // accepting runnables. We do this because it's possible we're on the main // thread because of getConnections(), and we REALLY don't want to // transfer I/O to the main thread if we can avoid it. if (IsOnCurrentSerialEventTarget(eventTargetOpenedOn)) { // This could cause SpinningSynchronousClose() to be invoked and AddRef // triggered for AsyncCloseConnection's strong ref if the conn was ever // use for async purposes. (Main-thread only, though.)
Unused << synchronousClose();
} else {
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod("storage::Connection::synchronousClose", this,
&Connection::synchronousClose); if (NS_FAILED(eventTargetOpenedOn->Dispatch(event.forget(),
NS_DISPATCH_NORMAL))) { // The event target was dead and so we've just leaked our runnable. // This should not happen because our non-main-thread consumers should // be explicitly closing their connections, not relying on us to close // them for them. (It's okay to let a statement go out of scope for // automatic cleanup, but not a Connection.)
MOZ_ASSERT(false, "Leaked Connection::synchronousClose(), ownership fail.");
Unused << synchronousClose();
}
}
// This will drop its strong reference right here, right now.
mStorageService->unregisterConnection(this);
}
} elseif (0 == count) {
mRefCnt = 1; /* stabilize */ #if 0 /* enable this to find non-threadsafe destructors: */
NS_ASSERT_OWNINGTHREAD(Connection); #endif delete (this); return 0;
} return count;
}
int32_t Connection::getSqliteRuntimeStatus(int32_t aStatusOption,
int32_t* aMaxValue) {
MOZ_ASSERT(connectionReady(), "A connection must exist at this point"); int curr = 0, max = 0;
DebugOnly<int> rc =
::sqlite3_db_status(mDBConn, aStatusOption, &curr, &max, 0);
MOZ_ASSERT(NS_SUCCEEDED(convertResultCode(rc))); if (aMaxValue) *aMaxValue = max; return curr;
}
// Don't return the asynchronous event target if we are shutting down. if (mAsyncExecutionThreadShuttingDown) { return nullptr;
}
// Create the async event target if there's none yet. if (!mAsyncExecutionThread) { // Names start with "sqldb:" followed by a recognizable name, like the // database file name, or a specially crafted name like "memory". // This name will be surfaced on https://crash-stats.mozilla.org, so any // sensitive part of the file name (e.g. an URL origin) should be replaced // by passing an explicit telemetryName to openDatabaseWithFileURL.
nsAutoCString name("sqldb:"_ns);
name.Append(mTelemetryFilename); static nsThreadPoolNaming naming;
nsresult rv = NS_NewNamedThread(naming.GetNextThreadName(name),
getter_AddRefs(mAsyncExecutionThread)); if (NS_FAILED(rv)) {
NS_WARNING("Failed to create async thread."); return nullptr;
}
}
if (histogramKey.IsEmpty()) {
histogramKey.AssignLiteral("unknown");
}
switch (srv) { case SQLITE_OK: case SQLITE_ROW: case SQLITE_DONE:
// Note that these are returned when we intentionally cancel a statement so // they aren't indicating a failure. case SQLITE_ABORT: case SQLITE_INTERRUPT:
AccumulateCategoricalKeyed(histogramKey,
LABELS_SQLITE_STORE_QUERY::success); break; case SQLITE_CORRUPT: case SQLITE_NOTADB:
AccumulateCategoricalKeyed(histogramKey,
LABELS_SQLITE_STORE_QUERY::corrupt); break; case SQLITE_PERM: case SQLITE_CANTOPEN: case SQLITE_LOCKED: case SQLITE_READONLY:
AccumulateCategoricalKeyed(histogramKey,
LABELS_SQLITE_STORE_QUERY::access); break; case SQLITE_IOERR: case SQLITE_NOLFS:
AccumulateCategoricalKeyed(histogramKey,
LABELS_SQLITE_STORE_QUERY::diskio); break; case SQLITE_FULL: case SQLITE_TOOBIG:
AccumulateCategoricalKeyed(histogramKey,
LABELS_SQLITE_STORE_QUERY::diskspace); break; case SQLITE_CONSTRAINT: case SQLITE_RANGE: case SQLITE_MISMATCH: case SQLITE_MISUSE:
AccumulateCategoricalKeyed(histogramKey,
LABELS_SQLITE_STORE_QUERY::misuse); break; case SQLITE_BUSY:
AccumulateCategoricalKeyed(histogramKey, LABELS_SQLITE_STORE_QUERY::busy); break; default:
AccumulateCategoricalKeyed(histogramKey,
LABELS_SQLITE_STORE_QUERY::failure);
}
}
nsresult Connection::initialize(const nsACString& aStorageKey, const nsACString& aName) {
MOZ_ASSERT(aStorageKey.Equals(kMozStorageMemoryStorageKey));
NS_ASSERTION(!connectionReady(), "Initialize called on already opened database!");
MOZ_ASSERT(!mIgnoreLockingMode, "Can't ignore locking on an in-memory db.");
AUTO_PROFILER_LABEL("Connection::initialize", OTHER);
mStorageKey = aStorageKey;
mName = aName;
// in memory database requested, sqlite uses a magic file name
bool exclusive =
StaticPrefs::storage_sqlite_exclusiveLock_enabled() && !mOpenNotExclusive; int srv; if (mIgnoreLockingMode) {
exclusive = false;
srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags, "readonly-immutable-nolock");
} else {
srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags,
basevfs::GetVFSName(exclusive)); if (exclusive && (srv == SQLITE_LOCKED || srv == SQLITE_BUSY)) {
::sqlite3_close(mDBConn); // Retry without trying to get an exclusive lock.
exclusive = false;
srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn,
mFlags, basevfs::GetVFSName(false));
}
} if (srv != SQLITE_OK) {
::sqlite3_close(mDBConn);
mDBConn = nullptr;
rv = convertResultCode(srv);
RecordOpenStatus(rv); return rv;
}
rv = initializeInternal(); if (exclusive &&
(rv == NS_ERROR_STORAGE_BUSY || rv == NS_ERROR_FILE_IS_LOCKED)) { // Usually SQLite will fail to acquire an exclusive lock on opening, but in // some cases it may successfully open the database and then lock on the // first query execution. When initializeInternal fails it closes the // connection, so we can try to restart it in non-exclusive mode.
srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags,
basevfs::GetVFSName(false)); if (srv == SQLITE_OK) {
rv = initializeInternal();
} else {
::sqlite3_close(mDBConn);
mDBConn = nullptr;
}
}
RecordOpenStatus(rv);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult Connection::initialize(nsIFileURL* aFileURL) {
NS_ASSERTION(aFileURL, "Passed null file URL!");
NS_ASSERTION(!connectionReady(), "Initialize called on already opened database!");
AUTO_PROFILER_LABEL("Connection::initialize", OTHER);
#ifdef MOZ_SQLITE_FTS3_TOKENIZER
DebugOnly<int> srv2 =
::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0);
MOZ_ASSERT(srv2 == SQLITE_OK, "SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER should be enabled"); #endif
// Properly wrap the database handle's mutex.
sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn));
// SQLite tracing can slow down queries (especially long queries) // significantly. Don't trace unless the user is actively monitoring SQLite. if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
::sqlite3_trace_v2(mDBConn, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE,
tracefunc, this);
// Set page_size to the preferred default value. This is effective only if // the database has just been created, otherwise, if the database does not // use WAL journal mode, a VACUUM operation will updated its page_size.
nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size = ");
pageSizeQuery.AppendInt(pageSize); int srv = executeSql(mDBConn, pageSizeQuery.get()); if (srv != SQLITE_OK) { return convertResultCode(srv);
}
// Setting the cache_size forces the database open, verifying if it is valid // or corrupt. So this is executed regardless it being actually needed. // The cache_size is calculated from the actual page_size, to save memory.
nsAutoCString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA cache_size = ");
cacheSizeQuery.AppendInt(-MAX_CACHE_SIZE_KIBIBYTES);
srv = executeSql(mDBConn, cacheSizeQuery.get()); if (srv != SQLITE_OK) { return convertResultCode(srv);
}
// Set the default synchronous value. Each consumer can switch this // accordingly to their needs. #ifdefined(ANDROID) // Android prefers synchronous = OFF for performance reasons.
Unused << ExecuteSimpleSQL("PRAGMA synchronous = OFF;"_ns); #else // Normal is the suggested value for WAL journals.
Unused << ExecuteSimpleSQL("PRAGMA synchronous = NORMAL;"_ns); #endif
// Initialization succeeded, we can stop guarding for failures.
guard.release(); return NS_OK;
}
// When constructing the query, make sure to SELECT the correct db's // sqlite_master if the user is prefixing the element with a specific db. ex: // sample.test
nsCString query("SELECT name FROM (SELECT * FROM ");
nsDependentCSubstring element;
int32_t ind = aElementName.FindChar('.'); if (ind == kNotFound) {
element.Assign(aElementName);
} else {
nsDependentCSubstring db(Substring(aElementName, 0, ind + 1));
element.Assign(Substring(aElementName, ind + 1, aElementName.Length()));
query.Append(db);
}
query.AppendLiteral( "sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = " "'");
switch (aElementType) { case INDEX:
query.AppendLiteral("index"); break; case TABLE:
query.AppendLiteral("table"); break;
}
query.AppendLiteral("' AND name ='");
query.Append(element);
query.Append('\'');
sqlite3_stmt* stmt; int srv = prepareStatement(mDBConn, query, &stmt); if (srv != SQLITE_OK) {
RecordQueryStatus(srv); return convertResultCode(srv);
}
srv = stepStatement(mDBConn, stmt); // we just care about the return value from step
(void)::sqlite3_finalize(stmt);
int Connection::progressHandler() {
sharedDBMutex.assertCurrentThreadOwns(); if (mProgressHandler) { bool result;
nsresult rv = mProgressHandler->OnProgress(this, &result); if (NS_FAILED(rv)) return 0; // Don't break request return result ? 1 : 0;
} return 0;
}
nsresult Connection::setClosedState() { // Flag that we are shutting down the async thread, so that // getAsyncExecutionTarget knows not to expose/create the async thread.
MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED);
mAsyncExecutionThreadShuttingDown = true;
// Set the property to null before closing the connection, otherwise the // other functions in the module may try to use the connection after it is // closed.
mDBConn = nullptr;
return NS_OK;
}
bool Connection::operationSupported(ConnectionOperation aOperationType) { if (aOperationType == ASYNCHRONOUS) { // Async operations are supported for all connections, on any thread. returntrue;
} // Sync operations are supported for sync connections (on any thread), and // async connections on a background thread.
MOZ_ASSERT(aOperationType == SYNCHRONOUS); return mSupportedOperations == SYNCHRONOUS || !NS_IsMainThread();
}
nsresult Connection::ensureOperationSupported(
ConnectionOperation aOperationType) { if (NS_WARN_IF(!operationSupported(aOperationType))) { #ifdef DEBUG if (NS_IsMainThread()) {
nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
Unused << xpc->DebugDumpJSStack(false, false, false);
} #endif
MOZ_ASSERT(false, "Don't use async connections synchronously on the main thread"); return NS_ERROR_NOT_AVAILABLE;
} return NS_OK;
}
nsresult Connection::internalClose(sqlite3* aNativeConnection) { #ifdef DEBUG
{ // Make sure we have marked our async thread as shutting down.
MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
MOZ_ASSERT(mAsyncExecutionThreadShuttingDown, "Did not call setClosedState!");
MOZ_ASSERT(!isClosed(lockedScope), "Unexpected closed state");
} #endif// DEBUG
if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
nsAutoCString leafName(":memory"); if (mDatabaseFile) (void)mDatabaseFile->GetNativeLeafName(leafName);
MOZ_LOG(gStorageLog, LogLevel::Debug,
("Closing connection to '%s'", leafName.get()));
}
// At this stage, we may still have statements that need to be // finalized. Attempt to close the database connection. This will // always disconnect any virtual tables and cleanly finalize their // internal statements. Once this is done, closing may fail due to // unfinalized client statements, in which case we need to finalize // these statements and close again.
{
MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
mConnectionClosed = true;
}
// Nothing else needs to be done if we don't have a connection here. if (!aNativeConnection) return NS_OK;
int srv = ::sqlite3_close(aNativeConnection);
if (srv == SQLITE_BUSY) {
{ // Nothing else should change the connection or statements status until we // are done here.
SQLiteMutexAutoLock lockedScope(sharedDBMutex); // We still have non-finalized statements. Finalize them.
sqlite3_stmt* stmt = nullptr; while ((stmt = ::sqlite3_next_stmt(aNativeConnection, stmt))) {
MOZ_LOG(gStorageLog, LogLevel::Debug,
("Auto-finalizing SQL statement '%s' (%p)", ::sqlite3_sql(stmt),
stmt));
#ifdef DEBUG
SmprintfPointer msg = ::mozilla::Smprintf( "SQL statement '%s' (%p) should have been finalized before closing " "the connection",
::sqlite3_sql(stmt), stmt);
NS_WARNING(msg.get()); #endif// DEBUG
// Ensure that the loop continues properly, whether closing has // succeeded or not. if (srv == SQLITE_OK) {
stmt = nullptr;
}
} // Scope exiting will unlock the mutex before we invoke sqlite3_close() // again, since Sqlite will try to acquire it.
}
// Now that all statements have been finalized, we // should be able to close.
srv = ::sqlite3_close(aNativeConnection);
MOZ_ASSERT(false, "Had to forcibly close the database connection because not all " "the statements have been finalized.");
}
if (srv == SQLITE_OK) {
sharedDBMutex.destroy();
} else {
MOZ_ASSERT(false, "sqlite3_close failed. There are probably outstanding " "statements that are listed above!");
}
// The connection may have been closed if the executing statement has been // created and cached after a call to asyncClose() but before the actual // sqlite3_close(). This usually happens when other tasks using cached // statements are asynchronously scheduled for execution and any of them ends // up after asyncClose. See bug 728653 for details. if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;
int srv; while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) { if (!checkedMainThread) {
checkedMainThread = true; if (::NS_IsMainThread()) {
NS_WARNING("We won't allow blocking on the main thread!"); break;
}
}
srv = WaitForUnlockNotify(aNativeConnection); if (srv != SQLITE_OK) { break;
}
(void)::sqlite3_extended_result_codes(aNativeConnection, 0); // Drop off the extended result bits of the result code. return srv & 0xFF;
}
int Connection::prepareStatement(sqlite3* aNativeConnection, const nsCString& aSQL, sqlite3_stmt** _stmt) { // We should not even try to prepare statements after the connection has // been closed. if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;
int srv; while ((srv = ::sqlite3_prepare_v2(aNativeConnection, aSQL.get(), -1, _stmt,
nullptr)) == SQLITE_LOCKED_SHAREDCACHE) { if (!checkedMainThread) {
checkedMainThread = true; if (::NS_IsMainThread()) {
NS_WARNING("We won't allow blocking on the main thread!"); break;
}
}
if (srv != SQLITE_OK) {
nsCString warnMsg;
warnMsg.AppendLiteral("The SQL statement '");
warnMsg.Append(aSQL);
warnMsg.AppendLiteral("' could not be compiled due to an error: ");
warnMsg.Append(::sqlite3_errmsg(aNativeConnection));
(void)::sqlite3_extended_result_codes(aNativeConnection, 0); // Drop off the extended result bits of the result code. int rc = srv & 0xFF; // sqlite will return OK on a comment only string and set _stmt to nullptr. // The callers of this function are used to only checking the return value, // so it is safer to return an error code. if (rc == SQLITE_OK && *_stmt == nullptr) { return SQLITE_MISUSE;
}
return rc;
}
int Connection::executeSql(sqlite3* aNativeConnection, constchar* aSqlString) { if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;
nsresult Connection::synchronousClose() { if (!connectionReady()) { return NS_ERROR_NOT_INITIALIZED;
}
#ifdef DEBUG // Since we're accessing mAsyncExecutionThread, we need to be on the opener // event target. We make this check outside of debug code below in // setClosedState, but this is here to be explicit.
MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn)); #endif// DEBUG
// Make sure we have not executed any asynchronous statements. // If this fails, the mDBConn may be left open, resulting in a leak. // We'll try to finalize the pending statements and close the connection. if (isAsyncExecutionThreadAvailable()) { #ifdef DEBUG if (NS_IsMainThread()) {
nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
Unused << xpc->DebugDumpJSStack(false, false, false);
} #endif
MOZ_ASSERT(false, "Close() was invoked on a connection that executed asynchronous " "statements. " "Should have used asyncClose()."); // Try to close the database regardless, to free up resources.
Unused << SpinningSynchronousClose(); return NS_ERROR_UNEXPECTED;
}
// setClosedState nullifies our connection pointer, so we take a raw pointer // off it, to pass it through the close procedure.
sqlite3* nativeConn = mDBConn;
nsresult rv = setClosedState();
NS_ENSURE_SUCCESS(rv, rv);
return internalClose(nativeConn);
}
NS_IMETHODIMP
Connection::SpinningSynchronousClose() {
nsresult rv = ensureOperationSupported(SYNCHRONOUS); if (NS_FAILED(rv)) { return rv;
} if (!IsOnCurrentSerialEventTarget(eventTargetOpenedOn)) { return NS_ERROR_NOT_SAME_THREAD;
}
// As currently implemented, we can't spin to wait for an existing AsyncClose. // Our only existing caller will never have called close; assert if misused // so that no new callers assume this works after an AsyncClose.
MOZ_DIAGNOSTIC_ASSERT(connectionReady()); if (!connectionReady()) { return NS_ERROR_UNEXPECTED;
}
RefPtr<CloseListener> listener = new CloseListener();
rv = AsyncClose(listener);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ALWAYS_TRUE(
SpinEventLoopUntil("storage::Connection::SpinningSynchronousClose"_ns,
[&]() { return listener->mClosed; }));
MOZ_ASSERT(isClosed(), "The connection should be closed at this point");
return rv;
}
NS_IMETHODIMP
Connection::AsyncClose(mozIStorageCompletionCallback* aCallback) {
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); // Check if AsyncClose or Close were already invoked. if (!connectionReady()) { return NS_ERROR_NOT_INITIALIZED;
}
nsresult rv = ensureOperationSupported(ASYNCHRONOUS); if (NS_FAILED(rv)) { return rv;
}
// The two relevant factors at this point are whether we have a database // connection and whether we have an async execution thread. Here's what the // states mean and how we handle them: // // - (mDBConn && asyncThread): The expected case where we are either an // async connection or a sync connection that has been used asynchronously. // Either way the caller must call us and not Close(). Nothing surprising // about this. We'll dispatch AsyncCloseConnection to the already-existing // async thread. // // - (mDBConn && !asyncThread): A somewhat unusual case where the caller // opened the connection synchronously and was planning to use it // asynchronously, but never got around to using it asynchronously before // needing to shutdown. This has been observed to happen for the cookie // service in a case where Firefox shuts itself down almost immediately // after startup (for unknown reasons). In the Firefox shutdown case, // we may also fail to create a new async execution thread if one does not // already exist. (nsThreadManager will refuse to create new threads when // it has already been told to shutdown.) As such, we need to handle a // failure to create the async execution thread by falling back to // synchronous Close() and also dispatching the completion callback because // at least Places likes to spin a nested event loop that depends on the // callback being invoked. // // Note that we have considered not trying to spin up the async execution // thread in this case if it does not already exist, but the overhead of // thread startup (if successful) is significantly less expensive than the // worst-case potential I/O hit of synchronously closing a database when we // could close it asynchronously. // // - (!mDBConn && asyncThread): This happens in some but not all cases where // OpenAsyncDatabase encountered a problem opening the database. If it // happened in all cases AsyncInitDatabase would just shut down the thread // directly and we would avoid this case. But it doesn't, so for simplicity // and consistency AsyncCloseConnection knows how to handle this and we // act like this was the (mDBConn && asyncThread) case in this method. // // - (!mDBConn && !asyncThread): The database was never successfully opened or // Close() or AsyncClose() has already been called (at least) once. This is // undeniably a misuse case by the caller. We could optimize for this // case by adding an additional check of mAsyncExecutionThread without using // getAsyncExecutionTarget() to avoid wastefully creating a thread just to // shut it down. But this complicates the method for broken caller code // whereas we're still correct and safe without the special-case.
nsIEventTarget* asyncThread = getAsyncExecutionTarget();
// Create our callback event if we were given a callback. This will // eventually be dispatched in all cases, even if we fall back to Close() and // the database wasn't open and we return an error. The rationale is that // no existing consumer checks our return value and several of them like to // spin nested event loops until the callback fires. Given that, it seems // preferable for us to dispatch the callback in all cases. (Except the // wrong thread misuse case we bailed on up above. But that's okay because // that is statically wrong whereas these edge cases are dynamic.)
nsCOMPtr<nsIRunnable> completeEvent; if (aCallback) {
completeEvent = newCompletionEvent(aCallback);
}
if (!asyncThread) { // We were unable to create an async thread, so we need to fall back to
--> --------------------
--> maximum size reached
--> --------------------
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.106Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.