View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.client;
20  
21  import java.io.IOException;
22  import java.util.HashMap;
23  import java.util.Map;
24  import java.util.concurrent.TimeUnit;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  
27  import org.eclipse.jetty.client.api.Connection;
28  import org.eclipse.jetty.client.api.Destination;
29  import org.eclipse.jetty.util.Callback;
30  import org.eclipse.jetty.util.annotation.ManagedAttribute;
31  import org.eclipse.jetty.util.component.ContainerLifeCycle;
32  import org.eclipse.jetty.util.log.Log;
33  import org.eclipse.jetty.util.log.Logger;
34  import org.eclipse.jetty.util.thread.Scheduler;
35  
36  /**
37   * <p>A connection pool that validates connections before
38   * making them available for use.</p>
39   * <p>Connections that have just been opened are not validated.
40   * Connections that are {@link #release(Connection) released} will
41   * be validated.</p>
42   * <p>Validation by reading from the EndPoint is not reliable,
43   * since the TCP FIN may arrive just after the validation read.</p>
44   * <p>This class validates connections by putting them in a
45   * "quarantine" for a configurable timeout, where they cannot
46   * be used to send requests. When the timeout expires, the
47   * quarantined connection is made idle and therefore available
48   * to send requests.</p>
49   * <p>The existing HttpClient mechanism to detect server closes
50   * will trigger and close quarantined connections, before they
51   * are made idle (and reusable) again.</p>
52   * <p>There still is a small chance that the timeout expires,
53   * the connection is made idle and available again, it is used
54   * to send a request exactly when the server decides to close.
55   * This case is however unavoidable and may be mitigated by
56   * tuning the idle timeout of the servers to be larger than
57   * that of the client.</p>
58   */
59  public class ValidatingConnectionPool extends ConnectionPool
60  {
61      private static final Logger LOG = Log.getLogger(ValidatingConnectionPool.class);
62  
63      private final Scheduler scheduler;
64      private final long timeout;
65      private final Map<Connection, Holder> quarantine;
66  
67      public ValidatingConnectionPool(Destination destination, int maxConnections, Callback requester, Scheduler scheduler, long timeout)
68      {
69          super(destination, maxConnections, requester);
70          this.scheduler = scheduler;
71          this.timeout = timeout;
72          this.quarantine = new HashMap<>(maxConnections);
73      }
74  
75      @ManagedAttribute(value = "The number of validating connections", readonly = true)
76      public int getValidatingConnectionCount()
77      {
78          return quarantine.size();
79      }
80  
81      @Override
82      public boolean release(Connection connection)
83      {
84          lock();
85          try
86          {
87              if (!getActiveConnections().remove(connection))
88                  return false;
89              Holder holder = new Holder(connection);
90              holder.task = scheduler.schedule(holder, timeout, TimeUnit.MILLISECONDS);
91              quarantine.put(connection, holder);
92              if (LOG.isDebugEnabled())
93                  LOG.debug("Validating for {}ms {}", timeout, connection);
94          }
95          finally
96          {
97              unlock();
98          }
99  
100         released(connection);
101         return true;
102     }
103 
104     @Override
105     public boolean remove(Connection connection)
106     {
107         Holder holder;
108         lock();
109         try
110         {
111             holder = quarantine.remove(connection);
112         }
113         finally
114         {
115             unlock();
116         }
117 
118         if (holder == null)
119             return super.remove(connection);
120 
121         if (LOG.isDebugEnabled())
122             LOG.debug("Removed while validating {}", connection);
123 
124         boolean cancelled = holder.cancel();
125         if (cancelled)
126             return remove(connection, true);
127 
128         return super.remove(connection);
129     }
130 
131     @Override
132     public void dump(Appendable out, String indent) throws IOException
133     {
134         super.dump(out, indent);
135         ContainerLifeCycle.dump(out, indent, quarantine.values());
136     }
137 
138     @Override
139     public String toString()
140     {
141         int size;
142         lock();
143         try
144         {
145             size = quarantine.size();
146         }
147         finally
148         {
149             unlock();
150         }
151         return String.format("%s[v=%d]", super.toString(), size);
152     }
153 
154     private class Holder implements Runnable
155     {
156         private final long timestamp = System.nanoTime();
157         private final AtomicBoolean latch = new AtomicBoolean();
158         private final Connection connection;
159         public Scheduler.Task task;
160 
161         public Holder(Connection connection)
162         {
163             this.connection = connection;
164         }
165 
166         @Override
167         public void run()
168         {
169             if (latch.compareAndSet(false, true))
170             {
171                 boolean idle;
172                 lock();
173                 try
174                 {
175                     quarantine.remove(connection);
176                     idle = offerIdle(connection);
177                     if (LOG.isDebugEnabled())
178                         LOG.debug("Validated {}", connection);
179                 }
180                 finally
181                 {
182                     unlock();
183                 }
184 
185                 if (idle(connection, idle))
186                     proceed();
187             }
188         }
189 
190         public boolean cancel()
191         {
192             if (latch.compareAndSet(false, true))
193             {
194                 task.cancel();
195                 return true;
196             }
197             return false;
198         }
199 
200         @Override
201         public String toString()
202         {
203             return String.format("%s[validationLeft=%dms]",
204                     connection,
205                     timeout - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - timestamp)
206             );
207         }
208     }
209 }