Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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
35 {
36 public:
37 NS_DECL_THREADSAFE_ISUPPORTS
38 NS_DECL_NSITHREADPOOLLISTENER
40 private:
41 virtual ~TransactionThreadPoolListener()
42 { }
43 };
45 #endif // MOZ_ENABLE_PROFILER_SPS
47 } // anonymous namespace
49 BEGIN_INDEXEDDB_NAMESPACE
51 class FinishTransactionRunnable MOZ_FINAL : public nsIRunnable
52 {
53 public:
54 NS_DECL_THREADSAFE_ISUPPORTS
55 NS_DECL_NSIRUNNABLE
57 inline FinishTransactionRunnable(IDBTransaction* aTransaction,
58 nsCOMPtr<nsIRunnable>& aFinishRunnable);
60 private:
61 IDBTransaction* mTransaction;
62 nsCOMPtr<nsIRunnable> mFinishRunnable;
63 };
65 END_INDEXEDDB_NAMESPACE
67 TransactionThreadPool::TransactionThreadPool()
68 {
69 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
70 NS_ASSERTION(!gThreadPool, "More than one instance!");
71 }
73 TransactionThreadPool::~TransactionThreadPool()
74 {
75 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
76 NS_ASSERTION(gThreadPool == this, "Different instances!");
77 gThreadPool = nullptr;
78 }
80 // static
81 TransactionThreadPool*
82 TransactionThreadPool::GetOrCreate()
83 {
84 if (!gThreadPool && !gShutdown) {
85 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
86 nsAutoPtr<TransactionThreadPool> pool(new TransactionThreadPool());
88 nsresult rv = pool->Init();
89 NS_ENSURE_SUCCESS(rv, nullptr);
91 gThreadPool = pool.forget();
92 }
93 return gThreadPool;
94 }
96 // static
97 TransactionThreadPool*
98 TransactionThreadPool::Get()
99 {
100 return gThreadPool;
101 }
103 // static
104 void
105 TransactionThreadPool::Shutdown()
106 {
107 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
109 gShutdown = true;
111 if (gThreadPool) {
112 if (NS_FAILED(gThreadPool->Cleanup())) {
113 NS_WARNING("Failed to shutdown thread pool!");
114 }
115 delete gThreadPool;
116 gThreadPool = nullptr;
117 }
118 }
120 nsresult
121 TransactionThreadPool::Init()
122 {
123 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
125 nsresult rv;
126 mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
127 NS_ENSURE_SUCCESS(rv, rv);
129 rv = mThreadPool->SetName(NS_LITERAL_CSTRING("IndexedDB Trans"));
130 NS_ENSURE_SUCCESS(rv, rv);
132 rv = mThreadPool->SetThreadLimit(kThreadLimit);
133 NS_ENSURE_SUCCESS(rv, rv);
135 rv = mThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
136 NS_ENSURE_SUCCESS(rv, rv);
138 rv = mThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
139 NS_ENSURE_SUCCESS(rv, rv);
141 #ifdef MOZ_ENABLE_PROFILER_SPS
142 nsCOMPtr<nsIThreadPoolListener> listener =
143 new TransactionThreadPoolListener();
145 rv = mThreadPool->SetListener(listener);
146 NS_ENSURE_SUCCESS(rv, rv);
147 #endif
149 return NS_OK;
150 }
152 nsresult
153 TransactionThreadPool::Cleanup()
154 {
155 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
157 PROFILER_MAIN_THREAD_LABEL("IndexedDB", "TransactionThreadPool::Cleanup");
159 nsresult rv = mThreadPool->Shutdown();
160 NS_ENSURE_SUCCESS(rv, rv);
162 // Make sure the pool is still accessible while any callbacks generated from
163 // the other threads are processed.
164 rv = NS_ProcessPendingEvents(nullptr);
165 NS_ENSURE_SUCCESS(rv, rv);
167 if (!mCompleteCallbacks.IsEmpty()) {
168 // Run all callbacks manually now.
169 for (uint32_t index = 0; index < mCompleteCallbacks.Length(); index++) {
170 mCompleteCallbacks[index].mCallback->Run();
171 }
172 mCompleteCallbacks.Clear();
174 // And make sure they get processed.
175 rv = NS_ProcessPendingEvents(nullptr);
176 NS_ENSURE_SUCCESS(rv, rv);
177 }
179 return NS_OK;
180 }
182 // static
183 PLDHashOperator
184 TransactionThreadPool::MaybeUnblockTransaction(nsPtrHashKey<TransactionInfo>* aKey,
185 void* aUserArg)
186 {
187 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
189 TransactionInfo* maybeUnblockedInfo = aKey->GetKey();
190 TransactionInfo* finishedInfo = static_cast<TransactionInfo*>(aUserArg);
192 NS_ASSERTION(maybeUnblockedInfo->blockedOn.Contains(finishedInfo),
193 "Huh?");
194 maybeUnblockedInfo->blockedOn.RemoveEntry(finishedInfo);
195 if (!maybeUnblockedInfo->blockedOn.Count()) {
196 // Let this transaction run.
197 maybeUnblockedInfo->queue->Unblock();
198 }
200 return PL_DHASH_NEXT;
201 }
203 void
204 TransactionThreadPool::FinishTransaction(IDBTransaction* aTransaction)
205 {
206 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
207 NS_ASSERTION(aTransaction, "Null pointer!");
209 PROFILER_MAIN_THREAD_LABEL("IndexedDB",
210 "TransactionThreadPool::FinishTransaction");
212 // AddRef here because removing from the hash will call Release.
213 nsRefPtr<IDBTransaction> transaction(aTransaction);
215 const nsACString& databaseId = aTransaction->mDatabase->Id();
217 DatabaseTransactionInfo* dbTransactionInfo;
218 if (!mTransactionsInProgress.Get(databaseId, &dbTransactionInfo)) {
219 NS_ERROR("We don't know anyting about this database?!");
220 return;
221 }
223 DatabaseTransactionInfo::TransactionHashtable& transactionsInProgress =
224 dbTransactionInfo->transactions;
226 uint32_t transactionCount = transactionsInProgress.Count();
228 #ifdef DEBUG
229 if (aTransaction->mMode == IDBTransaction::VERSION_CHANGE) {
230 NS_ASSERTION(transactionCount == 1,
231 "More transactions running than should be!");
232 }
233 #endif
235 if (transactionCount == 1) {
236 #ifdef DEBUG
237 {
238 const TransactionInfo* info = transactionsInProgress.Get(aTransaction);
239 NS_ASSERTION(info->transaction == aTransaction, "Transaction mismatch!");
240 }
241 #endif
242 mTransactionsInProgress.Remove(databaseId);
244 // See if we need to fire any complete callbacks.
245 uint32_t index = 0;
246 while (index < mCompleteCallbacks.Length()) {
247 if (MaybeFireCallback(mCompleteCallbacks[index])) {
248 mCompleteCallbacks.RemoveElementAt(index);
249 }
250 else {
251 index++;
252 }
253 }
255 return;
256 }
257 TransactionInfo* info = transactionsInProgress.Get(aTransaction);
258 NS_ASSERTION(info, "We've never heard of this transaction?!?");
260 const nsTArray<nsString>& objectStoreNames = aTransaction->mObjectStoreNames;
261 for (uint32_t index = 0, count = objectStoreNames.Length(); index < count;
262 index++) {
263 TransactionInfoPair* blockInfo =
264 dbTransactionInfo->blockingTransactions.Get(objectStoreNames[index]);
265 NS_ASSERTION(blockInfo, "Huh?");
267 if (aTransaction->mMode == IDBTransaction::READ_WRITE &&
268 blockInfo->lastBlockingReads == info) {
269 blockInfo->lastBlockingReads = nullptr;
270 }
272 uint32_t i = blockInfo->lastBlockingWrites.IndexOf(info);
273 if (i != blockInfo->lastBlockingWrites.NoIndex) {
274 blockInfo->lastBlockingWrites.RemoveElementAt(i);
275 }
276 }
278 info->blocking.EnumerateEntries(MaybeUnblockTransaction, info);
280 transactionsInProgress.Remove(aTransaction);
281 }
283 TransactionThreadPool::TransactionQueue&
284 TransactionThreadPool::GetQueueForTransaction(IDBTransaction* aTransaction)
285 {
286 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
287 NS_ASSERTION(aTransaction, "Null pointer!");
289 const nsACString& databaseId = aTransaction->mDatabase->Id();
291 const nsTArray<nsString>& objectStoreNames = aTransaction->mObjectStoreNames;
292 const uint16_t mode = aTransaction->mMode;
294 // See if we can run this transaction now.
295 DatabaseTransactionInfo* dbTransactionInfo;
296 if (!mTransactionsInProgress.Get(databaseId, &dbTransactionInfo)) {
297 // First transaction for this database.
298 dbTransactionInfo = new DatabaseTransactionInfo();
299 mTransactionsInProgress.Put(databaseId, dbTransactionInfo);
300 }
302 DatabaseTransactionInfo::TransactionHashtable& transactionsInProgress =
303 dbTransactionInfo->transactions;
304 TransactionInfo* info = transactionsInProgress.Get(aTransaction);
305 if (info) {
306 // We recognize this one.
307 return *info->queue;
308 }
310 TransactionInfo* transactionInfo = new TransactionInfo(aTransaction);
312 dbTransactionInfo->transactions.Put(aTransaction, transactionInfo);;
314 for (uint32_t index = 0, count = objectStoreNames.Length(); index < count;
315 index++) {
316 TransactionInfoPair* blockInfo =
317 dbTransactionInfo->blockingTransactions.Get(objectStoreNames[index]);
318 if (!blockInfo) {
319 blockInfo = new TransactionInfoPair();
320 blockInfo->lastBlockingReads = nullptr;
321 dbTransactionInfo->blockingTransactions.Put(objectStoreNames[index],
322 blockInfo);
323 }
325 // Mark what we are blocking on.
326 if (blockInfo->lastBlockingReads) {
327 TransactionInfo* blockingInfo = blockInfo->lastBlockingReads;
328 transactionInfo->blockedOn.PutEntry(blockingInfo);
329 blockingInfo->blocking.PutEntry(transactionInfo);
330 }
332 if (mode == IDBTransaction::READ_WRITE &&
333 blockInfo->lastBlockingWrites.Length()) {
334 for (uint32_t index = 0,
335 count = blockInfo->lastBlockingWrites.Length(); index < count;
336 index++) {
337 TransactionInfo* blockingInfo = blockInfo->lastBlockingWrites[index];
338 transactionInfo->blockedOn.PutEntry(blockingInfo);
339 blockingInfo->blocking.PutEntry(transactionInfo);
340 }
341 }
343 if (mode == IDBTransaction::READ_WRITE) {
344 blockInfo->lastBlockingReads = transactionInfo;
345 blockInfo->lastBlockingWrites.Clear();
346 }
347 else {
348 blockInfo->lastBlockingWrites.AppendElement(transactionInfo);
349 }
350 }
352 if (!transactionInfo->blockedOn.Count()) {
353 transactionInfo->queue->Unblock();
354 }
356 return *transactionInfo->queue;
357 }
359 nsresult
360 TransactionThreadPool::Dispatch(IDBTransaction* aTransaction,
361 nsIRunnable* aRunnable,
362 bool aFinish,
363 nsIRunnable* aFinishRunnable)
364 {
365 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
366 NS_ASSERTION(aTransaction, "Null pointer!");
367 NS_ASSERTION(aRunnable, "Null pointer!");
369 if (aTransaction->mDatabase->IsInvalidated() && !aFinish) {
370 return NS_ERROR_NOT_AVAILABLE;
371 }
373 TransactionQueue& queue = GetQueueForTransaction(aTransaction);
375 queue.Dispatch(aRunnable);
376 if (aFinish) {
377 queue.Finish(aFinishRunnable);
378 }
379 return NS_OK;
380 }
382 void
383 TransactionThreadPool::WaitForDatabasesToComplete(
384 nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
385 nsIRunnable* aCallback)
386 {
387 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
388 NS_ASSERTION(!aDatabases.IsEmpty(), "No databases to wait on!");
389 NS_ASSERTION(aCallback, "Null pointer!");
391 DatabasesCompleteCallback* callback = mCompleteCallbacks.AppendElement();
393 callback->mCallback = aCallback;
394 callback->mDatabases.SwapElements(aDatabases);
396 if (MaybeFireCallback(*callback)) {
397 mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1);
398 }
399 }
401 // static
402 PLDHashOperator
403 TransactionThreadPool::CollectTransactions(IDBTransaction* aKey,
404 TransactionInfo* aValue,
405 void* aUserArg)
406 {
407 nsAutoTArray<nsRefPtr<IDBTransaction>, 50>* transactionArray =
408 static_cast<nsAutoTArray<nsRefPtr<IDBTransaction>, 50>*>(aUserArg);
409 transactionArray->AppendElement(aKey);
411 return PL_DHASH_NEXT;
412 }
414 void
415 TransactionThreadPool::AbortTransactionsForDatabase(IDBDatabase* aDatabase)
416 {
417 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
418 NS_ASSERTION(aDatabase, "Null pointer!");
420 PROFILER_MAIN_THREAD_LABEL("IndexedDB",
421 "TransactionThreadPool::"
422 "AbortTransactionsForDatabase");
424 // Get list of transactions for this database id
425 DatabaseTransactionInfo* dbTransactionInfo;
426 if (!mTransactionsInProgress.Get(aDatabase->Id(), &dbTransactionInfo)) {
427 // If there are no transactions, we're done.
428 return;
429 }
431 // Collect any running transactions
432 DatabaseTransactionInfo::TransactionHashtable& transactionsInProgress =
433 dbTransactionInfo->transactions;
435 NS_ASSERTION(transactionsInProgress.Count(), "Should never be 0!");
437 nsAutoTArray<nsRefPtr<IDBTransaction>, 50> transactions;
438 transactionsInProgress.EnumerateRead(CollectTransactions, &transactions);
440 // Abort transactions. Do this after collecting the transactions in case
441 // calling Abort() modifies the data structures we're iterating above.
442 for (uint32_t index = 0; index < transactions.Length(); index++) {
443 if (transactions[index]->Database() != aDatabase) {
444 continue;
445 }
447 // This can fail, for example if the transaction is in the process of
448 // being comitted. That is expected and fine, so we ignore any returned
449 // errors.
450 ErrorResult rv;
451 transactions[index]->Abort(rv);
452 }
453 }
455 struct MOZ_STACK_CLASS TransactionSearchInfo
456 {
457 TransactionSearchInfo(nsIOfflineStorage* aDatabase)
458 : db(aDatabase), found(false)
459 {
460 }
462 nsIOfflineStorage* db;
463 bool found;
464 };
466 // static
467 PLDHashOperator
468 TransactionThreadPool::FindTransaction(IDBTransaction* aKey,
469 TransactionInfo* aValue,
470 void* aUserArg)
471 {
472 TransactionSearchInfo* info = static_cast<TransactionSearchInfo*>(aUserArg);
474 if (aKey->Database() == info->db) {
475 info->found = true;
476 return PL_DHASH_STOP;
477 }
479 return PL_DHASH_NEXT;
480 }
481 bool
482 TransactionThreadPool::HasTransactionsForDatabase(IDBDatabase* aDatabase)
483 {
484 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
485 NS_ASSERTION(aDatabase, "Null pointer!");
487 DatabaseTransactionInfo* dbTransactionInfo = nullptr;
488 dbTransactionInfo = mTransactionsInProgress.Get(aDatabase->Id());
489 if (!dbTransactionInfo) {
490 return false;
491 }
493 TransactionSearchInfo info(aDatabase);
494 dbTransactionInfo->transactions.EnumerateRead(FindTransaction, &info);
496 return info.found;
497 }
499 bool
500 TransactionThreadPool::MaybeFireCallback(DatabasesCompleteCallback aCallback)
501 {
502 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
504 PROFILER_MAIN_THREAD_LABEL("IndexedDB",
505 "TransactionThreadPool::MaybeFireCallback");
507 for (uint32_t index = 0; index < aCallback.mDatabases.Length(); index++) {
508 IDBDatabase* database = aCallback.mDatabases[index];
509 if (!database) {
510 MOZ_CRASH();
511 }
513 if (mTransactionsInProgress.Get(database->Id(), nullptr)) {
514 return false;
515 }
516 }
518 aCallback.mCallback->Run();
519 return true;
520 }
522 TransactionThreadPool::
523 TransactionQueue::TransactionQueue(IDBTransaction* aTransaction)
524 : mMonitor("TransactionQueue::mMonitor"),
525 mTransaction(aTransaction),
526 mShouldFinish(false)
527 {
528 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
529 NS_ASSERTION(aTransaction, "Null pointer!");
530 }
532 void
533 TransactionThreadPool::TransactionQueue::Unblock()
534 {
535 MonitorAutoLock lock(mMonitor);
537 // NB: Finish may be called before Unblock.
539 TransactionThreadPool::Get()->mThreadPool->
540 Dispatch(this, NS_DISPATCH_NORMAL);
541 }
543 void
544 TransactionThreadPool::TransactionQueue::Dispatch(nsIRunnable* aRunnable)
545 {
546 MonitorAutoLock lock(mMonitor);
548 NS_ASSERTION(!mShouldFinish, "Dispatch called after Finish!");
550 mQueue.AppendElement(aRunnable);
552 mMonitor.Notify();
553 }
555 void
556 TransactionThreadPool::TransactionQueue::Finish(nsIRunnable* aFinishRunnable)
557 {
558 MonitorAutoLock lock(mMonitor);
560 NS_ASSERTION(!mShouldFinish, "Finish called more than once!");
562 mShouldFinish = true;
563 mFinishRunnable = aFinishRunnable;
565 mMonitor.Notify();
566 }
568 NS_IMPL_ISUPPORTS(TransactionThreadPool::TransactionQueue, nsIRunnable)
570 NS_IMETHODIMP
571 TransactionThreadPool::TransactionQueue::Run()
572 {
573 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
574 NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
576 PROFILER_LABEL("IndexedDB", "TransactionQueue::Run");
578 IDB_PROFILER_MARK("IndexedDB Transaction %llu: Beginning database work",
579 "IDBTransaction[%llu] DT Start",
580 mTransaction->GetSerialNumber());
582 nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> queue;
583 nsCOMPtr<nsIRunnable> finishRunnable;
584 bool shouldFinish = false;
586 do {
587 NS_ASSERTION(queue.IsEmpty(), "Should have cleared this!");
589 {
590 MonitorAutoLock lock(mMonitor);
591 while (!mShouldFinish && mQueue.IsEmpty()) {
592 if (NS_FAILED(mMonitor.Wait())) {
593 NS_ERROR("Failed to wait!");
594 }
595 }
597 mQueue.SwapElements(queue);
598 if (mShouldFinish) {
599 mFinishRunnable.swap(finishRunnable);
600 shouldFinish = true;
601 }
602 }
604 uint32_t count = queue.Length();
605 for (uint32_t index = 0; index < count; index++) {
606 nsCOMPtr<nsIRunnable>& runnable = queue[index];
607 runnable->Run();
608 runnable = nullptr;
609 }
611 if (count) {
612 queue.Clear();
613 }
614 } while (!shouldFinish);
616 IDB_PROFILER_MARK("IndexedDB Transaction %llu: Finished database work",
617 "IDBTransaction[%llu] DT Done",
618 mTransaction->GetSerialNumber());
620 nsCOMPtr<nsIRunnable> finishTransactionRunnable =
621 new FinishTransactionRunnable(mTransaction, finishRunnable);
622 if (NS_FAILED(NS_DispatchToMainThread(finishTransactionRunnable,
623 NS_DISPATCH_NORMAL))) {
624 NS_WARNING("Failed to dispatch finishTransactionRunnable!");
625 }
627 return NS_OK;
628 }
630 FinishTransactionRunnable::FinishTransactionRunnable(
631 IDBTransaction* aTransaction,
632 nsCOMPtr<nsIRunnable>& aFinishRunnable)
633 : mTransaction(aTransaction)
634 {
635 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
636 NS_ASSERTION(aTransaction, "Null pointer!");
637 mFinishRunnable.swap(aFinishRunnable);
638 }
640 NS_IMPL_ISUPPORTS(FinishTransactionRunnable, nsIRunnable)
642 NS_IMETHODIMP
643 FinishTransactionRunnable::Run()
644 {
645 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
647 PROFILER_MAIN_THREAD_LABEL("IndexedDB", "FinishTransactionRunnable::Run");
649 if (!gThreadPool) {
650 NS_ERROR("Running after shutdown!");
651 return NS_ERROR_FAILURE;
652 }
654 gThreadPool->FinishTransaction(mTransaction);
656 if (mFinishRunnable) {
657 mFinishRunnable->Run();
658 mFinishRunnable = nullptr;
659 }
661 return NS_OK;
662 }
664 #ifdef MOZ_ENABLE_PROFILER_SPS
666 NS_IMPL_ISUPPORTS(TransactionThreadPoolListener, nsIThreadPoolListener)
668 NS_IMETHODIMP
669 TransactionThreadPoolListener::OnThreadCreated()
670 {
671 MOZ_ASSERT(!NS_IsMainThread());
672 char aLocal;
673 profiler_register_thread("IndexedDB Transaction", &aLocal);
674 return NS_OK;
675 }
677 NS_IMETHODIMP
678 TransactionThreadPoolListener::OnThreadShuttingDown()
679 {
680 MOZ_ASSERT(!NS_IsMainThread());
681 profiler_unregister_thread();
682 return NS_OK;
683 }
685 #endif // MOZ_ENABLE_PROFILER_SPS