Skip to content

Commit 0cb00db

Browse files
rvansadreab8
authored andcommitted
HHH-7898 Regression on org.hibernate.cache.infinispan.query.QueryResultsRegionImpl.put(Object, Object)
* Moved query cache update to second phase of transaction commit * Query caches are now recommended to be non-transactional (transactional ones will be slower)
1 parent 4fd7680 commit 0cb00db

File tree

5 files changed

+467
-325
lines changed

5 files changed

+467
-325
lines changed

hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,33 @@
77
package org.hibernate.cache.infinispan.query;
88

99
import javax.transaction.Transaction;
10+
import javax.transaction.TransactionManager;
11+
import javax.transaction.Status;
12+
import javax.transaction.Synchronization;
1013

14+
import org.hibernate.HibernateException;
1115
import org.hibernate.cache.CacheException;
1216
import org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion;
1317
import org.hibernate.cache.infinispan.util.Caches;
1418
import org.hibernate.cache.spi.QueryResultsRegion;
1519
import org.hibernate.cache.spi.RegionFactory;
1620
import org.hibernate.engine.spi.SessionImplementor;
21+
import org.hibernate.jdbc.WorkExecutor;
22+
import org.hibernate.jdbc.WorkExecutorVisitable;
23+
import org.hibernate.resource.transaction.TransactionCoordinator;
1724
import org.infinispan.AdvancedCache;
25+
import org.infinispan.configuration.cache.TransactionConfiguration;
1826
import org.infinispan.context.Flag;
27+
import org.infinispan.transaction.TransactionMode;
28+
import org.infinispan.util.logging.Log;
29+
import org.infinispan.util.logging.LogFactory;
30+
31+
import java.sql.Connection;
32+
import java.sql.SQLException;
33+
import java.util.HashMap;
34+
import java.util.Map;
35+
import java.util.concurrent.ConcurrentHashMap;
36+
import java.util.concurrent.ConcurrentMap;
1937

