普通文本  |  503行  |  16.15 KB

// 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 "content/browser/loader/resource_scheduler.h"

#include "base/stl_util.h"
#include "content/common/resource_messages.h"
#include "content/browser/loader/resource_message_delegate.h"
#include "content/public/browser/resource_controller.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/resource_throttle.h"
#include "ipc/ipc_message_macros.h"
#include "net/base/host_port_pair.h"
#include "net/base/load_flags.h"
#include "net/base/request_priority.h"
#include "net/http/http_server_properties.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"

namespace content {

static const size_t kMaxNumDelayableRequestsPerClient = 10;
static const size_t kMaxNumDelayableRequestsPerHost = 6;

// A thin wrapper around net::PriorityQueue that deals with
// ScheduledResourceRequests instead of PriorityQueue::Pointers.
class ResourceScheduler::RequestQueue {
 private:
  typedef net::PriorityQueue<ScheduledResourceRequest*> NetQueue;

 public:
  class Iterator {
   public:
    Iterator(NetQueue* queue) : queue_(queue) {
      DCHECK(queue != NULL);
      current_pointer_ = queue_->FirstMax();
    }

    Iterator& operator++() {
      current_pointer_ = queue_->GetNextTowardsLastMin(current_pointer_);
      return *this;
    }

    Iterator operator++(int) {
      Iterator result(*this);
      ++(*this);
      return result;
    }

    ScheduledResourceRequest* value() {
      return current_pointer_.value();
    }

    bool is_null() {
      return current_pointer_.is_null();
    }

   private:
    NetQueue* queue_;
    NetQueue::Pointer current_pointer_;
  };

  RequestQueue() : queue_(net::NUM_PRIORITIES) {}
  ~RequestQueue() {}

  // Adds |request| to the queue with given |priority|.
  void Insert(ScheduledResourceRequest* request,
              net::RequestPriority priority) {
    DCHECK(!ContainsKey(pointers_, request));
    NetQueue::Pointer pointer = queue_.Insert(request, priority);
    pointers_[request] = pointer;
  }

  // Removes |request| from the queue.
  void Erase(ScheduledResourceRequest* request) {
    PointerMap::iterator it = pointers_.find(request);
    DCHECK(it != pointers_.end());
    if (it == pointers_.end())
      return;
    queue_.Erase(it->second);
    pointers_.erase(it);
  }

  // Returns the highest priority request that's queued, or NULL if none are.
  ScheduledResourceRequest* FirstMax() {
    return queue_.FirstMax().value();
  }

  Iterator GetNextHighestIterator() {
    return Iterator(&queue_);
  }

  // Returns true if |request| is queued.
  bool IsQueued(ScheduledResourceRequest* request) const {
    return ContainsKey(pointers_, request);
  }

  // Returns true if no requests are queued.
  bool IsEmpty() const { return queue_.size() == 0; }

 private:
  typedef std::map<ScheduledResourceRequest*, NetQueue::Pointer> PointerMap;

