1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/cache/Context.h"
8 #include "CacheCommon.h"
10 #include "mozilla/AutoRestore.h"
11 #include "mozilla/StaticPrefs_dom.h"
12 #include "mozilla/dom/SafeRefPtr.h"
13 #include "mozilla/dom/cache/Action.h"
14 #include "mozilla/dom/cache/FileUtils.h"
15 #include "mozilla/dom/cache/Manager.h"
16 #include "mozilla/dom/cache/ManagerId.h"
17 #include "mozilla/dom/quota/Assertions.h"
18 #include "mozilla/dom/quota/DirectoryLock.h"
19 #include "mozilla/dom/quota/DirectoryLockInlines.h"
20 #include "mozilla/dom/quota/PrincipalUtils.h"
21 #include "mozilla/dom/quota/QuotaManager.h"
22 #include "mozilla/dom/quota/ResultExtensions.h"
23 #include "mozilla/dom/quota/ThreadUtils.h"
24 #include "mozilla/ipc/PBackgroundSharedTypes.h"
25 #include "mozilla/Maybe.h"
26 #include "mozIStorageConnection.h"
27 #include "NotifyUtils.h"
28 #include "nsIPrincipal.h"
29 #include "nsIRunnable.h"
30 #include "nsIThread.h"
31 #include "nsThreadUtils.h"
32 #include "QuotaClientImpl.h"
36 using mozilla::dom::cache::Action
;
37 using mozilla::dom::cache::CacheDirectoryMetadata
;
39 class NullAction final
: public Action
{
41 NullAction() = default;
43 virtual void RunOnTarget(mozilla::SafeRefPtr
<Resolver
> aResolver
,
44 const mozilla::Maybe
<CacheDirectoryMetadata
>&, Data
*,
45 const mozilla::Maybe
<mozilla::dom::cache::CipherKey
>&
46 /* aMaybeCipherKey */) override
{
47 // Resolve success immediately. This Action does no actual work.
48 MOZ_DIAGNOSTIC_ASSERT(aResolver
);
49 aResolver
->Resolve(NS_OK
);
55 namespace mozilla::dom::cache
{
57 using mozilla::dom::quota::AssertIsOnIOThread
;
58 using mozilla::dom::quota::DirectoryLock
;
59 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT
;
60 using mozilla::dom::quota::PersistenceType
;
61 using mozilla::dom::quota::QuotaManager
;
62 using mozilla::dom::quota::SleepIfEnabled
;
64 class Context::Data final
: public Action::Data
{
66 explicit Data(nsISerialEventTarget
* aTarget
) : mTarget(aTarget
) {
67 MOZ_DIAGNOSTIC_ASSERT(mTarget
);
70 virtual mozIStorageConnection
* GetConnection() const override
{
71 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
75 virtual void SetConnection(mozIStorageConnection
* aConn
) override
{
76 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
77 MOZ_DIAGNOSTIC_ASSERT(!mConnection
);
79 MOZ_DIAGNOSTIC_ASSERT(mConnection
);
84 // We could proxy release our data here, but instead just assert. The
85 // Context code should guarantee that we are destroyed on the target
86 // thread once the connection is initialized. If we're not, then
87 // QuotaManager might race and try to clear the origin out from under us.
88 MOZ_ASSERT_IF(mConnection
, mTarget
->IsOnCurrentThread());
91 nsCOMPtr
<nsISerialEventTarget
> mTarget
;
92 nsCOMPtr
<mozIStorageConnection
> mConnection
;
94 // Threadsafe counting because we're created on the PBackground thread
95 // and destroyed on the target IO thread.
96 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Context::Data
)
99 // Executed to perform the complicated dance of steps necessary to initialize
100 // the QuotaManager. This must be performed for each origin before any disk
102 class Context::QuotaInitRunnable final
: public nsIRunnable
{
104 QuotaInitRunnable(SafeRefPtr
<Context
> aContext
, SafeRefPtr
<Manager
> aManager
,
105 Data
* aData
, nsISerialEventTarget
* aTarget
,
106 SafeRefPtr
<Action
> aInitAction
)
107 : mContext(std::move(aContext
)),
108 mThreadsafeHandle(mContext
->CreateThreadsafeHandle()),
109 mManager(std::move(aManager
)),
112 mInitAction(std::move(aInitAction
)),
113 mInitiatingEventTarget(GetCurrentSerialEventTarget()),
117 MOZ_DIAGNOSTIC_ASSERT(mContext
);
118 MOZ_DIAGNOSTIC_ASSERT(mManager
);
119 MOZ_DIAGNOSTIC_ASSERT(mData
);
120 MOZ_DIAGNOSTIC_ASSERT(mTarget
);
121 MOZ_DIAGNOSTIC_ASSERT(mInitiatingEventTarget
);
122 MOZ_DIAGNOSTIC_ASSERT(mInitAction
);
125 Maybe
<DirectoryLock
&> MaybeDirectoryLockRef() const {
126 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
128 return ToMaybeRef(mDirectoryLock
.get());
131 nsresult
Dispatch() {
132 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
133 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_INIT
);
135 mState
= STATE_GET_INFO
;
136 nsresult rv
= NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL
);
137 if (NS_WARN_IF(NS_FAILED(rv
))) {
138 mState
= STATE_COMPLETE
;
145 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
146 MOZ_DIAGNOSTIC_ASSERT(!mCanceled
);
148 mInitAction
->CancelOnInitiatingThread();
151 void DirectoryLockAcquired(DirectoryLock
* aLock
);
153 void DirectoryLockFailed();
156 class SyncResolver final
: public Action::Resolver
{
158 SyncResolver() : mResolved(false), mResult(NS_OK
) {}
160 virtual void Resolve(nsresult aRv
) override
{
161 MOZ_DIAGNOSTIC_ASSERT(!mResolved
);
166 bool Resolved() const { return mResolved
; }
167 nsresult
Result() const { return mResult
; }
170 ~SyncResolver() = default;
175 NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver
,
179 ~QuotaInitRunnable() {
180 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_COMPLETE
);
181 MOZ_DIAGNOSTIC_ASSERT(!mContext
);
182 MOZ_DIAGNOSTIC_ASSERT(!mInitAction
);
188 STATE_CREATE_QUOTA_MANAGER
,
189 STATE_WAIT_FOR_DIRECTORY_LOCK
,
190 STATE_ENSURE_ORIGIN_INITIALIZED
,
197 void Complete(nsresult aResult
) {
198 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_RUNNING
|| NS_FAILED(aResult
));
200 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mResult
));
203 mState
= STATE_COMPLETING
;
205 mInitiatingEventTarget
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
209 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
210 MOZ_DIAGNOSTIC_ASSERT(mContext
);
213 mInitAction
= nullptr;
216 SafeRefPtr
<Context
> mContext
;
217 SafeRefPtr
<ThreadsafeHandle
> mThreadsafeHandle
;
218 SafeRefPtr
<Manager
> mManager
;
220 nsCOMPtr
<nsISerialEventTarget
> mTarget
;
221 SafeRefPtr
<Action
> mInitAction
;
222 nsCOMPtr
<nsIEventTarget
> mInitiatingEventTarget
;
224 Maybe
<mozilla::ipc::PrincipalInfo
> mPrincipalInfo
;
225 Maybe
<CacheDirectoryMetadata
> mDirectoryMetadata
;
226 RefPtr
<DirectoryLock
> mDirectoryLock
;
227 RefPtr
<CipherKeyManager
> mCipherKeyManager
;
229 Atomic
<bool> mCanceled
;
232 NS_DECL_THREADSAFE_ISUPPORTS
236 void Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock
* aLock
) {
237 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
238 MOZ_DIAGNOSTIC_ASSERT(aLock
);
239 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_WAIT_FOR_DIRECTORY_LOCK
);
240 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
242 mDirectoryLock
= aLock
;
244 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLock
->Id() >= 0);
245 mDirectoryMetadata
->mDirectoryLockId
= mDirectoryLock
->Id();
247 if (mCanceled
|| mDirectoryLock
->Invalidated()) {
248 Complete(NS_ERROR_ABORT
);
252 QuotaManager
* qm
= QuotaManager::Get();
253 MOZ_DIAGNOSTIC_ASSERT(qm
);
255 mState
= STATE_ENSURE_ORIGIN_INITIALIZED
;
256 nsresult rv
= qm
->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL
);
257 if (NS_WARN_IF(NS_FAILED(rv
))) {
262 NotifyDatabaseWorkStarted();
265 void Context::QuotaInitRunnable::DirectoryLockFailed() {
266 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
267 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_WAIT_FOR_DIRECTORY_LOCK
);
268 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
270 NS_WARNING("Failed to acquire a directory lock!");
272 Complete(NS_ERROR_FAILURE
);
275 NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable
, nsIRunnable
);
277 // The QuotaManager init state machine is represented in the following diagram:
280 // | Start | Resolve(error)
281 // | (Orig Thread) +---------------------+
282 // +-------+-------+ |
284 // +----------v-----------+ |
285 // | GetInfo | Resolve(error) |
286 // | (Main Thread) +-----------------+
287 // +----------+-----------+ |
289 // +----------v-----------+ |
290 // | CreateQuotaManager | Resolve(error) |
291 // | (Orig Thread) +-----------------+
292 // +----------+-----------+ |
294 // +----------v-----------+ |
295 // | WaitForDirectoryLock | Resolve(error) |
296 // | (Orig Thread) +-----------------+
297 // +----------+-----------+ |
299 // +----------v------------+ |
300 // |EnsureOriginInitialized| Resolve(error) |
301 // | (Quota IO Thread) +----------------+
302 // +----------+------------+ |
304 // +----------v------------+ |
305 // | RunOnTarget | Resolve(error) |
306 // | (Target Thread) +----------------+
307 // +----------+------------+ |
309 // +---------v---------+ +------v------+
310 // | Running | | Completing |
311 // | (Target Thread) +------------>(Orig Thread)|
312 // +-------------------+ +------+------+
318 // The initialization process proceeds through the main states. If an error
319 // occurs, then we transition to Completing state back on the original thread.
321 Context::QuotaInitRunnable::Run() {
322 // May run on different threads depending on the state. See individual
323 // state cases for thread assertions.
325 SafeRefPtr
<SyncResolver
> resolver
= MakeSafeRefPtr
<SyncResolver
>();
328 // -----------------------------------
329 case STATE_GET_INFO
: {
330 MOZ_ASSERT(NS_IsMainThread());
332 auto res
= [this]() -> Result
<Ok
, nsresult
> {
334 return Err(NS_ERROR_ABORT
);
337 nsCOMPtr
<nsIPrincipal
> principal
= mManager
->GetManagerId().Principal();
339 mozilla::ipc::PrincipalInfo principalInfo
;
341 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal
, &principalInfo
)));
343 mPrincipalInfo
.emplace(std::move(principalInfo
));
345 mState
= STATE_CREATE_QUOTA_MANAGER
;
348 mInitiatingEventTarget
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
354 resolver
->Resolve(res
.inspectErr());
359 // ----------------------------------
360 case STATE_CREATE_QUOTA_MANAGER
: {
361 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
363 if (mCanceled
|| QuotaManager::IsShuttingDown()) {
364 resolver
->Resolve(NS_ERROR_ABORT
);
368 QM_TRY(QuotaManager::EnsureCreated(), QM_PROPAGATE
,
369 [&resolver
](const auto rv
) { resolver
->Resolve(rv
); });
371 auto* const quotaManager
= QuotaManager::Get();
372 MOZ_DIAGNOSTIC_ASSERT(quotaManager
);
374 QM_TRY_UNWRAP(auto principalMetadata
,
375 quota::GetInfoFromValidatedPrincipalInfo(*quotaManager
,
378 mDirectoryMetadata
.emplace(std::move(principalMetadata
));
380 mState
= STATE_WAIT_FOR_DIRECTORY_LOCK
;
383 ->OpenClientDirectory({*mDirectoryMetadata
, quota::Client::DOMCACHE
})
385 GetCurrentSerialEventTarget(), __func__
,
386 [self
= RefPtr(this)](
387 const quota::ClientDirectoryLockPromise::ResolveOrRejectValue
&
389 if (aValue
.IsResolve()) {
390 self
->DirectoryLockAcquired(aValue
.ResolveValue());
392 self
->DirectoryLockFailed();
398 // ----------------------------------
399 case STATE_ENSURE_ORIGIN_INITIALIZED
: {
400 AssertIsOnIOThread();
402 auto res
= [this]() -> Result
<Ok
, nsresult
> {
404 return Err(NS_ERROR_ABORT
);
407 QuotaManager
* quotaManager
= QuotaManager::Get();
408 MOZ_DIAGNOSTIC_ASSERT(quotaManager
);
410 QM_TRY_UNWRAP(mDirectoryMetadata
->mDir
,
411 quotaManager
->GetOrCreateTemporaryOriginDirectory(
412 *mDirectoryMetadata
));
414 auto* cacheQuotaClient
= CacheQuotaClient::Get();
415 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient
);
418 cacheQuotaClient
->GetOrCreateCipherKeyManager(*mDirectoryMetadata
);
421 StaticPrefs::dom_cache_databaseInitialization_pauseOnIOThreadMs());
423 mState
= STATE_RUN_ON_TARGET
;
426 mTarget
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
432 resolver
->Resolve(res
.inspectErr());
437 // -------------------
438 case STATE_RUN_ON_TARGET
: {
439 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
441 mState
= STATE_RUNNING
;
443 // Execute the provided initialization Action. The Action must Resolve()
446 mInitAction
->RunOnTarget(
447 resolver
.clonePtr(), mDirectoryMetadata
, mData
,
448 mCipherKeyManager
? Some(mCipherKeyManager
->Ensure()) : Nothing
{});
450 MOZ_DIAGNOSTIC_ASSERT(resolver
->Resolved());
454 // If the database was opened, then we should always succeed when creating
455 // the marker file. If it wasn't opened successfully, then no need to
456 // create a marker file anyway.
457 if (NS_SUCCEEDED(resolver
->Result())) {
458 MOZ_ALWAYS_SUCCEEDS(CreateMarkerFile(*mDirectoryMetadata
));
463 // -------------------
464 case STATE_COMPLETING
: {
465 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
466 mInitAction
->CompleteOnInitiatingThread(mResult
);
468 mContext
->OnQuotaInit(mResult
, mDirectoryMetadata
,
469 std::move(mDirectoryLock
),
470 std::move(mCipherKeyManager
));
472 mState
= STATE_COMPLETE
;
474 // Explicitly cleanup here as the destructor could fire on any of
475 // the threads we have bounced through.
480 case STATE_WAIT_FOR_DIRECTORY_LOCK
:
482 MOZ_CRASH("unexpected state in QuotaInitRunnable");
486 if (resolver
->Resolved()) {
487 Complete(resolver
->Result());
493 // Runnable wrapper around Action objects dispatched on the Context. This
494 // runnable executes the Action on the appropriate threads while the Context
496 class Context::ActionRunnable final
: public nsIRunnable
,
497 public Action::Resolver
,
498 public Context::Activity
{
500 ActionRunnable(SafeRefPtr
<Context
> aContext
, Data
* aData
,
501 nsISerialEventTarget
* aTarget
, SafeRefPtr
<Action
> aAction
,
502 const Maybe
<CacheDirectoryMetadata
>& aDirectoryMetadata
,
503 RefPtr
<CipherKeyManager
> aCipherKeyManager
)
504 : mContext(std::move(aContext
)),
507 mAction(std::move(aAction
)),
508 mDirectoryMetadata(aDirectoryMetadata
),
509 mCipherKeyManager(std::move(aCipherKeyManager
)),
510 mInitiatingThread(GetCurrentSerialEventTarget()),
513 mExecutingRunOnTarget(false) {
514 MOZ_DIAGNOSTIC_ASSERT(mContext
);
515 // mData may be nullptr
516 MOZ_DIAGNOSTIC_ASSERT(mTarget
);
517 MOZ_DIAGNOSTIC_ASSERT(mAction
);
518 // mDirectoryMetadata.mDir may be nullptr if QuotaInitRunnable failed
519 MOZ_DIAGNOSTIC_ASSERT(mInitiatingThread
);
522 nsresult
Dispatch() {
523 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
524 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_INIT
);
526 mState
= STATE_RUN_ON_TARGET
;
527 nsresult rv
= mTarget
->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL
);
528 if (NS_WARN_IF(NS_FAILED(rv
))) {
529 mState
= STATE_COMPLETE
;
535 virtual bool MatchesCacheId(CacheId aCacheId
) const override
{
536 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
537 return mAction
->MatchesCacheId(aCacheId
);
540 virtual void Cancel() override
{
541 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
542 mAction
->CancelOnInitiatingThread();
545 virtual void Resolve(nsresult aRv
) override
{
546 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
547 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_RUNNING
);
551 // We ultimately must complete on the initiating thread, but bounce through
552 // the current thread again to ensure that we don't destroy objects and
553 // state out from under the currently running action's stack.
554 mState
= STATE_RESOLVING
;
556 // If we were resolved synchronously within Action::RunOnTarget() then we
557 // can avoid a thread bounce and just resolve once RunOnTarget() returns.
558 // The Run() method will handle this by looking at mState after
559 // RunOnTarget() returns.
560 if (mExecutingRunOnTarget
) {
564 // Otherwise we are in an asynchronous resolve. And must perform a thread
565 // bounce to run on the target thread again.
566 MOZ_ALWAYS_SUCCEEDS(mTarget
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
571 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_COMPLETE
);
572 MOZ_DIAGNOSTIC_ASSERT(!mContext
);
573 MOZ_DIAGNOSTIC_ASSERT(!mAction
);
576 void DoStringify(nsACString
& aData
) override
{
577 aData
.Append("ActionRunnable ("_ns
+
579 "State:"_ns
+ IntToCString(mState
) + kStringifyDelimiter
+
581 "Action:"_ns
+ IntToCString(static_cast<bool>(mAction
)) +
582 kStringifyDelimiter
+
583 // TODO: We might want to have Action::Stringify, too.
585 "Context:"_ns
+ IntToCString(static_cast<bool>(mContext
)) +
586 kStringifyDelimiter
+
587 // We do not print out mContext as we most probably were called
593 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
594 MOZ_DIAGNOSTIC_ASSERT(mContext
);
595 MOZ_DIAGNOSTIC_ASSERT(mAction
);
596 mContext
->RemoveActivity(*this);
610 SafeRefPtr
<Context
> mContext
;
612 nsCOMPtr
<nsISerialEventTarget
> mTarget
;
613 SafeRefPtr
<Action
> mAction
;
614 const Maybe
<CacheDirectoryMetadata
> mDirectoryMetadata
;
615 RefPtr
<CipherKeyManager
> mCipherKeyManager
;
616 nsCOMPtr
<nsIEventTarget
> mInitiatingThread
;
620 // Only accessible on target thread;
621 bool mExecutingRunOnTarget
;
624 NS_DECL_THREADSAFE_ISUPPORTS
628 NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable
, nsIRunnable
);
630 // The ActionRunnable has a simpler state machine. It basically needs to run
631 // the action on the target thread and then complete on the original thread.
638 // +-------v---------+
640 // |Target IO Thread)+---+ Resolve()
641 // +-------+---------+ |
643 // +-------v----------+ |
645 // |(Target IO Thread)| |
646 // +------------------+ |
648 // +-------v----------+ |
649 // | Resolving <--+ +-------------+
650 // | | | Completing |
651 // |(Target IO Thread)+---------------------->(Orig Thread)|
652 // +------------------+ +-------+-----+
659 // Its important to note that synchronous actions will effectively Resolve()
660 // out of the Running state immediately. Asynchronous Actions may remain
661 // in the Running state for some time, but normally the ActionRunnable itself
662 // does not see any execution there. Its all handled internal to the Action.
664 Context::ActionRunnable::Run() {
666 // ----------------------
667 case STATE_RUN_ON_TARGET
: {
668 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
669 MOZ_DIAGNOSTIC_ASSERT(!mExecutingRunOnTarget
);
671 // Note that we are calling RunOnTarget(). This lets us detect
672 // if Resolve() is called synchronously.
673 AutoRestore
<bool> executingRunOnTarget(mExecutingRunOnTarget
);
674 mExecutingRunOnTarget
= true;
676 mState
= STATE_RUNNING
;
677 mAction
->RunOnTarget(
678 SafeRefPtrFromThis(), mDirectoryMetadata
, mData
,
679 mCipherKeyManager
? Some(mCipherKeyManager
->Ensure()) : Nothing
{});
683 // Resolve was called synchronously from RunOnTarget(). We can
684 // immediately move to completing now since we are sure RunOnTarget()
686 if (mState
== STATE_RESOLVING
) {
687 // Use recursion instead of switch case fall-through... Seems slightly
688 // easier to understand.
695 case STATE_RESOLVING
: {
696 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
697 // The call to Action::RunOnTarget() must have returned now if we
698 // are running on the target thread again. We may now proceed
700 mState
= STATE_COMPLETING
;
701 // Shutdown must be delayed until all Contexts are destroyed. Crash
702 // for this invariant violation.
704 mInitiatingThread
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
707 // -------------------
708 case STATE_COMPLETING
: {
709 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
710 mAction
->CompleteOnInitiatingThread(mResult
);
711 mState
= STATE_COMPLETE
;
712 // Explicitly cleanup here as the destructor could fire on any of
713 // the threads we have bounced through.
719 MOZ_CRASH("unexpected state in ActionRunnable");
726 void Context::ThreadsafeHandle::AllowToClose() {
727 if (mOwningEventTarget
->IsOnCurrentThread()) {
728 AllowToCloseOnOwningThread();
732 // Dispatch is guaranteed to succeed here because we block shutdown until
733 // all Contexts have been destroyed.
734 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
735 "dom::cache::Context::ThreadsafeHandle::AllowToCloseOnOwningThread", this,
736 &ThreadsafeHandle::AllowToCloseOnOwningThread
);
737 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget
->Dispatch(runnable
.forget(),
738 nsIThread::DISPATCH_NORMAL
));
741 void Context::ThreadsafeHandle::InvalidateAndAllowToClose() {
742 if (mOwningEventTarget
->IsOnCurrentThread()) {
743 InvalidateAndAllowToCloseOnOwningThread();
747 // Dispatch is guaranteed to succeed here because we block shutdown until
748 // all Contexts have been destroyed.
749 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
750 "dom::cache::Context::ThreadsafeHandle::"
751 "InvalidateAndAllowToCloseOnOwningThread",
752 this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread
);
753 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget
->Dispatch(runnable
.forget(),
754 nsIThread::DISPATCH_NORMAL
));
757 Context::ThreadsafeHandle::ThreadsafeHandle(SafeRefPtr
<Context
> aContext
)
758 : mStrongRef(std::move(aContext
)),
759 mWeakRef(mStrongRef
.unsafeGetRawPtr()),
760 mOwningEventTarget(GetCurrentSerialEventTarget()) {}
762 Context::ThreadsafeHandle::~ThreadsafeHandle() {
763 // Normally we only touch mStrongRef on the owning thread. This is safe,
764 // however, because when we do use mStrongRef on the owning thread we are
765 // always holding a strong ref to the ThreadsafeHandle via the owning
766 // runnable. So we cannot run the ThreadsafeHandle destructor simultaneously.
767 if (!mStrongRef
|| mOwningEventTarget
->IsOnCurrentThread()) {
771 // Dispatch in NS_ProxyRelease is guaranteed to succeed here because we block
772 // shutdown until all Contexts have been destroyed. Therefore it is ok to have
773 // MOZ_ALWAYS_SUCCEED here.
774 MOZ_ALWAYS_SUCCEEDS(NS_ProxyRelease("Context::ThreadsafeHandle::mStrongRef",
775 mOwningEventTarget
, mStrongRef
.forget()));
778 void Context::ThreadsafeHandle::AllowToCloseOnOwningThread() {
779 MOZ_ASSERT(mOwningEventTarget
->IsOnCurrentThread());
781 // A Context "closes" when its ref count drops to zero. Dropping this
782 // strong ref is necessary, but not sufficient for the close to occur.
783 // Any outstanding IO will continue and keep the Context alive. Once
784 // the Context is idle, it will be destroyed.
786 // First, tell the context to flush any target thread shared data. This
787 // data must be released on the target thread prior to running the Context
788 // destructor. This will schedule an Action which ensures that the
789 // ~Context() is not immediately executed when we drop the strong ref.
791 mStrongRef
->DoomTargetData();
794 // Now drop our strong ref and let Context finish running any outstanding
796 mStrongRef
= nullptr;
799 void Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread() {
800 MOZ_ASSERT(mOwningEventTarget
->IsOnCurrentThread());
801 // Cancel the Context through the weak reference. This means we can
802 // allow the Context to close by dropping the strong ref, but then
803 // still cancel ongoing IO if necessary.
805 mWeakRef
->Invalidate();
807 // We should synchronously have AllowToCloseOnOwningThread called when
808 // the Context is canceled.
809 MOZ_DIAGNOSTIC_ASSERT(!mStrongRef
);
812 void Context::ThreadsafeHandle::ContextDestroyed(Context
& aContext
) {
813 MOZ_ASSERT(mOwningEventTarget
->IsOnCurrentThread());
814 MOZ_DIAGNOSTIC_ASSERT(!mStrongRef
);
815 MOZ_DIAGNOSTIC_ASSERT(mWeakRef
);
816 MOZ_DIAGNOSTIC_ASSERT(mWeakRef
== &aContext
);
821 SafeRefPtr
<Context
> Context::Create(SafeRefPtr
<Manager
> aManager
,
822 nsISerialEventTarget
* aTarget
,
823 SafeRefPtr
<Action
> aInitAction
,
824 Maybe
<Context
&> aOldContext
) {
825 auto context
= MakeSafeRefPtr
<Context
>(std::move(aManager
), aTarget
,
826 std::move(aInitAction
));
827 context
->Init(aOldContext
);
831 Context::Context(SafeRefPtr
<Manager
> aManager
, nsISerialEventTarget
* aTarget
,
832 SafeRefPtr
<Action
> aInitAction
)
833 : mManager(std::move(aManager
)),
835 mData(new Data(aTarget
)),
836 mState(STATE_CONTEXT_PREINIT
),
837 mOrphanedData(false),
838 mInitAction(std::move(aInitAction
)) {
839 MOZ_DIAGNOSTIC_ASSERT(mManager
);
840 MOZ_DIAGNOSTIC_ASSERT(mTarget
);
843 void Context::Dispatch(SafeRefPtr
<Action
> aAction
) {
844 NS_ASSERT_OWNINGTHREAD(Context
);
845 MOZ_DIAGNOSTIC_ASSERT(aAction
);
846 MOZ_DIAGNOSTIC_ASSERT(mState
!= STATE_CONTEXT_CANCELED
);
848 if (mState
== STATE_CONTEXT_CANCELED
) {
852 if (mState
== STATE_CONTEXT_INIT
|| mState
== STATE_CONTEXT_PREINIT
) {
853 PendingAction
* pending
= mPendingActions
.AppendElement();
854 pending
->mAction
= std::move(aAction
);
858 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_CONTEXT_READY
);
859 DispatchAction(std::move(aAction
));
862 Maybe
<DirectoryLock
&> Context::MaybeDirectoryLockRef() const {
863 NS_ASSERT_OWNINGTHREAD(Context
);
865 if (mState
== STATE_CONTEXT_PREINIT
) {
866 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable
);
867 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
872 if (mState
== STATE_CONTEXT_INIT
) {
873 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
875 return mInitRunnable
->MaybeDirectoryLockRef();
878 return ToMaybeRef(mDirectoryLock
.get());
881 CipherKeyManager
& Context::MutableCipherKeyManagerRef() {
882 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
883 MOZ_DIAGNOSTIC_ASSERT(mCipherKeyManager
);
885 return *mCipherKeyManager
;
888 const Maybe
<CacheDirectoryMetadata
>& Context::MaybeCacheDirectoryMetadataRef()
890 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
891 return mDirectoryMetadata
;
894 void Context::CancelAll() {
895 NS_ASSERT_OWNINGTHREAD(Context
);
897 // In PREINIT state we have not dispatch the init action yet. Just
899 if (mState
== STATE_CONTEXT_PREINIT
) {
900 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable
);
901 mInitAction
= nullptr;
903 // In INIT state we have dispatched the runnable, but not received the
904 // async completion yet. Cancel the runnable, but don't forget about it
905 // until we get OnQuotaInit() callback.
906 } else if (mState
== STATE_CONTEXT_INIT
) {
907 mInitRunnable
->Cancel();
910 mState
= STATE_CONTEXT_CANCELED
;
911 // Allow completion of pending actions in Context::OnQuotaInit
912 if (!mInitRunnable
) {
913 mPendingActions
.Clear();
915 for (const auto& activity
: mActivityList
.ForwardRange()) {
921 bool Context::IsCanceled() const {
922 NS_ASSERT_OWNINGTHREAD(Context
);
923 return mState
== STATE_CONTEXT_CANCELED
;
926 void Context::Invalidate() {
927 NS_ASSERT_OWNINGTHREAD(Context
);
928 mManager
->NoteClosing();
932 void Context::AllowToClose() {
933 NS_ASSERT_OWNINGTHREAD(Context
);
934 if (mThreadsafeHandle
) {
935 mThreadsafeHandle
->AllowToClose();
939 void Context::CancelForCacheId(CacheId aCacheId
) {
940 NS_ASSERT_OWNINGTHREAD(Context
);
942 // Remove matching pending actions
943 mPendingActions
.RemoveElementsBy([aCacheId
](const auto& pendingAction
) {
944 return pendingAction
.mAction
->MatchesCacheId(aCacheId
);
947 // Cancel activities and let them remove themselves
948 for (const auto& activity
: mActivityList
.ForwardRange()) {
949 if (activity
->MatchesCacheId(aCacheId
)) {
955 Context::~Context() {
956 NS_ASSERT_OWNINGTHREAD(Context
);
957 MOZ_DIAGNOSTIC_ASSERT(mManager
);
958 MOZ_DIAGNOSTIC_ASSERT(!mData
);
960 if (mThreadsafeHandle
) {
961 mThreadsafeHandle
->ContextDestroyed(*this);
964 SafeDropDirectoryLock(mDirectoryLock
);
966 // Note, this may set the mOrphanedData flag.
967 mManager
->RemoveContext(*this);
969 if (mDirectoryMetadata
&& mDirectoryMetadata
->mDir
&& !mOrphanedData
) {
970 MOZ_ALWAYS_SUCCEEDS(DeleteMarkerFile(*mDirectoryMetadata
));
974 mNextContext
->Start();
978 void Context::Init(Maybe
<Context
&> aOldContext
) {
979 NS_ASSERT_OWNINGTHREAD(Context
);
982 aOldContext
->SetNextContext(SafeRefPtrFromThis());
989 void Context::Start() {
990 NS_ASSERT_OWNINGTHREAD(Context
);
992 // Previous context closing delayed our start, but then we were canceled.
993 // In this case, just do nothing here.
994 if (mState
== STATE_CONTEXT_CANCELED
) {
995 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable
);
996 MOZ_DIAGNOSTIC_ASSERT(!mInitAction
);
997 // If we can't initialize the quota subsystem we will never be able to
998 // clear our shared data object via the target IO thread. Instead just
999 // clear it here to maintain the invariant that the shared data is
1000 // cleared before Context destruction.
1005 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_CONTEXT_PREINIT
);
1006 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable
);
1009 new QuotaInitRunnable(SafeRefPtrFromThis(), mManager
.clonePtr(), mData
,
1010 mTarget
, std::move(mInitAction
));
1011 mState
= STATE_CONTEXT_INIT
;
1013 nsresult rv
= mInitRunnable
->Dispatch();
1014 if (NS_FAILED(rv
)) {
1015 // Shutdown must be delayed until all Contexts are destroyed. Shutdown
1016 // must also prevent any new Contexts from being constructed. Crash
1017 // for this invariant violation.
1018 MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
1022 void Context::DispatchAction(SafeRefPtr
<Action
> aAction
, bool aDoomData
) {
1023 NS_ASSERT_OWNINGTHREAD(Context
);
1025 auto runnable
= MakeSafeRefPtr
<ActionRunnable
>(
1026 SafeRefPtrFromThis(), mData
, mTarget
, std::move(aAction
),
1027 mDirectoryMetadata
, mCipherKeyManager
);
1033 nsresult rv
= runnable
->Dispatch();
1034 if (NS_FAILED(rv
)) {
1035 // Shutdown must be delayed until all Contexts are destroyed. Crash
1036 // for this invariant violation.
1037 MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
1039 AddActivity(*runnable
);
1042 void Context::OnQuotaInit(
1043 nsresult aRv
, const Maybe
<CacheDirectoryMetadata
>& aDirectoryMetadata
,
1044 RefPtr
<DirectoryLock
> aDirectoryLock
,
1045 RefPtr
<CipherKeyManager
> aCipherKeyManager
) {
1046 NS_ASSERT_OWNINGTHREAD(Context
);
1048 MOZ_DIAGNOSTIC_ASSERT(mInitRunnable
);
1049 mInitRunnable
= nullptr;
1051 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryMetadata
);
1052 mDirectoryMetadata
= aDirectoryMetadata
;
1054 // Always save the directory lock to ensure QuotaManager does not shutdown
1055 // before the Context has gone away.
1056 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
1057 mDirectoryLock
= std::move(aDirectoryLock
);
1059 MOZ_DIAGNOSTIC_ASSERT(!mCipherKeyManager
);
1060 mCipherKeyManager
= std::move(aCipherKeyManager
);
1062 // If we opening the context failed, but we were not explicitly canceled,
1063 // still treat the entire context as canceled. We don't want to allow
1064 // new actions to be dispatched. We also cannot leave the context in
1065 // the INIT state after failing to open.
1066 if (NS_FAILED(aRv
)) {
1067 mState
= STATE_CONTEXT_CANCELED
;
1070 if (mState
== STATE_CONTEXT_CANCELED
) {
1071 if (NS_SUCCEEDED(aRv
)) {
1072 aRv
= NS_ERROR_ABORT
;
1074 for (uint32_t i
= 0; i
< mPendingActions
.Length(); ++i
) {
1075 mPendingActions
[i
].mAction
->CompleteOnInitiatingThread(aRv
);
1077 mPendingActions
.Clear();
1078 mThreadsafeHandle
->AllowToClose();
1079 // Context will destruct after return here and last ref is released.
1083 // We could only assert below if quota initialization was a success which
1084 // is ensured by NS_FAILED(aRv) above
1085 MOZ_DIAGNOSTIC_ASSERT(mDirectoryMetadata
);
1086 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLock
);
1087 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
->Invalidated());
1088 MOZ_DIAGNOSTIC_ASSERT_IF(mDirectoryMetadata
->mIsPrivate
, mCipherKeyManager
);
1090 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_CONTEXT_INIT
);
1091 mState
= STATE_CONTEXT_READY
;
1093 for (uint32_t i
= 0; i
< mPendingActions
.Length(); ++i
) {
1094 DispatchAction(std::move(mPendingActions
[i
].mAction
));
1096 mPendingActions
.Clear();
1099 void Context::AddActivity(Activity
& aActivity
) {
1100 NS_ASSERT_OWNINGTHREAD(Context
);
1101 MOZ_ASSERT(!mActivityList
.Contains(&aActivity
));
1102 mActivityList
.AppendElement(WrapNotNullUnchecked(&aActivity
));
1105 void Context::RemoveActivity(Activity
& aActivity
) {
1106 NS_ASSERT_OWNINGTHREAD(Context
);
1107 MOZ_ALWAYS_TRUE(mActivityList
.RemoveElement(&aActivity
));
1108 MOZ_ASSERT(!mActivityList
.Contains(&aActivity
));
1111 void Context::NoteOrphanedData() {
1112 NS_ASSERT_OWNINGTHREAD(Context
);
1113 // This may be called more than once
1114 mOrphanedData
= true;
1117 SafeRefPtr
<Context::ThreadsafeHandle
> Context::CreateThreadsafeHandle() {
1118 NS_ASSERT_OWNINGTHREAD(Context
);
1119 if (!mThreadsafeHandle
) {
1120 mThreadsafeHandle
= MakeSafeRefPtr
<ThreadsafeHandle
>(SafeRefPtrFromThis());
1122 return mThreadsafeHandle
.clonePtr();
1125 void Context::SetNextContext(SafeRefPtr
<Context
> aNextContext
) {
1126 NS_ASSERT_OWNINGTHREAD(Context
);
1127 MOZ_DIAGNOSTIC_ASSERT(aNextContext
);
1128 MOZ_DIAGNOSTIC_ASSERT(!mNextContext
);
1129 mNextContext
= std::move(aNextContext
);
1132 void Context::DoomTargetData() {
1133 NS_ASSERT_OWNINGTHREAD(Context
);
1134 MOZ_DIAGNOSTIC_ASSERT(mData
);
1136 // We are about to drop our reference to the Data. We need to ensure that
1137 // the ~Context() destructor does not run until contents of Data have been
1138 // released on the Target thread.
1140 // Dispatch a no-op Action. This will hold the Context alive through a
1141 // roundtrip to the target thread and back to the owning thread. The
1142 // ref to the Data object is cleared on the owning thread after creating
1143 // the ActionRunnable, but before dispatching it.
1144 DispatchAction(MakeSafeRefPtr
<NullAction
>(), true /* doomed data */);
1146 MOZ_DIAGNOSTIC_ASSERT(!mData
);
1149 void Context::DoStringify(nsACString
& aData
) {
1150 NS_ASSERT_OWNINGTHREAD(Context
);
1153 "Context "_ns
+ kStringifyStartInstance
+
1155 "State:"_ns
+ IntToCString(mState
) + kStringifyDelimiter
+
1157 "OrphanedData:"_ns
+ IntToCString(mOrphanedData
) + kStringifyDelimiter
+
1159 "InitRunnable:"_ns
+ IntToCString(static_cast<bool>(mInitRunnable
)) +
1160 kStringifyDelimiter
+
1162 "InitAction:"_ns
+ IntToCString(static_cast<bool>(mInitAction
)) +
1163 kStringifyDelimiter
+
1165 "PendingActions:"_ns
+
1166 IntToCString(static_cast<uint64_t>(mPendingActions
.Length())) +
1167 kStringifyDelimiter
+
1169 "ActivityList:"_ns
+
1170 IntToCString(static_cast<uint64_t>(mActivityList
.Length())));
1172 if (mActivityList
.Length() > 0) {
1173 aData
.Append(kStringifyStartSet
);
1174 for (auto activity
: mActivityList
.ForwardRange()) {
1175 activity
->Stringify(aData
);
1176 aData
.Append(kStringifyDelimiter
);
1178 aData
.Append(kStringifyEndSet
);
1182 kStringifyDelimiter
+
1184 "DirectoryLock:"_ns
+ IntToCString(static_cast<bool>(mDirectoryLock
)) +
1185 kStringifyDelimiter
+
1187 "NextContext:"_ns
+ IntToCString(static_cast<bool>(mNextContext
)) +
1189 kStringifyEndInstance
);
1192 aData
.Append(kStringifyDelimiter
);
1193 mNextContext
->Stringify(aData
);
1196 aData
.Append(kStringifyEndInstance
);
1199 } // namespace mozilla::dom::cache