Skip to content

Commit

Permalink
Bug 1802920, part 1 - Create helper thread and add async shutdown blo…
Browse files Browse the repository at this point in the history
…cker - r=timhuang

Attempt #2 with a lot of refactoring included

Differential Revision: https://phabricator.services.mozilla.com/D165296
  • Loading branch information
bvandersloot-mozilla committed Jan 20, 2023
1 parent 0b39f22 commit b4287d6
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,30 @@
* 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/. */

#include "IdentityCredentialStorageService.h"
#include "ErrorList.h"
#include "IdentityCredentialStorageService.h"
#include "MainThreadUtils.h"
#include "mozIStorageService.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozStorageCID.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/Base64.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Components.h"
#include "mozilla/OriginAttributes.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPtr.h"
#include "mozIStorageService.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozStorageCID.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsComponentManagerUtils.h"
#include "nsCRT.h"
#include "nsDebug.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIObserverService.h"
#include "nsIWritablePropertyBag2.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "prtime.h"

#define ACCOUNT_STATE_FILENAME "credentialstate.sqlite"_ns
Expand All @@ -35,15 +40,13 @@ StaticRefPtr<IdentityCredentialStorageService>
gIdentityCredentialStorageService;

NS_IMPL_ISUPPORTS(IdentityCredentialStorageService,
nsIIdentityCredentialStorageService, nsIObserver)

IdentityCredentialStorageService::~IdentityCredentialStorageService() {
AssertIsOnMainThread();
}
nsIIdentityCredentialStorageService, nsIObserver,
nsIAsyncShutdownBlocker)