  NetQueue queue_;
  PointerMap pointers_;
};

// This is the handle we return to the ResourceDispatcherHostImpl so it can
// interact with the request.
class ResourceScheduler::ScheduledResourceRequest
    : public ResourceMessageDelegate,
      public ResourceThrottle {
 public:
  ScheduledResourceRequest(const ClientId& client_id,
                           net::URLRequest* request,
                           ResourceScheduler* scheduler)
      : ResourceMessageDelegate(request),
        client_id_(client_id),
        request_(request),
        ready_(false),
        deferred_(false),
        scheduler_(scheduler) {
    TRACE_EVENT_ASYNC_BEGIN1("net", "URLRequest", request_,
                             "url", request->url().spec());
  }

  virtual ~ScheduledResourceRequest() {
    scheduler_->RemoveRequest(this);
  }

  void Start() {
    TRACE_EVENT_ASYNC_STEP_PAST0("net", "URLRequest", request_, "Queued");
    ready_ = true;
    if (deferred_ && request_->status().is_success()) {
      deferred_ = false;
      controller()->Resume();
    }
  }

  const ClientId& client_id() const { return client_id_; }
  net::URLRequest* url_request() { return request_; }
  const net::URLRequest* url_request() const { return request_; }

 private:
  // ResourceMessageDelegate interface:
  virtual bool OnMessageReceived(const IPC::Message& message,
                                 bool* message_was_ok) OVERRIDE {
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP_EX(ScheduledResourceRequest, message, *message_was_ok)
      IPC_MESSAGE_HANDLER(ResourceHostMsg_DidChangePriority, DidChangePriority)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP_EX()
    return handled;
  }

  // ResourceThrottle interface:
  virtual void WillStartRequest(bool* defer) OVERRIDE {
    deferred_ = *defer = !ready_;
  }

  virtual const char* GetNameForLogging() const OVERRIDE {
    return "ResourceScheduler";
  }

  void DidChangePriority(int request_id, net::RequestPriority new_priority) {
    scheduler_->ReprioritizeRequest(this, new_priority);
  }

  ClientId client_id_;
  net::URLRequest* request_;
  bool ready_;
  bool deferred_;
  ResourceScheduler* scheduler_;

  DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest);
};

// Each client represents a tab.
struct ResourceScheduler::Client {
  Client() : has_body(false) {}
  ~Client() {}

  bool has_body;
  RequestQueue pending_requests;
  RequestSet in_flight_requests;
};

ResourceScheduler::ResourceScheduler() {
}

ResourceScheduler::~ResourceScheduler() {
  DCHECK(unowned_requests_.empty());
  DCHECK(client_map_.empty());
}

scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest(
    int child_id,
    int route_id,
    net::URLRequest* url_request) {
  DCHECK(CalledOnValidThread());
  ClientId client_id = MakeClientId(child_id, route_id);
  scoped_ptr<ScheduledResourceRequest> request(
      new ScheduledResourceRequest(client_id, url_request, this));

  ClientMap::iterator it = client_map_.find(client_id);
  if (it == client_map_.end()) {
    // There are several ways this could happen:
    // 1. <a ping> requests don't have a route_id.
    // 2. Most unittests don't send the IPCs needed to register Clients.
    // 3. The tab is closed while a RequestResource IPC is in flight.
    unowned_requests_.insert(request.get());
    request->Start();
    return request.PassAs<ResourceThrottle>();
  }

  Client* client = it->second;
  if (ShouldStartRequest(request.get(), client) == START_REQUEST) {
    StartRequest(request.get(), client);
  } else {
    client->pending_requests.Insert(request.get(), url_request->priority());
  }
  return request.PassAs<ResourceThrottle>();
}

void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) {
  DCHECK(CalledOnValidThread());
  if (ContainsKey(unowned_requests_, request)) {
    unowned_requests_.erase(request);
    return;
  }

  ClientMap::iterator client_it = client_map_.find(request->client_id());
  if (client_it == client_map_.end()) {
    return;
  }

  Client* client = client_it->second;

  if (client->pending_requests.IsQueued(request)) {
    client->pending_requests.Erase(request);
    DCHECK(!ContainsKey(client->in_flight_requests, request));
  } else {
    size_t erased = client->in_flight_requests.erase(request);
    DCHECK(erased);

    // Removing this request may have freed up another to load.
    LoadAnyStartablePendingRequests(client);
  }
}

void ResourceScheduler::OnClientCreated(int child_id, int route_id) {
  DCHECK(CalledOnValidThread());
  ClientId client_id = MakeClientId(child_id, route_id);
  DCHECK(!ContainsKey(client_map_, client_id));

  client_map_[client_id] = new Client;
}

