Bumping manifests a=b2g-bump
[gecko.git] / dom / indexedDB / TransactionThreadPool.cpp
blob9d746ede8a10480f0e1bfe35fdcf370614269aab
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
23 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
36 public:
37 NS_DECL_THREADSAFE_ISUPPORTS
38 NS_DECL_NSITHREADPOOLLISTENER
40 private:
41 virtual ~TransactionThreadPoolListener()
42 { }
45 #endif // MOZ_ENABLE_PROFILER_SPS
47 } // anonymous namespace
49 BEGIN_INDEXEDDB_NAMESPACE
51 class FinishTransactionRunnable MOZ_FINAL : public nsIRunnable
53 public:
54 NS_DECL_THREADSAFE_ISUPPORTS
55 NS_DECL_NSIRUNNABLE
57 inline FinishTransactionRunnable(IDBTransaction* aTransaction,
58 nsCOMPtr<nsIRunnable>& aFinishRunnable);
60 private:
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;
82 // static
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();
95 return gThreadPool;
98 // static
99 TransactionThreadPool*
100 TransactionThreadPool::Get()
102 return gThreadPool;
105 // static
106 void
107 TransactionThreadPool::Shutdown()
109 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
111 gShutdown = true;
113 if (gThreadPool) {
114 if (NS_FAILED(gThreadPool->Cleanup())) {
115 NS_WARNING("Failed to shutdown thread pool!");
117 delete gThreadPool;
118 gThreadPool = nullptr;
122 nsresult
123 TransactionThreadPool::Init()
125 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
127 nsresult rv;
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);
149 #endif
151 return NS_OK;
154 nsresult
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);
182 return NS_OK;
185 // static
186 PLDHashOperator
187 TransactionThreadPool::MaybeUnblockTransaction(nsPtrHashKey<TransactionInfo>* aKey,
188 void* aUserArg)
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),
196 "Huh?");
197 maybeUnblockedInfo->blockedOn.RemoveEntry(finishedInfo);
198 if (!maybeUnblockedInfo->blockedOn.Count()) {
199 // Let this transaction run.
200 maybeUnblockedInfo->queue->Unblock();
203 return PL_DHASH_NEXT;
206 void
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?!");
223 return;
226 DatabaseTransactionInfo::TransactionHashtable& transactionsInProgress =
227 dbTransactionInfo->transactions;
229 uint32_t transactionCount = transactionsInProgress.Count();
231 #ifdef DEBUG
232 if (aTransaction->mMode == IDBTransaction::VERSION_CHANGE) {
233 NS_ASSERTION(transactionCount == 1,
234 "More transactions running than should be!");
236 #endif
238 if (transactionCount == 1) {
239 #ifdef DEBUG
241 const TransactionInfo* info = transactionsInProgress.Get(aTransaction);
242 NS_ASSERTION(info->transaction == aTransaction, "Transaction mismatch!");
244 #endif
245 mTransactionsInProgress.Remove(databaseId);
247 // See if we need to fire any complete callbacks.
248 uint32_t index = 0;
249 while (index < mCompleteCallbacks.Length()) {
250 if (MaybeFireCallback(mCompleteCallbacks[index])) {
251 mCompleteCallbacks.RemoveElementAt(index);
253 else {
254 index++;
258 return;
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;
265 index++) {
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);
308 if (info) {
309 // We recognize this one.
310 return *info->queue;
313 TransactionInfo* transactionInfo = new TransactionInfo(aTransaction);
315 dbTransactionInfo->transactions.Put(aTransaction, transactionInfo);;
317 for (uint32_t index = 0, count = objectStoreNames.Length(); index < count;
318 index++) {
319 TransactionInfoPair* blockInfo =
320 dbTransactionInfo->blockingTransactions.Get(objectStoreNames[index]);
321 if (!blockInfo) {
322 blockInfo = new TransactionInfoPair();
323 blockInfo->lastBlockingReads = nullptr;
324 dbTransactionInfo->blockingTransactions.Put(objectStoreNames[index],
325 blockInfo);
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;
339 index++) {
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();
350 else {
351 blockInfo->lastBlockingWrites.AppendElement(transactionInfo);
355 if (!transactionInfo->blockedOn.Count()) {
356 transactionInfo->queue->Unblock();
359 return *transactionInfo->queue;
362 nsresult
363 TransactionThreadPool::Dispatch(IDBTransaction* aTransaction,
364 nsIRunnable* aRunnable,
365 bool aFinish,
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);
379 if (aFinish) {
380 queue.Finish(aFinishRunnable);
382 return NS_OK;
385 void
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);
404 // static
405 PLDHashOperator
406 TransactionThreadPool::CollectTransactions(IDBTransaction* aKey,
407 TransactionInfo* aValue,
408 void* aUserArg)
410 nsAutoTArray<nsRefPtr<IDBTransaction>, 50>* transactionArray =
411 static_cast<nsAutoTArray<nsRefPtr<IDBTransaction>, 50>*>(aUserArg);
412 transactionArray->AppendElement(aKey);
414 return PL_DHASH_NEXT;
417 void
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.
430 return;
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) {
446 continue;
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
451 // errors.
452 ErrorResult rv;
453 transactions[index]->Abort(rv);
457 struct MOZ_STACK_CLASS TransactionSearchInfo
459 TransactionSearchInfo(nsIOfflineStorage* aDatabase)
460 : db(aDatabase), found(false)
464 nsIOfflineStorage* db;
465 bool found;
468 // static
469 PLDHashOperator
470 TransactionThreadPool::FindTransaction(IDBTransaction* aKey,
471 TransactionInfo* aValue,
472 void* aUserArg)
474 TransactionSearchInfo* info = static_cast<TransactionSearchInfo*>(aUserArg);
476 if (aKey->Database() == info->db) {
477 info->found = true;
478 return PL_DHASH_STOP;
481 return PL_DHASH_NEXT;
483 bool
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) {
492 return false;
495 TransactionSearchInfo info(aDatabase);
496 dbTransactionInfo->transactions.EnumerateRead(FindTransaction, &info);
498 return info.found;
501 bool
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];
511 if (!database) {
512 MOZ_CRASH();
515 if (mTransactionsInProgress.Get(database->Id(), nullptr)) {
516 return false;
520 aCallback.mCallback->Run();
521 return true;
524 TransactionThreadPool::
525 TransactionQueue::TransactionQueue(IDBTransaction* aTransaction)
526 : mMonitor("TransactionQueue::mMonitor"),
527 mTransaction(aTransaction),
528 mShouldFinish(false)
530 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
531 NS_ASSERTION(aTransaction, "Null pointer!");
534 void
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);
545 void
546 TransactionThreadPool::TransactionQueue::Dispatch(nsIRunnable* aRunnable)
548 MonitorAutoLock lock(mMonitor);
550 NS_ASSERTION(!mShouldFinish, "Dispatch called after Finish!");
552 mQueue.AppendElement(aRunnable);
554 mMonitor.Notify();
557 void
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;
567 mMonitor.Notify();
570 NS_IMPL_ISUPPORTS(TransactionThreadPool::TransactionQueue, nsIRunnable)
572 NS_IMETHODIMP
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;
589 do {
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);
601 if (mShouldFinish) {
602 mFinishRunnable.swap(finishRunnable);
603 shouldFinish = true;
607 uint32_t count = queue.Length();
608 for (uint32_t index = 0; index < count; index++) {
609 nsCOMPtr<nsIRunnable>& runnable = queue[index];
610 runnable->Run();
611 runnable = nullptr;
614 if (count) {
615 queue.Clear();
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!");
629 return NS_OK;
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)
644 NS_IMETHODIMP
645 FinishTransactionRunnable::Run()
647 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
649 PROFILER_MAIN_THREAD_LABEL("FinishTransactionRunnable", "Run",
650 js::ProfileEntry::Category::STORAGE);
652 if (!gThreadPool) {
653 NS_ERROR("Running after shutdown!");
654 return NS_ERROR_FAILURE;
657 gThreadPool->FinishTransaction(mTransaction);
659 if (mFinishRunnable) {
660 mFinishRunnable->Run();
661 mFinishRunnable = nullptr;
664 return NS_OK;
667 #ifdef MOZ_ENABLE_PROFILER_SPS
669 NS_IMPL_ISUPPORTS(TransactionThreadPoolListener, nsIThreadPoolListener)
671 NS_IMETHODIMP
672 TransactionThreadPoolListener::OnThreadCreated()
674 MOZ_ASSERT(!NS_IsMainThread());
675 char aLocal;
676 profiler_register_thread("IndexedDB Transaction", &aLocal);
677 return NS_OK;
680 NS_IMETHODIMP
681 TransactionThreadPoolListener::OnThreadShuttingDown()
683 MOZ_ASSERT(!NS_IsMainThread());
684 profiler_unregister_thread();
685 return NS_OK;
688 #endif // MOZ_ENABLE_PROFILER_SPS