2038
/**
2139
* Region for caching query results.
@@ -25,10 +43,13 @@
2543
* @since 3.5
2644
*/
2745
public class QueryResultsRegionImpl extends BaseTransactionalDataRegion implements QueryResultsRegion {
46+
private static final Log log = LogFactory.getLog( QueryResultsRegionImpl.class );
2847

2948
private final AdvancedCache evictCache;
3049
private final AdvancedCache putCache;
3150
private final AdvancedCache getCache;
51+
private final ConcurrentMap<SessionImplementor, Map> transactionContext = new ConcurrentHashMap<SessionImplementor, Map>();
52+
private final boolean putCacheRequiresTransaction;
3253

3354
/**
3455
* Query region constructor
@@ -50,15 +71,28 @@ public QueryResultsRegionImpl(AdvancedCache cache, String name, RegionFactory fa
5071
Caches.failSilentWriteCache( cache );
5172

5273
this.getCache = Caches.failSilentReadCache( cache );
74+
75+
TransactionConfiguration transactionConfiguration = putCache.getCacheConfiguration().transaction();
76+
boolean transactional = transactionConfiguration.transactionMode() != TransactionMode.NON_TRANSACTIONAL;
77+
this.putCacheRequiresTransaction = transactional && !transactionConfiguration.autoCommit();
78+
// Since we execute the query update explicitly form transaction synchronization, the putCache does not need
79+
// to be transactional anymore (it had to be in the past to prevent revealing uncommitted changes).
80+
if (transactional) {
81+
log.warn("Use non-transactional query caches for best performance!");
82+
}
5383
}
5484

5585
@Override
5686
public void evict(Object key) throws CacheException {
87+
for (Map map : transactionContext.values()) {
88+
map.remove(key);
89+
}
5790
evictCache.remove( key );
5891
}
5992

6093
@Override
6194
public void evictAll() throws CacheException {
95+
transactionContext.clear();
6296
final Transaction tx = suspend();
6397
try {
6498
// Invalidate the local region and then go remote
@@ -89,18 +123,42 @@ public Object get(SessionImplementor session, Object key) throws CacheException
89123
// to avoid holding locks that would prevent updates.
90124
// Add a zero (or low) timeout option so we don't block
91125
// waiting for tx's that did a put to commit
126+
Object result;
92127
if ( skipCacheStore ) {
93-
return getCache.withFlags( Flag.SKIP_CACHE_STORE ).get( key );
128+
result = getCache.withFlags( Flag.SKIP_CACHE_STORE ).get( key );
94129
}
95130
else {
96-
return getCache.get( key );
131+
result = getCache.get( key );
132+
}
133+
if (result == null) {
134+
Map map = transactionContext.get(session);
135+
if (map != null) {
136+
result = map.get(key);
137+
}
97138
}
139+
return result;
98140
}
99141

100142
@Override
101143
@SuppressWarnings("unchecked")
102144
public void put(SessionImplementor session, Object key, Object value) throws CacheException {
103145
if ( checkValid() ) {
146+
// See HHH-7898: Even with FAIL_SILENTLY flag, failure to write in transaction
147+
// fails the whole transaction. It is an Infinispan quirk that cannot be fixed
148+
// ISPN-5356 tracks that. This is because if the transaction continued the
149+
// value could be committed on backup owners, including the failed operation,
150+
// and the result would not be consistent.
151+
TransactionCoordinator tc = session.getTransactionCoordinator();
152+
if (tc != null && tc.isJoined()) {
153+
tc.getLocalSynchronizations().registerSynchronization(new PostTransactionQueryUpdate(tc, session, key, value));
154+
// no need to synchronize as the transaction will be accessed by only one thread
155+
Map map = transactionContext.get(session);
156+
if (map == null) {
157+
transactionContext.put(session, map = new HashMap());
158+
}
159+
map.put(key, value);
160+
return;
161+
}
104162
// Here we don't want to suspend the tx. If we do:
105163
// 1) We might be caching query results that reflect uncommitted
106164
// changes. No tx == no WL on cache node, so other threads
@@ -120,4 +178,52 @@ public void put(SessionImplementor session, Object key, Object value) throws Cac
120178
}
121179
}
122180

181+
private class PostTransactionQueryUpdate implements Synchronization {
182+
private final TransactionCoordinator tc;
183+
private final SessionImplementor session;
184+
private final Object key;
185+
private final Object value;
186+
187+
public PostTransactionQueryUpdate(TransactionCoordinator tc, SessionImplementor session, Object key, Object value) {
188+
this.tc = tc;
189+
this.session = session;
190+
this.key = key;
191+
this.value = value;
192+
}
193+
194+
@Override
195+
public void beforeCompletion() {
196+
}
197+
198+
@Override
199+
public void afterCompletion(int status) {
200+
transactionContext.remove(session);
201+
switch (status) {
202+
case Status.STATUS_COMMITTING:
203+
case Status.STATUS_COMMITTED:
204+
try {
205+
// TODO: isolation without obtaining Connection
206+
tc.createIsolationDelegate().delegateWork(new WorkExecutorVisitable<Void>() {
207+
@Override
208+
public Void accept(WorkExecutor<Void> executor, Connection connection) throws SQLException {
209+
putCache.put(key, value);
210+
return null;
211+
}
212+
}
213+
, putCacheRequiresTransaction);
214+
}
215+
catch (HibernateException e) {
216+
// silently fail any exceptions
217+
if (log.isTraceEnabled()) {
218+
log.trace("Exception during query cache update", e);
219+
}
220+
}
221+
break;
222+
default:
223+
// it would be nicer to react only on ROLLING_BACK and ROLLED_BACK statuses
224+
// but TransactionCoordinator gives us UNKNOWN on rollback
225+
break;
226+
}
227+
}
228+
}
123229
}

hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Caches.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ public static <T> T withinTx(
8787
}
8888
}
8989

90+
public static void withinTx(TransactionManager tm, final Runnable runnable) throws Exception {
91+
withinTx(tm, new Callable<Void>() {
92+
@Override
93+
public Void call() throws Exception {
94+
runnable.run();
95+
return null;
96+
}
97+
});
98+
}
99+
90100
/**
91101
* Transform a given cache into a local cache
92102
*

hibernate-infinispan/src/main/resources/org/hibernate/cache/infinispan/builder/infinispan-configs.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@
5454
<!-- A config appropriate for query caching. Does not replicate queries. -->
5555
<local-cache name="local-query">
5656
<locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false"/>
57-
<transaction mode="NON_XA" locking="OPTIMISTIC" auto-commit="false"/>
57+
<transaction mode="NONE" />
5858
<eviction max-entries="10000" strategy="LRU"/>
5959
<expiration max-idle="100000" interval="5000"/>
6060
</local-cache>
6161

6262
<!-- A query cache that replicates queries. Replication is asynchronous. -->
6363
<replicated-cache name="replicated-query" mode="ASYNC">
6464
<locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false"/>
65-
<transaction mode="NON_XA" locking="OPTIMISTIC" auto-commit="false"/>
65+
<transaction mode="NONE" />
6666
<eviction max-entries="10000" strategy="LRU"/>
6767
<expiration max-idle="100000" interval="5000"/>
6868
</replicated-cache>

0 commit comments

Comments
 (0)