• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // A ClientSocketPoolBase is used to restrict the number of sockets open at
6 // a time.  It also maintains a list of idle persistent sockets for reuse.
7 // Subclasses of ClientSocketPool should compose ClientSocketPoolBase to handle
8 // the core logic of (1) restricting the number of active (connected or
9 // connecting) sockets per "group" (generally speaking, the hostname), (2)
10 // maintaining a per-group list of idle, persistent sockets for reuse, and (3)
11 // limiting the total number of active sockets in the system.
12 //
13 // ClientSocketPoolBase abstracts socket connection details behind ConnectJob,
14 // ConnectJobFactory, and SocketParams.  When a socket "slot" becomes available,
15 // the ClientSocketPoolBase will ask the ConnectJobFactory to create a
16 // ConnectJob with a SocketParams.  Subclasses of ClientSocketPool should
17 // implement their socket specific connection by subclassing ConnectJob and
18 // implementing ConnectJob::ConnectInternal().  They can control the parameters
19 // passed to each new ConnectJob instance via their ConnectJobFactory subclass
20 // and templated SocketParams parameter.
21 //
22 #ifndef NET_SOCKET_CLIENT_SOCKET_POOL_BASE_H_
23 #define NET_SOCKET_CLIENT_SOCKET_POOL_BASE_H_
24 
25 #include <deque>
26 #include <map>
27 #include <set>
28 #include <string>
29 
30 #include "base/basictypes.h"
31 #include "base/ref_counted.h"
32 #include "base/scoped_ptr.h"
33 #include "base/time.h"
34 #include "base/timer.h"
35 #include "net/base/address_list.h"
36 #include "net/base/completion_callback.h"
37 #include "net/base/load_log.h"
38 #include "net/base/load_states.h"
39 #include "net/base/net_errors.h"
40 #include "net/base/network_change_notifier.h"
41 #include "net/base/request_priority.h"
42 #include "net/socket/client_socket.h"
43 #include "net/socket/client_socket_pool.h"
44 
45 namespace net {
46 
47 class ClientSocketHandle;
48 
49 // ConnectJob provides an abstract interface for "connecting" a socket.
50 // The connection may involve host resolution, tcp connection, ssl connection,
51 // etc.
52 class ConnectJob {
53  public:
54   class Delegate {
55    public:
Delegate()56     Delegate() {}
~Delegate()57     virtual ~Delegate() {}
58 
59     // Alerts the delegate that the connection completed.
60     virtual void OnConnectJobComplete(int result, ConnectJob* job) = 0;
61 
62    private:
63     DISALLOW_COPY_AND_ASSIGN(Delegate);
64   };
65 
66   // A |timeout_duration| of 0 corresponds to no timeout.
67   ConnectJob(const std::string& group_name,
68              base::TimeDelta timeout_duration,
69              Delegate* delegate,
70              LoadLog* load_log);
71   virtual ~ConnectJob();
72 
73   // Accessors
group_name()74   const std::string& group_name() const { return group_name_; }
load_log()75   LoadLog* load_log() { return load_log_; }
76 
77   // Releases |socket_| to the client.  On connection error, this should return
78   // NULL.
ReleaseSocket()79   ClientSocket* ReleaseSocket() { return socket_.release(); }
80 
81   // Begins connecting the socket.  Returns OK on success, ERR_IO_PENDING if it
82   // cannot complete synchronously without blocking, or another net error code
83   // on error.  In asynchronous completion, the ConnectJob will notify
84   // |delegate_| via OnConnectJobComplete.  In both asynchronous and synchronous
85   // completion, ReleaseSocket() can be called to acquire the connected socket
86   // if it succeeded.
87   int Connect();
88 
89   virtual LoadState GetLoadState() const = 0;
90 
91  protected:
set_socket(ClientSocket * socket)92   void set_socket(ClientSocket* socket) { socket_.reset(socket); }
socket()93   ClientSocket* socket() { return socket_.get(); }
94   void NotifyDelegateOfCompletion(int rv);
95 
96  private:
97   virtual int ConnectInternal() = 0;
98 
99   // Alerts the delegate that the ConnectJob has timed out.
100   void OnTimeout();
101 
102   const std::string group_name_;
103   const base::TimeDelta timeout_duration_;
104   // Timer to abort jobs that take too long.
105   base::OneShotTimer<ConnectJob> timer_;
106   Delegate* delegate_;
107   scoped_ptr<ClientSocket> socket_;
108   scoped_refptr<LoadLog> load_log_;
109 
110   DISALLOW_COPY_AND_ASSIGN(ConnectJob);
111 };
112 
113 namespace internal {
114 
115 // ClientSocketPoolBaseHelper is an internal class that implements almost all
116 // the functionality from ClientSocketPoolBase without using templates.
117 // ClientSocketPoolBase adds templated definitions built on top of
118 // ClientSocketPoolBaseHelper.  This class is not for external use, please use
119 // ClientSocketPoolBase instead.
120 class ClientSocketPoolBaseHelper
121     : public base::RefCounted<ClientSocketPoolBaseHelper>,
122       public ConnectJob::Delegate,
123       public NetworkChangeNotifier::Observer {
124  public:
125   class Request {
126    public:
Request(ClientSocketHandle * handle,CompletionCallback * callback,RequestPriority priority,LoadLog * load_log)127     Request(ClientSocketHandle* handle,
128             CompletionCallback* callback,
129             RequestPriority priority,
130             LoadLog* load_log)
131         : handle_(handle), callback_(callback), priority_(priority),
132           load_log_(load_log) {}
133 
~Request()134     virtual ~Request() {}
135 
handle()136     ClientSocketHandle* handle() const { return handle_; }
callback()137     CompletionCallback* callback() const { return callback_; }
priority()138     RequestPriority priority() const { return priority_; }
load_log()139     LoadLog* load_log() const { return load_log_.get(); }
140 
141    private:
142     ClientSocketHandle* const handle_;
143     CompletionCallback* const callback_;
144     const RequestPriority priority_;
145     const scoped_refptr<LoadLog> load_log_;
146 
147     DISALLOW_COPY_AND_ASSIGN(Request);
148   };
149 
150   class ConnectJobFactory {
151    public:
ConnectJobFactory()152     ConnectJobFactory() {}
~ConnectJobFactory()153     virtual ~ConnectJobFactory() {}
154 
155     virtual ConnectJob* NewConnectJob(
156         const std::string& group_name,
157         const Request& request,
158         ConnectJob::Delegate* delegate,
159         LoadLog* load_log) const = 0;
160 
161    private:
162     DISALLOW_COPY_AND_ASSIGN(ConnectJobFactory);
163   };
164 
165   ClientSocketPoolBaseHelper(
166       int max_sockets,
167       int max_sockets_per_group,
168       base::TimeDelta unused_idle_socket_timeout,
169       base::TimeDelta used_idle_socket_timeout,
170       ConnectJobFactory* connect_job_factory,
171       NetworkChangeNotifier* network_change_notifier);
172 
173   // See ClientSocketPool::RequestSocket for documentation on this function.
174   // Note that |request| must be heap allocated.  If ERR_IO_PENDING is returned,
175   // then ClientSocketPoolBaseHelper takes ownership of |request|.
176   int RequestSocket(const std::string& group_name, const Request* request);
177 
178   // See ClientSocketPool::CancelRequest for documentation on this function.
179   void CancelRequest(const std::string& group_name,
180                      const ClientSocketHandle* handle);
181 
182   // See ClientSocketPool::ReleaseSocket for documentation on this function.
183   void ReleaseSocket(const std::string& group_name,
184                      ClientSocket* socket);
185 
186   // See ClientSocketPool::CloseIdleSockets for documentation on this function.
187   void CloseIdleSockets();
188 
189   // See ClientSocketPool::IdleSocketCount() for documentation on this function.
idle_socket_count()190   int idle_socket_count() const {
191     return idle_socket_count_;
192   }
193 
194   // See ClientSocketPool::IdleSocketCountInGroup() for documentation on this
195   // function.
196   int IdleSocketCountInGroup(const std::string& group_name) const;
197 
198   // See ClientSocketPool::GetLoadState() for documentation on this function.
199   LoadState GetLoadState(const std::string& group_name,
200                          const ClientSocketHandle* handle) const;
201 
202   // ConnectJob::Delegate methods:
203   virtual void OnConnectJobComplete(int result, ConnectJob* job);
204 
205   // NetworkChangeNotifier::Observer methods:
206   virtual void OnIPAddressChanged();
207 
208   // For testing.
may_have_stalled_group()209   bool may_have_stalled_group() const { return may_have_stalled_group_; }
210 
NumConnectJobsInGroup(const std::string & group_name)211   int NumConnectJobsInGroup(const std::string& group_name) const {
212     return group_map_.find(group_name)->second.jobs.size();
213   }
214 
215   // Closes all idle sockets if |force| is true.  Else, only closes idle
216   // sockets that timed out or can't be reused.  Made public for testing.
217   void CleanupIdleSockets(bool force);
218 
219  private:
220   friend class base::RefCounted<ClientSocketPoolBaseHelper>;
221 
222   ~ClientSocketPoolBaseHelper();
223 
224   // Entry for a persistent socket which became idle at time |start_time|.
225   struct IdleSocket {
IdleSocketIdleSocket226     IdleSocket() : socket(NULL), used(false) {}
227     ClientSocket* socket;
228     base::TimeTicks start_time;
229     bool used;  // Indicates whether or not the socket has been used yet.
230 
231     // An idle socket should be removed if it can't be reused, or has been idle
232     // for too long. |now| is the current time value (TimeTicks::Now()).
233     // |timeout| is the length of time to wait before timing out an idle socket.
234     //
235     // An idle socket can't be reused if it is disconnected or has received
236     // data unexpectedly (hence no longer idle).  The unread data would be
237     // mistaken for the beginning of the next response if we were to reuse the
238     // socket for a new request.
239     bool ShouldCleanup(base::TimeTicks now, base::TimeDelta timeout) const;
240   };
241 
242   typedef std::deque<const Request*> RequestQueue;
243   typedef std::map<const ClientSocketHandle*, const Request*> RequestMap;
244 
245   // A Group is allocated per group_name when there are idle sockets or pending
246   // requests.  Otherwise, the Group object is removed from the map.
247   // |active_socket_count| tracks the number of sockets held by clients.  Of
248   // this number of sockets held by clients, some of them may be released soon,
249   // since ReleaseSocket() was called of them, but the DoReleaseSocket() task
250   // has not run yet for them.  |num_releasing_sockets| tracks these values,
251   // which is useful for not starting up new ConnectJobs when sockets may become
252   // available really soon.
253   struct Group {
GroupGroup254     Group() : active_socket_count(0), num_releasing_sockets(0) {}
255 
IsEmptyGroup256     bool IsEmpty() const {
257       return active_socket_count == 0 && idle_sockets.empty() && jobs.empty() &&
258           pending_requests.empty();
259     }
260 
HasAvailableSocketSlotGroup261     bool HasAvailableSocketSlot(int max_sockets_per_group) const {
262       return active_socket_count + static_cast<int>(jobs.size()) <
263           max_sockets_per_group;
264     }
265 
HasReleasingSocketsGroup266     bool HasReleasingSockets() const {
267       return num_releasing_sockets > 0;
268     }
269 
TopPendingPriorityGroup270     RequestPriority TopPendingPriority() const {
271       return pending_requests.front()->priority();
272     }
273 
274     std::deque<IdleSocket> idle_sockets;
275     std::set<const ConnectJob*> jobs;
276     RequestQueue pending_requests;
277     int active_socket_count;  // number of active sockets used by clients
278     // Number of sockets being released within one loop through the MessageLoop.
279     int num_releasing_sockets;
280   };
281 
282   typedef std::map<std::string, Group> GroupMap;
283 
284   typedef std::set<const ConnectJob*> ConnectJobSet;
285 
286   static void InsertRequestIntoQueue(const Request* r,
287                                      RequestQueue* pending_requests);
288   static const Request* RemoveRequestFromQueue(RequestQueue::iterator it,
289                                                RequestQueue* pending_requests);
290 
291   // Called when the number of idle sockets changes.
292   void IncrementIdleCount();
293   void DecrementIdleCount();
294 
295   // Called via PostTask by ReleaseSocket.
296   void DoReleaseSocket(const std::string& group_name, ClientSocket* socket);
297 
298   // Scans the group map for groups which have an available socket slot and
299   // at least one pending request. Returns number of groups found, and if found
300   // at least one, fills |group| and |group_name| with data of the stalled group
301   // having highest priority.
302   int FindTopStalledGroup(Group** group, std::string* group_name);
303 
304   // Called when timer_ fires.  This method scans the idle sockets removing
305   // sockets that timed out or can't be reused.
OnCleanupTimerFired()306   void OnCleanupTimerFired() {
307     CleanupIdleSockets(false);
308   }
309 
310   // Removes |job| from |connect_job_set_|.  Also updates |group| if non-NULL.
311   void RemoveConnectJob(const ConnectJob* job, Group* group);
312 
313   // Same as OnAvailableSocketSlot except it looks up the Group first to see if
314   // it's there.
315   void MaybeOnAvailableSocketSlot(const std::string& group_name);
316 
317   // Might delete the Group from |group_map_|.
318   void OnAvailableSocketSlot(const std::string& group_name, Group* group);
319 
320   // Process a request from a group's pending_requests queue.
321   void ProcessPendingRequest(const std::string& group_name, Group* group);
322 
323   // Assigns |socket| to |handle| and updates |group|'s counters appropriately.
324   void HandOutSocket(ClientSocket* socket,
325                      bool reused,
326                      ClientSocketHandle* handle,
327                      base::TimeDelta time_idle,
328                      Group* group);
329 
330   // Adds |socket| to the list of idle sockets for |group|.  |used| indicates
331   // whether or not the socket has previously been used.
332   void AddIdleSocket(ClientSocket* socket, bool used, Group* group);
333 
334   // Iterates through |connect_job_map_|, canceling all ConnectJobs.
335   // Afterwards, it iterates through all groups and deletes them if they are no
336   // longer needed.
337   void CancelAllConnectJobs();
338 
339   // Returns true if we can't create any more sockets due to the total limit.
340   // TODO(phajdan.jr): Also take idle sockets into account.
341   bool ReachedMaxSocketsLimit() const;
342 
343   GroupMap group_map_;
344 
345   // Timer used to periodically prune idle sockets that timed out or can't be
346   // reused.
347   base::RepeatingTimer<ClientSocketPoolBaseHelper> timer_;
348 
349   // The total number of idle sockets in the system.
350   int idle_socket_count_;
351 
352   // Number of connecting sockets across all groups.
353   int connecting_socket_count_;
354 
355   // Number of connected sockets we handed out across all groups.
356   int handed_out_socket_count_;
357 
358   // The maximum total number of sockets. See ReachedMaxSocketsLimit.
359   const int max_sockets_;
360 
361   // The maximum number of sockets kept per group.
362   const int max_sockets_per_group_;
363 
364   // The time to wait until closing idle sockets.
365   const base::TimeDelta unused_idle_socket_timeout_;
366   const base::TimeDelta used_idle_socket_timeout_;
367 
368   // Until the maximum number of sockets limit is reached, a group can only
369   // have pending requests if it exceeds the "max sockets per group" limit.
370   //
371   // This means when a socket is released, the only pending requests that can
372   // be started next belong to the same group.
373   //
374   // However once the |max_sockets_| limit is reached, this stops being true:
375   // groups can now have pending requests without having first reached the
376   // |max_sockets_per_group_| limit. So choosing the next request involves
377   // selecting the highest priority request across *all* groups.
378   //
379   // Since reaching the maximum number of sockets is an edge case, we make note
380   // of when it happens, and thus avoid doing the slower "scan all groups"
381   // in the common case.
382   bool may_have_stalled_group_;
383 
384   const scoped_ptr<ConnectJobFactory> connect_job_factory_;
385 
386   NetworkChangeNotifier* const network_change_notifier_;
387 };
388 
389 }  // namespace internal
390 
391 // The maximum duration, in seconds, to keep unused idle persistent sockets
392 // alive.
393 // TODO(willchan): Change this timeout after getting histogram data on how
394 // long it should be.
395 static const int kUnusedIdleSocketTimeout = 10;
396 // The maximum duration, in seconds, to keep used idle persistent sockets alive.
397 static const int kUsedIdleSocketTimeout = 300;  // 5 minutes
398 
399 template <typename SocketParams>
400 class ClientSocketPoolBase {
401  public:
402   class Request : public internal::ClientSocketPoolBaseHelper::Request {
403    public:
Request(ClientSocketHandle * handle,CompletionCallback * callback,RequestPriority priority,const SocketParams & params,LoadLog * load_log)404     Request(ClientSocketHandle* handle,
405             CompletionCallback* callback,
406             RequestPriority priority,
407             const SocketParams& params,
408             LoadLog* load_log)
409         : internal::ClientSocketPoolBaseHelper::Request(
410             handle, callback, priority, load_log),
411           params_(params) {}
412 
params()413     const SocketParams& params() const { return params_; }
414 
415    private:
416     SocketParams params_;
417   };
418 
419   class ConnectJobFactory {
420    public:
ConnectJobFactory()421     ConnectJobFactory() {}
~ConnectJobFactory()422     virtual ~ConnectJobFactory() {}
423 
424     virtual ConnectJob* NewConnectJob(
425         const std::string& group_name,
426         const Request& request,
427         ConnectJob::Delegate* delegate,
428         LoadLog* load_log) const = 0;
429 
430    private:
431     DISALLOW_COPY_AND_ASSIGN(ConnectJobFactory);
432   };
433 
434   // |max_sockets| is the maximum number of sockets to be maintained by this
435   // ClientSocketPool.  |max_sockets_per_group| specifies the maximum number of
436   // sockets a "group" can have.  |unused_idle_socket_timeout| specifies how
437   // long to leave an unused idle socket open before closing it.
438   // |used_idle_socket_timeout| specifies how long to leave a previously used
439   // idle socket open before closing it.
ClientSocketPoolBase(int max_sockets,int max_sockets_per_group,base::TimeDelta unused_idle_socket_timeout,base::TimeDelta used_idle_socket_timeout,ConnectJobFactory * connect_job_factory,NetworkChangeNotifier * network_change_notifier)440   ClientSocketPoolBase(
441       int max_sockets,
442       int max_sockets_per_group,
443       base::TimeDelta unused_idle_socket_timeout,
444       base::TimeDelta used_idle_socket_timeout,
445       ConnectJobFactory* connect_job_factory,
446       NetworkChangeNotifier* network_change_notifier)
447       : helper_(new internal::ClientSocketPoolBaseHelper(
448           max_sockets, max_sockets_per_group,
449           unused_idle_socket_timeout, used_idle_socket_timeout,
450           new ConnectJobFactoryAdaptor(connect_job_factory),
451           network_change_notifier)) {}
452 
~ClientSocketPoolBase()453   virtual ~ClientSocketPoolBase() {}
454 
455   // These member functions simply forward to ClientSocketPoolBaseHelper.
456 
457   // RequestSocket bundles up the parameters into a Request and then forwards to
458   // ClientSocketPoolBaseHelper::RequestSocket().  Note that the memory
459   // ownership is transferred in the asynchronous (ERR_IO_PENDING) case.
RequestSocket(const std::string & group_name,const SocketParams & params,RequestPriority priority,ClientSocketHandle * handle,CompletionCallback * callback,LoadLog * load_log)460   int RequestSocket(const std::string& group_name,
461                     const SocketParams& params,
462                     RequestPriority priority,
463                     ClientSocketHandle* handle,
464                     CompletionCallback* callback,
465                     LoadLog* load_log) {
466     scoped_ptr<Request> request(
467         new Request(handle, callback, priority, params, load_log));
468     LoadLog::BeginEvent(load_log, LoadLog::TYPE_SOCKET_POOL);
469     int rv = helper_->RequestSocket(group_name, request.get());
470     if (rv == ERR_IO_PENDING)
471       request.release();
472     else
473       LoadLog::EndEvent(load_log, LoadLog::TYPE_SOCKET_POOL);
474     return rv;
475   }
476 
CancelRequest(const std::string & group_name,const ClientSocketHandle * handle)477   void CancelRequest(const std::string& group_name,
478                      const ClientSocketHandle* handle) {
479     return helper_->CancelRequest(group_name, handle);
480   }
481 
ReleaseSocket(const std::string & group_name,ClientSocket * socket)482   void ReleaseSocket(const std::string& group_name, ClientSocket* socket) {
483     return helper_->ReleaseSocket(group_name, socket);
484   }
485 
CloseIdleSockets()486   void CloseIdleSockets() { return helper_->CloseIdleSockets(); }
487 
idle_socket_count()488   int idle_socket_count() const { return helper_->idle_socket_count(); }
489 
IdleSocketCountInGroup(const std::string & group_name)490   int IdleSocketCountInGroup(const std::string& group_name) const {
491     return helper_->IdleSocketCountInGroup(group_name);
492   }
493 
GetLoadState(const std::string & group_name,const ClientSocketHandle * handle)494   LoadState GetLoadState(const std::string& group_name,
495                          const ClientSocketHandle* handle) const {
496     return helper_->GetLoadState(group_name, handle);
497   }
498 
OnConnectJobComplete(int result,ConnectJob * job)499   virtual void OnConnectJobComplete(int result, ConnectJob* job) {
500     return helper_->OnConnectJobComplete(result, job);
501   }
502 
503   // For testing.
may_have_stalled_group()504   bool may_have_stalled_group() const {
505     return helper_->may_have_stalled_group();
506   }
507 
NumConnectJobsInGroup(const std::string & group_name)508   int NumConnectJobsInGroup(const std::string& group_name) const {
509     return helper_->NumConnectJobsInGroup(group_name);
510   }
511 
CleanupIdleSockets(bool force)512   void CleanupIdleSockets(bool force) {
513     return helper_->CleanupIdleSockets(force);
514   }
515 
516  private:
517   // This adaptor class exists to bridge the
518   // internal::ClientSocketPoolBaseHelper::ConnectJobFactory and
519   // ClientSocketPoolBase::ConnectJobFactory types, allowing clients to use the
520   // typesafe ClientSocketPoolBase::ConnectJobFactory, rather than having to
521   // static_cast themselves.
522   class ConnectJobFactoryAdaptor
523       : public internal::ClientSocketPoolBaseHelper::ConnectJobFactory {
524    public:
525     typedef typename ClientSocketPoolBase<SocketParams>::ConnectJobFactory
526         ConnectJobFactory;
527 
ConnectJobFactoryAdaptor(ConnectJobFactory * connect_job_factory)528     explicit ConnectJobFactoryAdaptor(
529         ConnectJobFactory* connect_job_factory)
530         : connect_job_factory_(connect_job_factory) {}
~ConnectJobFactoryAdaptor()531     virtual ~ConnectJobFactoryAdaptor() {}
532 
NewConnectJob(const std::string & group_name,const internal::ClientSocketPoolBaseHelper::Request & request,ConnectJob::Delegate * delegate,LoadLog * load_log)533     virtual ConnectJob* NewConnectJob(
534         const std::string& group_name,
535         const internal::ClientSocketPoolBaseHelper::Request& request,
536         ConnectJob::Delegate* delegate,
537         LoadLog* load_log) const {
538       const Request* casted_request = static_cast<const Request*>(&request);
539       return connect_job_factory_->NewConnectJob(
540           group_name, *casted_request, delegate, load_log);
541     }
542 
543     const scoped_ptr<ConnectJobFactory> connect_job_factory_;
544   };
545 
546   // One might ask why ClientSocketPoolBaseHelper is also refcounted if its
547   // containing ClientSocketPool is already refcounted.  The reason is because
548   // DoReleaseSocket() posts a task.  If ClientSocketPool gets deleted between
549   // the posting of the task and the execution, then we'll hit the DCHECK that
550   // |ClientSocketPoolBaseHelper::group_map_| is empty.
551   scoped_refptr<internal::ClientSocketPoolBaseHelper> helper_;
552 
553   DISALLOW_COPY_AND_ASSIGN(ClientSocketPoolBase);
554 };
555 
556 }  // namespace net
557 
558 #endif  // NET_SOCKET_CLIENT_SOCKET_POOL_BASE_H_
559