Skip to content

Commit 1eaa43d

Browse files
Copilothazendaz
andcommitted
Fix maxBytesLocalDisk not observed for Ehcache 2 (issue #62, support/ehcache2)
Co-Authored-By: hazendaz <975267+hazendaz@users.noreply.github.com>
1 parent 1bd0e7b commit 1eaa43d

File tree

4 files changed

+139
-2
lines changed

4 files changed

+139
-2
lines changed

src/main/java/org/mybatis/caches/ehcache/AbstractEhcacheCache.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public abstract class AbstractEhcacheCache implements Cache {
4949
* Instantiates a new abstract ehcache cache.
5050
*
5151
* @param id
52-
* the chache id (namespace)
52+
* the cache id (namespace)
5353
*/
5454
public AbstractEhcacheCache(final String id) {
5555
if (id == null) {
@@ -200,6 +200,47 @@ public void setMaxEntriesLocalDisk(long maxEntriesLocalDisk) {
200200
cache.getCacheConfiguration().setMaxEntriesLocalDisk(maxEntriesLocalDisk);
201201
}
202202

203+
/**
204+
* Sets the maximum bytes to be used for the disk tier. When greater than zero the cache will overflow to disk when
205+
* the heap tier is full.
206+
* <p>
207+
* In Ehcache 2, {@code maxBytesLocalDisk} and {@code maxEntriesLocalDisk} are mutually exclusive, and the disk pool
208+
* type cannot be changed on a running cache instance. This method therefore removes and recreates the underlying
209+
* cache with a fresh {@link net.sf.ehcache.config.CacheConfiguration} that applies the requested byte limit while
210+
* preserving the other settings (TTI, TTL, heap size, eviction policy) from the current configuration.
211+
* </p>
212+
*
213+
* @param maxBytesLocalDisk
214+
* the maximum number of bytes to allocate on disk. 0 means no disk tier (heap-only).
215+
*/
216+
public void setMaxBytesLocalDisk(long maxBytesLocalDisk) {
217+
net.sf.ehcache.config.CacheConfiguration current = cache.getCacheConfiguration();
218+
// Build a fresh CacheConfiguration so that onDiskPoolUsage is unset and setMaxBytesLocalDisk
219+
// can be applied without triggering the "can't switch disk pool" guard in Ehcache 2.
220+
net.sf.ehcache.config.CacheConfiguration newConfig = new net.sf.ehcache.config.CacheConfiguration(id,
221+
(int) current.getMaxEntriesLocalHeap());
222+
newConfig.setTimeToIdleSeconds(current.getTimeToIdleSeconds());
223+
newConfig.setTimeToLiveSeconds(current.getTimeToLiveSeconds());
224+
newConfig.setEternal(current.isEternal());
225+
newConfig.setMemoryStoreEvictionPolicy(current.getMemoryStoreEvictionPolicy().toString());
226+
newConfig.setMaxBytesLocalDisk(maxBytesLocalDisk);
227+
rebuildCacheWith(newConfig);
228+
}
229+
230+
/**
231+
* Removes the existing cache from the {@link CacheManager} and registers a new one built from {@code newConfig}.
232+
* Subclasses may override this method to apply additional decorators (e.g.
233+
* {@link net.sf.ehcache.constructs.blocking.BlockingCache}).
234+
*
235+
* @param newConfig
236+
* the configuration to use for the replacement cache
237+
*/
238+
protected void rebuildCacheWith(net.sf.ehcache.config.CacheConfiguration newConfig) {
239+
CACHE_MANAGER.removeCache(id);
240+
CACHE_MANAGER.addCache(new net.sf.ehcache.Cache(newConfig));
241+
this.cache = CACHE_MANAGER.getEhcache(id);
242+
}
243+
203244
/**
204245
* Sets the eviction policy. An invalid argument will set it to null.
205246
*

src/main/java/org/mybatis/caches/ehcache/EhBlockingCache.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
import net.sf.ehcache.constructs.blocking.BlockingCache;
2121

2222
/**
23-
* The Class EhBlockingCache.
23+
* Cache implementation that wraps Ehcache 2 with {@link BlockingCache} semantics.
24+
* <p>
25+
* {@link BlockingCache} acquires a per-key lock when a cache miss occurs so that only one thread computes the missing
26+
* value while others block. This prevents cache-stampede on a cold or expired entry.
27+
* </p>
2428
*
2529
* @author Iwao AVE!
2630
*/
@@ -51,4 +55,20 @@ public Object removeObject(Object key) {
5155
return null;
5256
}
5357

58+
/**
59+
* {@inheritDoc}
60+
* <p>
61+
* Re-wraps the rebuilt cache in a {@link BlockingCache} after replacing it.
62+
* </p>
63+
*/
64+
@Override
65+
protected void rebuildCacheWith(net.sf.ehcache.config.CacheConfiguration newConfig) {
66+
CACHE_MANAGER.removeCache(id);
67+
CACHE_MANAGER.addCache(new net.sf.ehcache.Cache(newConfig));
68+
Ehcache ehcache = CACHE_MANAGER.getEhcache(id);
69+
BlockingCache blockingCache = new BlockingCache(ehcache);
70+
CACHE_MANAGER.replaceCacheWithDecoratedCache(ehcache, blockingCache);
71+
this.cache = CACHE_MANAGER.getEhcache(id);
72+
}
73+
5474
}

src/test/java/org/mybatis/caches/ehcache/EhBlockingCacheTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,43 @@ void shouldTestEvictionPolicy() throws Exception {
109109
this.resetCache();
110110
}
111111

112+
@Test
113+
void shouldSetMaxBytesLocalDisk() {
114+
// Use a distinct cache ID to avoid interfering with the shared EHBLOCKINGCACHE used by other tests.
115+
// setMaxBytesLocalDisk triggers a cache rebuild because Ehcache 2 does not allow enabling
116+
// the byte-based disk pool on an already-running cache instance. The rebuild re-applies the
117+
// BlockingCache decorator so locking semantics are preserved.
118+
AbstractEhcacheCache diskCache = new EhBlockingCache("EHBLOCKINGCACHE_DISK_TEST");
119+
try {
120+
diskCache.setMaxBytesLocalDisk(10 * 1024 * 1024L); // 10 MB
121+
assertEquals(10 * 1024 * 1024L, diskCache.cache.getCacheConfiguration().getMaxBytesLocalDisk());
122+
assertEquals(0, diskCache.cache.getCacheConfiguration().getMaxEntriesLocalDisk());
123+
diskCache.putObject("key", "value");
124+
assertEquals("value", diskCache.getObject("key"));
125+
} finally {
126+
AbstractEhcacheCache.CACHE_MANAGER.removeCache("EHBLOCKINGCACHE_DISK_TEST");
127+
}
128+
}
129+
130+
@Test
131+
void shouldSupportDiskOverflow() {
132+
// Use a distinct cache ID to avoid interfering with the shared EHBLOCKINGCACHE used by other tests.
133+
AbstractEhcacheCache diskCache = new EhBlockingCache("EHBLOCKINGCACHE_DISK_OVERFLOW_TEST");
134+
try {
135+
diskCache.setMaxEntriesLocalHeap(1); // limit heap to 1 entry so others overflow to disk
136+
diskCache.setMaxBytesLocalDisk(10 * 1024 * 1024L); // 10 MB — triggers rebuild with disk tier
137+
diskCache.putObject("key1", "value1");
138+
diskCache.putObject("key2", "value2"); // key1 overflows to disk
139+
diskCache.putObject("key3", "value3"); // key2 overflows to disk
140+
// All entries must remain retrievable; heap-evicted entries should be found on disk.
141+
assertEquals("value1", diskCache.getObject("key1"));
142+
assertEquals("value2", diskCache.getObject("key2"));
143+
assertEquals("value3", diskCache.getObject("key3"));
144+
} finally {
145+
AbstractEhcacheCache.CACHE_MANAGER.removeCache("EHBLOCKINGCACHE_DISK_OVERFLOW_TEST");
146+
}
147+
}
148+
112149
@Test
113150
void shouldNotCreateCache() {
114151
assertThrows(IllegalArgumentException.class, () -> {

src/test/java/org/mybatis/caches/ehcache/EhcacheTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,45 @@ void shouldTestEvictionPolicy() throws Exception {
109109
this.resetCache();
110110
}
111111

112+
@Test
113+
void shouldSetMaxBytesLocalDisk() {
114+
// Use a distinct cache ID to avoid interfering with the shared EHCACHE used by other tests.
115+
AbstractEhcacheCache diskCache = new EhcacheCache("EHCACHE_DISK_TEST");
116+
try {
117+
// Setting maxBytesLocalDisk rebuilds the cache with the correct byte-based disk tier.
118+
diskCache.setMaxBytesLocalDisk(10 * 1024 * 1024L); // 10 MB
119+
assertEquals(10 * 1024 * 1024L, diskCache.cache.getCacheConfiguration().getMaxBytesLocalDisk());
120+
// maxEntriesLocalDisk must be 0 (the two settings are mutually exclusive in Ehcache 2).
121+
assertEquals(0, diskCache.cache.getCacheConfiguration().getMaxEntriesLocalDisk());
122+
// Cache must still be functional after the rebuild.
123+
diskCache.putObject("key", "value");
124+
assertEquals("value", diskCache.getObject("key"));
125+
} finally {
126+
AbstractEhcacheCache.CACHE_MANAGER.removeCache("EHCACHE_DISK_TEST");
127+
}
128+
}
129+
130+
@Test
131+
void shouldSupportDiskOverflow() {
132+
// Use a distinct cache ID to avoid interfering with the shared EHCACHE used by other tests.
133+
// setMaxBytesLocalDisk triggers a cache rebuild because Ehcache 2 does not allow enabling
134+
// the byte-based disk pool on an already-running cache instance.
135+
AbstractEhcacheCache diskCache = new EhcacheCache("EHCACHE_DISK_OVERFLOW_TEST");
136+
try {
137+
diskCache.setMaxEntriesLocalHeap(1); // limit heap to 1 entry so others overflow to disk
138+
diskCache.setMaxBytesLocalDisk(10 * 1024 * 1024L); // 10 MB — triggers rebuild with disk tier
139+
diskCache.putObject("key1", "value1");
140+
diskCache.putObject("key2", "value2"); // key1 overflows to disk
141+
diskCache.putObject("key3", "value3"); // key2 overflows to disk
142+
// All entries must remain retrievable; heap-evicted entries should be found on disk.
143+
assertEquals("value1", diskCache.getObject("key1"));
144+
assertEquals("value2", diskCache.getObject("key2"));
145+
assertEquals("value3", diskCache.getObject("key3"));
146+
} finally {
147+
AbstractEhcacheCache.CACHE_MANAGER.removeCache("EHCACHE_DISK_OVERFLOW_TEST");
148+
}
149+
}
150+
112151
@Test
113152
void shouldNotCreateCache() {
114153
assertThrows(IllegalArgumentException.class, () -> {

0 commit comments

Comments
 (0)