/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */
// Because of the `sync Preload` IPC, we need to be able to synchronously // initialize, which includes consulting and initializing // some main-thread-only APIs. Bug 1386441 discusses improving this situation.
nsresult SyncDispatchAndReturnProfilePath(nsAString& aProfilePath);
private:
~InitHelper() override = default;
nsresult RunOnMainThread();
NS_DECL_NSIRUNNABLE
};
class StorageDBThread::NoteBackgroundThreadRunnable final : public Runnable { // Expected to be only 0 or 1. const uint32_t mPrivateBrowsingId;
nsCOMPtr<nsIEventTarget> mOwningThread;
StorageDBThread*& storageThread = sStorageThread[aPrivateBrowsingId]; if (storageThread || sStorageThreadDown[aPrivateBrowsingId]) { // When sStorageThreadDown is at true, sStorageThread is null. // Checking sStorageThreadDown flag here prevents reinitialization of // the storage thread after shutdown. return storageThread;
}
auto newStorageThread = MakeUnique<StorageDBThread>(aPrivateBrowsingId);
nsresult rv = newStorageThread->Init(aProfilePath); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr;
}
// Need to determine location on the main thread, since // NS_GetSpecialDirectory accesses the atom table that can // only be accessed on the main thread.
nsCOMPtr<nsIFile> profileDir;
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(profileDir)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
rv = profileDir->GetPath(aProfilePath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
// This service has to be started on the main thread currently.
nsCOMPtr<mozIStorageService> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
// After we stop, no other operations can be accepted
mFlushImmediately = true;
mStopIOThread = true;
monitor.Notify();
}
PR_JoinThread(mThread);
mThread = nullptr;
return mStatus;
}
void StorageDBThread::SyncPreload(LocalStorageCacheBridge* aCache, bool aForceSync) {
AUTO_PROFILER_LABEL("StorageDBThread::SyncPreload", OTHER); if (!aForceSync && aCache->LoadedCount()) { // Preload already started for this cache, just wait for it to finish. // LoadWait will exit after LoadDone on the cache has been called.
SetHigherPriority();
aCache->LoadWait();
SetDefaultPriority(); return;
}
// Bypass sync load when an update is pending in the queue to write, we would // get incosistent data in the cache. Also don't allow sync main-thread // preload when DB open and init is still pending on the background thread. if (mDBReady && mWALModeEnabled) { bool pendingTasks;
{
MonitorAutoLock monitor(mThreadObserver->GetMonitor());
pendingTasks = mPendingTasks.IsOriginUpdatePending(
aCache->OriginSuffix(), aCache->OriginNoSuffix()) ||
mPendingTasks.IsOriginClearPending(
aCache->OriginSuffix(), aCache->OriginNoSuffix());
}
if (!pendingTasks) { // WAL is enabled, thus do the load synchronously on the main thread.
DBOperation preload(DBOperation::opPreload, aCache);
preload.PerformAndFinalize(this); return;
}
}
// Need to go asynchronously since WAL is not allowed or scheduled updates // need to be flushed first. // Schedule preload for this cache as the first operation.
nsresult rv =
InsertDBOp(MakeUnique<DBOperation>(DBOperation::opPreloadUrgent, aCache));
// LoadWait exits after LoadDone of the cache has been called. if (NS_SUCCEEDED(rv)) {
aCache->LoadWait();
}
}
if (NS_FAILED(mStatus)) {
MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
aOperation->Finalize(mStatus); return mStatus;
}
if (mStopIOThread) { // Thread use after shutdown demanded.
MOZ_ASSERT(false); return NS_ERROR_NOT_INITIALIZED;
}
switch (aOperation->Type()) { case DBOperation::opPreload: case DBOperation::opPreloadUrgent: if (mPendingTasks.IsOriginUpdatePending(aOperation->OriginSuffix(),
aOperation->OriginNoSuffix())) { // If there is a pending update operation for the scope first do the // flush before we preload the cache. This may happen in an extremely // rare case when a child process throws away its cache before flush on // the parent has finished. If we would preloaded the cache as a // priority operation before the pending flush, we would have got an // inconsistent cache content.
mFlushImmediately = true;
} elseif (mPendingTasks.IsOriginClearPending(
aOperation->OriginSuffix(),
aOperation->OriginNoSuffix())) { // The scope is scheduled to be cleared, so just quickly load as empty. // We need to do this to prevent load of the DB data before the scope // has actually been cleared from the database. Preloads are processed // immediately before update and clear operations on the database that // are flushed periodically in batches.
MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
aOperation->Finalize(NS_OK); return NS_OK;
}
[[fallthrough]];
case DBOperation::opGetUsage: if (aOperation->Type() == DBOperation::opPreloadUrgent) {
SetHigherPriority(); // Dropped back after urgent preload execution
mPreloads.InsertElementAt(0, aOperation.release());
} else {
mPreloads.AppendElement(aOperation.release());
}
// Create an nsIThread for the current PRThread, so we can observe runnables // dispatched to it.
nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
MOZ_ASSERT(threadInternal); // Should always succeed.
threadInternal->SetObserver(mThreadObserver);
while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
mPendingTasks.HasTasks() ||
mThreadObserver->HasPendingEvents())) { // Process xpcom events first. while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
mThreadObserver->ClearPendingEvents();
MonitorAutoUnlock unlock(mThreadObserver->GetMonitor()); bool processedEvent; do {
rv = thread->ProcessNextEvent(false, &processedEvent);
} while (NS_SUCCEEDED(rv) && processedEvent);
}
TimeDuration timeUntilFlush = TimeUntilFlush(); if (MOZ_UNLIKELY(timeUntilFlush.IsZero())) { // Flush time is up or flush has been forced, do it now.
UnscheduleFlush(); if (mPendingTasks.Prepare()) {
{
MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
rv = mPendingTasks.Execute(this);
}
rv = StorageDBUpdater::Update(mWorkerConnection); if (NS_FAILED(rv)) { if (mPrivateBrowsingId == 0) { // Update has failed, rather throw the database away and try // opening and setting it up again.
rv = mWorkerConnection->Close();
mWorkerConnection = nullptr;
NS_ENSURE_SUCCESS(rv, rv);
// Create a read-only clone
(void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
// Database open and all initiation operation are done. Switching this flag // to true allow main thread to read directly from the database. If we would // allow this sooner, we would have opened a window where main thread read // might operate on a totally broken and incosistent database.
mDBReady = true;
// List scopes having any stored data
nsCOMPtr<mozIStorageStatement> stmt; // Note: result of this select must match StorageManager::CreateOrigin()
rv = mWorkerConnection->CreateStatement(
nsLiteralCString("SELECT DISTINCT originAttributes || ':' || originKey " "FROM webappsstore2"),
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
mozStorageStatementScoper scope(stmt);
// Set the threshold for auto-checkpointing the WAL. // We don't want giant logs slowing down reads & shutdown. // Note there is a default journal_size_limit set by mozStorage.
int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
thresholdPragma.AppendInt(thresholdInPages);
rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult StorageDBThread::ShutdownDatabase() { // Has to be called on the worker thread.
MOZ_ASSERT(!NS_IsMainThread());
nsresult rv = mStatus;
mDBReady = false;
// Finalize the cached statements.
mReaderStatements.FinalizeStatements();
mWorkerStatements.FinalizeStatements();
if (mReaderConnection) { // No need to sync access to mReaderConnection since the main thread // is right now joining this thread, unable to execute any events.
mReaderConnection->Close();
mReaderConnection = nullptr;
}
if (mWorkerConnection) {
rv = mWorkerConnection->Close();
mWorkerConnection = nullptr;
}
// Must be non-zero to indicate we are scheduled
mDirtyEpoch = TimeStamp::Now();
// Wake the monitor from indefinite sleep...
(mThreadObserver->GetMonitor()).Notify();
}
void StorageDBThread::UnscheduleFlush() { // We are just about to do the flush, drop flags
mFlushImmediately = false;
mDirtyEpoch = TimeStamp();
}
TimeDuration StorageDBThread::TimeUntilFlush() { if (mFlushImmediately) { return 0; // Do it now regardless the timeout.
}
if (!mDirtyEpoch) { return TimeDuration::Forever(); // No pending task...
}
TimeStamp now = TimeStamp::Now();
TimeDuration age = now - mDirtyEpoch; staticconst TimeDuration kMaxAge =
TimeDuration::FromMilliseconds(FLUSHING_INTERVAL_MS); if (age > kMaxAge) { return 0; // It is time.
}
return kMaxAge - age; // Time left. This is used to sleep the monitor.
}
// OFFSET is an optimization when we have to do a sync load // and cache has already loaded some parts asynchronously. // It skips keys we have already loaded.
nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement( "SELECT key, value FROM webappsstore2 " "WHERE originAttributes = :originAttributes AND originKey = " ":originKey " "ORDER BY key LIMIT -1 OFFSET :offset");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scope(stmt);
if (!mCache->LoadItem(key, value)) { break;
}
} // The loop condition's call to ExecuteStep() may have terminated because // !NS_SUCCEEDED(), we need an early return to cover that case. This also // covers success cases as well, but that's inductively safe.
NS_ENSURE_SUCCESS(rv, rv); break;
}
case opGetUsage: { // Bug 1676410 fixed a regression caused by bug 1165214. However, it // turns out that 100% correct checking of the eTLD+1 usage is not // possible to recover easily, see bug 1683299. #if 0 // This is how it should be done, but due to other problems like lack // of usage synchronization between content processes, we temporarily // disabled the matching using "%".
// The database schema is built around cleverly reversing domain names // (the "originKey") so that we can efficiently group usage by eTLD+1. // "foo.example.org" has an eTLD+1 of ".example.org". They reverse to // "gro.elpmaxe.oof" and "gro.elpmaxe." respectively, noting that the // reversed eTLD+1 is a prefix of its reversed sub-domain. To this end, // we can calculate all of the usage for an eTLD+1 by summing up all the // rows which have the reversed eTLD+1 as a prefix. In SQL we can // accomplish this using LIKE which provides for case-insensitive // matching with "_" as a single-character wildcard match and "%" any // sequence of zero or more characters. So by suffixing the reversed // eTLD+1 and using "%" we get our case-insensitive (domain names are // case-insensitive) matching. Note that although legal domain names // don't include "_" or "%", file origins can include them, so we need // to escape our OriginScope for correctness.
nsAutoCString originScopeEscaped;
rv = stmt->EscapeUTF8StringForLIKE(mUsage->OriginScope(), '\\',
originScopeEscaped);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName("usageOrigin"_ns,
originScopeEscaped + "%"_ns);
NS_ENSURE_SUCCESS(rv, rv); #else // This is the code before bug 1676410 and bug 1676973. The returned // usage will be zero in most of the cases, but due to lack of usage // synchronization between content processes we have to live with this // semi-broken behaviour because it causes less harm than the matching // using "%".
nsCOMPtr<mozIStorageStatement> stmt =
aThread->mWorkerStatements.GetCachedStatement( "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 " "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin");
NS_ENSURE_STATE(stmt);
// No need to selectively clear mOriginsHavingData here. That hashtable // only prevents preload for scopes with no data. Leaving a false record // in it has a negligible effect on performance. break;
}
case opClearMatchingOriginAttributes: {
MOZ_ASSERT(!NS_IsMainThread());
// Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the // pattern
nsCOMPtr<mozIStorageFunction> patternMatchFunction( new OriginAttrsPatternMatchSQLFunction(mOriginPattern));
nsCOMPtr<mozIStorageStatement> stmt =
aThread->mWorkerStatements.GetCachedStatement( "DELETE FROM webappsstore2" " WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)");
// Always remove the function
aThread->mWorkerConnection->RemoveFunction( "ORIGIN_ATTRS_PATTERN_MATCH"_ns);
NS_ENSURE_SUCCESS(rv, rv);
// No need to selectively clear mOriginsHavingData here. That hashtable // only prevents preload for scopes with no data. Leaving a false record // in it has a negligible effect on performance. break;
}
default:
NS_ERROR("Unknown task type"); break;
}
return NS_OK;
}
void StorageDBThread::DBOperation::Finalize(nsresult aRv) { switch (mType) { case opPreloadUrgent: case opPreload: if (NS_FAILED(aRv)) { // When we are here, something failed when loading from the database. // Notify that the storage is loaded to prevent deadlock of the main // thread, even though it is actually empty or incomplete.
NS_WARNING("Failed to preload localStorage");
}
mCache->LoadDone(aRv); break;
case opGetUsage: if (NS_FAILED(aRv)) {
mUsage->LoadUsage(0);
}
break;
default: if (NS_FAILED(aRv)) {
NS_WARNING( "localStorage update/clear operation failed," " data may not persist or clean up");
}
StorageDBThread::DBOperation* pendingTask; if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) { returnfalse;
}
if (pendingTask->Type() != aPendingType) { returnfalse;
}
returntrue;
}
void StorageDBThread::PendingOperations::Add(
UniquePtr<StorageDBThread::DBOperation> aOperation) { // Optimize: when a key to remove has never been written to disk // just bypass this operation. A key is new when an operation scheduled // to write it to the database is of type opAddItem. if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opAddItem,
DBOperation::opRemoveItem)) {
mUpdates.Remove(aOperation->Target()); return;
}
// Optimize: when changing a key that is new and has never been // written to disk, keep type of the operation to store it at opAddItem. // This allows optimization to just forget adding a new key when // it is removed from the storage before flush. if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opAddItem,
DBOperation::opUpdateItem)) {
aOperation->mType = DBOperation::opAddItem;
}
// Optimize: to prevent lose of remove operation on a key when doing // remove/set/remove on a previously existing key we have to change // opAddItem to opUpdateItem on the new operation when there is opRemoveItem // pending for the key. if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opRemoveItem,
DBOperation::opAddItem)) {
aOperation->mType = DBOperation::opUpdateItem;
}
switch (aOperation->Type()) { // Operations on single keys
case DBOperation::opAddItem: case DBOperation::opUpdateItem: case DBOperation::opRemoveItem: // Override any existing operation for the target (=scope+key).
mUpdates.InsertOrUpdate(aOperation->Target(), std::move(aOperation)); break;
// Clear operations
case DBOperation::opClear: case DBOperation::opClearMatchingOrigin: case DBOperation::opClearMatchingOriginAttributes: // Drop all update (insert/remove) operations for equivavelent or matching // scope. We do this as an optimization as well as a must based on the // logic, if we would not delete the update tasks, changes would have been // stored to the database after clear operations have been executed. for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) { constauto& pendingTask = iter.Data();
case DBOperation::opClearAll: // Drop simply everything, this is a super-operation.
mUpdates.Clear();
mClears.Clear();
mClears.InsertOrUpdate(aOperation->Target(), std::move(aOperation)); break;
default:
MOZ_ASSERT(false); break;
}
}
bool StorageDBThread::PendingOperations::Prepare() { // Called under the lock
// First collect clear operations and then updates, we can // do this since whenever a clear operation for a scope is // scheduled, we drop all updates matching that scope. So, // all scope-related update operations we have here now were // scheduled after the clear operations. for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
mExecList.AppendElement(std::move(iter.Data()));
}
mClears.Clear();
for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
mExecList.AppendElement(std::move(iter.Data()));
}
mUpdates.Clear();
return !!mExecList.Length();
}
nsresult StorageDBThread::PendingOperations::Execute(StorageDBThread* aThread) { // Called outside the lock
nsresult rv = transaction.Start(); if (NS_FAILED(rv)) { return rv;
}
for (uint32_t i = 0; i < mExecList.Length(); ++i) { constauto& task = mExecList[i];
rv = task->Perform(aThread); if (NS_FAILED(rv)) { return rv;
}
}
rv = transaction.Commit(); if (NS_FAILED(rv)) { return rv;
}
return NS_OK;
}
bool StorageDBThread::PendingOperations::Finalize(nsresult aRv) { // Called under the lock
// The list is kept on a failure to retry it if (NS_FAILED(aRv)) { // XXX Followup: we may try to reopen the database and flush these // pending tasks, however testing showed that even though I/O is actually // broken some amount of operations is left in sqlite+system buffers and // seems like successfully flushed to disk. // Tested by removing a flash card and disconnecting from network while // using a network drive on Windows system.
NS_WARNING("Flush operation on localStorage database failed");
if (aPendingOperation->Type() ==
StorageDBThread::DBOperation::opClearMatchingOrigin &&
StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) { returntrue;
}
if (aPendingOperation->Type() ==
StorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) { returntrue;
}
returnfalse;
}
} // namespace
bool StorageDBThread::PendingOperations::IsOriginClearPending( const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const { // Called under the lock
for (constauto& clear : mClears.Values()) { if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix,
clear.get())) { returntrue;
}
}
for (uint32_t i = 0; i < mExecList.Length(); ++i) { if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix,
mExecList[i].get())) { returntrue;
}
}
bool StorageDBThread::PendingOperations::IsOriginUpdatePending( const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const { // Called under the lock
for (constauto& update : mUpdates.Values()) { if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix,
update.get())) { returntrue;
}
}
for (uint32_t i = 0; i < mExecList.Length(); ++i) { if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix,
mExecList[i].get())) { returntrue;
}
}
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.