already_AddRefed<IdentityCredentialStorageService>
IdentityCredentialStorageService::GetSingleton() {
AssertIsOnMainThread();
MOZ_ASSERT(XRE_IsParentProcess());
if (!gIdentityCredentialStorageService) {
gIdentityCredentialStorageService = new IdentityCredentialStorageService();
ClearOnShutdown(&gIdentityCredentialStorageService);
Expand All @@ -55,6 +58,166 @@ IdentityCredentialStorageService::GetSingleton() {
return service.forget();
}

NS_IMETHODIMP IdentityCredentialStorageService::GetName(nsAString& aName) {
aName = u"IdentityCredentialStorageService: Flushing data"_ns;
return NS_OK;
}

NS_IMETHODIMP IdentityCredentialStorageService::BlockShutdown(
nsIAsyncShutdownClient* aClient) {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = WaitForInitialization();
NS_ENSURE_SUCCESS(rv, rv);
if (mMemoryDatabaseConnection) {
Unused << mMemoryDatabaseConnection->Close();
mMemoryDatabaseConnection = nullptr;
}

RefPtr<IdentityCredentialStorageService> self = this;
mBackgroundThread->Dispatch(
NS_NewRunnableFunction(
"IdentityCredentialStorageService::BlockShutdown",
[self]() {
MonitorAutoLock lock(self->mMonitor);

if (self->mDiskDatabaseConnection) {
Unused << self->mDiskDatabaseConnection->Close();
self->mDiskDatabaseConnection = nullptr;
}

self->mFinalized.Flip();
self->mMonitor.NotifyAll();
NS_DispatchToMainThread(NS_NewRunnableFunction(
"IdentityCredentialStorageService::BlockShutdown "
"- mainthread callback",
[self]() { self->Finalize(); }));
}),
NS_DISPATCH_EVENT_MAY_BLOCK);

return NS_OK;
}

NS_IMETHODIMP
IdentityCredentialStorageService::GetState(nsIPropertyBag** aBagOut) {
return NS_OK;
}

already_AddRefed<nsIAsyncShutdownClient>
IdentityCredentialStorageService::GetAsyncShutdownBarrier() const {
nsresult rv;
nsCOMPtr<nsIAsyncShutdownService> svc = components::AsyncShutdown::Service();
MOZ_RELEASE_ASSERT(svc);

nsCOMPtr<nsIAsyncShutdownClient> client;
rv = svc->GetProfileBeforeChange(getter_AddRefs(client));
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
MOZ_RELEASE_ASSERT(client);
return client.forget();
}

nsresult IdentityCredentialStorageService::Init() {
AssertIsOnMainThread();
MonitorAutoLock lock(mMonitor);

if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) {
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}

nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier();
if (!asc) {
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = asc->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
__LINE__, u""_ns);
NS_ENSURE_SUCCESS(rv, rv);

rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mDatabaseFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDatabaseFile->AppendNative(nsLiteralCString(ACCOUNT_STATE_FILENAME));
NS_ENSURE_SUCCESS(rv, rv);

// Register the PBMode cleaner (IdentityCredentialStorageService::Observe) as
// an observer.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
NS_ENSURE_TRUE(observerService, NS_ERROR_FAILURE);
observerService->AddObserver(this, "last-pb-context-exited", false);

// rv = GetMemoryDatabaseConnection();
// if (NS_WARN_IF(NS_FAILED(rv))) {
// mErrored.Flip();
// return rv;
// }

NS_ENSURE_SUCCESS(
NS_CreateBackgroundTaskQueue("IdentityCredentialStorage",
getter_AddRefs(mBackgroundThread)),
NS_ERROR_FAILURE);

RefPtr<IdentityCredentialStorageService> self = this;

mBackgroundThread->Dispatch(
NS_NewRunnableFunction("IdentityCredentialStorageService::Init",
[self]() {
MonitorAutoLock lock(self->mMonitor);
// nsresult rv =
// self->GetDiskDatabaseConnection(); if
// (NS_WARN_IF(NS_FAILED(rv))) {
// self->mErrored.Flip();
// self->mMonitor.Notify();
// return;
// }

// rv = self->LoadMemoryTableFromDisk();
// if (NS_WARN_IF(NS_FAILED(rv))) {
// self->mErrored.Flip();
// self->mMonitor.NotifyAll();
// return;
// }

self->mInitialized.Flip();
self->mMonitor.Notify();
}),
NS_DISPATCH_EVENT_MAY_BLOCK);

return NS_OK;
}

nsresult IdentityCredentialStorageService::WaitForInitialization() {
MonitorAutoLock lock(mMonitor);
while (!mInitialized && !mErrored) {
mMonitor.Wait();
}
if (mErrored) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}

void IdentityCredentialStorageService::Finalize() {
nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier();
MOZ_ASSERT(asc);
DebugOnly<nsresult> rv = asc->RemoveBlocker(this);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}

// static
nsresult IdentityCredentialStorageService::ValidatePrincipal(
nsIPrincipal* aPrincipal) {
// We add some constraints on the RP principal where it is provided to reduce
// edge cases in implementation. These are reasonable constraints with the
// semantics of the store: it must be a http or https content principal.
NS_ENSURE_ARG_POINTER(aPrincipal);
NS_ENSURE_TRUE(aPrincipal->GetIsContentPrincipal(), NS_ERROR_FAILURE);
nsCString scheme;
nsresult rv = aPrincipal->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(scheme.Equals("http"_ns) || scheme.Equals("https"_ns),
NS_ERROR_FAILURE);
return NS_OK;
}

nsresult createTable(mozIStorageConnection* aDatabase) {
// Currently there is only one schema version, so we just need to create the
// table. The definition uses no explicit rowid column, instead primary keying
Expand Down Expand Up @@ -126,39 +289,6 @@ nsresult getDiskDatabaseConnection(mozIStorageConnection** aDatabase) {
return NS_OK;
}

nsresult IdentityCredentialStorageService::Init() {
AssertIsOnMainThread();
if (!StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
return NS_OK;
}

nsresult rv;
RefPtr<mozIStorageConnection> database;
rv = getDiskDatabaseConnection(getter_AddRefs(database));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<mozIStorageConnection> privateBrowsingDatabase;
rv = getMemoryDatabaseConnection(getter_AddRefs(privateBrowsingDatabase));
NS_ENSURE_SUCCESS(rv, rv);

// Create the database table for memory and disk if it doesn't already exist
bool tableExists = false;
database->TableExists("identity"_ns, &tableExists);
if (!tableExists) {
rv = createTable(database);
NS_ENSURE_SUCCESS(rv, rv);
}

// Register the PBMode cleaner (IdentityCredentialStorageService::Observe) as
// an observer.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
NS_ENSURE_TRUE(observerService, NS_ERROR_FAILURE);
observerService->AddObserver(this, "last-pb-context-exited", false);

mInitialized.Flip();
return NS_OK;
}

NS_IMETHODIMP IdentityCredentialStorageService::SetState(
nsIPrincipal* aRPPrincipal, nsIPrincipal* aIDPPrincipal,
nsACString const& aCredentialID, bool aRegistered, bool aAllowLogout) {
Expand Down Expand Up @@ -594,20 +724,4 @@ IdentityCredentialStorageService::Observe(nsISupports* aSubject,
return NS_OK;
}

// static
nsresult IdentityCredentialStorageService::ValidatePrincipal(
nsIPrincipal* aPrincipal) {
// We add some constraints on the RP principal where it is provided to reduce
// edge cases in implementation. These are reasonable constraints with the
// semantics of the store: it must be a http or https content principal.
NS_ENSURE_ARG_POINTER(aPrincipal);
NS_ENSURE_TRUE(aPrincipal->GetIsContentPrincipal(), NS_ERROR_FAILURE);
nsCString scheme;
nsresult rv = aPrincipal->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(scheme.Equals("http"_ns) || scheme.Equals("https"_ns),
NS_ERROR_FAILURE);
return NS_OK;
}

} // namespace mozilla
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,88 @@
#define MOZILLA_IDENTITYCREDENTIALSTORAGESERVICE_H_

#include "ErrorList.h"
#include "mozIStorageConnection.h"
#include "mozilla/AlreadyAddRefed.h"
#include "nsIIdentityCredentialStorageService.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/Monitor.h"
#include "mozIStorageConnection.h"
#include "nsIAsyncShutdown.h"
#include "nsIIdentityCredentialStorageService.h"
#include "nsIObserver.h"
#include "nsISupports.h"
#include "nsThreadUtils.h"

namespace mozilla {

class IdentityCredentialStorageService final
: public nsIIdentityCredentialStorageService,
public nsIObserver {
public nsIObserver,
public nsIAsyncShutdownBlocker {
public:
NS_DECL_ISUPPORTS
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIIDENTITYCREDENTIALSTORAGESERVICE
NS_DECL_NSIOBSERVER
NS_DECL_NSIASYNCSHUTDOWNBLOCKER

// Returns the singleton instance which is addreffed.
// Returns the singleton instance. Used by the component manager
static already_AddRefed<IdentityCredentialStorageService> GetSingleton();

// Singletons shouldn't have copy constructors or assignment operators
IdentityCredentialStorageService(const IdentityCredentialStorageService&) =
delete;
IdentityCredentialStorageService& operator=(
const IdentityCredentialStorageService&) = delete;

private:
IdentityCredentialStorageService() = default;
~IdentityCredentialStorageService();
IdentityCredentialStorageService()
: mMonitor("mozilla::IdentityCredentialStorageService::mMonitor"){};
~IdentityCredentialStorageService() = default;

// Spins up the service. This includes firing off async work in a worker
// thread. This should always be called before other use of the service to
// prevent deadlock.
nsresult Init();

// Wait (non-blocking) until the service is fully initialized. We may be
// waiting for that async work started by Init().
nsresult WaitForInitialization();

// Utility function to grab the correct barrier this service needs to shut
// down by
already_AddRefed<nsIAsyncShutdownClient> GetAsyncShutdownBarrier() const;

// Called to indicate to the async shutdown service that we are all wrapped
// up. This also spins down the worker thread, since it is called after all
// disk database connections are closed.
void Finalize();

// Utility function to make sure a principal is an acceptable primary (RP)
// principal
static nsresult ValidatePrincipal(nsIPrincipal* aPrincipal);

FlippedOnce<false> mInitialized;
// Database connections. Guaranteed to be non-null and working once
// initialized and not-yet finalized
RefPtr<mozIStorageConnection> mDiskDatabaseConnection; // Worker thread only
RefPtr<mozIStorageConnection>
mMemoryDatabaseConnection; // Main thread only after initialization,
// worker thread only before initialization.

// Worker thread. This should be a valid thread after Init() returns and be
// destroyed when we finalize
nsCOMPtr<nsISerialEventTarget> mBackgroundThread; // main thread only

// The database file handle. We can only create this in the main thread and
// need it in the worker to perform blocking disk IO. So we put it on this,
// since we pass this to the worker anyway
nsCOMPtr<nsIFile> mDatabaseFile; // initialized in the main thread, read-only
// in worker thread

// Service state management. We protect these variables with a monitor. This
// monitor is also used to signal the completion of initialization and
// finalization performed in the worker thread.
Monitor mMonitor;
FlippedOnce<false> mInitialized MOZ_GUARDED_BY(mMonitor);
FlippedOnce<false> mErrored MOZ_GUARDED_BY(mMonitor);
FlippedOnce<false> mFinalized MOZ_GUARDED_BY(mMonitor);
};

} // namespace mozilla
Expand Down

0 comments on commit b4287d6

Please sign in to comment.