Skip to content

Commit 6191489

Browse files
rvansagalderz
authored andcommitted
HHH-9928 Pending put leaks when the entity is not found in DB
1 parent 93d39fa commit 6191489

File tree

2 files changed

+92
-8
lines changed

2 files changed

+92
-8
lines changed

hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ public boolean beginInvalidatingRegion() {
416416
PendingPutMap entry = it.next();
417417
if (entry.acquireLock(60, TimeUnit.SECONDS)) {
418418
try {
419-
entry.invalidate(now, expirationPeriod);
419+
entry.invalidate(now);
420420
}
421421
finally {
422422
entry.releaseLock();
@@ -548,7 +548,7 @@ public boolean beginInvalidatingWithPFER(Object lockOwner, Object key, Object va
548548
continue;
549549
}
550550
long now = System.currentTimeMillis();
551-
pending.invalidate(now, expirationPeriod);
551+
pending.invalidate(now);
552552
pending.addInvalidator(lockOwner, valueForPFER, now);
553553
}
554554
finally {
@@ -638,6 +638,8 @@ private static String lockOwnerToString(Object lockOwner) {
638638
* This class is NOT THREAD SAFE. All operations on it must be performed with the lock held.
639639
*/
640640
private class PendingPutMap extends Lock {
641+
// Number of pending puts which trigger garbage collection
642+
private static final int GC_THRESHOLD = 10;
641643
private PendingPut singlePendingPut;
642644
private Map<Object, PendingPut> fullMap;
643645
private final java.util.concurrent.locks.Lock lock = new ReentrantLock();
@@ -705,6 +707,9 @@ public void put(PendingPut pendingPut) {
705707
}
706708
else {
707709
fullMap.put( pendingPut.owner, pendingPut );
710+
if (fullMap.size() >= GC_THRESHOLD) {
711+
gc();
712+
}
708713
}
709714
}
710715
else {
@@ -750,7 +755,7 @@ public void releaseLock() {
750755
lock.unlock();
751756
}
752757

753-
public void invalidate(long now, long expirationPeriod) {
758+
public void invalidate(long now) {
754759
if ( singlePendingPut != null ) {
755760
if (singlePendingPut.invalidate(now, expirationPeriod)) {
756761
singlePendingPut = null;
@@ -766,6 +771,27 @@ else if ( fullMap != null ) {
766771
}
767772
}
768773

774+
/**
775+
* Running {@link #gc()} is important when the key is regularly queried but it is not
776+
* present in DB. In such case, the putFromLoad would not be called at all and we would
777+
* leak pending puts. Cache expiration should handle the case when the pending puts
778+
* are not accessed frequently; when these are accessed, we have to do the housekeeping
779+
* internally to prevent unlimited growth of the map.
780+
* The pending puts will get their timestamps when the map reaches {@link #GC_THRESHOLD}
781+
* entries; after expiration period these will be removed completely either through
782+
* invalidation or when we try to register next pending put.
783+
*/
784+
private void gc() {
785+
assert fullMap != null;
786+
long now = System.currentTimeMillis();
787+
for ( Iterator<PendingPut> it = fullMap.values().iterator(); it.hasNext(); ) {
788+
PendingPut pp = it.next();
789+
if (pp.gc(now, expirationPeriod)) {
790+
it.remove();
791+
}
792+
}
793+
}
794+
769795
public void addInvalidator(Object owner, Object valueForPFER, long now) {
770796
assert owner != null;
771797
if (invalidators == null) {
@@ -885,6 +911,10 @@ public String toString() {
885911

886912
public boolean invalidate(long now, long expirationPeriod) {
887913
completed = true;
914+
return gc(now, expirationPeriod);
915+
}
916+
917+
public boolean gc(long now, long expirationPeriod) {
888918
if (registeredTimestamp == Long.MIN_VALUE) {
889919
registeredTimestamp = now;
890920
}

hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/access/PutFromLoadValidatorUnitTestCase.java

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import javax.transaction.TransactionManager;
1010

11+
import java.lang.reflect.Method;
12+
import java.util.Map;
1113
import java.util.concurrent.Callable;
1214
import java.util.concurrent.CountDownLatch;
1315
import java.util.concurrent.ExecutorService;
@@ -23,6 +25,8 @@
2325
import org.hibernate.engine.spi.SessionImplementor;
2426
import org.hibernate.test.cache.infinispan.functional.cluster.DualNodeJtaTransactionManagerImpl;
2527
import org.hibernate.test.cache.infinispan.util.InfinispanTestingSetup;
28+
import org.hibernate.testing.TestForIssue;
29+
import org.infinispan.configuration.cache.ConfigurationBuilder;
2630
import org.infinispan.manager.EmbeddedCacheManager;
2731
import org.infinispan.test.CacheManagerCallable;
2832
import org.infinispan.test.fwk.TestCacheManagerFactory;
@@ -35,12 +39,8 @@
3539

3640
import static org.infinispan.test.TestingUtil.withCacheManager;
3741
import static org.infinispan.test.TestingUtil.withTx;
38-
import static org.junit.Assert.assertEquals;
39-
import static org.junit.Assert.assertNotNull;
40-
import static org.junit.Assert.assertNull;
41-
import static org.junit.Assert.assertTrue;
42-
import static org.junit.Assert.fail;
4342
import static org.mockito.Mockito.mock;
43+
import static org.junit.Assert.*;
4444

4545
/**
4646
* Tests of {@link PutFromLoadValidator}.
@@ -508,4 +508,58 @@ public Void call() throws Exception {
508508
return null;
509509
}
510510
}
511+
512+
@Test
513+
@TestForIssue(jiraKey = "HHH-9928")
514+
public void testGetForNullReleasePuts() {
515+
EmbeddedCacheManager cm = TestCacheManagerFactory.createCacheManager(false);
516+
ConfigurationBuilder cb = new ConfigurationBuilder().read(InfinispanRegionFactory.PENDING_PUTS_CACHE_CONFIGURATION);
517+
cb.expiration().maxIdle(500);
518+
cm.defineConfiguration(InfinispanRegionFactory.PENDING_PUTS_CACHE_NAME, cb.build());
519+
withCacheManager(new CacheManagerCallable(cm) {
520+
@Override
521+
public void call() {
522+
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm);
523+
long lastInsert = Long.MAX_VALUE;
524+
for (int i = 0; i < 100; ++i) {
525+
lastInsert = System.currentTimeMillis();
526+
try {
527+
withTx(tm, new Callable<Object>() {
528+
@Override
529+
public Object call() throws Exception {
530+
SessionImplementor session = mock (SessionImplementor.class);
531+
testee.registerPendingPut(session, KEY1, 0);
532+
return null;
533+
}
534+
});
535+
Thread.sleep(10);
536+
} catch (Exception e) {
537+
throw new RuntimeException(e);
538+
}
539+
}
540+
String ppName = cm.getCache().getName() + "-" + InfinispanRegionFactory.PENDING_PUTS_CACHE_NAME;
541+
Map ppCache = cm.getCache(ppName, false);
542+
assertNotNull(ppCache);
543+
Object pendingPutMap = ppCache.get(KEY1);
544+
long end = System.currentTimeMillis();
545+
if (end - lastInsert > 500) {
546+
log.warn("Test took too long");
547+
return;
548+
}
549+
assertNotNull(pendingPutMap);
550+
int size;
551+
try {
552+
Method sizeMethod = pendingPutMap.getClass().getMethod("size");
553+
sizeMethod.setAccessible(true);
554+
size = (Integer) sizeMethod.invoke(pendingPutMap);
555+
} catch (Exception e) {
556+
throw new RuntimeException(e);
557+
}
558+
// some of the pending puts need to be expired by now
559+
assertTrue(size < 100);
560+
// but some are still registered
561+
assertTrue(size > 0);
562+
}
563+
});
564+
}
511565
}

0 commit comments

Comments
 (0)