void ResourceScheduler::OnClientDeleted(int child_id, int route_id) {
  DCHECK(CalledOnValidThread());
  ClientId client_id = MakeClientId(child_id, route_id);
  DCHECK(ContainsKey(client_map_, client_id));
  ClientMap::iterator it = client_map_.find(client_id);
  if (it == client_map_.end())
    return;

  Client* client = it->second;

  // FYI, ResourceDispatcherHost cancels all of the requests after this function
  // is called. It should end up canceling all of the requests except for a
  // cross-renderer navigation.
  for (RequestSet::iterator it = client->in_flight_requests.begin();
       it != client->in_flight_requests.end(); ++it) {
    unowned_requests_.insert(*it);
  }
  client->in_flight_requests.clear();

  delete client;
  client_map_.erase(it);
}

void ResourceScheduler::OnNavigate(int child_id, int route_id) {
  DCHECK(CalledOnValidThread());
  ClientId client_id = MakeClientId(child_id, route_id);

  ClientMap::iterator it = client_map_.find(client_id);
  if (it == client_map_.end()) {
    // The client was likely deleted shortly before we received this IPC.
    return;
  }

  Client* client = it->second;
  client->has_body = false;
}

void ResourceScheduler::OnWillInsertBody(int child_id, int route_id) {
  DCHECK(CalledOnValidThread());
  ClientId client_id = MakeClientId(child_id, route_id);

  ClientMap::iterator it = client_map_.find(client_id);
  if (it == client_map_.end()) {
    // The client was likely deleted shortly before we received this IPC.
    return;
  }

  Client* client = it->second;
  client->has_body = false;
  if (!client->has_body) {
    client->has_body = true;
    LoadAnyStartablePendingRequests(client);
  }
}

void ResourceScheduler::StartRequest(ScheduledResourceRequest* request,
                                     Client* client) {
  client->in_flight_requests.insert(request);
  request->Start();
}

void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
                                            net::RequestPriority new_priority) {
  if (request->url_request()->load_flags() & net::LOAD_IGNORE_LIMITS) {
    // We should not be re-prioritizing requests with the
    // IGNORE_LIMITS flag.
    NOTREACHED();
    return;
  }
  net::RequestPriority old_priority = request->url_request()->priority();
  DCHECK_NE(new_priority, old_priority);
  request->url_request()->SetPriority(new_priority);
  ClientMap::iterator client_it = client_map_.find(request->client_id());
  if (client_it == client_map_.end()) {
    // The client was likely deleted shortly before we received this IPC.
    return;
  }

  Client *client = client_it->second;
  if (!client->pending_requests.IsQueued(request)) {
    DCHECK(ContainsKey(client->in_flight_requests, request));
    // Request has already started.
    return;
  }

  client->pending_requests.Erase(request);
  client->pending_requests.Insert(request,
                                  request->url_request()->priority());

  if (new_priority > old_priority) {
    // Check if this request is now able to load at its new priority.
    LoadAnyStartablePendingRequests(client);
  }
}

void ResourceScheduler::LoadAnyStartablePendingRequests(Client* client) {
  // We iterate through all the pending requests, starting with the highest
  // priority one. For each entry, one of three things can happen:
  // 1) We start the request, remove it from the list, and keep checking.
  // 2) We do NOT start the request, but ShouldStartRequest() signals us that
  //    there may be room for other requests, so we keep checking and leave
  //    the previous request still in the list.
  // 3) We do not start the request, same as above, but StartRequest() tells
  //    us there's no point in checking any further requests.

  RequestQueue::Iterator request_iter =
      client->pending_requests.GetNextHighestIterator();

  while (!request_iter.is_null()) {
    ScheduledResourceRequest* request = request_iter.value();
    ShouldStartReqResult query_result = ShouldStartRequest(request, client);

    if (query_result == START_REQUEST) {
      client->pending_requests.Erase(request);
      StartRequest(request, client);

      // StartRequest can modify the pending list, so we (re)start evaluation
      // from the currently highest priority request. Avoid copying a singular
      // iterator, which would trigger undefined behavior.
      if (client->pending_requests.GetNextHighestIterator().is_null())
        break;
      request_iter = client->pending_requests.GetNextHighestIterator();
    } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) {
      ++request_iter;
      continue;
    } else {
      DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING);
      break;
    }
  }
}

