Skip to content

Commit a3d3d8b

Browse files
authored
IGNITE-28727 Create internal API method to lock cache entry with specific version (#13264)
1 parent 2a5af08 commit a3d3d8b

24 files changed

Lines changed: 1998 additions & 86 deletions

modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
import org.apache.ignite.internal.processors.cache.dr.GridCacheDrInfo;
9999
import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
100100
import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
101+
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
101102
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
102103
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalAdapter;
103104
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalEx;
@@ -159,6 +160,7 @@
159160

160161
import static org.apache.ignite.IgniteSystemProperties.IGNITE_CACHE_RETRIES_COUNT;
161162
import static org.apache.ignite.internal.GridClosureCallMode.BROADCAST;
163+
import static org.apache.ignite.internal.processors.cache.GridCacheOperation.READ;
162164
import static org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.OWNING;
163165
import static org.apache.ignite.internal.processors.dr.GridDrType.DR_LOAD;
164166
import static org.apache.ignite.internal.processors.dr.GridDrType.DR_NONE;
@@ -538,7 +540,8 @@ public void active(boolean active) {
538540

539541
/**
540542
* @param keys Keys to lock.
541-
* @param timeout Lock timeout.
543+
* @param timeout Transaction timeout.
544+
* @param waitTimeout Lock wait timeout.
542545
* @param tx Transaction.
543546
* @param isRead {@code True} for read operations.
544547
* @param retval Flag to return value.
@@ -551,6 +554,7 @@ public void active(boolean active) {
551554
public abstract IgniteInternalFuture<Boolean> txLockAsync(
552555
Collection<KeyCacheObject> keys,
553556
long timeout,
557+
long waitTimeout,
554558
IgniteTxLocalEx tx,
555559
boolean isRead,
556560
boolean retval,
@@ -3028,6 +3032,196 @@ public CacheMetricsImpl metrics0() {
30283032
}
30293033
}
30303034

3035+
/** {@inheritDoc} */
3036+
@Override public boolean lockTxEntry(CacheEntry<K, V> entry, long waitTimeout) throws IgniteCheckedException {
3037+
A.notNull(entry, "entry");
3038+
3039+
return lockTxEntryAsync(entry, waitTimeout).get();
3040+
}
3041+
3042+
/** {@inheritDoc} */
3043+
@Override public boolean lockTxEntries(Collection<CacheEntry<K, V>> entries, long waitTimeout)
3044+
throws IgniteCheckedException {
3045+
A.notNull(entries, "entries");
3046+
3047+
return lockTxEntriesAsync(entries, waitTimeout).get();
3048+
}
3049+
3050+
/** {@inheritDoc} */
3051+
@Override public IgniteInternalFuture<Boolean> lockTxEntryAsync(CacheEntry<K, V> entry, long waitTimeout) {
3052+
A.notNull(entry, "entry");
3053+
3054+
return lockTxEntriesAsync(Collections.singleton(entry), waitTimeout);
3055+
}
3056+
3057+
/** {@inheritDoc} */
3058+
@Override public IgniteInternalFuture<Boolean> lockTxEntriesAsync(
3059+
Collection<CacheEntry<K, V>> entries,
3060+
long waitTimeout
3061+
) {
3062+
A.notNull(entries, "entries");
3063+
3064+
GridNearTxLocal tx = tx();
3065+
3066+
if (tx == null)
3067+
return new GridFinishedFuture<>(
3068+
new IgniteCheckedException("Failed to acquire transactional lock without transaction."));
3069+
3070+
if (!tx.pessimistic())
3071+
return new GridFinishedFuture<>(
3072+
new IgniteCheckedException("Failed to acquire transactional lock in optimistic transaction."));
3073+
3074+
// Wait for previous per-transaction async operations to finish.
3075+
tx.txState().awaitLastFuture();
3076+
3077+
if (!tx.init())
3078+
return new GridFinishedFuture<>(new IgniteTxRollbackCheckedException(
3079+
"Failed to acquire transactional lock because transaction has been completed: " + tx));
3080+
3081+
if (entries.isEmpty())
3082+
return new GridFinishedFuture<>(true);
3083+
3084+
try {
3085+
tx.addActiveCache(ctx, false);
3086+
}
3087+
catch (IgniteCheckedException e) {
3088+
return new GridFinishedFuture<>(e);
3089+
}
3090+
3091+
Collection<KeyCacheObject> keys = new ArrayList<>(entries.size());
3092+
List<IgniteTxEntry> txEntries = new ArrayList<>(entries.size());
3093+
List<GridCacheVersion> expVers = new ArrayList<>(entries.size());
3094+
Set<IgniteTxKey> txKeys = new HashSet<>(entries.size());
3095+
3096+
CacheOperationContext opCtx = ctx.operationContextPerCall();
3097+
3098+
for (CacheEntry<K, V> entry : entries) {
3099+
A.notNull(entry, "entry");
3100+
3101+
KeyCacheObject key = ctx.toCacheKeyObject(entry.getKey());
3102+
IgniteTxKey txKey = ctx.txKey(key);
3103+
3104+
if (!txKeys.add(txKey))
3105+
continue;
3106+
3107+
IgniteTxEntry lockedTxEntry = tx.entry(txKey);
3108+
3109+
if (lockedTxEntry != null && (lockedTxEntry.op() != READ || lockedTxEntry.locked()))
3110+
continue;
3111+
3112+
if (!(entry.version() instanceof GridCacheVersion)) {
3113+
tx.removeAndUnlockTxEntries(txEntries);
3114+
3115+
return new GridFinishedFuture<>(new IgniteCheckedException("Failed to acquire transactional lock for entry with " +
3116+
"unsupported version type [entry=" + entry + ", version=" + entry.version() + ']'));
3117+
}
3118+
3119+
CacheObject val = ctx.toCacheObject(entry.getValue());
3120+
GridCacheEntryEx entryEx = ctx.isColocated() ? ctx.colocated().entryExx(key, tx.topologyVersion(), true) : entryEx(key);
3121+
3122+
IgniteTxEntry txEntry = tx.addEntry(
3123+
READ,
3124+
val,
3125+
null,
3126+
null,
3127+
entryEx,
3128+
null,
3129+
null,
3130+
true,
3131+
-1L,
3132+
-1L,
3133+
null,
3134+
opCtx != null && opCtx.skipStore(),
3135+
opCtx != null && opCtx.skipReadThrough(),
3136+
opCtx != null && opCtx.keepBinaryInInterceptor(),
3137+
opCtx != null && opCtx.isKeepBinary(),
3138+
CU.isNearEnabled(ctx)
3139+
);
3140+
3141+
keys.add(key);
3142+
txEntries.add(txEntry);
3143+
expVers.add((GridCacheVersion)entry.version());
3144+
}
3145+
3146+
if (keys.isEmpty())
3147+
return new GridFinishedFuture<>(true);
3148+
3149+
// Acquire transactional lock future from concrete cache implementation. Use txLockAsync which
3150+
// delegates to cache-specific lockAllAsync implementations for distributed caches.
3151+
long timeout = tx.remainingTime();
3152+
3153+
IgniteInternalFuture<Boolean> lockFut = txLockAsync(keys,
3154+
timeout,
3155+
waitTimeout,
3156+
tx,
3157+
/*isRead*/true,
3158+
/*retval*/false,
3159+
tx.isolation(),
3160+
/*invalidate*/false,
3161+
/*createTtl*/0L,
3162+
/*accessTtl*/0L);
3163+
3164+
IgniteInternalFuture<Boolean> res = new GridEmbeddedFuture<>(
3165+
lockFut,
3166+
(locked, ex) -> {
3167+
if (ex != null)
3168+
return new GridFinishedFuture<>(ex);
3169+
3170+
if (!locked) {
3171+
tx.removeAndUnlockTxEntries(txEntries);
3172+
3173+
return new GridFinishedFuture<>(false);
3174+
}
3175+
3176+
try {
3177+
for (int i = 0; i < txEntries.size(); i++) {
3178+
GridCacheEntryEx cached = txEntries.get(i).cached();
3179+
EntryGetResult getRes = cached.innerGetVersioned(
3180+
null,
3181+
tx,
3182+
/*update-metrics*/false,
3183+
/*event*/false,
3184+
null,
3185+
tx.resolveTaskName(),
3186+
null,
3187+
false,
3188+
null);
3189+
3190+
if (getRes == null || !expVers.get(i).equals(getRes.version())) {
3191+
tx.removeAndUnlockTxEntries(txEntries);
3192+
3193+
return new GridFinishedFuture<>(false);
3194+
}
3195+
}
3196+
3197+
return new GridFinishedFuture<>(true);
3198+
}
3199+
catch (IgniteCheckedException | GridCacheEntryRemovedException e) {
3200+
tx.removeAndUnlockTxEntries(txEntries);
3201+
3202+
return new GridFinishedFuture<>(e);
3203+
}
3204+
}
3205+
);
3206+
3207+
// Register this future in transaction's async-holder so that subsequent operations
3208+
// that call tx.txState().awaitLastFuture() will wait for it.
3209+
GridCacheAdapter.FutureHolder holder = tx.txState().lastAsyncFuture();
3210+
3211+
if (holder != null) {
3212+
holder.lock();
3213+
3214+
try {
3215+
holder.saveFuture(res);
3216+
}
3217+
finally {
3218+
holder.unlock();
3219+
}
3220+
}
3221+
3222+
return res;
3223+
}
3224+
30313225
/** {@inheritDoc} */
30323226
@Override public boolean isLockedByThread(K key) {
30333227
A.notNull(key, "key");

modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProxyImpl.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,58 @@ public IgniteInternalCache<K, V> delegate() {
13111311
}
13121312
}
13131313

1314+
/** {@inheritDoc} */
1315+
@Override public boolean lockTxEntry(CacheEntry<K, V> entry, long waitTimeout) throws IgniteCheckedException {
1316+
CacheOperationContext prev = gate.enter(opCtx);
1317+
1318+
try {
1319+
return delegate.lockTxEntry(entry, waitTimeout);
1320+
}
1321+
finally {
1322+
gate.leave(prev);
1323+
}
1324+
}
1325+
1326+
/** {@inheritDoc} */
1327+
@Override public IgniteInternalFuture<Boolean> lockTxEntryAsync(CacheEntry<K, V> entry, long waitTimeout) {
1328+
CacheOperationContext prev = gate.enter(opCtx);
1329+
1330+
try {
1331+
return delegate.lockTxEntryAsync(entry, waitTimeout);
1332+
}
1333+
finally {
1334+
gate.leave(prev);
1335+
}
1336+
}
1337+
1338+
/** {@inheritDoc} */
1339+
@Override public boolean lockTxEntries(Collection<CacheEntry<K, V>> entries, long waitTimeout)
1340+
throws IgniteCheckedException {
1341+
CacheOperationContext prev = gate.enter(opCtx);
1342+
1343+
try {
1344+
return delegate.lockTxEntries(entries, waitTimeout);
1345+
}
1346+
finally {
1347+
gate.leave(prev);
1348+
}
1349+
}
1350+
1351+
/** {@inheritDoc} */
1352+
@Override public IgniteInternalFuture<Boolean> lockTxEntriesAsync(
1353+
Collection<CacheEntry<K, V>> entries,
1354+
long waitTimeout
1355+
) {
1356+
CacheOperationContext prev = gate.enter(opCtx);
1357+
1358+
try {
1359+
return delegate.lockTxEntriesAsync(entries, waitTimeout);
1360+
}
1361+
finally {
1362+
gate.leave(prev);
1363+
}
1364+
}
1365+
13141366
/** {@inheritDoc} */
13151367
@Override public boolean isLockedByThread(K key) {
13161368
CacheOperationContext prev = gate.enter(opCtx);

modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,17 @@ public static boolean cheatCache(int id) {
178178
return cheatCacheId != 0 && id == cheatCacheId;
179179
}
180180

181+
/**
182+
* Checks whether the separate lock wait timeout expires before the transaction timeout.
183+
*
184+
* @param waitTimeout Lock wait timeout. {@code 0} means that there is no separate lock wait timeout.
185+
* @param timeout Transaction timeout. {@code 0} means that the transaction timeout is infinite.
186+
* @return {@code True} if the separate lock wait timeout expires before the transaction timeout.
187+
*/
188+
public static boolean isWaitTimeoutExpiresFirst(long waitTimeout, long timeout) {
189+
return timeout >= 0 && waitTimeout != 0 && (timeout == 0 || timeout > waitTimeout);
190+
}
191+
181192
/** System cache name. */
182193
public static final String UTILITY_CACHE_NAME = "ignite-sys-cache";
183194

modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteInternalCache.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,65 @@ public boolean lock(K key, long timeout)
13451345
*/
13461346
public boolean isLocked(K key);
13471347

1348+
/**
1349+
* Acquires a transactional lock for the cached object represented by the given entry if the current cached version
1350+
* matches the entry version. This method works only in a {@link TransactionConcurrency#PESSIMISTIC} transaction.
1351+
*
1352+
* @param entry Entry whose key, value and version should be used.
1353+
* @param waitTimeout Timeout in milliseconds to wait for lock to be acquired
1354+
* ({@code 0} to use the transaction timeout, {@code -1} for immediate failure if
1355+
* lock cannot be acquired immediately).
1356+
* @return {@code True} if lock was acquired with the same entry version.
1357+
* @throws IgniteCheckedException If lock acquisition resulted in an error.
1358+
* @throws NullPointerException If entry is {@code null}.
1359+
*/
1360+
public boolean lockTxEntry(CacheEntry<K, V> entry, long waitTimeout) throws IgniteCheckedException;
1361+
1362+
/**
1363+
* Acquires transactional locks for the cached objects represented by the given entries if all current cached
1364+
* versions match the corresponding entry versions. This method works only in a
1365+
* {@link TransactionConcurrency#PESSIMISTIC} transaction.
1366+
*
1367+
* @param entries Entries whose keys, values and versions should be used.
1368+
* @param waitTimeout Timeout in milliseconds to wait for locks to be acquired
1369+
* ({@code 0} to use the transaction timeout, {@code -1} for immediate failure if
1370+
* locks cannot be acquired immediately).
1371+
* @return {@code True} if all locks were acquired with the same entry versions.
1372+
* @throws IgniteCheckedException If lock acquisition resulted in an error.
1373+
* @throws NullPointerException If entries is {@code null}.
1374+
*/
1375+
public boolean lockTxEntries(Collection<CacheEntry<K, V>> entries, long waitTimeout) throws IgniteCheckedException;
1376+
1377+
/**
1378+
* Asynchronously acquires a transactional lock for the cached object represented by the given entry if the current
1379+
* cached version matches the entry version. This method works only in a
1380+
* {@link TransactionConcurrency#PESSIMISTIC} transaction.
1381+
*
1382+
* @param entry Entry whose key, value and version should be used.
1383+
* @param waitTimeout Timeout in milliseconds to wait for lock to be acquired
1384+
* ({@code 0} to use the transaction timeout, {@code -1} for immediate failure if
1385+
* lock cannot be acquired immediately).
1386+
* @return Future that resolves to {@code true} if the lock was acquired and the versions matched, or to
1387+
* {@code false} otherwise.
1388+
* @throws NullPointerException If entry is {@code null}.
1389+
*/
1390+
public IgniteInternalFuture<Boolean> lockTxEntryAsync(CacheEntry<K, V> entry, long waitTimeout);
1391+
1392+
/**
1393+
* Asynchronously acquires transactional locks for the cached objects represented by the given entries if all
1394+
* current cached versions match the corresponding entry versions. This method works only in a
1395+
* {@link TransactionConcurrency#PESSIMISTIC} transaction.
1396+
*
1397+
* @param entries Entries whose keys, values and versions should be used.
1398+
* @param waitTimeout Timeout in milliseconds to wait for locks to be acquired
1399+
* ({@code 0} to use the transaction timeout, {@code -1} for immediate failure if
1400+
* locks cannot be acquired immediately).
1401+
* @return Future that resolves to {@code true} if all locks were acquired and all versions matched, or to
1402+
* {@code false} otherwise.
1403+
* @throws NullPointerException If entries is {@code null}.
1404+
*/
1405+
public IgniteInternalFuture<Boolean> lockTxEntriesAsync(Collection<CacheEntry<K, V>> entries, long waitTimeout);
1406+
13481407
/**
13491408
* Checks if current thread owns a lock on this key.
13501409
* <p>

0 commit comments

Comments
 (0)