Skip to content

Commit 156d316

Browse files
committed
Fixes #12663 - Improve scalability of HttpCookieStore.Default.
Now using a ReadWriteLock for HttpCookieStore.Default, to allow concurrent access to match(URI). Signed-off-by: Simone Bordet <[email protected]>
1 parent 5bf31ef commit 156d316

File tree

1 file changed

+91
-39
lines changed

1 file changed

+91
-39
lines changed

jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookieStore.java

+91-39
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@
1717
import java.time.Instant;
1818
import java.util.ArrayList;
1919
import java.util.Collection;
20-
import java.util.Collections;
2120
import java.util.HashMap;
2221
import java.util.Iterator;
2322
import java.util.List;
2423
import java.util.Locale;
2524
import java.util.Map;
2625
import java.util.Objects;
26+
import java.util.concurrent.locks.ReadWriteLock;
27+
import java.util.concurrent.locks.ReentrantReadWriteLock;
2728
import java.util.function.Predicate;
2829

2930
import org.eclipse.jetty.util.NanoTime;
3031
import org.eclipse.jetty.util.StringUtil;
31-
import org.eclipse.jetty.util.thread.AutoLock;
3232

3333
/**
3434
* <p>A container for {@link HttpCookie}s.</p>
@@ -51,20 +51,20 @@ public interface HttpCookieStore
5151
* @param cookie the cookie to add
5252
* @return whether the cookie has been added
5353
*/
54-
public boolean add(URI uri, HttpCookie cookie);
54+
boolean add(URI uri, HttpCookie cookie);
5555

5656
/**
5757
* @return all the cookies
5858
*/
59-
public List<HttpCookie> all();
59+
List<HttpCookie> all();
6060

6161
/**
6262
* <p>Returns the cookies that match the given {@code URI}.</p>
6363
*
6464
* @param uri the {@code URI} to match against
6565
* @return a list of cookies that match the given {@code URI}
6666
*/
67-
public List<HttpCookie> match(URI uri);
67+
List<HttpCookie> match(URI uri);
6868

6969
/**
7070
* <p>Removes the cookie associated with the given {@code URI}.</p>
@@ -73,19 +73,19 @@ public interface HttpCookieStore
7373
* @param cookie the cookie to remove
7474
* @return whether the cookie has been removed
7575
*/
76-
public boolean remove(URI uri, HttpCookie cookie);
76+
boolean remove(URI uri, HttpCookie cookie);
7777

7878
/**
7979
* <p>Removes all the cookies from this store.</p>
8080
*
8181
* @return whether the store modified by this call
8282
*/
83-
public boolean clear();
83+
boolean clear();
8484

8585
/**
8686
* <p>An implementation of {@link HttpCookieStore} that does not store any cookie.</p>
8787
*/
88-
public static class Empty implements HttpCookieStore
88+
class Empty implements HttpCookieStore
8989
{
9090
@Override
9191
public boolean add(URI uri, HttpCookie cookie)
@@ -121,9 +121,9 @@ public boolean clear()
121121
/**
122122
* <p>A default implementation of {@link HttpCookieStore}.</p>
123123
*/
124-
public static class Default implements HttpCookieStore
124+
class Default implements HttpCookieStore
125125
{
126-
private final AutoLock lock = new AutoLock();
126+
private final ReadWriteLock lock = new ReentrantReadWriteLock();
127127
private final Map<String, List<StoredHttpCookie>> cookies = new HashMap<>();
128128

129129
@Override
@@ -143,7 +143,8 @@ public boolean add(URI uri, HttpCookie cookie)
143143
// This facilitates the matching algorithm.
144144
boolean[] added = new boolean[1];
145145
StoredHttpCookie storedCookie = new StoredHttpCookie(cookie, uri, resolvedDomain, resolvedPath);
146-
try (AutoLock ignored = lock.lock())
146+
lock.writeLock().lock();
147+
try
147148
{
148149
String key = resolvedDomain.toLowerCase(Locale.ENGLISH);
149150
cookies.compute(key, (k, v) ->
@@ -164,9 +165,12 @@ public boolean add(URI uri, HttpCookie cookie)
164165
v.add(storedCookie);
165166
return v;
166167
});
168+
return added[0];
169+
}
170+
finally
171+
{
172+
lock.writeLock().unlock();
167173
}
168-
169-
return added[0];
170174
}
171175

172176
private String resolveDomain(URI uri, HttpCookie cookie)
@@ -260,14 +264,19 @@ protected boolean allowDomain(String domain)
260264
@Override
261265
public List<HttpCookie> all()
262266
{
263-
try (AutoLock ignored = lock.lock())
267+
lock.readLock().lock();
268+
try
264269
{
265270
return cookies.values().stream()
266271
.flatMap(Collection::stream)
267272
.filter(Predicate.not(StoredHttpCookie::isExpired))
268273
.map(HttpCookie.class::cast)
269274
.toList();
270275
}
276+
finally
277+
{
278+
lock.readLock().unlock();
279+
}
271280
}
272281

