BlockingCache.java

  1. /*
  2.  *    Copyright 2009-2024 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.cache.decorators;

  17. import java.util.concurrent.ConcurrentHashMap;
  18. import java.util.concurrent.CountDownLatch;
  19. import java.util.concurrent.TimeUnit;

  20. import org.apache.ibatis.cache.Cache;
  21. import org.apache.ibatis.cache.CacheException;

  22. /**
  23.  * <p>
  24.  * Simple blocking decorator
  25.  * <p>
  26.  * Simple and inefficient version of EhCache's BlockingCache decorator. It sets a lock over a cache key when the element
  27.  * is not found in cache. This way, other threads will wait until this element is filled instead of hitting the
  28.  * database.
  29.  * <p>
  30.  * By its nature, this implementation can cause deadlock when used incorrectly.
  31.  *
  32.  * @author Eduardo Macarron
  33.  */
  34. public class BlockingCache implements Cache {

  35.   private long timeout;
  36.   private final Cache delegate;
  37.   private final ConcurrentHashMap<Object, CountDownLatch> locks;

  38.   public BlockingCache(Cache delegate) {
  39.     this.delegate = delegate;
  40.     this.locks = new ConcurrentHashMap<>();
  41.   }

  42.   @Override
  43.   public String getId() {
  44.     return delegate.getId();
  45.   }

  46.   @Override
  47.   public int getSize() {
  48.     return delegate.getSize();
  49.   }

  50.   @Override
  51.   public void putObject(Object key, Object value) {
  52.     try {
  53.       delegate.putObject(key, value);
  54.     } finally {
  55.       releaseLock(key);
  56.     }
  57.   }

  58.   @Override
  59.   public Object getObject(Object key) {
  60.     acquireLock(key);
  61.     Object value = delegate.getObject(key);
  62.     if (value != null) {
  63.       releaseLock(key);
  64.     }
  65.     return value;
  66.   }

  67.   @Override
  68.   public Object removeObject(Object key) {
  69.     // despite its name, this method is called only to release locks
  70.     releaseLock(key);
  71.     return null;
  72.   }

  73.   @Override
  74.   public void clear() {
  75.     delegate.clear();
  76.   }

  77.   private void acquireLock(Object key) {
  78.     CountDownLatch newLatch = new CountDownLatch(1);
  79.     while (true) {
  80.       CountDownLatch latch = locks.putIfAbsent(key, newLatch);
  81.       if (latch == null) {
  82.         break;
  83.       }
  84.       try {
  85.         if (timeout > 0) {
  86.           boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
  87.           if (!acquired) {
  88.             throw new CacheException(
  89.                 "Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
  90.           }
  91.         } else {
  92.           latch.await();
  93.         }
  94.       } catch (InterruptedException e) {
  95.         throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
  96.       }
  97.     }
  98.   }

  99.   private void releaseLock(Object key) {
  100.     CountDownLatch latch = locks.remove(key);
  101.     if (latch == null) {
  102.       throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
  103.     }
  104.     latch.countDown();
  105.   }

  106.   public long getTimeout() {
  107.     return timeout;
  108.   }

  109.   public void setTimeout(long timeout) {
  110.     this.timeout = timeout;
  111.   }
  112. }