Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / cache / Context.cpp
blob17d60269e91a6611b60f2fc691ebdec82d7b8d99
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"
29 namespace {
31 using mozilla::dom::cache::Action;
32 using mozilla::dom::cache::CacheDirectoryMetadata;
34 class NullAction final : public Action {
35 public:
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);
48 } // namespace
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 {
59 public:
60 explicit Data(nsISerialEventTarget* aTarget) : mTarget(aTarget) {
61 MOZ_DIAGNOSTIC_ASSERT(mTarget);
64 virtual mozIStorageConnection* GetConnection() const override {
65 MOZ_ASSERT(mTarget->IsOnCurrentThread());
66 return mConnection;
69 virtual void SetConnection(mozIStorageConnection* aConn) override {
70 MOZ_ASSERT(mTarget->IsOnCurrentThread());
71 MOZ_DIAGNOSTIC_ASSERT(!mConnection);
72 mConnection = aConn;
73 MOZ_DIAGNOSTIC_ASSERT(mConnection);
76 private:
77 ~Data() {
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
95 // IO occurrs.
96 class Context::QuotaInitRunnable final : public nsIRunnable {
97 public:
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)),
104 mData(aData),
105 mTarget(aTarget),
106 mInitAction(std::move(aInitAction)),
107 mInitiatingEventTarget(GetCurrentSerialEventTarget()),
108 mResult(NS_OK),
109 mState(STATE_INIT),
110 mCanceled(false) {
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;
133 Clear();
135 return rv;
138 void Cancel() {
139 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
140 MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
141 mCanceled = true;
142 mInitAction->CancelOnInitiatingThread();
145 void DirectoryLockAcquired(DirectoryLock* aLock);
147 void DirectoryLockFailed();
149 private:
150 class SyncResolver final : public Action::Resolver {
151 public:
152 SyncResolver() : mResolved(false), mResult(NS_OK) {}
154 virtual void Resolve(nsresult aRv) override {
155 MOZ_DIAGNOSTIC_ASSERT(!mResolved);
156 mResolved = true;
157 mResult = aRv;
160 bool Resolved() const { return mResolved; }
161 nsresult Result() const { return mResult; }
163 private:
164 ~SyncResolver() = default;
166 bool mResolved;
167 nsresult mResult;
169 NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver,
170 override)
173 ~QuotaInitRunnable() {
174 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE);
175 MOZ_DIAGNOSTIC_ASSERT(!mContext);
176 MOZ_DIAGNOSTIC_ASSERT(!mInitAction);
179 enum State {
180 STATE_INIT,
181 STATE_GET_INFO,
182 STATE_CREATE_QUOTA_MANAGER,
183 STATE_WAIT_FOR_DIRECTORY_LOCK,
184 STATE_ENSURE_ORIGIN_INITIALIZED,
185 STATE_RUN_ON_TARGET,
186 STATE_RUNNING,
187 STATE_COMPLETING,
188 STATE_COMPLETE
191 void Complete(nsresult aResult) {
192 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING || NS_FAILED(aResult));
194 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mResult));
195 mResult = aResult;
197 mState = STATE_COMPLETING;
198 MOZ_ALWAYS_SUCCEEDS(
199 mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
202 void Clear() {
203 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
204 MOZ_DIAGNOSTIC_ASSERT(mContext);
205 mContext = nullptr;
206 mManager = nullptr;
207 mInitAction = nullptr;
210 SafeRefPtr<Context> mContext;
211 SafeRefPtr<ThreadsafeHandle> mThreadsafeHandle;
212 SafeRefPtr<Manager> mManager;
213 RefPtr<Data> mData;
214 nsCOMPtr<nsISerialEventTarget> mTarget;
215 SafeRefPtr<Action> mInitAction;
216 nsCOMPtr<nsIEventTarget> mInitiatingEventTarget;
217 nsresult mResult;
218 Maybe<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
219 Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
220 RefPtr<DirectoryLock> mDirectoryLock;
221 RefPtr<CipherKeyManager> mCipherKeyManager;
222 State mState;
223 Atomic<bool> mCanceled;
225 public:
226 NS_DECL_THREADSAFE_ISUPPORTS
227 NS_DECL_NSIRUNNABLE
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();
241 if (mCanceled) {
242 Complete(NS_ERROR_ABORT);
243 return;
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))) {
252 Complete(rv);
253 return;
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:
271 // +---------------+
272 // | Start | Resolve(error)
273 // | (Orig Thread) +---------------------+
274 // +-------+-------+ |
275 // | |
276 // +----------v-----------+ |
277 // | GetInfo | Resolve(error) |
278 // | (Main Thread) +-----------------+
279 // +----------+-----------+ |
280 // | |
281 // +----------v-----------+ |
282 // | CreateQuotaManager | Resolve(error) |
283 // | (Orig Thread) +-----------------+
284 // +----------+-----------+ |
285 // | |
286 // +----------v-----------+ |
287 // | WaitForDirectoryLock | Resolve(error) |
288 // | (Orig Thread) +-----------------+
289 // +----------+-----------+ |
290 // | |
291 // +----------v------------+ |
292 // |EnsureOriginInitialized| Resolve(error) |
293 // | (Quota IO Thread) +----------------+
294 // +----------+------------+ |
295 // | |
296 // +----------v------------+ |
297 // | RunOnTarget | Resolve(error) |
298 // | (Target Thread) +----------------+
299 // +----------+------------+ |
300 // | |
301 // +---------v---------+ +------v------+
302 // | Running | | Completing |
303 // | (Target Thread) +------------>(Orig Thread)|
304 // +-------------------+ +------+------+
305 // |
306 // +-----v----+
307 // | Complete |
308 // +----------+
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.
312 NS_IMETHODIMP
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>();
319 switch (mState) {
320 // -----------------------------------
321 case STATE_GET_INFO: {
322 MOZ_ASSERT(NS_IsMainThread());
324 auto res = [this]() -> Result<Ok, nsresult> {
325 if (mCanceled) {
326 return Err(NS_ERROR_ABORT);
329 nsCOMPtr<nsIPrincipal> principal = mManager->GetManagerId().Principal();
331 mozilla::ipc::PrincipalInfo principalInfo;
332 QM_TRY(
333 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
335 mPrincipalInfo.emplace(std::move(principalInfo));
337 mState = STATE_CREATE_QUOTA_MANAGER;
339 MOZ_ALWAYS_SUCCEEDS(
340 mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
342 return Ok{};
343 }();
345 if (res.isErr()) {
346 resolver->Resolve(res.inspectErr());
349 break;
351 // ----------------------------------
352 case STATE_CREATE_QUOTA_MANAGER: {
353 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
355 if (mCanceled || QuotaManager::IsShuttingDown()) {
356 resolver->Resolve(NS_ERROR_ABORT);
357 break;
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);
366 QM_TRY_UNWRAP(
367 auto principalMetadata,
368 quotaManager->GetInfoFromValidatedPrincipalInfo(*mPrincipalInfo));
370 mDirectoryMetadata.emplace(std::move(principalMetadata));
372 mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
374 quotaManager
375 ->OpenClientDirectory({*mDirectoryMetadata, quota::Client::DOMCACHE})
376 ->Then(
377 GetCurrentSerialEventTarget(), __func__,
378 [self = RefPtr(this)](
379 const quota::ClientDirectoryLockPromise::ResolveOrRejectValue&
380 aValue) {
381 if (aValue.IsResolve()) {
382 self->DirectoryLockAcquired(aValue.ResolveValue());
383 } else {
384 self->DirectoryLockFailed();
388 break;
390 // ----------------------------------
391 case STATE_ENSURE_ORIGIN_INITIALIZED: {
392 AssertIsOnIOThread();
394 auto res = [this]() -> Result<Ok, nsresult> {
395 if (mCanceled) {
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()));
405 QM_TRY_UNWRAP(
406 mDirectoryMetadata->mDir,
407 quotaManager
408 ->EnsureTemporaryOriginIsInitialized(
409 mDirectoryMetadata->mPersistenceType, *mDirectoryMetadata)
410 .map([](const auto& res) { return res.first; }));
412 auto* cacheQuotaClient = CacheQuotaClient::Get();
413 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
415 mCipherKeyManager =
416 cacheQuotaClient->GetOrCreateCipherKeyManager(*mDirectoryMetadata);
418 mState = STATE_RUN_ON_TARGET;
420 MOZ_ALWAYS_SUCCEEDS(
421 mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
423 return Ok{};
424 }();
426 if (res.isErr()) {
427 resolver->Resolve(res.inspectErr());
430 break;
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()
439 // before returning.
441 mInitAction->RunOnTarget(
442 resolver.clonePtr(), mDirectoryMetadata, mData,
443 mCipherKeyManager ? Some(mCipherKeyManager->Ensure()) : Nothing{});
445 MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved());
447 mData = nullptr;
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));
456 break;
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.
471 Clear();
472 break;
474 // -----
475 case STATE_WAIT_FOR_DIRECTORY_LOCK:
476 default: {
477 MOZ_CRASH("unexpected state in QuotaInitRunnable");
481 if (resolver->Resolved()) {
482 Complete(resolver->Result());
485 return NS_OK;
488 // Runnable wrapper around Action objects dispatched on the Context. This
489 // runnable executes the Action on the appropriate threads while the Context
490 // is initialized.
491 class Context::ActionRunnable final : public nsIRunnable,
492 public Action::Resolver,
493 public Context::Activity {
494 public:
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)),
500 mData(aData),
501 mTarget(aTarget),
502 mAction(std::move(aAction)),
503 mDirectoryMetadata(aDirectoryMetadata),
504 mCipherKeyManager(std::move(aCipherKeyManager)),
505 mInitiatingThread(GetCurrentSerialEventTarget()),
506 mState(STATE_INIT),
507 mResult(NS_OK),
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;
525 Clear();
527 return rv;
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);
544 mResult = aRv;
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) {
556 return;
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));
564 private:
565 ~ActionRunnable() {
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
583 // by its Stringify.
584 ")"_ns);
587 void Clear() {
588 NS_ASSERT_OWNINGTHREAD(ActionRunnable);
589 MOZ_DIAGNOSTIC_ASSERT(mContext);
590 MOZ_DIAGNOSTIC_ASSERT(mAction);
591 mContext->RemoveActivity(*this);
592 mContext = nullptr;
593 mAction = nullptr;
596 enum State {
597 STATE_INIT,
598 STATE_RUN_ON_TARGET,
599 STATE_RUNNING,
600 STATE_RESOLVING,
601 STATE_COMPLETING,
602 STATE_COMPLETE
605 SafeRefPtr<Context> mContext;
606 RefPtr<Data> mData;
607 nsCOMPtr<nsISerialEventTarget> mTarget;
608 SafeRefPtr<Action> mAction;
609 const Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
610 RefPtr<CipherKeyManager> mCipherKeyManager;
611 nsCOMPtr<nsIEventTarget> mInitiatingThread;
612 State mState;
613 nsresult mResult;
615 // Only accessible on target thread;
616 bool mExecutingRunOnTarget;
618 public:
619 NS_DECL_THREADSAFE_ISUPPORTS
620 NS_DECL_NSIRUNNABLE
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.
628 // +-------------+
629 // | Start |
630 // |(Orig Thread)|
631 // +-----+-------+
632 // |
633 // +-------v---------+
634 // | RunOnTarget |
635 // |Target IO Thread)+---+ Resolve()
636 // +-------+---------+ |
637 // | |
638 // +-------v----------+ |
639 // | Running | |
640 // |(Target IO Thread)| |
641 // +------------------+ |
642 // | Resolve() |
643 // +-------v----------+ |
644 // | Resolving <--+ +-------------+
645 // | | | Completing |
646 // |(Target IO Thread)+---------------------->(Orig Thread)|
647 // +------------------+ +-------+-----+
648 // |
649 // |
650 // +----v---+
651 // |Complete|
652 // +--------+
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.
658 NS_IMETHODIMP
659 Context::ActionRunnable::Run() {
660 switch (mState) {
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{});
676 mData = nullptr;
678 // Resolve was called synchronously from RunOnTarget(). We can
679 // immediately move to completing now since we are sure RunOnTarget()
680 // completed.
681 if (mState == STATE_RESOLVING) {
682 // Use recursion instead of switch case fall-through... Seems slightly
683 // easier to understand.
684 Run();
687 break;
689 // -----------------
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
694 // with completion.
695 mState = STATE_COMPLETING;
696 // Shutdown must be delayed until all Contexts are destroyed. Crash
697 // for this invariant violation.
698 MOZ_ALWAYS_SUCCEEDS(
699 mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
700 break;
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.
709 Clear();
710 break;
712 // -----------------
713 default: {
714 MOZ_CRASH("unexpected state in ActionRunnable");
715 break;
718 return NS_OK;
721 void Context::ThreadsafeHandle::AllowToClose() {
722 if (mOwningEventTarget->IsOnCurrentThread()) {
723 AllowToCloseOnOwningThread();
724 return;
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();
739 return;
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()) {
763 return;
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.
785 if (mStrongRef) {
786 mStrongRef->DoomTargetData();
789 // Now drop our strong ref and let Context finish running any outstanding
790 // Actions.
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.
799 if (mWeakRef) {
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);
812 mWeakRef = nullptr;
815 // static
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);
823 return context;
826 Context::Context(SafeRefPtr<Manager> aManager, nsISerialEventTarget* aTarget,
827 SafeRefPtr<Action> aInitAction)
828 : mManager(std::move(aManager)),
829 mTarget(aTarget),
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) {
844 return;
847 if (mState == STATE_CONTEXT_INIT || mState == STATE_CONTEXT_PREINIT) {
848 PendingAction* pending = mPendingActions.AppendElement();
849 pending->mAction = std::move(aAction);
850 return;
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);
864 return Nothing();
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()
884 const {
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
893 // forget it.
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()) {
908 activity->Cancel();
910 AllowToClose();
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();
921 CancelAll();
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)) {
942 activity->Cancel();
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));
963 if (mNextContext) {
964 mNextContext->Start();
968 void Context::Init(Maybe<Context&> aOldContext) {
969 NS_ASSERT_OWNINGTHREAD(Context);
971 if (aOldContext) {
972 aOldContext->SetNextContext(SafeRefPtrFromThis());
973 return;
976 Start();
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.
991 mData = nullptr;
992 return;
995 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_PREINIT);
996 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
998 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);
1019 if (aDoomData) {
1020 mData = nullptr;
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.
1067 return;
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);
1138 aData.Append(
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);
1167 aData.Append(
1168 kStringifyDelimiter +
1170 "DirectoryLock:"_ns + IntToCString(static_cast<bool>(mDirectoryLock)) +
1171 kStringifyDelimiter +
1173 "NextContext:"_ns + IntToCString(static_cast<bool>(mNextContext)) +
1175 kStringifyEndInstance);
1177 if (mNextContext) {
1178 aData.Append(kStringifyDelimiter);
1179 mNextContext->Stringify(aData);
1182 aData.Append(kStringifyEndInstance);
1185 } // namespace mozilla::dom::cache