/* vim:set ts=4 sw=2 sts=2 et cin: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // HttpLog.h should generally be included first #include "HttpLog.h" // Log on level :5, instead of default :4. #undef LOG #define LOG(args) LOG5(args) #undef LOG_ENABLED #define LOG_ENABLED() LOG5_ENABLED() #include "PendingTransactionQueue.h" #include "nsHttpHandler.h" #include "mozilla/ChaosMode.h" namespace mozilla { namespace net { static uint64_t TabIdForQueuing(nsAHttpTransaction* transaction) { return gHttpHandler->ActiveTabPriority() ? transaction->TopLevelOuterContentWindowId() : 0; } // This function decides the transaction's order in the pending queue. // Given two transactions t1 and t2, returning true means that t2 is // more important than t1 and thus should be dispatched first. static bool TransactionComparator(nsHttpTransaction* t1, nsHttpTransaction* t2) { bool t1Blocking = t1->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED); bool t2Blocking = t2->Caps() & (NS_HTTP_LOAD_AS_BLOCKING | NS_HTTP_LOAD_UNBLOCKED); if (t1Blocking > t2Blocking) { return false; } if (t2Blocking > t1Blocking) { return true; } return t1->Priority() >= t2->Priority(); } void PendingTransactionQueue::InsertTransactionNormal( PendingTransactionInfo* info, bool aInsertAsFirstForTheSamePriority /*= false*/) { LOG( ("PendingTransactionQueue::InsertTransactionNormal" " trans=%p, windowId=%" PRIu64 "\n", info->Transaction(), info->Transaction()->TopLevelOuterContentWindowId())); uint64_t windowId = TabIdForQueuing(info->Transaction()); nsTArray>* infoArray; if (!mPendingTransactionTable.Get(windowId, &infoArray)) { infoArray = new nsTArray>(); mPendingTransactionTable.Put(windowId, infoArray); } InsertTransactionSorted(*infoArray, info, aInsertAsFirstForTheSamePriority); } void PendingTransactionQueue::InsertTransactionSorted( nsTArray>& pendingQ, PendingTransactionInfo* pendingTransInfo, bool aInsertAsFirstForTheSamePriority /*= false*/) { // insert the transaction into the front of the queue based on following // rules: // 1. The transaction has NS_HTTP_LOAD_AS_BLOCKING or NS_HTTP_LOAD_UNBLOCKED. // 2. The transaction's priority is higher. // // search in reverse order under the assumption that many of the // existing transactions will have the same priority (usually 0). nsHttpTransaction* trans = pendingTransInfo->Transaction(); for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) { nsHttpTransaction* t = pendingQ[i]->Transaction(); if (TransactionComparator(trans, t)) { if (ChaosMode::isActive(ChaosFeature::NetworkScheduling) || aInsertAsFirstForTheSamePriority) { int32_t samePriorityCount; for (samePriorityCount = 0; i - samePriorityCount >= 0; ++samePriorityCount) { if (pendingQ[i - samePriorityCount]->Transaction()->Priority() != trans->Priority()) { break; } } if (aInsertAsFirstForTheSamePriority) { i -= samePriorityCount; } else { // skip over 0...all of the elements with the same priority. i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1); } } pendingQ.InsertElementAt(i + 1, pendingTransInfo); return; } } pendingQ.InsertElementAt(0, pendingTransInfo); } void PendingTransactionQueue::InsertTransaction( PendingTransactionInfo* pendingTransInfo, bool aInsertAsFirstForTheSamePriority /* = false */) { if (pendingTransInfo->Transaction()->Caps() & NS_HTTP_URGENT_START) { LOG( (" adding transaction to pending queue " "[trans=%p urgent-start-count=%zu]\n", pendingTransInfo->Transaction(), mUrgentStartQ.Length() + 1)); // put this transaction on the urgent-start queue... InsertTransactionSorted(mUrgentStartQ, pendingTransInfo); } else { LOG( (" adding transaction to pending queue " "[trans=%p pending-count=%zu]\n", pendingTransInfo->Transaction(), PendingQueueLength() + 1)); // put this transaction on the pending queue... InsertTransactionNormal(pendingTransInfo); } } nsTArray>* PendingTransactionQueue::GetTransactionPendingQHelper( nsAHttpTransaction* trans) { nsTArray>* pendingQ = nullptr; int32_t caps = trans->Caps(); if (caps & NS_HTTP_URGENT_START) { pendingQ = &(mUrgentStartQ); } else { pendingQ = mPendingTransactionTable.Get(TabIdForQueuing(trans)); } return pendingQ; } void PendingTransactionQueue::AppendPendingUrgentStartQ( nsTArray>& result) { result.InsertElementsAt(0, mUrgentStartQ.Elements(), mUrgentStartQ.Length()); mUrgentStartQ.Clear(); } void PendingTransactionQueue::AppendPendingQForFocusedWindow( uint64_t windowId, nsTArray>& result, uint32_t maxCount) { nsTArray>* infoArray = nullptr; if (!mPendingTransactionTable.Get(windowId, &infoArray)) { result.Clear(); return; } uint32_t countToAppend = maxCount; countToAppend = countToAppend > infoArray->Length() || countToAppend == 0 ? infoArray->Length() : countToAppend; result.InsertElementsAt(result.Length(), infoArray->Elements(), countToAppend); infoArray->RemoveElementsAt(0, countToAppend); LOG( ("PendingTransactionQueue::AppendPendingQForFocusedWindow, " "pendingQ count=%zu window.count=%zu for focused window (id=%" PRIu64 ")\n", result.Length(), infoArray->Length(), windowId)); } void PendingTransactionQueue::AppendPendingQForNonFocusedWindows( uint64_t windowId, nsTArray>& result, uint32_t maxCount) { // XXX Adjust the order of transactions in a smarter manner. uint32_t totalCount = 0; for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) { if (windowId && it.Key() == windowId) { continue; } uint32_t count = 0; for (; count < it.UserData()->Length(); ++count) { if (maxCount && totalCount == maxCount) { break; } // Because elements in |result| could come from multiple penndingQ, // call |InsertTransactionSorted| to make sure the order is correct. InsertTransactionSorted(result, it.UserData()->ElementAt(count)); ++totalCount; } it.UserData()->RemoveElementsAt(0, count); if (maxCount && totalCount == maxCount) { if (it.UserData()->Length()) { // There are still some pending transactions for background // tabs but we limit their dispatch. This is considered as // an active tab optimization. nsHttp::NotifyActiveTabLoadOptimization(); } break; } } } void PendingTransactionQueue::ReschedTransaction(nsHttpTransaction* aTrans) { nsTArray>* pendingQ = GetTransactionPendingQHelper(aTrans); int32_t index = pendingQ ? pendingQ->IndexOf(aTrans, 0, PendingComparator()) : -1; if (index >= 0) { RefPtr pendingTransInfo = (*pendingQ)[index]; pendingQ->RemoveElementAt(index); InsertTransactionSorted(*pendingQ, pendingTransInfo); } } void PendingTransactionQueue::RemoveEmptyPendingQ() { for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) { if (it.UserData()->IsEmpty()) { it.Remove(); } } } size_t PendingTransactionQueue::PendingQueueLength() const { size_t length = 0; for (auto it = mPendingTransactionTable.ConstIter(); !it.Done(); it.Next()) { length += it.UserData()->Length(); } return length; } size_t PendingTransactionQueue::PendingQueueLengthForWindow( uint64_t windowId) const { auto* pendingQ = mPendingTransactionTable.Get(windowId); return (pendingQ) ? pendingQ->Length() : 0; } size_t PendingTransactionQueue::UrgentStartQueueLength() { return mUrgentStartQ.Length(); } void PendingTransactionQueue::PrintPendingQ() { LOG(("urgent queue [")); for (const auto& info : mUrgentStartQ) { LOG((" %p", info->Transaction())); } for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) { LOG(("] window id = %" PRIx64 " queue [", it.Key())); for (const auto& info : *it.UserData()) { LOG((" %p", info->Transaction())); } } LOG(("]")); } void PendingTransactionQueue::Compact() { mUrgentStartQ.Compact(); for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) { it.UserData()->Compact(); } } void PendingTransactionQueue::CancelAllTransactions(nsresult reason) { for (const auto& pendingTransInfo : mUrgentStartQ) { LOG(("PendingTransactionQueue::CancelAllTransactions %p\n", pendingTransInfo->Transaction())); pendingTransInfo->Transaction()->Close(reason); } mUrgentStartQ.Clear(); for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) { for (const auto& pendingTransInfo : *it.UserData()) { LOG(("PendingTransactionQueue::CancelAllTransactions %p\n", pendingTransInfo->Transaction())); pendingTransInfo->Transaction()->Close(reason); } it.UserData()->Clear(); } mPendingTransactionTable.Clear(); } } // namespace net } // namespace mozilla