void ResourceScheduler::GetNumDelayableRequestsInFlight(
    Client* client,
    const net::HostPortPair& active_request_host,
    size_t* total_delayable,
    size_t* total_for_active_host) const {
  DCHECK(client != NULL && total_delayable != NULL &&
         total_for_active_host != NULL);

  size_t total_delayable_count = 0;
  size_t same_host_count = 0;
  for (RequestSet::iterator it = client->in_flight_requests.begin();
       it != client->in_flight_requests.end(); ++it) {
    net::HostPortPair host_port_pair =
        net::HostPortPair::FromURL((*it)->url_request()->url());

    if (active_request_host.Equals(host_port_pair)) {
      same_host_count++;
    }

    if ((*it)->url_request()->priority() < net::LOW) {
      const net::HttpServerProperties& http_server_properties =
          *(*it)->url_request()->context()->http_server_properties();

      if (!http_server_properties.SupportsSpdy(host_port_pair)) {
        ++total_delayable_count;
      }
    }
  }
  *total_delayable = total_delayable_count;
  *total_for_active_host = same_host_count;
}

// ShouldStartRequest is the main scheduling algorithm.
//
// Requests are categorized into two categories:
//
// 1. Immediately issued requests, which are:
//
//   * Higher priority requests (>= net::LOW).
//   * Synchronous requests.
//   * Requests to SPDY-capable origin servers.
//   * Non-HTTP[S] requests.
//
// 2. The remainder are delayable requests, which follow these rules:
//
//   * If no high priority requests are in flight, start loading low priority
//     requests.
//   * Once the renderer has a <body>, start loading delayable requests.
//   * Never exceed 10 delayable requests in flight per client.
//   * Never exceed 6 delayable requests for a given host.
//   * Prior to <body>, allow one delayable request to load at a time.
ResourceScheduler::ShouldStartReqResult ResourceScheduler::ShouldStartRequest(
    ScheduledResourceRequest* request,
    Client* client) const {
  const net::URLRequest& url_request = *request->url_request();

  // TODO(simonjam): This may end up causing disk contention. We should
  // experiment with throttling if that happens.
  if (!url_request.url().SchemeIsHTTPOrHTTPS()) {
    return START_REQUEST;
  }

  const net::HttpServerProperties& http_server_properties =
      *url_request.context()->http_server_properties();

  if (url_request.priority() >= net::LOW ||
      !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) {
    return START_REQUEST;
  }

  net::HostPortPair host_port_pair =
      net::HostPortPair::FromURL(url_request.url());

  // TODO(willchan): We should really improve this algorithm as described in
  // crbug.com/164101. Also, theoretically we should not count a SPDY request
  // against the delayable requests limit.
  if (http_server_properties.SupportsSpdy(host_port_pair)) {
    return START_REQUEST;
  }

  size_t num_delayable_requests_in_flight = 0;
  size_t num_requests_in_flight_for_host = 0;
  GetNumDelayableRequestsInFlight(client, host_port_pair,
                                  &num_delayable_requests_in_flight,
                                  &num_requests_in_flight_for_host);

  if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) {
    return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
  }

  if (num_requests_in_flight_for_host >= kMaxNumDelayableRequestsPerHost) {
    // There may be other requests for other hosts we'd allow, so keep checking.
    return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
  }

  bool have_immediate_requests_in_flight =
      client->in_flight_requests.size() > num_delayable_requests_in_flight;
  if (have_immediate_requests_in_flight && !client->has_body &&
      num_delayable_requests_in_flight != 0) {
    return DO_NOT_START_REQUEST_AND_STOP_SEARCHING;
  }

  return START_REQUEST;
}

ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
    int child_id, int route_id) {
  return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
}

}  // namespace content