1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=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 "TransactionThreadPool.h"
9 #include "nsIObserverService.h"
10 #include "nsIThreadPool.h"
12 #include "nsComponentManagerUtils.h"
13 #include "nsThreadUtils.h"
14 #include "nsServiceManagerUtils.h"
15 #include "nsXPCOMCIDInternal.h"
17 #include "ProfilerHelpers.h"
19 using mozilla::MonitorAutoLock
;
21 USING_INDEXEDDB_NAMESPACE
25 const uint32_t kThreadLimit
= 20;
26 const uint32_t kIdleThreadLimit
= 5;
27 const uint32_t kIdleThreadTimeoutMs
= 30000;
29 TransactionThreadPool
* gThreadPool
= nullptr;
30 bool gShutdown
= false;
32 #ifdef MOZ_ENABLE_PROFILER_SPS
34 class TransactionThreadPoolListener
: public nsIThreadPoolListener
37 NS_DECL_THREADSAFE_ISUPPORTS
38 NS_DECL_NSITHREADPOOLLISTENER
41 virtual ~TransactionThreadPoolListener()
45 #endif // MOZ_ENABLE_PROFILER_SPS
47 } // anonymous namespace
49 BEGIN_INDEXEDDB_NAMESPACE
51 class FinishTransactionRunnable MOZ_FINAL
: public nsIRunnable
54 NS_DECL_THREADSAFE_ISUPPORTS
57 inline FinishTransactionRunnable(IDBTransaction
* aTransaction
,
58 nsCOMPtr
<nsIRunnable
>& aFinishRunnable
);
61 ~FinishTransactionRunnable() {}
63 IDBTransaction
* mTransaction
;
64 nsCOMPtr
<nsIRunnable
> mFinishRunnable
;
67 END_INDEXEDDB_NAMESPACE
69 TransactionThreadPool::TransactionThreadPool()
71 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
72 NS_ASSERTION(!gThreadPool
, "More than one instance!");
75 TransactionThreadPool::~TransactionThreadPool()
77 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
78 NS_ASSERTION(gThreadPool
== this, "Different instances!");
79 gThreadPool
= nullptr;
83 TransactionThreadPool
*
84 TransactionThreadPool::GetOrCreate()
86 if (!gThreadPool
&& !gShutdown
) {
87 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
88 nsAutoPtr
<TransactionThreadPool
> pool(new TransactionThreadPool());
90 nsresult rv
= pool
->Init();
91 NS_ENSURE_SUCCESS(rv
, nullptr);
93 gThreadPool
= pool
.forget();
99 TransactionThreadPool
*
100 TransactionThreadPool::Get()
107 TransactionThreadPool::Shutdown()
109 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
114 if (NS_FAILED(gThreadPool
->Cleanup())) {
115 NS_WARNING("Failed to shutdown thread pool!");
118 gThreadPool
= nullptr;
123 TransactionThreadPool::Init()
125 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
128 mThreadPool
= do_CreateInstance(NS_THREADPOOL_CONTRACTID
, &rv
);
129 NS_ENSURE_SUCCESS(rv
, rv
);
131 rv
= mThreadPool
->SetName(NS_LITERAL_CSTRING("IndexedDB Trans"));
132 NS_ENSURE_SUCCESS(rv
, rv
);
134 rv
= mThreadPool
->SetThreadLimit(kThreadLimit
);
135 NS_ENSURE_SUCCESS(rv
, rv
);
137 rv
= mThreadPool
->SetIdleThreadLimit(kIdleThreadLimit
);
138 NS_ENSURE_SUCCESS(rv
, rv
);
140 rv
= mThreadPool
->SetIdleThreadTimeout(kIdleThreadTimeoutMs
);
141 NS_ENSURE_SUCCESS(rv
, rv
);
143 #ifdef MOZ_ENABLE_PROFILER_SPS
144 nsCOMPtr
<nsIThreadPoolListener
> listener
=
145 new TransactionThreadPoolListener();
147 rv
= mThreadPool
->SetListener(listener
);
148 NS_ENSURE_SUCCESS(rv
, rv
);
155 TransactionThreadPool::Cleanup()
157 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
159 PROFILER_MAIN_THREAD_LABEL("TransactionThreadPool", "Cleanup",
160 js::ProfileEntry::Category::STORAGE
);
162 nsresult rv
= mThreadPool
->Shutdown();
163 NS_ENSURE_SUCCESS(rv
, rv
);
165 // Make sure the pool is still accessible while any callbacks generated from
166 // the other threads are processed.
167 rv
= NS_ProcessPendingEvents(nullptr);
168 NS_ENSURE_SUCCESS(rv
, rv
);
170 if (!mCompleteCallbacks
.IsEmpty()) {
171 // Run all callbacks manually now.
172 for (uint32_t index
= 0; index
< mCompleteCallbacks
.Length(); index
++) {
173 mCompleteCallbacks
[index
].mCallback
->Run();
175 mCompleteCallbacks
.Clear();
177 // And make sure they get processed.
178 rv
= NS_ProcessPendingEvents(nullptr);
179 NS_ENSURE_SUCCESS(rv
, rv
);
187 TransactionThreadPool::MaybeUnblockTransaction(nsPtrHashKey
<TransactionInfo
>* aKey
,
190 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
192 TransactionInfo
* maybeUnblockedInfo
= aKey
->GetKey();
193 TransactionInfo
* finishedInfo
= static_cast<TransactionInfo
*>(aUserArg
);
195 NS_ASSERTION(maybeUnblockedInfo
->blockedOn
.Contains(finishedInfo
),
197 maybeUnblockedInfo
->blockedOn
.RemoveEntry(finishedInfo
);
198 if (!maybeUnblockedInfo
->blockedOn
.Count()) {
199 // Let this transaction run.
200 maybeUnblockedInfo
->queue
->Unblock();
203 return PL_DHASH_NEXT
;
207 TransactionThreadPool::FinishTransaction(IDBTransaction
* aTransaction
)
209 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
210 NS_ASSERTION(aTransaction
, "Null pointer!");
212 PROFILER_MAIN_THREAD_LABEL("TransactionThreadPool", "FinishTransaction",
213 js::ProfileEntry::Category::STORAGE
);
215 // AddRef here because removing from the hash will call Release.
216 nsRefPtr
<IDBTransaction
> transaction(aTransaction
);
218 const nsACString
& databaseId
= aTransaction
->mDatabase
->Id();
220 DatabaseTransactionInfo
* dbTransactionInfo
;
221 if (!mTransactionsInProgress
.Get(databaseId
, &dbTransactionInfo
)) {
222 NS_ERROR("We don't know anyting about this database?!");
226 DatabaseTransactionInfo::TransactionHashtable
& transactionsInProgress
=
227 dbTransactionInfo
->transactions
;
229 uint32_t transactionCount
= transactionsInProgress
.Count();
232 if (aTransaction
->mMode
== IDBTransaction::VERSION_CHANGE
) {
233 NS_ASSERTION(transactionCount
== 1,
234 "More transactions running than should be!");
238 if (transactionCount
== 1) {
241 const TransactionInfo
* info
= transactionsInProgress
.Get(aTransaction
);
242 NS_ASSERTION(info
->transaction
== aTransaction
, "Transaction mismatch!");
245 mTransactionsInProgress
.Remove(databaseId
);
247 // See if we need to fire any complete callbacks.
249 while (index
< mCompleteCallbacks
.Length()) {
250 if (MaybeFireCallback(mCompleteCallbacks
[index
])) {
251 mCompleteCallbacks
.RemoveElementAt(index
);
260 TransactionInfo
* info
= transactionsInProgress
.Get(aTransaction
);
261 NS_ASSERTION(info
, "We've never heard of this transaction?!?");
263 const nsTArray
<nsString
>& objectStoreNames
= aTransaction
->mObjectStoreNames
;
264 for (size_t index
= 0, count
= objectStoreNames
.Length(); index
< count
;
266 TransactionInfoPair
* blockInfo
=
267 dbTransactionInfo
->blockingTransactions
.Get(objectStoreNames
[index
]);
268 NS_ASSERTION(blockInfo
, "Huh?");
270 if (aTransaction
->mMode
== IDBTransaction::READ_WRITE
&&
271 blockInfo
->lastBlockingReads
== info
) {
272 blockInfo
->lastBlockingReads
= nullptr;
275 size_t i
= blockInfo
->lastBlockingWrites
.IndexOf(info
);
276 if (i
!= blockInfo
->lastBlockingWrites
.NoIndex
) {
277 blockInfo
->lastBlockingWrites
.RemoveElementAt(i
);
281 info
->blocking
.EnumerateEntries(MaybeUnblockTransaction
, info
);
283 transactionsInProgress
.Remove(aTransaction
);
286 TransactionThreadPool::TransactionQueue
&
287 TransactionThreadPool::GetQueueForTransaction(IDBTransaction
* aTransaction
)
289 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
290 NS_ASSERTION(aTransaction
, "Null pointer!");
292 const nsACString
& databaseId
= aTransaction
->mDatabase
->Id();
294 const nsTArray
<nsString
>& objectStoreNames
= aTransaction
->mObjectStoreNames
;
295 const uint16_t mode
= aTransaction
->mMode
;
297 // See if we can run this transaction now.
298 DatabaseTransactionInfo
* dbTransactionInfo
;
299 if (!mTransactionsInProgress
.Get(databaseId
, &dbTransactionInfo
)) {
300 // First transaction for this database.
301 dbTransactionInfo
= new DatabaseTransactionInfo();
302 mTransactionsInProgress
.Put(databaseId
, dbTransactionInfo
);
305 DatabaseTransactionInfo::TransactionHashtable
& transactionsInProgress
=
306 dbTransactionInfo
->transactions
;
307 TransactionInfo
* info
= transactionsInProgress
.Get(aTransaction
);
309 // We recognize this one.
313 TransactionInfo
* transactionInfo
= new TransactionInfo(aTransaction
);
315 dbTransactionInfo
->transactions
.Put(aTransaction
, transactionInfo
);;
317 for (uint32_t index
= 0, count
= objectStoreNames
.Length(); index
< count
;
319 TransactionInfoPair
* blockInfo
=
320 dbTransactionInfo
->blockingTransactions
.Get(objectStoreNames
[index
]);
322 blockInfo
= new TransactionInfoPair();
323 blockInfo
->lastBlockingReads
= nullptr;
324 dbTransactionInfo
->blockingTransactions
.Put(objectStoreNames
[index
],
328 // Mark what we are blocking on.
329 if (blockInfo
->lastBlockingReads
) {
330 TransactionInfo
* blockingInfo
= blockInfo
->lastBlockingReads
;
331 transactionInfo
->blockedOn
.PutEntry(blockingInfo
);
332 blockingInfo
->blocking
.PutEntry(transactionInfo
);
335 if (mode
== IDBTransaction::READ_WRITE
&&
336 blockInfo
->lastBlockingWrites
.Length()) {
337 for (uint32_t index
= 0,
338 count
= blockInfo
->lastBlockingWrites
.Length(); index
< count
;
340 TransactionInfo
* blockingInfo
= blockInfo
->lastBlockingWrites
[index
];
341 transactionInfo
->blockedOn
.PutEntry(blockingInfo
);
342 blockingInfo
->blocking
.PutEntry(transactionInfo
);
346 if (mode
== IDBTransaction::READ_WRITE
) {
347 blockInfo
->lastBlockingReads
= transactionInfo
;
348 blockInfo
->lastBlockingWrites
.Clear();
351 blockInfo
->lastBlockingWrites
.AppendElement(transactionInfo
);
355 if (!transactionInfo
->blockedOn
.Count()) {
356 transactionInfo
->queue
->Unblock();
359 return *transactionInfo
->queue
;
363 TransactionThreadPool::Dispatch(IDBTransaction
* aTransaction
,
364 nsIRunnable
* aRunnable
,
366 nsIRunnable
* aFinishRunnable
)
368 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
369 NS_ASSERTION(aTransaction
, "Null pointer!");
370 NS_ASSERTION(aRunnable
, "Null pointer!");
372 if (aTransaction
->mDatabase
->IsInvalidated() && !aFinish
) {
373 return NS_ERROR_NOT_AVAILABLE
;
376 TransactionQueue
& queue
= GetQueueForTransaction(aTransaction
);
378 queue
.Dispatch(aRunnable
);
380 queue
.Finish(aFinishRunnable
);
386 TransactionThreadPool::WaitForDatabasesToComplete(
387 nsTArray
<nsRefPtr
<IDBDatabase
> >& aDatabases
,
388 nsIRunnable
* aCallback
)
390 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
391 NS_ASSERTION(!aDatabases
.IsEmpty(), "No databases to wait on!");
392 NS_ASSERTION(aCallback
, "Null pointer!");
394 DatabasesCompleteCallback
* callback
= mCompleteCallbacks
.AppendElement();
396 callback
->mCallback
= aCallback
;
397 callback
->mDatabases
.SwapElements(aDatabases
);
399 if (MaybeFireCallback(*callback
)) {
400 mCompleteCallbacks
.RemoveElementAt(mCompleteCallbacks
.Length() - 1);
406 TransactionThreadPool::CollectTransactions(IDBTransaction
* aKey
,
407 TransactionInfo
* aValue
,
410 nsAutoTArray
<nsRefPtr
<IDBTransaction
>, 50>* transactionArray
=
411 static_cast<nsAutoTArray
<nsRefPtr
<IDBTransaction
>, 50>*>(aUserArg
);
412 transactionArray
->AppendElement(aKey
);
414 return PL_DHASH_NEXT
;
418 TransactionThreadPool::AbortTransactionsForDatabase(IDBDatabase
* aDatabase
)
420 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
421 NS_ASSERTION(aDatabase
, "Null pointer!");
423 PROFILER_MAIN_THREAD_LABEL("TransactionThreadPool", "AbortTransactionsForDatabase",
424 js::ProfileEntry::Category::STORAGE
);
426 // Get list of transactions for this database id
427 DatabaseTransactionInfo
* dbTransactionInfo
;
428 if (!mTransactionsInProgress
.Get(aDatabase
->Id(), &dbTransactionInfo
)) {
429 // If there are no transactions, we're done.
433 // Collect any running transactions
434 DatabaseTransactionInfo::TransactionHashtable
& transactionsInProgress
=
435 dbTransactionInfo
->transactions
;
437 NS_ASSERTION(transactionsInProgress
.Count(), "Should never be 0!");
439 nsAutoTArray
<nsRefPtr
<IDBTransaction
>, 50> transactions
;
440 transactionsInProgress
.EnumerateRead(CollectTransactions
, &transactions
);
442 // Abort transactions. Do this after collecting the transactions in case
443 // calling Abort() modifies the data structures we're iterating above.
444 for (uint32_t index
= 0; index
< transactions
.Length(); index
++) {
445 if (transactions
[index
]->Database() != aDatabase
) {
449 // This can fail, for example if the transaction is in the process of
450 // being comitted. That is expected and fine, so we ignore any returned
453 transactions
[index
]->Abort(rv
);
457 struct MOZ_STACK_CLASS TransactionSearchInfo
459 TransactionSearchInfo(nsIOfflineStorage
* aDatabase
)
460 : db(aDatabase
), found(false)
464 nsIOfflineStorage
* db
;
470 TransactionThreadPool::FindTransaction(IDBTransaction
* aKey
,
471 TransactionInfo
* aValue
,
474 TransactionSearchInfo
* info
= static_cast<TransactionSearchInfo
*>(aUserArg
);
476 if (aKey
->Database() == info
->db
) {
478 return PL_DHASH_STOP
;
481 return PL_DHASH_NEXT
;
484 TransactionThreadPool::HasTransactionsForDatabase(IDBDatabase
* aDatabase
)
486 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
487 NS_ASSERTION(aDatabase
, "Null pointer!");
489 DatabaseTransactionInfo
* dbTransactionInfo
= nullptr;
490 dbTransactionInfo
= mTransactionsInProgress
.Get(aDatabase
->Id());
491 if (!dbTransactionInfo
) {
495 TransactionSearchInfo
info(aDatabase
);
496 dbTransactionInfo
->transactions
.EnumerateRead(FindTransaction
, &info
);
502 TransactionThreadPool::MaybeFireCallback(DatabasesCompleteCallback aCallback
)
504 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
506 PROFILER_MAIN_THREAD_LABEL("TransactionThreadPool", "MaybeFireCallback",
507 js::ProfileEntry::Category::STORAGE
);
509 for (uint32_t index
= 0; index
< aCallback
.mDatabases
.Length(); index
++) {
510 IDBDatabase
* database
= aCallback
.mDatabases
[index
];
515 if (mTransactionsInProgress
.Get(database
->Id(), nullptr)) {
520 aCallback
.mCallback
->Run();
524 TransactionThreadPool::
525 TransactionQueue::TransactionQueue(IDBTransaction
* aTransaction
)
526 : mMonitor("TransactionQueue::mMonitor"),
527 mTransaction(aTransaction
),
530 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
531 NS_ASSERTION(aTransaction
, "Null pointer!");
535 TransactionThreadPool::TransactionQueue::Unblock()
537 MonitorAutoLock
lock(mMonitor
);
539 // NB: Finish may be called before Unblock.
541 TransactionThreadPool::Get()->mThreadPool
->
542 Dispatch(this, NS_DISPATCH_NORMAL
);
546 TransactionThreadPool::TransactionQueue::Dispatch(nsIRunnable
* aRunnable
)
548 MonitorAutoLock
lock(mMonitor
);
550 NS_ASSERTION(!mShouldFinish
, "Dispatch called after Finish!");
552 mQueue
.AppendElement(aRunnable
);
558 TransactionThreadPool::TransactionQueue::Finish(nsIRunnable
* aFinishRunnable
)
560 MonitorAutoLock
lock(mMonitor
);
562 NS_ASSERTION(!mShouldFinish
, "Finish called more than once!");
564 mShouldFinish
= true;
565 mFinishRunnable
= aFinishRunnable
;
570 NS_IMPL_ISUPPORTS(TransactionThreadPool::TransactionQueue
, nsIRunnable
)
573 TransactionThreadPool::TransactionQueue::Run()
575 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
576 NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
578 PROFILER_LABEL("TransactionQueue", "Run",
579 js::ProfileEntry::Category::STORAGE
);
581 IDB_PROFILER_MARK("IndexedDB Transaction %llu: Beginning database work",
582 "IDBTransaction[%llu] DT Start",
583 mTransaction
->GetSerialNumber());
585 nsAutoTArray
<nsCOMPtr
<nsIRunnable
>, 10> queue
;
586 nsCOMPtr
<nsIRunnable
> finishRunnable
;
587 bool shouldFinish
= false;
590 NS_ASSERTION(queue
.IsEmpty(), "Should have cleared this!");
593 MonitorAutoLock
lock(mMonitor
);
594 while (!mShouldFinish
&& mQueue
.IsEmpty()) {
595 if (NS_FAILED(mMonitor
.Wait())) {
596 NS_ERROR("Failed to wait!");
600 mQueue
.SwapElements(queue
);
602 mFinishRunnable
.swap(finishRunnable
);
607 uint32_t count
= queue
.Length();
608 for (uint32_t index
= 0; index
< count
; index
++) {
609 nsCOMPtr
<nsIRunnable
>& runnable
= queue
[index
];
617 } while (!shouldFinish
);
619 IDB_PROFILER_MARK("IndexedDB Transaction %llu: Finished database work",
620 "IDBTransaction[%llu] DT Done",
621 mTransaction
->GetSerialNumber());
623 nsCOMPtr
<nsIRunnable
> finishTransactionRunnable
=
624 new FinishTransactionRunnable(mTransaction
, finishRunnable
);
625 if (NS_FAILED(NS_DispatchToMainThread(finishTransactionRunnable
))) {
626 NS_WARNING("Failed to dispatch finishTransactionRunnable!");
632 FinishTransactionRunnable::FinishTransactionRunnable(
633 IDBTransaction
* aTransaction
,
634 nsCOMPtr
<nsIRunnable
>& aFinishRunnable
)
635 : mTransaction(aTransaction
)
637 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
638 NS_ASSERTION(aTransaction
, "Null pointer!");
639 mFinishRunnable
.swap(aFinishRunnable
);
642 NS_IMPL_ISUPPORTS(FinishTransactionRunnable
, nsIRunnable
)
645 FinishTransactionRunnable::Run()
647 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
649 PROFILER_MAIN_THREAD_LABEL("FinishTransactionRunnable", "Run",
650 js::ProfileEntry::Category::STORAGE
);
653 NS_ERROR("Running after shutdown!");
654 return NS_ERROR_FAILURE
;
657 gThreadPool
->FinishTransaction(mTransaction
);
659 if (mFinishRunnable
) {
660 mFinishRunnable
->Run();
661 mFinishRunnable
= nullptr;
667 #ifdef MOZ_ENABLE_PROFILER_SPS
669 NS_IMPL_ISUPPORTS(TransactionThreadPoolListener
, nsIThreadPoolListener
)
672 TransactionThreadPoolListener::OnThreadCreated()
674 MOZ_ASSERT(!NS_IsMainThread());
676 profiler_register_thread("IndexedDB Transaction", &aLocal
);
681 TransactionThreadPoolListener::OnThreadShuttingDown()
683 MOZ_ASSERT(!NS_IsMainThread());
684 profiler_unregister_thread();
688 #endif // MOZ_ENABLE_PROFILER_SPS