273282
@Override
@@ -283,8 +292,10 @@ public List<HttpCookie> match(URI uri)
283292

284293
boolean secure = HttpScheme.isSecure(uri.getScheme());
285294

286-
List<HttpCookie> result = new ArrayList<>();
287-
try (AutoLock ignored = lock.lock())
295+
List<HttpCookie> result = null;
296+
Map<String, List<StoredHttpCookie>> expired = null;
297+
lock.readLock().lock();
298+
try
288299
{
289300
// Given the way cookies are stored in the Map, the matching
290301
// algorithm starts with the URI domain and iterates chopping
@@ -300,37 +311,68 @@ public List<HttpCookie> match(URI uri)
300311
while (domain != null)
301312
{
302313
List<StoredHttpCookie> stored = cookies.get(domain);
303-
Iterator<StoredHttpCookie> iterator = stored == null ? Collections.emptyIterator() : stored.iterator();
304-
while (iterator.hasNext())
314+
if (stored != null)
305315
{
306-
StoredHttpCookie cookie = iterator.next();
307-
308-
// Check and remove expired cookies.
309-
if (cookie.isExpired())
316+
for (StoredHttpCookie cookie : stored)
310317
{
311-
iterator.remove();
312-
continue;
313-
}
318+
// Check for expired cookies.
319+
if (cookie.isExpired())
320+
{
321+
if (expired == null)
322+
expired = new HashMap<>();
323+
expired.computeIfAbsent(domain, k -> new ArrayList<>()).add(cookie);
324+
continue;
325+
}
314326

315-
// Check whether the cookie is secure.
316-
if (cookie.isSecure() && !secure)
317-
continue;
327+
// Match whether the cookie is secure.
328+
if (cookie.isSecure() && !secure)
329+
continue;
318330

319-
// Match the domain.
320-
if (!domainMatches(uriDomain, cookie.domain, cookie.getWrapped().getDomain()))
321-
continue;
331+
// Match the domain.
332+
if (!domainMatches(uriDomain, cookie.domain, cookie.getWrapped().getDomain()))
333+
continue;
322334

323-
// Match the path.
324-
if (!pathMatches(path, cookie.path))
325-
continue;
335+
// Match the path.
336+
if (!pathMatches(path, cookie.path))
337+
continue;
326338

327-
result.add(cookie);
339+
if (result == null)
340+
result = new ArrayList<>();
341+
result.add(cookie);
342+
}
328343
}
329344
domain = parentDomain(domain);
330345
}
331346
}
347+
finally
348+
{
349+
lock.readLock().unlock();
350+
}
332351

333-
return result;
352+
if (expired != null)
353+
{
354+
lock.writeLock().lock();
355+
try
356+
{
357+
for (Map.Entry<String, List<StoredHttpCookie>> entry : expired.entrySet())
358+
{
359+
String domain = entry.getKey();
360+
List<StoredHttpCookie> stored = cookies.get(domain);
361+
if (stored != null)
362+
{
363+
stored.removeAll(entry.getValue());
364+
if (stored.isEmpty())
365+
cookies.remove(domain);
366+
}
367+
}
368+
}
369+
finally
370+
{
371+
lock.writeLock().unlock();
372+
}
373+
}
374+
375+
return result == null ? List.of() : result;
334376
}
335377

336378
private static boolean domainMatches(String uriDomain, String domain, String cookieDomain)
@@ -380,7 +422,8 @@ public boolean remove(URI uri, HttpCookie cookie)
380422
String resolvedPath = resolvePath(uri, cookie);
381423

382424
boolean[] removed = new boolean[1];
383-
try (AutoLock ignored = lock.lock())
425+
lock.writeLock().lock();
426+
try
384427
{
385428
String domain = uriDomain.toLowerCase(Locale.ENGLISH);
386429
while (domain != null)
@@ -411,8 +454,12 @@ public boolean remove(URI uri, HttpCookie cookie)
411454
});
412455
domain = parentDomain(domain);
413456
}
457+
return removed[0];
458+
}
459+
finally
460+
{
461+
lock.writeLock().unlock();
414462
}
415-
return removed[0];
416463
}
417464

418465
private String parentDomain(String domain)
@@ -431,13 +478,18 @@ private String parentDomain(String domain)
431478
@Override
432479
public boolean clear()
433480
{
434-
try (AutoLock ignored = lock.lock())
481+
lock.writeLock().lock();
482+
try
435483
{
436484
if (cookies.isEmpty())
437485
return false;
438486
cookies.clear();
439487
return true;
440488
}
489+
finally
490+
{
491+
lock.writeLock().unlock();
492+
}
441493
}
442494

443495
private static class StoredHttpCookie extends HttpCookie.Wrapper

0 commit comments

Comments
 (0)