| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "net/base/prioritized_dispatcher.h" | 
 |  | 
 | #include "base/logging.h" | 
 |  | 
 | namespace net { | 
 |  | 
 | PrioritizedDispatcher::Limits::Limits(Priority num_priorities, | 
 |                                       size_t total_jobs) | 
 |     : total_jobs(total_jobs), reserved_slots(num_priorities) {} | 
 |  | 
 | PrioritizedDispatcher::Limits::Limits(const Limits& other) = default; | 
 |  | 
 | PrioritizedDispatcher::Limits::~Limits() = default; | 
 |  | 
 | PrioritizedDispatcher::PrioritizedDispatcher(const Limits& limits) | 
 |     : queue_(limits.reserved_slots.size()), | 
 |       max_running_jobs_(limits.reserved_slots.size()), | 
 |       num_running_jobs_(0) { | 
 |   SetLimits(limits); | 
 | } | 
 |  | 
 | PrioritizedDispatcher::~PrioritizedDispatcher() = default; | 
 |  | 
 | PrioritizedDispatcher::Handle PrioritizedDispatcher::Add( | 
 |     Job* job, Priority priority) { | 
 |   DCHECK(job); | 
 |   DCHECK_LT(priority, num_priorities()); | 
 |   if (num_running_jobs_ < max_running_jobs_[priority]) { | 
 |     ++num_running_jobs_; | 
 |     job->Start(); | 
 |     return Handle(); | 
 |   } | 
 |   return queue_.Insert(job, priority); | 
 | } | 
 |  | 
 | PrioritizedDispatcher::Handle PrioritizedDispatcher::AddAtHead( | 
 |     Job* job, Priority priority) { | 
 |   DCHECK(job); | 
 |   DCHECK_LT(priority, num_priorities()); | 
 |   if (num_running_jobs_ < max_running_jobs_[priority]) { | 
 |     ++num_running_jobs_; | 
 |     job->Start(); | 
 |     return Handle(); | 
 |   } | 
 |   return queue_.InsertAtFront(job, priority); | 
 | } | 
 |  | 
 | void PrioritizedDispatcher::Cancel(const Handle& handle) { | 
 |   queue_.Erase(handle); | 
 | } | 
 |  | 
 | PrioritizedDispatcher::Job* PrioritizedDispatcher::EvictOldestLowest() { | 
 |   Handle handle = queue_.FirstMin(); | 
 |   if (handle.is_null()) | 
 |     return NULL; | 
 |   Job* job = handle.value(); | 
 |   Cancel(handle); | 
 |   return job; | 
 | } | 
 |  | 
 | PrioritizedDispatcher::Handle PrioritizedDispatcher::ChangePriority( | 
 |     const Handle& handle, Priority priority) { | 
 |   DCHECK(!handle.is_null()); | 
 |   DCHECK_LT(priority, num_priorities()); | 
 |   DCHECK_GE(num_running_jobs_, max_running_jobs_[handle.priority()]) << | 
 |       "Job should not be in queue when limits permit it to start."; | 
 |  | 
 |   if (handle.priority() == priority) | 
 |     return handle; | 
 |  | 
 |   if (MaybeDispatchJob(handle, priority)) | 
 |     return Handle(); | 
 |   Job* job = handle.value(); | 
 |   queue_.Erase(handle); | 
 |   return queue_.Insert(job, priority); | 
 | } | 
 |  | 
 | void PrioritizedDispatcher::OnJobFinished() { | 
 |   DCHECK_GT(num_running_jobs_, 0u); | 
 |   --num_running_jobs_; | 
 |   MaybeDispatchNextJob(); | 
 | } | 
 |  | 
 | PrioritizedDispatcher::Limits PrioritizedDispatcher::GetLimits() const { | 
 |   size_t num_priorities = max_running_jobs_.size(); | 
 |   Limits limits(num_priorities, max_running_jobs_.back()); | 
 |  | 
 |   // Calculate the number of jobs reserved for each priority and higher.  Leave | 
 |   // the number of jobs reserved for the lowest priority or higher as 0. | 
 |   for (size_t i = 1; i < num_priorities; ++i) { | 
 |     limits.reserved_slots[i] = max_running_jobs_[i] - max_running_jobs_[i - 1]; | 
 |   } | 
 |  | 
 |   return limits; | 
 | } | 
 |  | 
 | void PrioritizedDispatcher::SetLimits(const Limits& limits) { | 
 |   DCHECK_EQ(queue_.num_priorities(), limits.reserved_slots.size()); | 
 |   size_t total = 0; | 
 |   for (size_t i = 0; i < limits.reserved_slots.size(); ++i) { | 
 |     total += limits.reserved_slots[i]; | 
 |     max_running_jobs_[i] = total; | 
 |   } | 
 |   // Unreserved slots are available for all priorities. | 
 |   DCHECK_LE(total, limits.total_jobs) << "sum(reserved_slots) <= total_jobs"; | 
 |   size_t spare = limits.total_jobs - total; | 
 |   for (size_t i = limits.reserved_slots.size(); i > 0; --i) { | 
 |     max_running_jobs_[i - 1] += spare; | 
 |   } | 
 |  | 
 |   // Start pending jobs, if limits permit. | 
 |   while (true) { | 
 |     if (!MaybeDispatchNextJob()) | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | void PrioritizedDispatcher::SetLimitsToZero() { | 
 |   SetLimits(Limits(queue_.num_priorities(), 0)); | 
 | } | 
 |  | 
 | bool PrioritizedDispatcher::MaybeDispatchJob(const Handle& handle, | 
 |                                              Priority job_priority) { | 
 |   DCHECK_LT(job_priority, num_priorities()); | 
 |   if (num_running_jobs_ >= max_running_jobs_[job_priority]) | 
 |     return false; | 
 |   Job* job = handle.value(); | 
 |   queue_.Erase(handle); | 
 |   ++num_running_jobs_; | 
 |   job->Start(); | 
 |   return true; | 
 | } | 
 |  | 
 | bool PrioritizedDispatcher::MaybeDispatchNextJob() { | 
 |   Handle handle = queue_.FirstMax(); | 
 |   if (handle.is_null()) { | 
 |     DCHECK_EQ(0u, queue_.size()); | 
 |     return false; | 
 |   } | 
 |   return MaybeDispatchJob(handle, handle.priority()); | 
 | } | 
 |  | 
 | }  // namespace net |