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/dom/SafeRefPtr.h"
12 #include "mozilla/dom/cache/Action.h"
13 #include "mozilla/dom/cache/FileUtils.h"
14 #include "mozilla/dom/cache/Manager.h"
15 #include "mozilla/dom/cache/ManagerId.h"
16 #include "mozilla/dom/quota/Assertions.h"
17 #include "mozilla/dom/quota/DirectoryLock.h"
18 #include "mozilla/dom/quota/QuotaManager.h"
19 #include "mozilla/dom/quota/ResultExtensions.h"
20 #include "mozilla/ipc/PBackgroundSharedTypes.h"
21 #include "mozilla/Maybe.h"
22 #include "mozIStorageConnection.h"
23 #include "nsIPrincipal.h"
24 #include "nsIRunnable.h"
25 #include "nsIThread.h"
26 #include "nsThreadUtils.h"
27 #include "QuotaClientImpl.h"
31 using mozilla::dom::cache::Action
;
32 using mozilla::dom::cache::CacheDirectoryMetadata
;
34 class NullAction final
: public Action
{
36 NullAction() = default;
38 virtual void RunOnTarget(mozilla::SafeRefPtr
<Resolver
> aResolver
,
39 const mozilla::Maybe
<CacheDirectoryMetadata
>&, Data
*,
40 const mozilla::Maybe
<mozilla::dom::cache::CipherKey
>&
41 /* aMaybeCipherKey */) override
{
42 // Resolve success immediately. This Action does no actual work.
43 MOZ_DIAGNOSTIC_ASSERT(aResolver
);
44 aResolver
->Resolve(NS_OK
);
50 namespace mozilla::dom::cache
{
52 using mozilla::dom::quota::AssertIsOnIOThread
;
53 using mozilla::dom::quota::DirectoryLock
;
54 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT
;
55 using mozilla::dom::quota::PersistenceType
;
56 using mozilla::dom::quota::QuotaManager
;
58 class Context::Data final
: public Action::Data
{
60 explicit Data(nsISerialEventTarget
* aTarget
) : mTarget(aTarget
) {
61 MOZ_DIAGNOSTIC_ASSERT(mTarget
);
64 virtual mozIStorageConnection
* GetConnection() const override
{
65 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
69 virtual void SetConnection(mozIStorageConnection
* aConn
) override
{
70 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
71 MOZ_DIAGNOSTIC_ASSERT(!mConnection
);
73 MOZ_DIAGNOSTIC_ASSERT(mConnection
);
78 // We could proxy release our data here, but instead just assert. The
79 // Context code should guarantee that we are destroyed on the target
80 // thread once the connection is initialized. If we're not, then
81 // QuotaManager might race and try to clear the origin out from under us.
82 MOZ_ASSERT_IF(mConnection
, mTarget
->IsOnCurrentThread());
85 nsCOMPtr
<nsISerialEventTarget
> mTarget
;
86 nsCOMPtr
<mozIStorageConnection
> mConnection
;
88 // Threadsafe counting because we're created on the PBackground thread
89 // and destroyed on the target IO thread.
90 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Context::Data
)
93 // Executed to perform the complicated dance of steps necessary to initialize
94 // the QuotaManager. This must be performed for each origin before any disk
96 class Context::QuotaInitRunnable final
: public nsIRunnable
{
98 QuotaInitRunnable(SafeRefPtr
<Context
> aContext
, SafeRefPtr
<Manager
> aManager
,
99 Data
* aData
, nsISerialEventTarget
* aTarget
,
100 SafeRefPtr
<Action
> aInitAction
)
101 : mContext(std::move(aContext
)),
102 mThreadsafeHandle(mContext
->CreateThreadsafeHandle()),
103 mManager(std::move(aManager
)),
106 mInitAction(std::move(aInitAction
)),
107 mInitiatingEventTarget(GetCurrentSerialEventTarget()),
111 MOZ_DIAGNOSTIC_ASSERT(mContext
);
112 MOZ_DIAGNOSTIC_ASSERT(mManager
);
113 MOZ_DIAGNOSTIC_ASSERT(mData
);
114 MOZ_DIAGNOSTIC_ASSERT(mTarget
);
115 MOZ_DIAGNOSTIC_ASSERT(mInitiatingEventTarget
);
116 MOZ_DIAGNOSTIC_ASSERT(mInitAction
);
119 Maybe
<DirectoryLock
&> MaybeDirectoryLockRef() const {
120 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
122 return ToMaybeRef(mDirectoryLock
.get());
125 nsresult
Dispatch() {
126 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
127 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_INIT
);
129 mState
= STATE_GET_INFO
;
130 nsresult rv
= NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL
);
131 if (NS_WARN_IF(NS_FAILED(rv
))) {
132 mState
= STATE_COMPLETE
;
139 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
140 MOZ_DIAGNOSTIC_ASSERT(!mCanceled
);
142 mInitAction
->CancelOnInitiatingThread();
145 void DirectoryLockAcquired(DirectoryLock
* aLock
);
147 void DirectoryLockFailed();
150 class SyncResolver final
: public Action::Resolver
{
152 SyncResolver() : mResolved(false), mResult(NS_OK
) {}
154 virtual void Resolve(nsresult aRv
) override
{
155 MOZ_DIAGNOSTIC_ASSERT(!mResolved
);
160 bool Resolved() const { return mResolved
; }
161 nsresult
Result() const { return mResult
; }
164 ~SyncResolver() = default;
169 NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver
,
173 ~QuotaInitRunnable() {
174 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_COMPLETE
);
175 MOZ_DIAGNOSTIC_ASSERT(!mContext
);
176 MOZ_DIAGNOSTIC_ASSERT(!mInitAction
);
182 STATE_CREATE_QUOTA_MANAGER
,
183 STATE_WAIT_FOR_DIRECTORY_LOCK
,
184 STATE_ENSURE_ORIGIN_INITIALIZED
,
191 void Complete(nsresult aResult
) {
192 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_RUNNING
|| NS_FAILED(aResult
));
194 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mResult
));
197 mState
= STATE_COMPLETING
;
199 mInitiatingEventTarget
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
203 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
204 MOZ_DIAGNOSTIC_ASSERT(mContext
);
207 mInitAction
= nullptr;
210 SafeRefPtr
<Context
> mContext
;
211 SafeRefPtr
<ThreadsafeHandle
> mThreadsafeHandle
;
212 SafeRefPtr
<Manager
> mManager
;
214 nsCOMPtr
<nsISerialEventTarget
> mTarget
;
215 SafeRefPtr
<Action
> mInitAction
;
216 nsCOMPtr
<nsIEventTarget
> mInitiatingEventTarget
;
218 Maybe
<mozilla::ipc::PrincipalInfo
> mPrincipalInfo
;
219 Maybe
<CacheDirectoryMetadata
> mDirectoryMetadata
;
220 RefPtr
<DirectoryLock
> mDirectoryLock
;
221 RefPtr
<CipherKeyManager
> mCipherKeyManager
;
223 Atomic
<bool> mCanceled
;
226 NS_DECL_THREADSAFE_ISUPPORTS
230 void Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock
* aLock
) {
231 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
232 MOZ_DIAGNOSTIC_ASSERT(aLock
);
233 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_WAIT_FOR_DIRECTORY_LOCK
);
234 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
236 mDirectoryLock
= aLock
;
238 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLock
->Id() >= 0);
239 mDirectoryMetadata
->mDirectoryLockId
= mDirectoryLock
->Id();
242 Complete(NS_ERROR_ABORT
);
246 QuotaManager
* qm
= QuotaManager::Get();
247 MOZ_DIAGNOSTIC_ASSERT(qm
);
249 mState
= STATE_ENSURE_ORIGIN_INITIALIZED
;
250 nsresult rv
= qm
->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL
);
251 if (NS_WARN_IF(NS_FAILED(rv
))) {
257 void Context::QuotaInitRunnable::DirectoryLockFailed() {
258 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
259 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_WAIT_FOR_DIRECTORY_LOCK
);
260 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
262 NS_WARNING("Failed to acquire a directory lock!");
264 Complete(NS_ERROR_FAILURE
);
267 NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable
, nsIRunnable
);
269 // The QuotaManager init state machine is represented in the following diagram:
272 // | Start | Resolve(error)
273 // | (Orig Thread) +---------------------+
274 // +-------+-------+ |
276 // +----------v-----------+ |
277 // | GetInfo | Resolve(error) |
278 // | (Main Thread) +-----------------+
279 // +----------+-----------+ |
281 // +----------v-----------+ |
282 // | CreateQuotaManager | Resolve(error) |
283 // | (Orig Thread) +-----------------+
284 // +----------+-----------+ |
286 // +----------v-----------+ |
287 // | WaitForDirectoryLock | Resolve(error) |
288 // | (Orig Thread) +-----------------+
289 // +----------+-----------+ |
291 // +----------v------------+ |
292 // |EnsureOriginInitialized| Resolve(error) |
293 // | (Quota IO Thread) +----------------+
294 // +----------+------------+ |
296 // +----------v------------+ |
297 // | RunOnTarget | Resolve(error) |
298 // | (Target Thread) +----------------+
299 // +----------+------------+ |
301 // +---------v---------+ +------v------+
302 // | Running | | Completing |
303 // | (Target Thread) +------------>(Orig Thread)|
304 // +-------------------+ +------+------+
310 // The initialization process proceeds through the main states. If an error
311 // occurs, then we transition to Completing state back on the original thread.
313 Context::QuotaInitRunnable::Run() {
314 // May run on different threads depending on the state. See individual
315 // state cases for thread assertions.
317 SafeRefPtr
<SyncResolver
> resolver
= MakeSafeRefPtr
<SyncResolver
>();
320 // -----------------------------------
321 case STATE_GET_INFO
: {
322 MOZ_ASSERT(NS_IsMainThread());
324 auto res
= [this]() -> Result
<Ok
, nsresult
> {
326 return Err(NS_ERROR_ABORT
);
329 nsCOMPtr
<nsIPrincipal
> principal
= mManager
->GetManagerId().Principal();
331 mozilla::ipc::PrincipalInfo principalInfo
;
333 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal
, &principalInfo
)));
335 mPrincipalInfo
.emplace(std::move(principalInfo
));
337 mState
= STATE_CREATE_QUOTA_MANAGER
;
340 mInitiatingEventTarget
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
346 resolver
->Resolve(res
.inspectErr());
351 // ----------------------------------
352 case STATE_CREATE_QUOTA_MANAGER
: {
353 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
355 if (mCanceled
|| QuotaManager::IsShuttingDown()) {
356 resolver
->Resolve(NS_ERROR_ABORT
);
360 QM_TRY(QuotaManager::EnsureCreated(), QM_PROPAGATE
,
361 [&resolver
](const auto rv
) { resolver
->Resolve(rv
); });
363 auto* const quotaManager
= QuotaManager::Get();
364 MOZ_DIAGNOSTIC_ASSERT(quotaManager
);
367 auto principalMetadata
,
368 quotaManager
->GetInfoFromValidatedPrincipalInfo(*mPrincipalInfo
));
370 mDirectoryMetadata
.emplace(std::move(principalMetadata
));
372 mState
= STATE_WAIT_FOR_DIRECTORY_LOCK
;
375 ->OpenClientDirectory({*mDirectoryMetadata
, quota::Client::DOMCACHE
})
377 GetCurrentSerialEventTarget(), __func__
,
378 [self
= RefPtr(this)](
379 const quota::ClientDirectoryLockPromise::ResolveOrRejectValue
&
381 if (aValue
.IsResolve()) {
382 self
->DirectoryLockAcquired(aValue
.ResolveValue());
384 self
->DirectoryLockFailed();
390 // ----------------------------------
391 case STATE_ENSURE_ORIGIN_INITIALIZED
: {
392 AssertIsOnIOThread();
394 auto res
= [this]() -> Result
<Ok
, nsresult
> {
396 return Err(NS_ERROR_ABORT
);
399 QuotaManager
* quotaManager
= QuotaManager::Get();
400 MOZ_DIAGNOSTIC_ASSERT(quotaManager
);
402 QM_TRY(MOZ_TO_RESULT(
403 quotaManager
->EnsureTemporaryStorageIsInitializedInternal()));
406 mDirectoryMetadata
->mDir
,
408 ->EnsureTemporaryOriginIsInitialized(
409 mDirectoryMetadata
->mPersistenceType
, *mDirectoryMetadata
)
410 .map([](const auto& res
) { return res
.first
; }));
412 auto* cacheQuotaClient
= CacheQuotaClient::Get();
413 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient
);
416 cacheQuotaClient
->GetOrCreateCipherKeyManager(*mDirectoryMetadata
);
418 mState
= STATE_RUN_ON_TARGET
;
421 mTarget
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
427 resolver
->Resolve(res
.inspectErr());
432 // -------------------
433 case STATE_RUN_ON_TARGET
: {
434 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
436 mState
= STATE_RUNNING
;
438 // Execute the provided initialization Action. The Action must Resolve()
441 mInitAction
->RunOnTarget(
442 resolver
.clonePtr(), mDirectoryMetadata
, mData
,
443 mCipherKeyManager
? Some(mCipherKeyManager
->Ensure()) : Nothing
{});
445 MOZ_DIAGNOSTIC_ASSERT(resolver
->Resolved());
449 // If the database was opened, then we should always succeed when creating
450 // the marker file. If it wasn't opened successfully, then no need to
451 // create a marker file anyway.
452 if (NS_SUCCEEDED(resolver
->Result())) {
453 MOZ_ALWAYS_SUCCEEDS(CreateMarkerFile(*mDirectoryMetadata
));
458 // -------------------
459 case STATE_COMPLETING
: {
460 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable
);
461 mInitAction
->CompleteOnInitiatingThread(mResult
);
463 mContext
->OnQuotaInit(mResult
, mDirectoryMetadata
,
464 std::move(mDirectoryLock
),
465 std::move(mCipherKeyManager
));
467 mState
= STATE_COMPLETE
;
469 // Explicitly cleanup here as the destructor could fire on any of
470 // the threads we have bounced through.
475 case STATE_WAIT_FOR_DIRECTORY_LOCK
:
477 MOZ_CRASH("unexpected state in QuotaInitRunnable");
481 if (resolver
->Resolved()) {
482 Complete(resolver
->Result());
488 // Runnable wrapper around Action objects dispatched on the Context. This
489 // runnable executes the Action on the appropriate threads while the Context
491 class Context::ActionRunnable final
: public nsIRunnable
,
492 public Action::Resolver
,
493 public Context::Activity
{
495 ActionRunnable(SafeRefPtr
<Context
> aContext
, Data
* aData
,
496 nsISerialEventTarget
* aTarget
, SafeRefPtr
<Action
> aAction
,
497 const Maybe
<CacheDirectoryMetadata
>& aDirectoryMetadata
,
498 RefPtr
<CipherKeyManager
> aCipherKeyManager
)
499 : mContext(std::move(aContext
)),
502 mAction(std::move(aAction
)),
503 mDirectoryMetadata(aDirectoryMetadata
),
504 mCipherKeyManager(std::move(aCipherKeyManager
)),
505 mInitiatingThread(GetCurrentSerialEventTarget()),
508 mExecutingRunOnTarget(false) {
509 MOZ_DIAGNOSTIC_ASSERT(mContext
);
510 // mData may be nullptr
511 MOZ_DIAGNOSTIC_ASSERT(mTarget
);
512 MOZ_DIAGNOSTIC_ASSERT(mAction
);
513 // mDirectoryMetadata.mDir may be nullptr if QuotaInitRunnable failed
514 MOZ_DIAGNOSTIC_ASSERT(mInitiatingThread
);
517 nsresult
Dispatch() {
518 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
519 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_INIT
);
521 mState
= STATE_RUN_ON_TARGET
;
522 nsresult rv
= mTarget
->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL
);
523 if (NS_WARN_IF(NS_FAILED(rv
))) {
524 mState
= STATE_COMPLETE
;
530 virtual bool MatchesCacheId(CacheId aCacheId
) const override
{
531 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
532 return mAction
->MatchesCacheId(aCacheId
);
535 virtual void Cancel() override
{
536 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
537 mAction
->CancelOnInitiatingThread();
540 virtual void Resolve(nsresult aRv
) override
{
541 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
542 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_RUNNING
);
546 // We ultimately must complete on the initiating thread, but bounce through
547 // the current thread again to ensure that we don't destroy objects and
548 // state out from under the currently running action's stack.
549 mState
= STATE_RESOLVING
;
551 // If we were resolved synchronously within Action::RunOnTarget() then we
552 // can avoid a thread bounce and just resolve once RunOnTarget() returns.
553 // The Run() method will handle this by looking at mState after
554 // RunOnTarget() returns.
555 if (mExecutingRunOnTarget
) {
559 // Otherwise we are in an asynchronous resolve. And must perform a thread
560 // bounce to run on the target thread again.
561 MOZ_ALWAYS_SUCCEEDS(mTarget
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
566 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_COMPLETE
);
567 MOZ_DIAGNOSTIC_ASSERT(!mContext
);
568 MOZ_DIAGNOSTIC_ASSERT(!mAction
);
571 void DoStringify(nsACString
& aData
) override
{
572 aData
.Append("ActionRunnable ("_ns
+
574 "State:"_ns
+ IntToCString(mState
) + kStringifyDelimiter
+
576 "Action:"_ns
+ IntToCString(static_cast<bool>(mAction
)) +
577 kStringifyDelimiter
+
578 // TODO: We might want to have Action::Stringify, too.
580 "Context:"_ns
+ IntToCString(static_cast<bool>(mContext
)) +
581 kStringifyDelimiter
+
582 // We do not print out mContext as we most probably were called
588 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
589 MOZ_DIAGNOSTIC_ASSERT(mContext
);
590 MOZ_DIAGNOSTIC_ASSERT(mAction
);
591 mContext
->RemoveActivity(*this);
605 SafeRefPtr
<Context
> mContext
;
607 nsCOMPtr
<nsISerialEventTarget
> mTarget
;
608 SafeRefPtr
<Action
> mAction
;
609 const Maybe
<CacheDirectoryMetadata
> mDirectoryMetadata
;
610 RefPtr
<CipherKeyManager
> mCipherKeyManager
;
611 nsCOMPtr
<nsIEventTarget
> mInitiatingThread
;
615 // Only accessible on target thread;
616 bool mExecutingRunOnTarget
;
619 NS_DECL_THREADSAFE_ISUPPORTS
623 NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable
, nsIRunnable
);
625 // The ActionRunnable has a simpler state machine. It basically needs to run
626 // the action on the target thread and then complete on the original thread.
633 // +-------v---------+
635 // |Target IO Thread)+---+ Resolve()
636 // +-------+---------+ |
638 // +-------v----------+ |
640 // |(Target IO Thread)| |
641 // +------------------+ |
643 // +-------v----------+ |
644 // | Resolving <--+ +-------------+
645 // | | | Completing |
646 // |(Target IO Thread)+---------------------->(Orig Thread)|
647 // +------------------+ +-------+-----+
654 // Its important to note that synchronous actions will effectively Resolve()
655 // out of the Running state immediately. Asynchronous Actions may remain
656 // in the Running state for some time, but normally the ActionRunnable itself
657 // does not see any execution there. Its all handled internal to the Action.
659 Context::ActionRunnable::Run() {
661 // ----------------------
662 case STATE_RUN_ON_TARGET
: {
663 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
664 MOZ_DIAGNOSTIC_ASSERT(!mExecutingRunOnTarget
);
666 // Note that we are calling RunOnTarget(). This lets us detect
667 // if Resolve() is called synchronously.
668 AutoRestore
<bool> executingRunOnTarget(mExecutingRunOnTarget
);
669 mExecutingRunOnTarget
= true;
671 mState
= STATE_RUNNING
;
672 mAction
->RunOnTarget(
673 SafeRefPtrFromThis(), mDirectoryMetadata
, mData
,
674 mCipherKeyManager
? Some(mCipherKeyManager
->Ensure()) : Nothing
{});
678 // Resolve was called synchronously from RunOnTarget(). We can
679 // immediately move to completing now since we are sure RunOnTarget()
681 if (mState
== STATE_RESOLVING
) {
682 // Use recursion instead of switch case fall-through... Seems slightly
683 // easier to understand.
690 case STATE_RESOLVING
: {
691 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
692 // The call to Action::RunOnTarget() must have returned now if we
693 // are running on the target thread again. We may now proceed
695 mState
= STATE_COMPLETING
;
696 // Shutdown must be delayed until all Contexts are destroyed. Crash
697 // for this invariant violation.
699 mInitiatingThread
->Dispatch(this, nsIThread::DISPATCH_NORMAL
));
702 // -------------------
703 case STATE_COMPLETING
: {
704 NS_ASSERT_OWNINGTHREAD(ActionRunnable
);
705 mAction
->CompleteOnInitiatingThread(mResult
);
706 mState
= STATE_COMPLETE
;
707 // Explicitly cleanup here as the destructor could fire on any of
708 // the threads we have bounced through.
714 MOZ_CRASH("unexpected state in ActionRunnable");
721 void Context::ThreadsafeHandle::AllowToClose() {
722 if (mOwningEventTarget
->IsOnCurrentThread()) {
723 AllowToCloseOnOwningThread();
727 // Dispatch is guaranteed to succeed here because we block shutdown until
728 // all Contexts have been destroyed.
729 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
730 "dom::cache::Context::ThreadsafeHandle::AllowToCloseOnOwningThread", this,
731 &ThreadsafeHandle::AllowToCloseOnOwningThread
);
732 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget
->Dispatch(runnable
.forget(),
733 nsIThread::DISPATCH_NORMAL
));
736 void Context::ThreadsafeHandle::InvalidateAndAllowToClose() {
737 if (mOwningEventTarget
->IsOnCurrentThread()) {
738 InvalidateAndAllowToCloseOnOwningThread();
742 // Dispatch is guaranteed to succeed here because we block shutdown until
743 // all Contexts have been destroyed.
744 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
745 "dom::cache::Context::ThreadsafeHandle::"
746 "InvalidateAndAllowToCloseOnOwningThread",
747 this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread
);
748 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget
->Dispatch(runnable
.forget(),
749 nsIThread::DISPATCH_NORMAL
));
752 Context::ThreadsafeHandle::ThreadsafeHandle(SafeRefPtr
<Context
> aContext
)
753 : mStrongRef(std::move(aContext
)),
754 mWeakRef(mStrongRef
.unsafeGetRawPtr()),
755 mOwningEventTarget(GetCurrentSerialEventTarget()) {}
757 Context::ThreadsafeHandle::~ThreadsafeHandle() {
758 // Normally we only touch mStrongRef on the owning thread. This is safe,
759 // however, because when we do use mStrongRef on the owning thread we are
760 // always holding a strong ref to the ThreadsafeHandle via the owning
761 // runnable. So we cannot run the ThreadsafeHandle destructor simultaneously.
762 if (!mStrongRef
|| mOwningEventTarget
->IsOnCurrentThread()) {
766 // Dispatch in NS_ProxyRelease is guaranteed to succeed here because we block
767 // shutdown until all Contexts have been destroyed. Therefore it is ok to have
768 // MOZ_ALWAYS_SUCCEED here.
769 MOZ_ALWAYS_SUCCEEDS(NS_ProxyRelease("Context::ThreadsafeHandle::mStrongRef",
770 mOwningEventTarget
, mStrongRef
.forget()));
773 void Context::ThreadsafeHandle::AllowToCloseOnOwningThread() {
774 MOZ_ASSERT(mOwningEventTarget
->IsOnCurrentThread());
776 // A Context "closes" when its ref count drops to zero. Dropping this
777 // strong ref is necessary, but not sufficient for the close to occur.
778 // Any outstanding IO will continue and keep the Context alive. Once
779 // the Context is idle, it will be destroyed.
781 // First, tell the context to flush any target thread shared data. This
782 // data must be released on the target thread prior to running the Context
783 // destructor. This will schedule an Action which ensures that the
784 // ~Context() is not immediately executed when we drop the strong ref.
786 mStrongRef
->DoomTargetData();
789 // Now drop our strong ref and let Context finish running any outstanding
791 mStrongRef
= nullptr;
794 void Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread() {
795 MOZ_ASSERT(mOwningEventTarget
->IsOnCurrentThread());
796 // Cancel the Context through the weak reference. This means we can
797 // allow the Context to close by dropping the strong ref, but then
798 // still cancel ongoing IO if necessary.
800 mWeakRef
->Invalidate();
802 // We should synchronously have AllowToCloseOnOwningThread called when
803 // the Context is canceled.
804 MOZ_DIAGNOSTIC_ASSERT(!mStrongRef
);
807 void Context::ThreadsafeHandle::ContextDestroyed(Context
& aContext
) {
808 MOZ_ASSERT(mOwningEventTarget
->IsOnCurrentThread());
809 MOZ_DIAGNOSTIC_ASSERT(!mStrongRef
);
810 MOZ_DIAGNOSTIC_ASSERT(mWeakRef
);
811 MOZ_DIAGNOSTIC_ASSERT(mWeakRef
== &aContext
);
816 SafeRefPtr
<Context
> Context::Create(SafeRefPtr
<Manager
> aManager
,
817 nsISerialEventTarget
* aTarget
,
818 SafeRefPtr
<Action
> aInitAction
,
819 Maybe
<Context
&> aOldContext
) {
820 auto context
= MakeSafeRefPtr
<Context
>(std::move(aManager
), aTarget
,
821 std::move(aInitAction
));
822 context
->Init(aOldContext
);
826 Context::Context(SafeRefPtr
<Manager
> aManager
, nsISerialEventTarget
* aTarget
,
827 SafeRefPtr
<Action
> aInitAction
)
828 : mManager(std::move(aManager
)),
830 mData(new Data(aTarget
)),
831 mState(STATE_CONTEXT_PREINIT
),
832 mOrphanedData(false),
833 mInitAction(std::move(aInitAction
)) {
834 MOZ_DIAGNOSTIC_ASSERT(mManager
);
835 MOZ_DIAGNOSTIC_ASSERT(mTarget
);
838 void Context::Dispatch(SafeRefPtr
<Action
> aAction
) {
839 NS_ASSERT_OWNINGTHREAD(Context
);
840 MOZ_DIAGNOSTIC_ASSERT(aAction
);
841 MOZ_DIAGNOSTIC_ASSERT(mState
!= STATE_CONTEXT_CANCELED
);
843 if (mState
== STATE_CONTEXT_CANCELED
) {
847 if (mState
== STATE_CONTEXT_INIT
|| mState
== STATE_CONTEXT_PREINIT
) {
848 PendingAction
* pending
= mPendingActions
.AppendElement();
849 pending
->mAction
= std::move(aAction
);
853 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_CONTEXT_READY
);
854 DispatchAction(std::move(aAction
));
857 Maybe
<DirectoryLock
&> Context::MaybeDirectoryLockRef() const {
858 NS_ASSERT_OWNINGTHREAD(Context
);
860 if (mState
== STATE_CONTEXT_PREINIT
) {
861 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable
);
862 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
867 if (mState
== STATE_CONTEXT_INIT
) {
868 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
870 return mInitRunnable
->MaybeDirectoryLockRef();
873 return ToMaybeRef(mDirectoryLock
.get());
876 CipherKeyManager
& Context::MutableCipherKeyManagerRef() {
877 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
878 MOZ_DIAGNOSTIC_ASSERT(mCipherKeyManager
);
880 return *mCipherKeyManager
;
883 const Maybe
<CacheDirectoryMetadata
>& Context::MaybeCacheDirectoryMetadataRef()
885 MOZ_ASSERT(mTarget
->IsOnCurrentThread());
886 return mDirectoryMetadata
;
889 void Context::CancelAll() {
890 NS_ASSERT_OWNINGTHREAD(Context
);
892 // In PREINIT state we have not dispatch the init action yet. Just
894 if (mState
== STATE_CONTEXT_PREINIT
) {
895 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable
);
896 mInitAction
= nullptr;
898 // In INIT state we have dispatched the runnable, but not received the
899 // async completion yet. Cancel the runnable, but don't forget about it
900 // until we get OnQuotaInit() callback.
901 } else if (mState
== STATE_CONTEXT_INIT
) {
902 mInitRunnable
->Cancel();
905 mState
= STATE_CONTEXT_CANCELED
;
906 mPendingActions
.Clear();
907 for (const auto& activity
: mActivityList
.ForwardRange()) {
913 bool Context::IsCanceled() const {
914 NS_ASSERT_OWNINGTHREAD(Context
);
915 return mState
== STATE_CONTEXT_CANCELED
;
918 void Context::Invalidate() {
919 NS_ASSERT_OWNINGTHREAD(Context
);
920 mManager
->NoteClosing();
924 void Context::AllowToClose() {
925 NS_ASSERT_OWNINGTHREAD(Context
);
926 if (mThreadsafeHandle
) {
927 mThreadsafeHandle
->AllowToClose();
931 void Context::CancelForCacheId(CacheId aCacheId
) {
932 NS_ASSERT_OWNINGTHREAD(Context
);
934 // Remove matching pending actions
935 mPendingActions
.RemoveElementsBy([aCacheId
](const auto& pendingAction
) {
936 return pendingAction
.mAction
->MatchesCacheId(aCacheId
);
939 // Cancel activities and let them remove themselves
940 for (const auto& activity
: mActivityList
.ForwardRange()) {
941 if (activity
->MatchesCacheId(aCacheId
)) {
947 Context::~Context() {
948 NS_ASSERT_OWNINGTHREAD(Context
);
949 MOZ_DIAGNOSTIC_ASSERT(mManager
);
950 MOZ_DIAGNOSTIC_ASSERT(!mData
);
952 if (mThreadsafeHandle
) {
953 mThreadsafeHandle
->ContextDestroyed(*this);
956 // Note, this may set the mOrphanedData flag.
957 mManager
->RemoveContext(*this);
959 if (mDirectoryMetadata
&& mDirectoryMetadata
->mDir
&& !mOrphanedData
) {
960 MOZ_ALWAYS_SUCCEEDS(DeleteMarkerFile(*mDirectoryMetadata
));
964 mNextContext
->Start();
968 void Context::Init(Maybe
<Context
&> aOldContext
) {
969 NS_ASSERT_OWNINGTHREAD(Context
);
972 aOldContext
->SetNextContext(SafeRefPtrFromThis());
979 void Context::Start() {
980 NS_ASSERT_OWNINGTHREAD(Context
);
982 // Previous context closing delayed our start, but then we were canceled.
983 // In this case, just do nothing here.
984 if (mState
== STATE_CONTEXT_CANCELED
) {
985 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable
);
986 MOZ_DIAGNOSTIC_ASSERT(!mInitAction
);
987 // If we can't initialize the quota subsystem we will never be able to
988 // clear our shared data object via the target IO thread. Instead just
989 // clear it here to maintain the invariant that the shared data is
990 // cleared before Context destruction.
995 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_CONTEXT_PREINIT
);
996 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable
);
999 new QuotaInitRunnable(SafeRefPtrFromThis(), mManager
.clonePtr(), mData
,
1000 mTarget
, std::move(mInitAction
));
1001 mState
= STATE_CONTEXT_INIT
;
1003 nsresult rv
= mInitRunnable
->Dispatch();
1004 if (NS_FAILED(rv
)) {
1005 // Shutdown must be delayed until all Contexts are destroyed. Shutdown
1006 // must also prevent any new Contexts from being constructed. Crash
1007 // for this invariant violation.
1008 MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
1012 void Context::DispatchAction(SafeRefPtr
<Action
> aAction
, bool aDoomData
) {
1013 NS_ASSERT_OWNINGTHREAD(Context
);
1015 auto runnable
= MakeSafeRefPtr
<ActionRunnable
>(
1016 SafeRefPtrFromThis(), mData
, mTarget
, std::move(aAction
),
1017 mDirectoryMetadata
, mCipherKeyManager
);
1023 nsresult rv
= runnable
->Dispatch();
1024 if (NS_FAILED(rv
)) {
1025 // Shutdown must be delayed until all Contexts are destroyed. Crash
1026 // for this invariant violation.
1027 MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
1029 AddActivity(*runnable
);
1032 void Context::OnQuotaInit(
1033 nsresult aRv
, const Maybe
<CacheDirectoryMetadata
>& aDirectoryMetadata
,
1034 RefPtr
<DirectoryLock
> aDirectoryLock
,
1035 RefPtr
<CipherKeyManager
> aCipherKeyManager
) {
1036 NS_ASSERT_OWNINGTHREAD(Context
);
1038 MOZ_DIAGNOSTIC_ASSERT(mInitRunnable
);
1039 mInitRunnable
= nullptr;
1041 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryMetadata
);
1042 mDirectoryMetadata
= aDirectoryMetadata
;
1044 // Always save the directory lock to ensure QuotaManager does not shutdown
1045 // before the Context has gone away.
1046 MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock
);
1047 mDirectoryLock
= std::move(aDirectoryLock
);
1049 MOZ_DIAGNOSTIC_ASSERT(!mCipherKeyManager
);
1050 mCipherKeyManager
= std::move(aCipherKeyManager
);
1052 // If we opening the context failed, but we were not explicitly canceled,
1053 // still treat the entire context as canceled. We don't want to allow
1054 // new actions to be dispatched. We also cannot leave the context in
1055 // the INIT state after failing to open.
1056 if (NS_FAILED(aRv
)) {
1057 mState
= STATE_CONTEXT_CANCELED
;
1060 if (mState
== STATE_CONTEXT_CANCELED
) {
1061 for (uint32_t i
= 0; i
< mPendingActions
.Length(); ++i
) {
1062 mPendingActions
[i
].mAction
->CompleteOnInitiatingThread(aRv
);
1064 mPendingActions
.Clear();
1065 mThreadsafeHandle
->AllowToClose();
1066 // Context will destruct after return here and last ref is released.
1070 // We could only assert below if quota initialization was a success which
1071 // is ensured by NS_FAILED(aRv) above
1072 MOZ_DIAGNOSTIC_ASSERT(mDirectoryMetadata
);
1073 MOZ_DIAGNOSTIC_ASSERT(mDirectoryLock
);
1074 MOZ_DIAGNOSTIC_ASSERT_IF(mDirectoryMetadata
->mIsPrivate
, mCipherKeyManager
);
1076 MOZ_DIAGNOSTIC_ASSERT(mState
== STATE_CONTEXT_INIT
);
1077 mState
= STATE_CONTEXT_READY
;
1079 for (uint32_t i
= 0; i
< mPendingActions
.Length(); ++i
) {
1080 DispatchAction(std::move(mPendingActions
[i
].mAction
));
1082 mPendingActions
.Clear();
1085 void Context::AddActivity(Activity
& aActivity
) {
1086 NS_ASSERT_OWNINGTHREAD(Context
);
1087 MOZ_ASSERT(!mActivityList
.Contains(&aActivity
));
1088 mActivityList
.AppendElement(WrapNotNullUnchecked(&aActivity
));
1091 void Context::RemoveActivity(Activity
& aActivity
) {
1092 NS_ASSERT_OWNINGTHREAD(Context
);
1093 MOZ_ALWAYS_TRUE(mActivityList
.RemoveElement(&aActivity
));
1094 MOZ_ASSERT(!mActivityList
.Contains(&aActivity
));
1097 void Context::NoteOrphanedData() {
1098 NS_ASSERT_OWNINGTHREAD(Context
);
1099 // This may be called more than once
1100 mOrphanedData
= true;
1103 SafeRefPtr
<Context::ThreadsafeHandle
> Context::CreateThreadsafeHandle() {
1104 NS_ASSERT_OWNINGTHREAD(Context
);
1105 if (!mThreadsafeHandle
) {
1106 mThreadsafeHandle
= MakeSafeRefPtr
<ThreadsafeHandle
>(SafeRefPtrFromThis());
1108 return mThreadsafeHandle
.clonePtr();
1111 void Context::SetNextContext(SafeRefPtr
<Context
> aNextContext
) {
1112 NS_ASSERT_OWNINGTHREAD(Context
);
1113 MOZ_DIAGNOSTIC_ASSERT(aNextContext
);
1114 MOZ_DIAGNOSTIC_ASSERT(!mNextContext
);
1115 mNextContext
= std::move(aNextContext
);
1118 void Context::DoomTargetData() {
1119 NS_ASSERT_OWNINGTHREAD(Context
);
1120 MOZ_DIAGNOSTIC_ASSERT(mData
);
1122 // We are about to drop our reference to the Data. We need to ensure that
1123 // the ~Context() destructor does not run until contents of Data have been
1124 // released on the Target thread.
1126 // Dispatch a no-op Action. This will hold the Context alive through a
1127 // roundtrip to the target thread and back to the owning thread. The
1128 // ref to the Data object is cleared on the owning thread after creating
1129 // the ActionRunnable, but before dispatching it.
1130 DispatchAction(MakeSafeRefPtr
<NullAction
>(), true /* doomed data */);
1132 MOZ_DIAGNOSTIC_ASSERT(!mData
);
1135 void Context::DoStringify(nsACString
& aData
) {
1136 NS_ASSERT_OWNINGTHREAD(Context
);
1139 "Context "_ns
+ kStringifyStartInstance
+
1141 "State:"_ns
+ IntToCString(mState
) + kStringifyDelimiter
+
1143 "OrphanedData:"_ns
+ IntToCString(mOrphanedData
) + kStringifyDelimiter
+
1145 "InitRunnable:"_ns
+ IntToCString(static_cast<bool>(mInitRunnable
)) +
1146 kStringifyDelimiter
+
1148 "InitAction:"_ns
+ IntToCString(static_cast<bool>(mInitAction
)) +
1149 kStringifyDelimiter
+
1151 "PendingActions:"_ns
+
1152 IntToCString(static_cast<uint64_t>(mPendingActions
.Length())) +
1153 kStringifyDelimiter
+
1155 "ActivityList:"_ns
+
1156 IntToCString(static_cast<uint64_t>(mActivityList
.Length())));
1158 if (mActivityList
.Length() > 0) {
1159 aData
.Append(kStringifyStartSet
);
1160 for (auto activity
: mActivityList
.ForwardRange()) {
1161 activity
->Stringify(aData
);
1162 aData
.Append(kStringifyDelimiter
);
1164 aData
.Append(kStringifyEndSet
);
1168 kStringifyDelimiter
+
1170 "DirectoryLock:"_ns
+ IntToCString(static_cast<bool>(mDirectoryLock
)) +
1171 kStringifyDelimiter
+
1173 "NextContext:"_ns
+ IntToCString(static_cast<bool>(mNextContext
)) +
1175 kStringifyEndInstance
);
1178 aData
.Append(kStringifyDelimiter
);
1179 mNextContext
->Stringify(aData
);
1182 aData
.Append(kStringifyEndInstance
);
1185 } // namespace mozilla::dom::cache