Line data Source code
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/. */
6 :
7 : #include "mozilla/dom/cache/Context.h"
8 :
9 : #include "mozilla/AutoRestore.h"
10 : #include "mozilla/dom/cache/Action.h"
11 : #include "mozilla/dom/cache/FileUtils.h"
12 : #include "mozilla/dom/cache/Manager.h"
13 : #include "mozilla/dom/cache/ManagerId.h"
14 : #include "mozilla/dom/quota/QuotaManager.h"
15 : #include "mozIStorageConnection.h"
16 : #include "nsIFile.h"
17 : #include "nsIPrincipal.h"
18 : #include "nsIRunnable.h"
19 : #include "nsThreadUtils.h"
20 :
21 : namespace {
22 :
23 : using mozilla::dom::cache::Action;
24 : using mozilla::dom::cache::QuotaInfo;
25 :
26 0 : class NullAction final : public Action
27 : {
28 : public:
29 0 : NullAction()
30 0 : {
31 0 : }
32 :
33 : virtual void
34 0 : RunOnTarget(Resolver* aResolver, const QuotaInfo&, Data*) override
35 : {
36 : // Resolve success immediately. This Action does no actual work.
37 0 : MOZ_DIAGNOSTIC_ASSERT(aResolver);
38 0 : aResolver->Resolve(NS_OK);
39 0 : }
40 : };
41 :
42 : } // namespace
43 :
44 : namespace mozilla {
45 : namespace dom {
46 : namespace cache {
47 :
48 : using mozilla::dom::quota::AssertIsOnIOThread;
49 : using mozilla::dom::quota::OpenDirectoryListener;
50 : using mozilla::dom::quota::QuotaManager;
51 : using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
52 : using mozilla::dom::quota::PersistenceType;
53 :
54 : class Context::Data final : public Action::Data
55 : {
56 : public:
57 0 : explicit Data(nsISerialEventTarget* aTarget)
58 0 : : mTarget(aTarget)
59 : {
60 0 : MOZ_DIAGNOSTIC_ASSERT(mTarget);
61 0 : }
62 :
63 : virtual mozIStorageConnection*
64 0 : GetConnection() const override
65 : {
66 0 : MOZ_ASSERT(mTarget->IsOnCurrentThread());
67 0 : return mConnection;
68 : }
69 :
70 : virtual void
71 0 : SetConnection(mozIStorageConnection* aConn) override
72 : {
73 0 : MOZ_ASSERT(mTarget->IsOnCurrentThread());
74 0 : MOZ_DIAGNOSTIC_ASSERT(!mConnection);
75 0 : mConnection = aConn;
76 0 : MOZ_DIAGNOSTIC_ASSERT(mConnection);
77 0 : }
78 :
79 : private:
80 0 : ~Data()
81 0 : {
82 : // We could proxy release our data here, but instead just assert. The
83 : // Context code should guarantee that we are destroyed on the target
84 : // thread once the connection is initialized. If we're not, then
85 : // QuotaManager might race and try to clear the origin out from under us.
86 0 : MOZ_ASSERT_IF(mConnection, mTarget->IsOnCurrentThread());
87 0 : }
88 :
89 : nsCOMPtr<nsISerialEventTarget> mTarget;
90 : nsCOMPtr<mozIStorageConnection> mConnection;
91 :
92 : // Threadsafe counting because we're created on the PBackground thread
93 : // and destroyed on the target IO thread.
94 0 : NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Context::Data)
95 : };
96 :
97 : // Executed to perform the complicated dance of steps necessary to initialize
98 : // the QuotaManager. This must be performed for each origin before any disk
99 : // IO occurrs.
100 : class Context::QuotaInitRunnable final : public nsIRunnable
101 : , public OpenDirectoryListener
102 : {
103 : public:
104 0 : QuotaInitRunnable(Context* aContext,
105 : Manager* aManager,
106 : Data* aData,
107 : nsISerialEventTarget* aTarget,
108 : Action* aInitAction)
109 0 : : mContext(aContext)
110 0 : , mThreadsafeHandle(aContext->CreateThreadsafeHandle())
111 : , mManager(aManager)
112 : , mData(aData)
113 : , mTarget(aTarget)
114 : , mInitAction(aInitAction)
115 : , mInitiatingEventTarget(GetCurrentThreadEventTarget())
116 : , mResult(NS_OK)
117 : , mState(STATE_INIT)
118 0 : , mCanceled(false)
119 : {
120 0 : MOZ_DIAGNOSTIC_ASSERT(mContext);
121 0 : MOZ_DIAGNOSTIC_ASSERT(mManager);
122 0 : MOZ_DIAGNOSTIC_ASSERT(mData);
123 0 : MOZ_DIAGNOSTIC_ASSERT(mTarget);
124 0 : MOZ_DIAGNOSTIC_ASSERT(mInitiatingEventTarget);
125 0 : MOZ_DIAGNOSTIC_ASSERT(mInitAction);
126 0 : }
127 :
128 0 : nsresult Dispatch()
129 : {
130 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
131 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_INIT);
132 :
133 0 : mState = STATE_GET_INFO;
134 0 : nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
135 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
136 0 : mState = STATE_COMPLETE;
137 0 : Clear();
138 : }
139 0 : return rv;
140 : }
141 :
142 0 : void Cancel()
143 : {
144 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
145 0 : MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
146 0 : mCanceled = true;
147 0 : mInitAction->CancelOnInitiatingThread();
148 0 : }
149 :
150 : void OpenDirectory();
151 :
152 : // OpenDirectoryListener methods
153 : virtual void
154 : DirectoryLockAcquired(DirectoryLock* aLock) override;
155 :
156 : virtual void
157 : DirectoryLockFailed() override;
158 :
159 : private:
160 : class SyncResolver final : public Action::Resolver
161 : {
162 : public:
163 0 : SyncResolver()
164 0 : : mResolved(false)
165 0 : , mResult(NS_OK)
166 0 : { }
167 :
168 : virtual void
169 0 : Resolve(nsresult aRv) override
170 : {
171 0 : MOZ_DIAGNOSTIC_ASSERT(!mResolved);
172 0 : mResolved = true;
173 0 : mResult = aRv;
174 0 : };
175 :
176 0 : bool Resolved() const { return mResolved; }
177 0 : nsresult Result() const { return mResult; }
178 :
179 : private:
180 0 : ~SyncResolver() { }
181 :
182 : bool mResolved;
183 : nsresult mResult;
184 :
185 0 : NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver, override)
186 : };
187 :
188 0 : ~QuotaInitRunnable()
189 0 : {
190 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE);
191 0 : MOZ_DIAGNOSTIC_ASSERT(!mContext);
192 0 : MOZ_DIAGNOSTIC_ASSERT(!mInitAction);
193 0 : }
194 :
195 : enum State
196 : {
197 : STATE_INIT,
198 : STATE_GET_INFO,
199 : STATE_CREATE_QUOTA_MANAGER,
200 : STATE_OPEN_DIRECTORY,
201 : STATE_WAIT_FOR_DIRECTORY_LOCK,
202 : STATE_ENSURE_ORIGIN_INITIALIZED,
203 : STATE_RUN_ON_TARGET,
204 : STATE_RUNNING,
205 : STATE_COMPLETING,
206 : STATE_COMPLETE
207 : };
208 :
209 0 : void Complete(nsresult aResult)
210 : {
211 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING || NS_FAILED(aResult));
212 :
213 0 : MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mResult));
214 0 : mResult = aResult;
215 :
216 0 : mState = STATE_COMPLETING;
217 0 : MOZ_ALWAYS_SUCCEEDS(
218 : mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
219 0 : }
220 :
221 0 : void Clear()
222 : {
223 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
224 0 : MOZ_DIAGNOSTIC_ASSERT(mContext);
225 0 : mContext = nullptr;
226 0 : mManager = nullptr;
227 0 : mInitAction = nullptr;
228 0 : }
229 :
230 : RefPtr<Context> mContext;
231 : RefPtr<ThreadsafeHandle> mThreadsafeHandle;
232 : RefPtr<Manager> mManager;
233 : RefPtr<Data> mData;
234 : nsCOMPtr<nsISerialEventTarget> mTarget;
235 : RefPtr<Action> mInitAction;
236 : nsCOMPtr<nsIEventTarget> mInitiatingEventTarget;
237 : nsresult mResult;
238 : QuotaInfo mQuotaInfo;
239 : RefPtr<DirectoryLock> mDirectoryLock;
240 : State mState;
241 : Atomic<bool> mCanceled;
242 :
243 : public:
244 : NS_DECL_THREADSAFE_ISUPPORTS
245 : NS_DECL_NSIRUNNABLE
246 : };
247 :
248 : void
249 0 : Context::QuotaInitRunnable::OpenDirectory()
250 : {
251 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
252 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CREATE_QUOTA_MANAGER ||
253 : mState == STATE_OPEN_DIRECTORY);
254 0 : MOZ_DIAGNOSTIC_ASSERT(QuotaManager::Get());
255 :
256 : // QuotaManager::OpenDirectory() will hold a reference to us as
257 : // a listener. We will then get DirectoryLockAcquired() on the owning
258 : // thread when it is safe to access our storage directory.
259 0 : mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
260 0 : QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
261 : mQuotaInfo.mGroup,
262 : mQuotaInfo.mOrigin,
263 : quota::Client::DOMCACHE,
264 : /* aExclusive */ false,
265 0 : this);
266 0 : }
267 :
268 : void
269 0 : Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
270 : {
271 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
272 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
273 0 : MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
274 :
275 0 : mDirectoryLock = aLock;
276 :
277 0 : if (mCanceled) {
278 0 : Complete(NS_ERROR_ABORT);
279 0 : return;
280 : }
281 :
282 0 : QuotaManager* qm = QuotaManager::Get();
283 0 : MOZ_DIAGNOSTIC_ASSERT(qm);
284 :
285 0 : mState = STATE_ENSURE_ORIGIN_INITIALIZED;
286 0 : nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
287 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
288 0 : Complete(rv);
289 0 : return;
290 : }
291 : }
292 :
293 : void
294 0 : Context::QuotaInitRunnable::DirectoryLockFailed()
295 : {
296 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
297 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
298 0 : MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
299 :
300 0 : NS_WARNING("Failed to acquire a directory lock!");
301 :
302 0 : Complete(NS_ERROR_FAILURE);
303 0 : }
304 :
305 0 : NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable);
306 :
307 : // The QuotaManager init state machine is represented in the following diagram:
308 : //
309 : // +---------------+
310 : // | Start | Resolve(error)
311 : // | (Orig Thread) +---------------------+
312 : // +-------+-------+ |
313 : // | |
314 : // +----------v-----------+ |
315 : // | GetInfo | Resolve(error) |
316 : // | (Main Thread) +-----------------+
317 : // +----------+-----------+ |
318 : // | |
319 : // +----------v-----------+ |
320 : // | CreateQuotaManager | Resolve(error) |
321 : // | (Orig Thread) +-----------------+
322 : // +----------+-----------+ |
323 : // | |
324 : // +----------v-----------+ |
325 : // | OpenDirectory | Resolve(error) |
326 : // | (Orig Thread) +-----------------+
327 : // +----------+-----------+ |
328 : // | |
329 : // +----------v-----------+ |
330 : // | WaitForDirectoryLock | Resolve(error) |
331 : // | (Orig Thread) +-----------------+
332 : // +----------+-----------+ |
333 : // | |
334 : // +----------v------------+ |
335 : // |EnsureOriginInitialized| Resolve(error) |
336 : // | (Quota IO Thread) +----------------+
337 : // +----------+------------+ |
338 : // | |
339 : // +----------v------------+ |
340 : // | RunOnTarget | Resolve(error) |
341 : // | (Target Thread) +----------------+
342 : // +----------+------------+ |
343 : // | |
344 : // +---------v---------+ +------v------+
345 : // | Running | | Completing |
346 : // | (Target Thread) +------------>(Orig Thread)|
347 : // +-------------------+ +------+------+
348 : // |
349 : // +-----v----+
350 : // | Complete |
351 : // +----------+
352 : //
353 : // The initialization process proceeds through the main states. If an error
354 : // occurs, then we transition to Completing state back on the original thread.
355 : NS_IMETHODIMP
356 0 : Context::QuotaInitRunnable::Run()
357 : {
358 : // May run on different threads depending on the state. See individual
359 : // state cases for thread assertions.
360 :
361 0 : RefPtr<SyncResolver> resolver = new SyncResolver();
362 :
363 0 : switch(mState) {
364 : // -----------------------------------
365 : case STATE_GET_INFO:
366 : {
367 0 : MOZ_ASSERT(NS_IsMainThread());
368 :
369 0 : if (mCanceled) {
370 0 : resolver->Resolve(NS_ERROR_ABORT);
371 0 : break;
372 : }
373 :
374 0 : RefPtr<ManagerId> managerId = mManager->GetManagerId();
375 0 : nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
376 0 : nsresult rv = QuotaManager::GetInfoFromPrincipal(principal,
377 : &mQuotaInfo.mSuffix,
378 : &mQuotaInfo.mGroup,
379 0 : &mQuotaInfo.mOrigin);
380 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
381 0 : resolver->Resolve(rv);
382 0 : break;
383 : }
384 :
385 0 : mState = STATE_CREATE_QUOTA_MANAGER;
386 0 : MOZ_ALWAYS_SUCCEEDS(
387 : mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
388 0 : break;
389 : }
390 : // ----------------------------------
391 : case STATE_CREATE_QUOTA_MANAGER:
392 : {
393 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
394 :
395 0 : if (mCanceled || QuotaManager::IsShuttingDown()) {
396 0 : resolver->Resolve(NS_ERROR_ABORT);
397 0 : break;
398 : }
399 :
400 0 : if (QuotaManager::Get()) {
401 0 : OpenDirectory();
402 0 : return NS_OK;
403 : }
404 :
405 0 : mState = STATE_OPEN_DIRECTORY;
406 0 : QuotaManager::GetOrCreate(this);
407 0 : break;
408 : }
409 : // ----------------------------------
410 : case STATE_OPEN_DIRECTORY:
411 : {
412 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
413 :
414 0 : if (NS_WARN_IF(!QuotaManager::Get())) {
415 0 : resolver->Resolve(NS_ERROR_FAILURE);
416 0 : break;
417 : }
418 :
419 0 : OpenDirectory();
420 0 : break;
421 : }
422 : // ----------------------------------
423 : case STATE_ENSURE_ORIGIN_INITIALIZED:
424 : {
425 0 : AssertIsOnIOThread();
426 :
427 0 : if (mCanceled) {
428 0 : resolver->Resolve(NS_ERROR_ABORT);
429 0 : break;
430 : }
431 :
432 0 : QuotaManager* qm = QuotaManager::Get();
433 0 : MOZ_DIAGNOSTIC_ASSERT(qm);
434 0 : nsresult rv = qm->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
435 : mQuotaInfo.mSuffix,
436 : mQuotaInfo.mGroup,
437 : mQuotaInfo.mOrigin,
438 0 : getter_AddRefs(mQuotaInfo.mDir));
439 0 : if (NS_FAILED(rv)) {
440 0 : resolver->Resolve(rv);
441 0 : break;
442 : }
443 :
444 0 : mState = STATE_RUN_ON_TARGET;
445 :
446 0 : MOZ_ALWAYS_SUCCEEDS(
447 : mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
448 0 : break;
449 : }
450 : // -------------------
451 : case STATE_RUN_ON_TARGET:
452 : {
453 0 : MOZ_ASSERT(mTarget->IsOnCurrentThread());
454 :
455 0 : mState = STATE_RUNNING;
456 :
457 : // Execute the provided initialization Action. The Action must Resolve()
458 : // before returning.
459 0 : mInitAction->RunOnTarget(resolver, mQuotaInfo, mData);
460 0 : MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved());
461 :
462 0 : mData = nullptr;
463 :
464 : // If the database was opened, then we should always succeed when creating
465 : // the marker file. If it wasn't opened successfully, then no need to
466 : // create a marker file anyway.
467 0 : if (NS_SUCCEEDED(resolver->Result())) {
468 0 : MOZ_ALWAYS_SUCCEEDS(CreateMarkerFile(mQuotaInfo));
469 : }
470 :
471 0 : break;
472 : }
473 : // -------------------
474 : case STATE_COMPLETING:
475 : {
476 0 : NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
477 0 : mInitAction->CompleteOnInitiatingThread(mResult);
478 0 : mContext->OnQuotaInit(mResult, mQuotaInfo, mDirectoryLock.forget());
479 0 : mState = STATE_COMPLETE;
480 :
481 : // Explicitly cleanup here as the destructor could fire on any of
482 : // the threads we have bounced through.
483 0 : Clear();
484 0 : break;
485 : }
486 : // -----
487 : case STATE_WAIT_FOR_DIRECTORY_LOCK:
488 : default:
489 : {
490 0 : MOZ_CRASH("unexpected state in QuotaInitRunnable");
491 : }
492 : }
493 :
494 0 : if (resolver->Resolved()) {
495 0 : Complete(resolver->Result());
496 : }
497 :
498 0 : return NS_OK;
499 : }
500 :
501 : // Runnable wrapper around Action objects dispatched on the Context. This
502 : // runnable executes the Action on the appropriate threads while the Context
503 : // is initialized.
504 : class Context::ActionRunnable final : public nsIRunnable
505 : , public Action::Resolver
506 : , public Context::Activity
507 : {
508 : public:
509 0 : ActionRunnable(Context* aContext, Data* aData, nsISerialEventTarget* aTarget,
510 : Action* aAction, const QuotaInfo& aQuotaInfo)
511 0 : : mContext(aContext)
512 : , mData(aData)
513 : , mTarget(aTarget)
514 : , mAction(aAction)
515 : , mQuotaInfo(aQuotaInfo)
516 : , mInitiatingThread(GetCurrentThreadEventTarget())
517 : , mState(STATE_INIT)
518 : , mResult(NS_OK)
519 0 : , mExecutingRunOnTarget(false)
520 : {
521 0 : MOZ_DIAGNOSTIC_ASSERT(mContext);
522 : // mData may be nullptr
523 0 : MOZ_DIAGNOSTIC_ASSERT(mTarget);
524 0 : MOZ_DIAGNOSTIC_ASSERT(mAction);
525 : // mQuotaInfo.mDir may be nullptr if QuotaInitRunnable failed
526 0 : MOZ_DIAGNOSTIC_ASSERT(mInitiatingThread);
527 0 : }
528 :
529 0 : nsresult Dispatch()
530 : {
531 0 : NS_ASSERT_OWNINGTHREAD(ActionRunnable);
532 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_INIT);
533 :
534 0 : mState = STATE_RUN_ON_TARGET;
535 0 : nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
536 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
537 0 : mState = STATE_COMPLETE;
538 0 : Clear();
539 : }
540 0 : return rv;
541 : }
542 :
543 : virtual bool
544 0 : MatchesCacheId(CacheId aCacheId) const override
545 : {
546 0 : NS_ASSERT_OWNINGTHREAD(ActionRunnable);
547 0 : return mAction->MatchesCacheId(aCacheId);
548 : }
549 :
550 : virtual void
551 0 : Cancel() override
552 : {
553 0 : NS_ASSERT_OWNINGTHREAD(ActionRunnable);
554 0 : mAction->CancelOnInitiatingThread();
555 0 : }
556 :
557 0 : virtual void Resolve(nsresult aRv) override
558 : {
559 0 : MOZ_ASSERT(mTarget->IsOnCurrentThread());
560 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING);
561 :
562 0 : mResult = aRv;
563 :
564 : // We ultimately must complete on the initiating thread, but bounce through
565 : // the current thread again to ensure that we don't destroy objects and
566 : // state out from under the currently running action's stack.
567 0 : mState = STATE_RESOLVING;
568 :
569 : // If we were resolved synchronously within Action::RunOnTarget() then we
570 : // can avoid a thread bounce and just resolve once RunOnTarget() returns.
571 : // The Run() method will handle this by looking at mState after
572 : // RunOnTarget() returns.
573 0 : if (mExecutingRunOnTarget) {
574 0 : return;
575 : }
576 :
577 : // Otherwise we are in an asynchronous resolve. And must perform a thread
578 : // bounce to run on the target thread again.
579 0 : MOZ_ALWAYS_SUCCEEDS(
580 : mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
581 : }
582 :
583 : private:
584 0 : ~ActionRunnable()
585 0 : {
586 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE);
587 0 : MOZ_DIAGNOSTIC_ASSERT(!mContext);
588 0 : MOZ_DIAGNOSTIC_ASSERT(!mAction);
589 0 : }
590 :
591 0 : void Clear()
592 : {
593 0 : NS_ASSERT_OWNINGTHREAD(ActionRunnable);
594 0 : MOZ_DIAGNOSTIC_ASSERT(mContext);
595 0 : MOZ_DIAGNOSTIC_ASSERT(mAction);
596 0 : mContext->RemoveActivity(this);
597 0 : mContext = nullptr;
598 0 : mAction = nullptr;
599 0 : }
600 :
601 : enum State
602 : {
603 : STATE_INIT,
604 : STATE_RUN_ON_TARGET,
605 : STATE_RUNNING,
606 : STATE_RESOLVING,
607 : STATE_COMPLETING,
608 : STATE_COMPLETE
609 : };
610 :
611 : RefPtr<Context> mContext;
612 : RefPtr<Data> mData;
613 : nsCOMPtr<nsISerialEventTarget> mTarget;
614 : RefPtr<Action> mAction;
615 : const QuotaInfo mQuotaInfo;
616 : nsCOMPtr<nsIEventTarget> mInitiatingThread;
617 : State mState;
618 : nsresult mResult;
619 :
620 : // Only accessible on target thread;
621 : bool mExecutingRunOnTarget;
622 :
623 : public:
624 : NS_DECL_THREADSAFE_ISUPPORTS
625 : NS_DECL_NSIRUNNABLE
626 : };
627 :
628 0 : NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable, nsIRunnable);
629 :
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.
632 : //
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 : // +--------+
658 : //
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 0 : Context::ActionRunnable::Run()
665 : {
666 0 : switch(mState) {
667 : // ----------------------
668 : case STATE_RUN_ON_TARGET:
669 : {
670 0 : MOZ_ASSERT(mTarget->IsOnCurrentThread());
671 0 : MOZ_DIAGNOSTIC_ASSERT(!mExecutingRunOnTarget);
672 :
673 : // Note that we are calling RunOnTarget(). This lets us detect
674 : // if Resolve() is called synchronously.
675 0 : AutoRestore<bool> executingRunOnTarget(mExecutingRunOnTarget);
676 0 : mExecutingRunOnTarget = true;
677 :
678 0 : mState = STATE_RUNNING;
679 0 : mAction->RunOnTarget(this, mQuotaInfo, mData);
680 :
681 0 : mData = nullptr;
682 :
683 : // Resolve was called synchronously from RunOnTarget(). We can
684 : // immediately move to completing now since we are sure RunOnTarget()
685 : // completed.
686 0 : if (mState == STATE_RESOLVING) {
687 : // Use recursion instead of switch case fall-through... Seems slightly
688 : // easier to understand.
689 0 : Run();
690 : }
691 :
692 0 : break;
693 : }
694 : // -----------------
695 : case STATE_RESOLVING:
696 : {
697 0 : MOZ_ASSERT(mTarget->IsOnCurrentThread());
698 : // The call to Action::RunOnTarget() must have returned now if we
699 : // are running on the target thread again. We may now proceed
700 : // with completion.
701 0 : mState = STATE_COMPLETING;
702 : // Shutdown must be delayed until all Contexts are destroyed. Crash
703 : // for this invariant violation.
704 0 : MOZ_ALWAYS_SUCCEEDS(
705 : mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
706 0 : break;
707 : }
708 : // -------------------
709 : case STATE_COMPLETING:
710 : {
711 0 : NS_ASSERT_OWNINGTHREAD(ActionRunnable);
712 0 : mAction->CompleteOnInitiatingThread(mResult);
713 0 : mState = STATE_COMPLETE;
714 : // Explicitly cleanup here as the destructor could fire on any of
715 : // the threads we have bounced through.
716 0 : Clear();
717 0 : break;
718 : }
719 : // -----------------
720 : default:
721 : {
722 0 : MOZ_CRASH("unexpected state in ActionRunnable");
723 : break;
724 : }
725 : }
726 0 : return NS_OK;
727 : }
728 :
729 : void
730 0 : Context::ThreadsafeHandle::AllowToClose()
731 : {
732 0 : if (mOwningEventTarget->IsOnCurrentThread()) {
733 0 : AllowToCloseOnOwningThread();
734 0 : return;
735 : }
736 :
737 : // Dispatch is guaranteed to succeed here because we block shutdown until
738 : // all Contexts have been destroyed.
739 0 : nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
740 : "dom::cache::Context::ThreadsafeHandle::AllowToCloseOnOwningThread",
741 : this,
742 0 : &ThreadsafeHandle::AllowToCloseOnOwningThread);
743 0 : MOZ_ALWAYS_SUCCEEDS(
744 : mOwningEventTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL));
745 : }
746 :
747 : void
748 0 : Context::ThreadsafeHandle::InvalidateAndAllowToClose()
749 : {
750 0 : if (mOwningEventTarget->IsOnCurrentThread()) {
751 0 : InvalidateAndAllowToCloseOnOwningThread();
752 0 : return;
753 : }
754 :
755 : // Dispatch is guaranteed to succeed here because we block shutdown until
756 : // all Contexts have been destroyed.
757 0 : nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
758 : "dom::cache::Context::ThreadsafeHandle::"
759 : "InvalidateAndAllowToCloseOnOwningThread",
760 : this,
761 0 : &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
762 0 : MOZ_ALWAYS_SUCCEEDS(
763 : mOwningEventTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL));
764 : }
765 :
766 0 : Context::ThreadsafeHandle::ThreadsafeHandle(Context* aContext)
767 : : mStrongRef(aContext)
768 : , mWeakRef(aContext)
769 0 : , mOwningEventTarget(GetCurrentThreadSerialEventTarget())
770 : {
771 0 : }
772 :
773 0 : Context::ThreadsafeHandle::~ThreadsafeHandle()
774 : {
775 : // Normally we only touch mStrongRef on the owning thread. This is safe,
776 : // however, because when we do use mStrongRef on the owning thread we are
777 : // always holding a strong ref to the ThreadsafeHandle via the owning
778 : // runnable. So we cannot run the ThreadsafeHandle destructor simultaneously.
779 0 : if (!mStrongRef || mOwningEventTarget->IsOnCurrentThread()) {
780 0 : return;
781 : }
782 :
783 : // Dispatch is guaranteed to succeed here because we block shutdown until
784 : // all Contexts have been destroyed.
785 : NS_ProxyRelease(
786 0 : "Context::ThreadsafeHandle::mStrongRef", mOwningEventTarget, mStrongRef.forget());
787 0 : }
788 :
789 : void
790 0 : Context::ThreadsafeHandle::AllowToCloseOnOwningThread()
791 : {
792 0 : MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
793 :
794 : // A Context "closes" when its ref count drops to zero. Dropping this
795 : // strong ref is necessary, but not sufficient for the close to occur.
796 : // Any outstanding IO will continue and keep the Context alive. Once
797 : // the Context is idle, it will be destroyed.
798 :
799 : // First, tell the context to flush any target thread shared data. This
800 : // data must be released on the target thread prior to running the Context
801 : // destructor. This will schedule an Action which ensures that the
802 : // ~Context() is not immediately executed when we drop the strong ref.
803 0 : if (mStrongRef) {
804 0 : mStrongRef->DoomTargetData();
805 : }
806 :
807 : // Now drop our strong ref and let Context finish running any outstanding
808 : // Actions.
809 0 : mStrongRef = nullptr;
810 0 : }
811 :
812 : void
813 0 : Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread()
814 : {
815 0 : MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
816 : // Cancel the Context through the weak reference. This means we can
817 : // allow the Context to close by dropping the strong ref, but then
818 : // still cancel ongoing IO if necessary.
819 0 : if (mWeakRef) {
820 0 : mWeakRef->Invalidate();
821 : }
822 : // We should synchronously have AllowToCloseOnOwningThread called when
823 : // the Context is canceled.
824 0 : MOZ_DIAGNOSTIC_ASSERT(!mStrongRef);
825 0 : }
826 :
827 : void
828 0 : Context::ThreadsafeHandle::ContextDestroyed(Context* aContext)
829 : {
830 0 : MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
831 0 : MOZ_DIAGNOSTIC_ASSERT(!mStrongRef);
832 0 : MOZ_DIAGNOSTIC_ASSERT(mWeakRef);
833 0 : MOZ_DIAGNOSTIC_ASSERT(mWeakRef == aContext);
834 0 : mWeakRef = nullptr;
835 0 : }
836 :
837 : // static
838 : already_AddRefed<Context>
839 0 : Context::Create(Manager* aManager, nsISerialEventTarget* aTarget,
840 : Action* aInitAction, Context* aOldContext)
841 : {
842 0 : RefPtr<Context> context = new Context(aManager, aTarget, aInitAction);
843 0 : context->Init(aOldContext);
844 0 : return context.forget();
845 : }
846 :
847 0 : Context::Context(Manager* aManager, nsISerialEventTarget* aTarget, Action* aInitAction)
848 : : mManager(aManager)
849 : , mTarget(aTarget)
850 0 : , mData(new Data(aTarget))
851 : , mState(STATE_CONTEXT_PREINIT)
852 : , mOrphanedData(false)
853 0 : , mInitAction(aInitAction)
854 : {
855 0 : MOZ_DIAGNOSTIC_ASSERT(mManager);
856 0 : MOZ_DIAGNOSTIC_ASSERT(mTarget);
857 0 : }
858 :
859 : void
860 0 : Context::Dispatch(Action* aAction)
861 : {
862 0 : NS_ASSERT_OWNINGTHREAD(Context);
863 0 : MOZ_DIAGNOSTIC_ASSERT(aAction);
864 :
865 0 : MOZ_DIAGNOSTIC_ASSERT(mState != STATE_CONTEXT_CANCELED);
866 0 : if (mState == STATE_CONTEXT_CANCELED) {
867 0 : return;
868 0 : } else if (mState == STATE_CONTEXT_INIT ||
869 0 : mState == STATE_CONTEXT_PREINIT) {
870 0 : PendingAction* pending = mPendingActions.AppendElement();
871 0 : pending->mAction = aAction;
872 0 : return;
873 : }
874 :
875 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_READY);
876 0 : DispatchAction(aAction);
877 : }
878 :
879 : void
880 0 : Context::CancelAll()
881 : {
882 0 : NS_ASSERT_OWNINGTHREAD(Context);
883 :
884 : // In PREINIT state we have not dispatch the init action yet. Just
885 : // forget it.
886 0 : if (mState == STATE_CONTEXT_PREINIT) {
887 0 : MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
888 0 : mInitAction = nullptr;
889 :
890 : // In INIT state we have dispatched the runnable, but not received the
891 : // async completion yet. Cancel the runnable, but don't forget about it
892 : // until we get OnQuotaInit() callback.
893 0 : } else if (mState == STATE_CONTEXT_INIT) {
894 0 : mInitRunnable->Cancel();
895 : }
896 :
897 0 : mState = STATE_CONTEXT_CANCELED;
898 0 : mPendingActions.Clear();
899 : {
900 0 : ActivityList::ForwardIterator iter(mActivityList);
901 0 : while (iter.HasMore()) {
902 0 : iter.GetNext()->Cancel();
903 : }
904 : }
905 0 : AllowToClose();
906 0 : }
907 :
908 : bool
909 0 : Context::IsCanceled() const
910 : {
911 0 : NS_ASSERT_OWNINGTHREAD(Context);
912 0 : return mState == STATE_CONTEXT_CANCELED;
913 : }
914 :
915 : void
916 0 : Context::Invalidate()
917 : {
918 0 : NS_ASSERT_OWNINGTHREAD(Context);
919 0 : mManager->NoteClosing();
920 0 : CancelAll();
921 0 : }
922 :
923 : void
924 0 : Context::AllowToClose()
925 : {
926 0 : NS_ASSERT_OWNINGTHREAD(Context);
927 0 : if (mThreadsafeHandle) {
928 0 : mThreadsafeHandle->AllowToClose();
929 : }
930 0 : }
931 :
932 : void
933 0 : Context::CancelForCacheId(CacheId aCacheId)
934 : {
935 0 : NS_ASSERT_OWNINGTHREAD(Context);
936 :
937 : // Remove matching pending actions
938 0 : for (int32_t i = mPendingActions.Length() - 1; i >= 0; --i) {
939 0 : if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) {
940 0 : mPendingActions.RemoveElementAt(i);
941 : }
942 : }
943 :
944 : // Cancel activities and let them remove themselves
945 0 : ActivityList::ForwardIterator iter(mActivityList);
946 0 : while (iter.HasMore()) {
947 0 : Activity* activity = iter.GetNext();
948 0 : if (activity->MatchesCacheId(aCacheId)) {
949 0 : activity->Cancel();
950 : }
951 : }
952 0 : }
953 :
954 0 : Context::~Context()
955 : {
956 0 : NS_ASSERT_OWNINGTHREAD(Context);
957 0 : MOZ_DIAGNOSTIC_ASSERT(mManager);
958 0 : MOZ_DIAGNOSTIC_ASSERT(!mData);
959 :
960 0 : if (mThreadsafeHandle) {
961 0 : mThreadsafeHandle->ContextDestroyed(this);
962 : }
963 :
964 : // Note, this may set the mOrphanedData flag.
965 0 : mManager->RemoveContext(this);
966 :
967 0 : if (mQuotaInfo.mDir && !mOrphanedData) {
968 0 : MOZ_ALWAYS_SUCCEEDS(DeleteMarkerFile(mQuotaInfo));
969 : }
970 :
971 0 : if (mNextContext) {
972 0 : mNextContext->Start();
973 : }
974 0 : }
975 :
976 : void
977 0 : Context::Init(Context* aOldContext)
978 : {
979 0 : NS_ASSERT_OWNINGTHREAD(Context);
980 :
981 0 : if (aOldContext) {
982 0 : aOldContext->SetNextContext(this);
983 0 : return;
984 : }
985 :
986 0 : Start();
987 : }
988 :
989 : void
990 0 : Context::Start()
991 : {
992 0 : NS_ASSERT_OWNINGTHREAD(Context);
993 :
994 : // Previous context closing delayed our start, but then we were canceled.
995 : // In this case, just do nothing here.
996 0 : if (mState == STATE_CONTEXT_CANCELED) {
997 0 : MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
998 0 : MOZ_DIAGNOSTIC_ASSERT(!mInitAction);
999 : // If we can't initialize the quota subsystem we will never be able to
1000 : // clear our shared data object via the target IO thread. Instead just
1001 : // clear it here to maintain the invariant that the shared data is
1002 : // cleared before Context destruction.
1003 0 : mData = nullptr;
1004 0 : return;
1005 : }
1006 :
1007 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_PREINIT);
1008 0 : MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
1009 :
1010 : mInitRunnable = new QuotaInitRunnable(this, mManager, mData, mTarget,
1011 0 : mInitAction);
1012 0 : mInitAction = nullptr;
1013 :
1014 0 : mState = STATE_CONTEXT_INIT;
1015 :
1016 0 : nsresult rv = mInitRunnable->Dispatch();
1017 0 : if (NS_FAILED(rv)) {
1018 : // Shutdown must be delayed until all Contexts are destroyed. Shutdown
1019 : // must also prevent any new Contexts from being constructed. Crash
1020 : // for this invariant violation.
1021 0 : MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
1022 : }
1023 : }
1024 :
1025 : void
1026 0 : Context::DispatchAction(Action* aAction, bool aDoomData)
1027 : {
1028 0 : NS_ASSERT_OWNINGTHREAD(Context);
1029 :
1030 : RefPtr<ActionRunnable> runnable =
1031 0 : new ActionRunnable(this, mData, mTarget, aAction, mQuotaInfo);
1032 :
1033 0 : if (aDoomData) {
1034 0 : mData = nullptr;
1035 : }
1036 :
1037 0 : nsresult rv = runnable->Dispatch();
1038 0 : if (NS_FAILED(rv)) {
1039 : // Shutdown must be delayed until all Contexts are destroyed. Crash
1040 : // for this invariant violation.
1041 0 : MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
1042 : }
1043 0 : AddActivity(runnable);
1044 0 : }
1045 :
1046 : void
1047 0 : Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
1048 : already_AddRefed<DirectoryLock> aDirectoryLock)
1049 : {
1050 0 : NS_ASSERT_OWNINGTHREAD(Context);
1051 :
1052 0 : MOZ_DIAGNOSTIC_ASSERT(mInitRunnable);
1053 0 : mInitRunnable = nullptr;
1054 :
1055 0 : mQuotaInfo = aQuotaInfo;
1056 :
1057 : // Always save the directory lock to ensure QuotaManager does not shutdown
1058 : // before the Context has gone away.
1059 0 : MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
1060 0 : mDirectoryLock = aDirectoryLock;
1061 :
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 0 : if (NS_FAILED(aRv)) {
1067 0 : mState = STATE_CONTEXT_CANCELED;
1068 : }
1069 :
1070 0 : if (mState == STATE_CONTEXT_CANCELED) {
1071 0 : for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
1072 0 : mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
1073 : }
1074 0 : mPendingActions.Clear();
1075 0 : mThreadsafeHandle->AllowToClose();
1076 : // Context will destruct after return here and last ref is released.
1077 0 : return;
1078 : }
1079 :
1080 0 : MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_INIT);
1081 0 : mState = STATE_CONTEXT_READY;
1082 :
1083 0 : for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
1084 0 : DispatchAction(mPendingActions[i].mAction);
1085 : }
1086 0 : mPendingActions.Clear();
1087 : }
1088 :
1089 : void
1090 0 : Context::AddActivity(Activity* aActivity)
1091 : {
1092 0 : NS_ASSERT_OWNINGTHREAD(Context);
1093 0 : MOZ_DIAGNOSTIC_ASSERT(aActivity);
1094 0 : MOZ_ASSERT(!mActivityList.Contains(aActivity));
1095 0 : mActivityList.AppendElement(aActivity);
1096 0 : }
1097 :
1098 : void
1099 0 : Context::RemoveActivity(Activity* aActivity)
1100 : {
1101 0 : NS_ASSERT_OWNINGTHREAD(Context);
1102 0 : MOZ_DIAGNOSTIC_ASSERT(aActivity);
1103 0 : MOZ_ALWAYS_TRUE(mActivityList.RemoveElement(aActivity));
1104 0 : MOZ_ASSERT(!mActivityList.Contains(aActivity));
1105 0 : }
1106 :
1107 : void
1108 0 : Context::NoteOrphanedData()
1109 : {
1110 0 : NS_ASSERT_OWNINGTHREAD(Context);
1111 : // This may be called more than once
1112 0 : mOrphanedData = true;
1113 0 : }
1114 :
1115 : already_AddRefed<Context::ThreadsafeHandle>
1116 0 : Context::CreateThreadsafeHandle()
1117 : {
1118 0 : NS_ASSERT_OWNINGTHREAD(Context);
1119 0 : if (!mThreadsafeHandle) {
1120 0 : mThreadsafeHandle = new ThreadsafeHandle(this);
1121 : }
1122 0 : RefPtr<ThreadsafeHandle> ref = mThreadsafeHandle;
1123 0 : return ref.forget();
1124 : }
1125 :
1126 : void
1127 0 : Context::SetNextContext(Context* aNextContext)
1128 : {
1129 0 : NS_ASSERT_OWNINGTHREAD(Context);
1130 0 : MOZ_DIAGNOSTIC_ASSERT(aNextContext);
1131 0 : MOZ_DIAGNOSTIC_ASSERT(!mNextContext);
1132 0 : mNextContext = aNextContext;
1133 0 : }
1134 :
1135 : void
1136 0 : Context::DoomTargetData()
1137 : {
1138 0 : NS_ASSERT_OWNINGTHREAD(Context);
1139 0 : MOZ_DIAGNOSTIC_ASSERT(mData);
1140 :
1141 : // We are about to drop our reference to the Data. We need to ensure that
1142 : // the ~Context() destructor does not run until contents of Data have been
1143 : // released on the Target thread.
1144 :
1145 : // Dispatch a no-op Action. This will hold the Context alive through a
1146 : // roundtrip to the target thread and back to the owning thread. The
1147 : // ref to the Data object is cleared on the owning thread after creating
1148 : // the ActionRunnable, but before dispatching it.
1149 0 : RefPtr<Action> action = new NullAction();
1150 0 : DispatchAction(action, true /* doomed data */);
1151 :
1152 0 : MOZ_DIAGNOSTIC_ASSERT(!mData);
1153 0 : }
1154 :
1155 : } // namespace cache
1156 : } // namespace dom
1157 : } // namespace mozilla
|