Bug 1925561 - Use a quicker radius calculation for ArcParams. r=aosmond
[gecko.git] / dom / cache / Context.cpp
blob4d006fccab57229c1a758ddf595c397c48c79b1c
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"
34 namespace {
36 using mozilla::dom::cache::Action;
37 using mozilla::dom::cache::CacheDirectoryMetadata;
39 class NullAction final : public Action {
40 public:
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);
53 } // namespace
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 {
65 public:
66 explicit Data(nsISerialEventTarget* aTarget) : mTarget(aTarget) {
67 MOZ_DIAGNOSTIC_ASSERT(mTarget);
70 virtual mozIStorageConnection* GetConnection() const override {
71 MOZ_ASSERT(mTarget->IsOnCurrentThread());
72 return mConnection;
75 virtual void SetConnection(mozIStorageConnection* aConn) override {
76 MOZ_ASSERT(mTarget->IsOnCurrentThread());
77 MOZ_DIAGNOSTIC_ASSERT(!mConnection);
78 mConnection = aConn;
79 MOZ_DIAGNOSTIC_ASSERT(mConnection);
82 private:
83 ~Data() {
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
101 // IO occurrs.
102 class Context::QuotaInitRunnable final : public nsIRunnable {
103 public:
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)),
110 mData(aData),
111 mTarget(aTarget),
112 mInitAction(std::move(aInitAction)),
113 mInitiatingEventTarget(GetCurrentSerialEventTarget()),
114 mResult(NS_OK),
115 mState(STATE_INIT),
116 mCanceled(false) {
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;
139 Clear();
141 return rv;
144 void Cancel() {
145 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
146 MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
147 mCanceled = true;
148 mInitAction->CancelOnInitiatingThread();
151 void DirectoryLockAcquired(DirectoryLock* aLock);
153 void DirectoryLockFailed();
155 private:
156 class SyncResolver final : public Action::Resolver {
157 public:
158 SyncResolver() : mResolved(false), mResult(NS_OK) {}
160 virtual void Resolve(nsresult aRv) override {
161 MOZ_DIAGNOSTIC_ASSERT(!mResolved);
162 mResolved = true;
163 mResult = aRv;
166 bool Resolved() const { return mResolved; }
167 nsresult Result() const { return mResult; }
169 private:
170 ~SyncResolver() = default;
172 bool mResolved;
173 nsresult mResult;
175 NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver,
176 override)
179 ~QuotaInitRunnable() {
180 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE);
181 MOZ_DIAGNOSTIC_ASSERT(!mContext);
182 MOZ_DIAGNOSTIC_ASSERT(!mInitAction);
185 enum State {
186 STATE_INIT,
187 STATE_GET_INFO,
188 STATE_CREATE_QUOTA_MANAGER,
189 STATE_WAIT_FOR_DIRECTORY_LOCK,
190 STATE_ENSURE_ORIGIN_INITIALIZED,
191 STATE_RUN_ON_TARGET,
192 STATE_RUNNING,
193 STATE_COMPLETING,
194 STATE_COMPLETE
197 void Complete(nsresult aResult) {
198 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING || NS_FAILED(aResult));
200 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mResult));
201 mResult = aResult;
203 mState = STATE_COMPLETING;
204 MOZ_ALWAYS_SUCCEEDS(
205 mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
208 void Clear() {
209 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
210 MOZ_DIAGNOSTIC_ASSERT(mContext);
211 mContext = nullptr;
212 mManager = nullptr;
213 mInitAction = nullptr;
216 SafeRefPtr<Context> mContext;
217 SafeRefPtr<ThreadsafeHandle> mThreadsafeHandle;
218 SafeRefPtr<Manager> mManager;
219 RefPtr<Data> mData;
220 nsCOMPtr<nsISerialEventTarget> mTarget;
221 SafeRefPtr<Action> mInitAction;
222 nsCOMPtr<nsIEventTarget> mInitiatingEventTarget;
223 nsresult mResult;
224 Maybe<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
225 Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
226 RefPtr<DirectoryLock> mDirectoryLock;
227 RefPtr<CipherKeyManager> mCipherKeyManager;
228 State mState;
229 Atomic<bool> mCanceled;
231 public:
232 NS_DECL_THREADSAFE_ISUPPORTS
233 NS_DECL_NSIRUNNABLE
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);
249 return;
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))) {
258 Complete(rv);
259 return;
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:
279 // +---------------+
280 // | Start | Resolve(error)
281 // | (Orig Thread) +---------------------+
282 // +-------+-------+ |
283 // | |
284 // +----------v-----------+ |
285 // | GetInfo | Resolve(error) |
286 // | (Main Thread) +-----------------+
287 // +----------+-----------+ |
288 // | |
289 // +----------v-----------+ |
290 // | CreateQuotaManager | Resolve(error) |
291 // | (Orig Thread) +-----------------+
292 // +----------+-----------+ |
293 // | |
294 // +----------v-----------+ |
295 // | WaitForDirectoryLock | Resolve(error) |
296 // | (Orig Thread) +-----------------+
297 // +----------+-----------+ |
298 // | |
299 // +----------v------------+ |
300 // |EnsureOriginInitialized| Resolve(error) |
301 // | (Quota IO Thread) +----------------+
302 // +----------+------------+ |
303 // | |
304 // +----------v------------+ |
305 // | RunOnTarget | Resolve(error) |
306 // | (Target Thread) +----------------+
307 // +----------+------------+ |
308 // | |
309 // +---------v---------+ +------v------+
310 // | Running | | Completing |
311 // | (Target Thread) +------------>(Orig Thread)|
312 // +-------------------+ +------+------+
313 // |
314 // +-----v----+
315 // | Complete |
316 // +----------+
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.
320 NS_IMETHODIMP
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>();
327 switch (mState) {
328 // -----------------------------------
329 case STATE_GET_INFO: {
330 MOZ_ASSERT(NS_IsMainThread());
332 auto res = [this]() -> Result<Ok, nsresult> {
333 if (mCanceled) {
334 return Err(NS_ERROR_ABORT);
337 nsCOMPtr<nsIPrincipal> principal = mManager->GetManagerId().Principal();
339 mozilla::ipc::PrincipalInfo principalInfo;
340 QM_TRY(
341 MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)));
343 mPrincipalInfo.emplace(std::move(principalInfo));
345 mState = STATE_CREATE_QUOTA_MANAGER;
347 MOZ_ALWAYS_SUCCEEDS(
348 mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
350 return Ok{};
351 }();
353 if (res.isErr()) {
354 resolver->Resolve(res.inspectErr());
357 break;
359 // ----------------------------------
360 case STATE_CREATE_QUOTA_MANAGER: {
361 NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
363 if (mCanceled || QuotaManager::IsShuttingDown()) {
364 resolver->Resolve(NS_ERROR_ABORT);
365 break;
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,
376 *mPrincipalInfo));
378 mDirectoryMetadata.emplace(std::move(principalMetadata));
380 mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
382 quotaManager
383 ->OpenClientDirectory({*mDirectoryMetadata, quota::Client::DOMCACHE})
384 ->Then(
385 GetCurrentSerialEventTarget(), __func__,
386 [self = RefPtr(this)](
387 const quota::ClientDirectoryLockPromise::ResolveOrRejectValue&
388 aValue) {
389 if (aValue.IsResolve()) {
390 self->DirectoryLockAcquired(aValue.ResolveValue());
391 } else {
392 self->DirectoryLockFailed();
396 break;
398 // ----------------------------------
399 case STATE_ENSURE_ORIGIN_INITIALIZED: {
400 AssertIsOnIOThread();
402 auto res = [this]() -> Result<Ok, nsresult> {
403 if (mCanceled) {
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);
417 mCipherKeyManager =
418 cacheQuotaClient->GetOrCreateCipherKeyManager(*mDirectoryMetadata);
420 SleepIfEnabled(
421 StaticPrefs::dom_cache_databaseInitialization_pauseOnIOThreadMs());
423 mState = STATE_RUN_ON_TARGET;
425 MOZ_ALWAYS_SUCCEEDS(
426 mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
428 return Ok{};
429 }();
431 if (res.isErr()) {
432 resolver->Resolve(res.inspectErr());
435 break;
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()
444 // before returning.
446 mInitAction->RunOnTarget(
447 resolver.clonePtr(), mDirectoryMetadata, mData,
448 mCipherKeyManager ? Some(mCipherKeyManager->Ensure()) : Nothing{});
450 MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved());
452 mData = nullptr;
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));
461 break;
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.
476 Clear();
477 break;
479 // -----
480 case STATE_WAIT_FOR_DIRECTORY_LOCK:
481 default: {
482 MOZ_CRASH("unexpected state in QuotaInitRunnable");
486 if (resolver->Resolved()) {
487 Complete(resolver->Result());
490 return NS_OK;
493 // Runnable wrapper around Action objects dispatched on the Context. This
494 // runnable executes the Action on the appropriate threads while the Context
495 // is initialized.
496 class Context::ActionRunnable final : public nsIRunnable,
497 public Action::Resolver,
498 public Context::Activity {
499 public:
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)),
505 mData(aData),
506 mTarget(aTarget),
507 mAction(std::move(aAction)),
508 mDirectoryMetadata(aDirectoryMetadata),
509 mCipherKeyManager(std::move(aCipherKeyManager)),
510 mInitiatingThread(GetCurrentSerialEventTarget()),
511 mState(STATE_INIT),
512 mResult(NS_OK),
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;
530 Clear();
532 return rv;
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);
549 mResult = aRv;
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) {
561 return;
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));
569 private:
570 ~ActionRunnable() {
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
588 // by its Stringify.
589 ")"_ns);
592 void Clear() {
593 NS_ASSERT_OWNINGTHREAD(ActionRunnable);
594 MOZ_DIAGNOSTIC_ASSERT(mContext);
595 MOZ_DIAGNOSTIC_ASSERT(mAction);
596 mContext->RemoveActivity(*this);
597 mContext = nullptr;
598 mAction = nullptr;
601 enum State {
602 STATE_INIT,
603 STATE_RUN_ON_TARGET,
604 STATE_RUNNING,
605 STATE_RESOLVING,
606 STATE_COMPLETING,
607 STATE_COMPLETE
610 SafeRefPtr<Context> mContext;
611 RefPtr<Data> mData;
612 nsCOMPtr<nsISerialEventTarget> mTarget;
613 SafeRefPtr<Action> mAction;
614 const Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
615 RefPtr<CipherKeyManager> mCipherKeyManager;
616 nsCOMPtr<nsIEventTarget> mInitiatingThread;
617 State mState;
618 nsresult mResult;
620 // Only accessible on target thread;
621 bool mExecutingRunOnTarget;
623 public:
624 NS_DECL_THREADSAFE_ISUPPORTS
625 NS_DECL_NSIRUNNABLE
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.
633 // +-------------+
634 // | Start |
635 // |(Orig Thread)|
636 // +-----+-------+
637 // |
638 // +-------v---------+
639 // | RunOnTarget |
640 // |Target IO Thread)+---+ Resolve()
641 // +-------+---------+ |
642 // | |
643 // +-------v----------+ |
644 // | Running | |
645 // |(Target IO Thread)| |
646 // +------------------+ |
647 // | Resolve() |
648 // +-------v----------+ |
649 // | Resolving <--+ +-------------+
650 // | | | Completing |
651 // |(Target IO Thread)+---------------------->(Orig Thread)|
652 // +------------------+ +-------+-----+
653 // |
654 // |
655 // +----v---+
656 // |Complete|
657 // +--------+
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.
663 NS_IMETHODIMP
664 Context::ActionRunnable::Run() {
665 switch (mState) {
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{});
681 mData = nullptr;
683 // Resolve was called synchronously from RunOnTarget(). We can
684 // immediately move to completing now since we are sure RunOnTarget()
685 // completed.
686 if (mState == STATE_RESOLVING) {
687 // Use recursion instead of switch case fall-through... Seems slightly
688 // easier to understand.
689 Run();
692 break;
694 // -----------------
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
699 // with completion.
700 mState = STATE_COMPLETING;
701 // Shutdown must be delayed until all Contexts are destroyed. Crash
702 // for this invariant violation.
703 MOZ_ALWAYS_SUCCEEDS(
704 mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
705 break;
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.
714 Clear();
715 break;
717 // -----------------
718 default: {
719 MOZ_CRASH("unexpected state in ActionRunnable");
720 break;
723 return NS_OK;
726 void Context::ThreadsafeHandle::AllowToClose() {
727 if (mOwningEventTarget->IsOnCurrentThread()) {
728 AllowToCloseOnOwningThread();
729 return;
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();
744 return;
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()) {
768 return;
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.
790 if (mStrongRef) {
791 mStrongRef->DoomTargetData();
794 // Now drop our strong ref and let Context finish running any outstanding
795 // Actions.
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.
804 if (mWeakRef) {
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);
817 mWeakRef = nullptr;
820 // static
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);
828 return context;
831 Context::Context(SafeRefPtr<Manager> aManager, nsISerialEventTarget* aTarget,
832 SafeRefPtr<Action> aInitAction)
833 : mManager(std::move(aManager)),
834 mTarget(aTarget),
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) {
849 return;
852 if (mState == STATE_CONTEXT_INIT || mState == STATE_CONTEXT_PREINIT) {
853 PendingAction* pending = mPendingActions.AppendElement();
854 pending->mAction = std::move(aAction);
855 return;
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);
869 return Nothing();
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()
889 const {
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
898 // forget it.
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()) {
916 activity->Cancel();
918 AllowToClose();
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();
929 CancelAll();
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)) {
950 activity->Cancel();
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));
973 if (mNextContext) {
974 mNextContext->Start();
978 void Context::Init(Maybe<Context&> aOldContext) {
979 NS_ASSERT_OWNINGTHREAD(Context);
981 if (aOldContext) {
982 aOldContext->SetNextContext(SafeRefPtrFromThis());
983 return;
986 Start();
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.
1001 mData = nullptr;
1002 return;
1005 MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_PREINIT);
1006 MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
1008 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);
1029 if (aDoomData) {
1030 mData = nullptr;
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.
1080 return;
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);
1152 aData.Append(
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);
1181 aData.Append(
1182 kStringifyDelimiter +
1184 "DirectoryLock:"_ns + IntToCString(static_cast<bool>(mDirectoryLock)) +
1185 kStringifyDelimiter +
1187 "NextContext:"_ns + IntToCString(static_cast<bool>(mNextContext)) +
1189 kStringifyEndInstance);
1191 if (mNextContext) {
1192 aData.Append(kStringifyDelimiter);
1193 mNextContext->Stringify(aData);
1196 aData.Append(kStringifyEndInstance);
1199 } // namespace mozilla::dom::cache