3232import java .util .Map ;
3333import java .util .Set ;
3434import java .util .concurrent .atomic .AtomicBoolean ;
35- import java .util .concurrent .locks .ReentrantLock ;
35+ import java .util .concurrent .atomic .LongAdder ;
36+ import java .util .concurrent .locks .ReentrantReadWriteLock ;
3637import java .util .function .Predicate ;
3738import org .apache .lucene .index .IndexReader ;
3839import org .apache .lucene .index .IndexReaderContext ;
@@ -96,14 +97,15 @@ public class LRUQueryCache implements QueryCache, Accountable {
9697 // mostRecentlyUsedQueries. This is why write operations are performed under a lock
9798 private final Set <Query > mostRecentlyUsedQueries ;
9899 private final Map <IndexReader .CacheKey , LeafCache > cache ;
99- private final ReentrantLock lock ;
100+ private final ReentrantReadWriteLock .ReadLock readLock ;
101+ private final ReentrantReadWriteLock .WriteLock writeLock ;
100102 private final float skipCacheFactor ;
103+ private final LongAdder hitCount ;
104+ private final LongAdder missCount ;
101105
102106 // these variables are volatile so that we do not need to sync reads
103107 // but increments need to be performed under the lock
104108 private volatile long ramBytesUsed ;
105- private volatile long hitCount ;
106- private volatile long missCount ;
107109 private volatile long cacheCount ;
108110 private volatile long cacheSize ;
109111
@@ -129,11 +131,15 @@ public LRUQueryCache(
129131 }
130132 this .skipCacheFactor = skipCacheFactor ;
131133
132- uniqueQueries = new LinkedHashMap <>(16 , 0.75f , true );
134+ uniqueQueries = Collections . synchronizedMap ( new LinkedHashMap <>(16 , 0.75f , true ) );
133135 mostRecentlyUsedQueries = uniqueQueries .keySet ();
134136 cache = new IdentityHashMap <>();
135- lock = new ReentrantLock ();
137+ ReentrantReadWriteLock lock = new ReentrantReadWriteLock ();
138+ writeLock = lock .writeLock ();
139+ readLock = lock .readLock ();
136140 ramBytesUsed = 0 ;
141+ hitCount = new LongAdder ();
142+ missCount = new LongAdder ();
137143 }
138144
139145 /**
@@ -177,8 +183,7 @@ public boolean test(LeafReaderContext context) {
177183 * @lucene.experimental
178184 */
179185 protected void onHit (Object readerCoreKey , Query query ) {
180- assert lock .isHeldByCurrentThread ();
181- hitCount += 1 ;
186+ hitCount .add (1 );
182187 }
183188
184189 /**
@@ -188,9 +193,8 @@ protected void onHit(Object readerCoreKey, Query query) {
188193 * @lucene.experimental
189194 */
190195 protected void onMiss (Object readerCoreKey , Query query ) {
191- assert lock .isHeldByCurrentThread ();
192196 assert query != null ;
193- missCount += 1 ;
197+ missCount . add ( 1 ) ;
194198 }
195199
196200 /**
@@ -201,7 +205,7 @@ protected void onMiss(Object readerCoreKey, Query query) {
201205 * @lucene.experimental
202206 */
203207 protected void onQueryCache (Query query , long ramBytesUsed ) {
204- assert lock .isHeldByCurrentThread ();
208+ assert writeLock .isHeldByCurrentThread ();
205209 this .ramBytesUsed += ramBytesUsed ;
206210 }
207211
@@ -212,7 +216,7 @@ protected void onQueryCache(Query query, long ramBytesUsed) {
212216 * @lucene.experimental
213217 */
214218 protected void onQueryEviction (Query query , long ramBytesUsed ) {
215- assert lock .isHeldByCurrentThread ();
219+ assert writeLock .isHeldByCurrentThread ();
216220 this .ramBytesUsed -= ramBytesUsed ;
217221 }
218222
@@ -224,7 +228,7 @@ protected void onQueryEviction(Query query, long ramBytesUsed) {
224228 * @lucene.experimental
225229 */
226230 protected void onDocIdSetCache (Object readerCoreKey , long ramBytesUsed ) {
227- assert lock .isHeldByCurrentThread ();
231+ assert writeLock .isHeldByCurrentThread ();
228232 cacheSize += 1 ;
229233 cacheCount += 1 ;
230234 this .ramBytesUsed += ramBytesUsed ;
@@ -237,7 +241,7 @@ protected void onDocIdSetCache(Object readerCoreKey, long ramBytesUsed) {
237241 * @lucene.experimental
238242 */
239243 protected void onDocIdSetEviction (Object readerCoreKey , int numEntries , long sumRamBytesUsed ) {
240- assert lock .isHeldByCurrentThread ();
244+ assert writeLock .isHeldByCurrentThread ();
241245 this .ramBytesUsed -= sumRamBytesUsed ;
242246 cacheSize -= numEntries ;
243247 }
@@ -248,14 +252,14 @@ protected void onDocIdSetEviction(Object readerCoreKey, int numEntries, long sum
248252 * @lucene.experimental
249253 */
250254 protected void onClear () {
251- assert lock .isHeldByCurrentThread ();
255+ assert writeLock .isHeldByCurrentThread ();
252256 ramBytesUsed = 0 ;
253257 cacheSize = 0 ;
254258 }
255259
256260 /** Whether evictions are required. */
257261 boolean requiresEviction () {
258- assert lock .isHeldByCurrentThread ();
262+ assert writeLock .isHeldByCurrentThread ();
259263 final int size = mostRecentlyUsedQueries .size ();
260264 if (size == 0 ) {
261265 return false ;
@@ -265,7 +269,6 @@ boolean requiresEviction() {
265269 }
266270
267271 CacheAndCount get (Query key , IndexReader .CacheHelper cacheHelper ) {
268- assert lock .isHeldByCurrentThread ();
269272 assert key instanceof BoostQuery == false ;
270273 assert key instanceof ConstantScoreQuery == false ;
271274 final IndexReader .CacheKey readerKey = cacheHelper .getKey ();
@@ -293,7 +296,7 @@ private void putIfAbsent(Query query, CacheAndCount cached, IndexReader.CacheHel
293296 assert query instanceof BoostQuery == false ;
294297 assert query instanceof ConstantScoreQuery == false ;
295298 // under a lock to make sure that mostRecentlyUsedQueries and cache remain sync'ed
296- lock .lock ();
299+ writeLock .lock ();
297300 try {
298301 Query singleton = uniqueQueries .putIfAbsent (query , query );
299302 if (singleton == null ) {
@@ -314,12 +317,12 @@ private void putIfAbsent(Query query, CacheAndCount cached, IndexReader.CacheHel
314317 leafCache .putIfAbsent (query , cached );
315318 evictIfNecessary ();
316319 } finally {
317- lock .unlock ();
320+ writeLock .unlock ();
318321 }
319322 }
320323
321324 private void evictIfNecessary () {
322- assert lock .isHeldByCurrentThread ();
325+ assert writeLock .isHeldByCurrentThread ();
323326 // under a lock to make sure that mostRecentlyUsedQueries and cache keep sync'ed
324327 if (requiresEviction ()) {
325328
@@ -347,7 +350,7 @@ private void evictIfNecessary() {
347350
348351 /** Remove all cache entries for the given core cache key. */
349352 public void clearCoreCacheKey (Object coreKey ) {
350- lock .lock ();
353+ writeLock .lock ();
351354 try {
352355 final LeafCache leafCache = cache .remove (coreKey );
353356 if (leafCache != null ) {
@@ -361,25 +364,25 @@ public void clearCoreCacheKey(Object coreKey) {
361364 }
362365 }
363366 } finally {
364- lock .unlock ();
367+ writeLock .unlock ();
365368 }
366369 }
367370
368371 /** Remove all cache entries for the given query. */
369372 public void clearQuery (Query query ) {
370- lock .lock ();
373+ writeLock .lock ();
371374 try {
372375 final Query singleton = uniqueQueries .remove (query );
373376 if (singleton != null ) {
374377 onEviction (singleton );
375378 }
376379 } finally {
377- lock .unlock ();
380+ writeLock .unlock ();
378381 }
379382 }
380383
381384 private void onEviction (Query singleton ) {
382- assert lock .isHeldByCurrentThread ();
385+ assert writeLock .isHeldByCurrentThread ();
383386 onQueryEviction (singleton , getRamBytesUsed (singleton ));
384387 for (LeafCache leafCache : cache .values ()) {
385388 leafCache .remove (singleton );
@@ -388,15 +391,15 @@ private void onEviction(Query singleton) {
388391
389392 /** Clear the content of this cache. */
390393 public void clear () {
391- lock .lock ();
394+ writeLock .lock ();
392395 try {
393396 cache .clear ();
394397 // Note that this also clears the uniqueQueries map since mostRecentlyUsedQueries is the
395398 // uniqueQueries.keySet view:
396399 mostRecentlyUsedQueries .clear ();
397400 onClear ();
398401 } finally {
399- lock .unlock ();
402+ writeLock .unlock ();
400403 }
401404 }
402405
@@ -409,7 +412,7 @@ private static long getRamBytesUsed(Query query) {
409412
410413 // pkg-private for testing
411414 void assertConsistent () {
412- lock .lock ();
415+ writeLock .lock ();
413416 try {
414417 if (requiresEviction ()) {
415418 throw new AssertionError (
@@ -455,18 +458,18 @@ void assertConsistent() {
455458 "cacheSize mismatch : " + getCacheSize () + " != " + recomputedCacheSize );
456459 }
457460 } finally {
458- lock .unlock ();
461+ writeLock .unlock ();
459462 }
460463 }
461464
462465 // pkg-private for testing
463466 // return the list of cached queries in LRU order
464467 List <Query > cachedQueries () {
465- lock .lock ();
468+ readLock .lock ();
466469 try {
467470 return new ArrayList <>(mostRecentlyUsedQueries );
468471 } finally {
469- lock .unlock ();
472+ readLock .unlock ();
470473 }
471474 }
472475
@@ -486,11 +489,11 @@ public long ramBytesUsed() {
486489
487490 @ Override
488491 public Collection <Accountable > getChildResources () {
489- lock .lock ();
492+ writeLock .lock ();
490493 try {
491494 return Accountables .namedAccountables ("segment" , cache );
492495 } finally {
493- lock .unlock ();
496+ writeLock .unlock ();
494497 }
495498 }
496499
@@ -568,7 +571,7 @@ public final long getTotalCount() {
568571 * @see #getMissCount()
569572 */
570573 public final long getHitCount () {
571- return hitCount ;
574+ return hitCount . sum () ;
572575 }
573576
574577 /**
@@ -579,7 +582,7 @@ public final long getHitCount() {
579582 * @see #getHitCount()
580583 */
581584 public final long getMissCount () {
582- return missCount ;
585+ return missCount . sum () ;
583586 }
584587
585588 /**
@@ -633,11 +636,13 @@ private class LeafCache implements Accountable {
633636 }
634637
635638 private void onDocIdSetCache (long ramBytesUsed ) {
639+ assert writeLock .isHeldByCurrentThread ();
636640 this .ramBytesUsed += ramBytesUsed ;
637641 LRUQueryCache .this .onDocIdSetCache (key , ramBytesUsed );
638642 }
639643
640644 private void onDocIdSetEviction (long ramBytesUsed ) {
645+ assert writeLock .isHeldByCurrentThread ();
641646 this .ramBytesUsed -= ramBytesUsed ;
642647 LRUQueryCache .this .onDocIdSetEviction (key , 1 , ramBytesUsed );
643648 }
@@ -649,6 +654,7 @@ CacheAndCount get(Query query) {
649654 }
650655
651656 void putIfAbsent (Query query , CacheAndCount cached ) {
657+ assert writeLock .isHeldByCurrentThread ();
652658 assert query instanceof BoostQuery == false ;
653659 assert query instanceof ConstantScoreQuery == false ;
654660 if (cache .putIfAbsent (query , cached ) == null ) {
@@ -658,6 +664,7 @@ void putIfAbsent(Query query, CacheAndCount cached) {
658664 }
659665
660666 void remove (Query query ) {
667+ assert writeLock .isHeldByCurrentThread ();
661668 assert query instanceof BoostQuery == false ;
662669 assert query instanceof ConstantScoreQuery == false ;
663670 CacheAndCount removed = cache .remove (query );
@@ -744,15 +751,15 @@ public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOExcepti
744751 }
745752
746753 // If the lock is already busy, prefer using the uncached version than waiting
747- if (lock .tryLock () == false ) {
754+ if (readLock .tryLock () == false ) {
748755 return in .scorerSupplier (context );
749756 }
750757
751758 CacheAndCount cached ;
752759 try {
753760 cached = get (in .getQuery (), cacheHelper );
754761 } finally {
755- lock .unlock ();
762+ readLock .unlock ();
756763 }
757764
758765 if (cached == null ) {
@@ -865,15 +872,15 @@ public int count(LeafReaderContext context) throws IOException {
865872 }
866873
867874 // If the lock is already busy, prefer using the uncached version than waiting
868- if (lock .tryLock () == false ) {
875+ if (readLock .tryLock () == false ) {
869876 return -1 ;
870877 }
871878
872879 CacheAndCount cached ;
873880 try {
874881 cached = get (in .getQuery (), cacheHelper );
875882 } finally {
876- lock .unlock ();
883+ readLock .unlock ();
877884 }
878885 if (cached == null ) {
879886 // Not cached
@@ -911,15 +918,15 @@ public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
911918 }
912919
913920 // If the lock is already busy, prefer using the uncached version than waiting
914- if (lock .tryLock () == false ) {
921+ if (readLock .tryLock () == false ) {
915922 return in .bulkScorer (context );
916923 }
917924
918925 CacheAndCount cached ;
919926 try {
920927 cached = get (in .getQuery (), cacheHelper );
921928 } finally {
922- lock .unlock ();
929+ readLock .unlock ();
923930 }
924931
925932 if (cached == null ) {
0 commit comments