2626import static org .openqa .selenium .remote .tracing .Tags .HTTP_REQUEST_EVENT ;
2727import static org .openqa .selenium .remote .tracing .Tags .HTTP_RESPONSE ;
2828
29+ import com .google .common .cache .Cache ;
30+ import com .google .common .cache .CacheBuilder ;
31+ import com .google .common .cache .RemovalListener ;
2932import com .google .common .collect .ImmutableMap ;
3033import java .net .URL ;
31- import java .time .Instant ;
32- import java .time .temporal .ChronoUnit ;
33- import java .util .Iterator ;
34+ import java .time .Duration ;
3435import java .util .concurrent .Callable ;
35- import java .util .concurrent .ConcurrentHashMap ;
36- import java .util .concurrent .ConcurrentMap ;
3736import java .util .concurrent .Executors ;
3837import java .util .concurrent .ScheduledExecutorService ;
3938import java .util .concurrent .TimeUnit ;
40- import java .util .logging .Level ;
41- import java .util .logging .Logger ;
4239import org .openqa .selenium .NoSuchSessionException ;
4340import org .openqa .selenium .concurrent .GuardedRunnable ;
41+ import org .openqa .selenium .grid .data .Session ;
4442import org .openqa .selenium .grid .sessionmap .SessionMap ;
4543import org .openqa .selenium .grid .web .ReverseProxyHandler ;
4644import org .openqa .selenium .internal .Require ;
6058
6159class HandleSession implements HttpHandler {
6260
63- private static final Logger LOG = Logger .getLogger (HandleSession .class .getName ());
64-
65- private static class CacheEntry {
66- private final SessionId sessionId ;
67- private final HttpClient httpClient ;
68- // volatile as the ConcurrentMap will not take care of synchronization
69- private volatile Instant lastUse ;
70-
71- public CacheEntry (SessionId sessionId , HttpClient httpClient ) {
72- this .sessionId = sessionId ;
73- this .httpClient = httpClient ;
74- this .lastUse = Instant .now ();
75- }
76- }
77-
7861 private final Tracer tracer ;
7962 private final HttpClient .Factory httpClientFactory ;
8063 private final SessionMap sessions ;
81- private final ConcurrentMap <URL , CacheEntry > httpClients ;
64+ private final Cache <URL , HttpClient > httpClients ;
8265
8366 HandleSession (Tracer tracer , HttpClient .Factory httpClientFactory , SessionMap sessions ) {
8467 this .tracer = Require .nonNull ("Tracer" , tracer );
8568 this .httpClientFactory = Require .nonNull ("HTTP client factory" , httpClientFactory );
8669 this .sessions = Require .nonNull ("Sessions" , sessions );
8770
88- this .httpClients = new ConcurrentHashMap <>();
89-
90- Runnable cleanUpHttpClients =
91- () -> {
92- Instant revalidateBefore = Instant .now ().minus (1 , ChronoUnit .MINUTES );
93- Iterator <CacheEntry > iterator = httpClients .values ().iterator ();
94-
95- while (iterator .hasNext ()) {
96- CacheEntry entry = iterator .next ();
97-
98- if (!entry .lastUse .isBefore (revalidateBefore )) {
99- // the session was recently used
100- return ;
101- }
102-
103- try {
104- sessions .get (entry .sessionId );
105- } catch (NoSuchSessionException e ) {
106- // the session is dead, remove it from the cache
107- iterator .remove ();
108-
109- try {
110- entry .httpClient .close ();
111- } catch (Exception ex ) {
112- LOG .log (Level .WARNING , "failed to close a stale httpclient" , ex );
113- }
114- }
115- }
116- };
71+ this .httpClients =
72+ CacheBuilder .newBuilder ()
73+ // this timeout must be bigger than default connection + read timeout, to ensure we do
74+ // not close HttpClients which might have requests waiting for responses
75+ .expireAfterAccess (Duration .ofMinutes (4 ))
76+ .removalListener (
77+ (RemovalListener <URL , HttpClient >) removal -> removal .getValue ().close ())
78+ .build ();
11779
11880 ScheduledExecutorService cleanUpHttpClientsCacheService =
11981 Executors .newSingleThreadScheduledExecutor (
@@ -124,7 +86,7 @@ public CacheEntry(SessionId sessionId, HttpClient httpClient) {
12486 return thread ;
12587 });
12688 cleanUpHttpClientsCacheService .scheduleAtFixedRate (
127- GuardedRunnable .guard (cleanUpHttpClients ), 1 , 1 , TimeUnit .MINUTES );
89+ GuardedRunnable .guard (httpClients :: cleanUp ), 1 , 1 , TimeUnit .MINUTES );
12890 }
12991
13092 @ Override
@@ -203,18 +165,11 @@ public HttpResponse execute(HttpRequest req) {
203165 private Callable <HttpHandler > loadSessionId (Tracer tracer , Span span , SessionId id ) {
204166 return span .wrap (
205167 () -> {
206- CacheEntry cacheEntry =
207- httpClients .computeIfAbsent (
208- Urls .fromUri (sessions .getUri (id )),
209- (sessionUrl ) -> {
210- ClientConfig config =
211- ClientConfig .defaultConfig ().baseUrl (sessionUrl ).withRetries ();
212- HttpClient httpClient = httpClientFactory .createClient (config );
213-
214- return new CacheEntry (id , httpClient );
215- });
216- cacheEntry .lastUse = Instant .now ();
217- return new ReverseProxyHandler (tracer , cacheEntry .httpClient );
168+ Session session = sessions .get (id );
169+ URL url = Urls .fromUri (session .getUri ());
170+ ClientConfig config = ClientConfig .defaultConfig ().baseUrl (url ).withRetries ();
171+ HttpClient client = httpClients .get (url , () -> httpClientFactory .createClient (config ));
172+ return new ReverseProxyHandler (tracer , client );
218173 });
219174 }
220175}
0 commit comments