// 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