Index: content/renderer/resource_dispatch_throttler.cc |
diff --git a/content/renderer/resource_dispatch_throttler.cc b/content/renderer/resource_dispatch_throttler.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4ba1291caa3f14de02e3fba253f2851a80d72924 |
--- /dev/null |
+++ b/content/renderer/resource_dispatch_throttler.cc |
@@ -0,0 +1,239 @@ |
+// Copyright 2015 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 "content/renderer/resource_dispatch_throttler.h" |
+ |
+#include "base/auto_reset.h" |
+#include "base/debug/trace_event.h" |
+#include "content/common/resource_messages.h" |
+#include "content/renderer/scheduler/renderer_scheduler.h" |
+#include "ipc/ipc_message_macros.h" |
+ |
+namespace content { |
+namespace { |
+ |
+int GetRequestId(const IPC::Message& msg) { |
+ int request_id = -1; |
+ switch (msg.type()) { |
+ case ResourceHostMsg_RequestResource::ID: { |
+ PickleIterator iter(msg); |
+ int routing_id = -1; |
+ if (!iter.ReadInt(&routing_id) || !iter.ReadInt(&request_id)) |
+ NOTREACHED() << "Invalid id for resource request message."; |
+ } break; |
+ |
+ case ResourceHostMsg_DidChangePriority::ID: |
+ case ResourceHostMsg_ReleaseDownloadedFile::ID: |
+ case ResourceHostMsg_CancelRequest::ID: |
+ if (!PickleIterator(msg).ReadInt(&request_id)) |
+ NOTREACHED() << "Invalid id for resource message."; |
+ break; |
+ |
+ default: |
+ NOTREACHED() << "Invalid message for resource throttling."; |
+ break; |
+ } |
+ return request_id; |
+} |
+ |
+bool UpdateRequestPriority(const IPC::Message& priority_msg, |
+ IPC::Message* request_msg) { |
+ ResourceHostMsg_DidChangePriority::Param priority_params; |
+ if (!ResourceHostMsg_DidChangePriority::Read(&priority_msg, &priority_params)) |
+ return false; |
+ |
+ ResourceHostMsg_RequestResource::Param request_params; |
+ if (!ResourceHostMsg_RequestResource::Read(request_msg, &request_params)) |
+ return false; |
+ |
+ int routing_id = get<0>(request_params); |
+ int request_id = get<1>(request_params); |
+ DCHECK_EQ(request_id, get<0>(priority_params)); |
+ |
+ ResourceHostMsg_Request& updated_request = get<2>(request_params); |
+ updated_request.priority = get<1>(priority_params); |
+ *request_msg = |
+ ResourceHostMsg_RequestResource(routing_id, request_id, updated_request); |
+ return true; |
+} |
+ |
+bool UpdateRequestToReleaseDownloadedFile(IPC::Message* request_msg) { |
+ ResourceHostMsg_RequestResource::Param request_params; |
+ if (!ResourceHostMsg_RequestResource::Read(request_msg, &request_params)) |
+ return false; |
+ |
+ int routing_id = get<0>(request_params); |
+ int request_id = get<1>(request_params); |
+ ResourceHostMsg_Request& updated_request = get<2>(request_params); |
+ // No need to update the request message if no file download was specified. |
+ if (!updated_request.download_to_file) |
+ return true; |
+ |
+ updated_request.download_to_file = false; |
+ *request_msg = |
+ ResourceHostMsg_RequestResource(routing_id, request_id, updated_request); |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+ResourceDispatchThrottler::ResourceDispatchThrottler( |
+ IPC::Sender* proxied_sender, |
+ RendererScheduler* scheduler, |
+ base::TimeDelta flush_period, |
+ uint32 max_requests_per_flush) |
+ : proxied_sender_(proxied_sender), |
+ scheduler_(scheduler), |
+ flush_period_(flush_period), |
+ max_requests_per_flush_(max_requests_per_flush), |
+ flush_timer_( |
+ FROM_HERE, |
+ flush_period_, |
+ base::Bind(&ResourceDispatchThrottler::Flush, base::Unretained(this)), |
+ false /* is_repeating */), |
+ sent_requests_since_last_flush_(0), |
+ is_forwarding_request_(false) { |
+ DCHECK(proxied_sender); |
+ DCHECK(scheduler); |
+ DCHECK(flush_period_ != base::TimeDelta()); |
+ DCHECK(max_requests_per_flush_); |
+ flush_timer_.SetTaskRunner(scheduler->DefaultTaskRunner()); |
+} |
+ |
+ResourceDispatchThrottler::~ResourceDispatchThrottler() { |
+ for (auto& request : throttled_requests_) |
+ ForwardRequest(request.second); |
+ throttled_requests_.clear(); |
+} |
+ |
+bool ResourceDispatchThrottler::Send(IPC::Message* msg) { |
+ thread_checker_.CalledOnValidThread(); |
+ switch (msg->type()) { |
+ case ResourceHostMsg_RequestResource::ID: |
+ return OnRequestResource(msg); |
+ |
+ case ResourceHostMsg_DidChangePriority::ID: |
+ return OnDidChangePriority(msg); |
+ |
+ case ResourceHostMsg_ReleaseDownloadedFile::ID: |
+ return OnReleaseDownloadedFile(msg); |
+ |
+ case ResourceHostMsg_CancelRequest::ID: |
+ return OnCancelRequest(msg); |
+ |
+ default: |
+ return proxied_sender_->Send(msg); |
+ } |
+} |
+ |
+base::TimeTicks ResourceDispatchThrottler::Now() const { |
+ return base::TimeTicks::Now(); |
+} |
+ |
+void ResourceDispatchThrottler::ScheduleFlush() { |
+ DCHECK(!flush_timer_.IsRunning()); |
+ flush_timer_.Reset(); |
+} |
+ |
+void ResourceDispatchThrottler::Flush() { |
+ TRACE_EVENT1("loader", "ResourceDispatchThrottler::Flush", |
+ "total_throttled_requests", throttled_requests_.size()); |
+ sent_requests_since_last_flush_ = 0; |
+ |
+ // If high-priority work is no longer anticipated, dispatch can be safely |
+ // accelerated. Avoid completely flushing in such case in the event that |
+ // a large number of requests have been throttled. |
+ uint32 max_requests = scheduler_->ShouldAnticipateHighPriorityWork() |
+ ? max_requests_per_flush_ |
+ : max_requests_per_flush_ * 2; |
+ |
+ while (!throttled_requests_.empty() && |
+ sent_requests_since_last_flush_ < max_requests) { |
+ auto request_it = throttled_requests_.begin(); |
+ scoped_ptr<IPC::Message> forwarded_msg(request_it->second); |
+ throttled_requests_.erase(request_it); |
+ ForwardRequest(forwarded_msg.release()); |
+ } |
+ |
+ if (!throttled_requests_.empty()) |
+ ScheduleFlush(); |
+} |
+ |
+bool ResourceDispatchThrottler::ForwardRequest(IPC::Message* msg) { |
+ DCHECK(!is_forwarding_request_); |
+ base::AutoReset<bool> is_forwarding_request_resetter(&is_forwarding_request_, |
+ true); |
+ last_sent_request_time_ = Now(); |
+ ++sent_requests_since_last_flush_; |
+ return proxied_sender_->Send(msg); |
+} |
+ |
+bool ResourceDispatchThrottler::OnRequestResource(IPC::Message* msg) { |
+ DCHECK(!is_forwarding_request_); |
+ const int request_id = GetRequestId(*msg); |
+ |
+ // Shift responsibility for handling an invalid request ID downstream. |
+ if (request_id == -1) |
+ return ForwardRequest(msg); |
+ |
+ if (!throttled_requests_.empty()) { |
+ // Valid request ids must be monotonically increasing. |
+ DCHECK_LT(throttled_requests_.rbegin()->first, request_id); |
+ throttled_requests_.insert(std::make_pair(request_id, msg)); |
+ TRACE_EVENT_INSTANT0("loader", "ResourceDispatchThrottler::ThrottleRequest", |
+ TRACE_EVENT_SCOPE_THREAD); |
+ return true; |
+ } |
+ |
+ if (!scheduler_->ShouldAnticipateHighPriorityWork()) |
+ return ForwardRequest(msg); |
+ |
+ if (Now() > (last_sent_request_time_ + flush_period_)) { |
+ // If sufficient time has passed since the previous send, we can effectively |
+ // mark the pipeline as flushed. |
+ sent_requests_since_last_flush_ = 0; |
+ return ForwardRequest(msg); |
+ } |
+ |
+ if (sent_requests_since_last_flush_ < max_requests_per_flush_) |
+ return ForwardRequest(msg); |
+ |
+ TRACE_EVENT_INSTANT0("loader", "ResourceDispatchThrottler::ThrottleRequest", |
+ TRACE_EVENT_SCOPE_THREAD); |
+ throttled_requests_.insert(std::make_pair(request_id, msg)); |
+ ScheduleFlush(); |
+ return true; |
+} |
+ |
+bool ResourceDispatchThrottler::OnDidChangePriority(IPC::Message* msg) { |
+ scoped_ptr<IPC::Message> scoped_msg(msg); |
+ auto request_it = throttled_requests_.find(GetRequestId(*msg)); |
+ if (request_it == throttled_requests_.end()) |
+ return proxied_sender_->Send(scoped_msg.release()); |
+ |
+ return UpdateRequestPriority(*scoped_msg, request_it->second); |
+} |
+ |
+bool ResourceDispatchThrottler::OnReleaseDownloadedFile(IPC::Message* msg) { |
+ scoped_ptr<IPC::Message> scoped_msg(msg); |
+ auto request_it = throttled_requests_.find(GetRequestId(*msg)); |
+ if (request_it == throttled_requests_.end()) |
+ return proxied_sender_->Send(scoped_msg.release()); |
+ |
+ // TODO(jdduke): Should this simply cancel the outstanding request? |
+ return UpdateRequestToReleaseDownloadedFile(request_it->second); |
+} |
+ |
+bool ResourceDispatchThrottler::OnCancelRequest(IPC::Message* msg) { |
+ scoped_ptr<IPC::Message> scoped_msg(msg); |
+ auto request_it = throttled_requests_.find(GetRequestId(*msg)); |
+ if (request_it == throttled_requests_.end()) |
+ return proxied_sender_->Send(scoped_msg.release()); |
+ |
+ scoped_ptr<IPC::Message> cancelled_msg(request_it->second); |
+ throttled_requests_.erase(request_it); |
+ return true; |
+} |
+ |
+} // namespace content |