18
18
package org .apache .ignite .internal .client .thin ;
19
19
20
20
import java .io .IOException ;
21
+ import java .util .Collection ;
21
22
import java .util .HashMap ;
22
23
import java .util .LinkedHashMap ;
23
24
import java .util .List ;
24
25
import java .util .Map ;
25
26
import java .util .Set ;
27
+ import java .util .SortedMap ;
28
+ import java .util .SortedSet ;
26
29
import java .util .concurrent .CompletableFuture ;
27
30
import java .util .function .Consumer ;
28
31
import java .util .function .Function ;
37
40
import javax .cache .processor .EntryProcessor ;
38
41
import javax .cache .processor .EntryProcessorException ;
39
42
import javax .cache .processor .EntryProcessorResult ;
43
+ import org .apache .ignite .IgniteLogger ;
40
44
import org .apache .ignite .binary .BinaryObjectException ;
41
45
import org .apache .ignite .cache .CachePeekMode ;
42
46
import org .apache .ignite .cache .query .ContinuousQuery ;
71
75
import org .apache .ignite .internal .util .typedef .X ;
72
76
import org .apache .ignite .internal .util .typedef .internal .A ;
73
77
import org .apache .ignite .internal .util .typedef .internal .U ;
78
+ import org .apache .ignite .transactions .TransactionConcurrency ;
79
+ import org .apache .ignite .transactions .TransactionIsolation ;
74
80
import org .jetbrains .annotations .Nullable ;
75
81
76
82
import static org .apache .ignite .internal .binary .GridBinaryMarshaller .ARR_LIST ;
77
83
import static org .apache .ignite .internal .client .thin .ProtocolVersionFeature .EXPIRY_POLICY ;
78
84
import static org .apache .ignite .internal .processors .platform .cache .expiry .PlatformExpiryPolicy .convertDuration ;
85
+ import static org .apache .ignite .transactions .TransactionConcurrency .OPTIMISTIC ;
86
+ import static org .apache .ignite .transactions .TransactionConcurrency .PESSIMISTIC ;
87
+ import static org .apache .ignite .transactions .TransactionIsolation .READ_COMMITTED ;
88
+ import static org .apache .ignite .transactions .TransactionIsolation .SERIALIZABLE ;
79
89
80
90
/**
81
91
* Implementation of {@link ClientCache} over TCP protocol.
@@ -123,19 +133,23 @@ public class TcpClientCache<K, V> implements ClientCache<K, V> {
123
133
/** JCache adapter. */
124
134
private final Cache <K , V > jCacheAdapter ;
125
135
136
+ /** */
137
+ private final IgniteLogger log ;
138
+
126
139
/** Exception thrown when a non-transactional ClientCache operation is invoked within a transaction. */
127
140
public static final String NON_TRANSACTIONAL_CLIENT_CACHE_IN_TX_ERROR_MESSAGE = "Failed to invoke a " +
128
141
"non-transactional ClientCache %s operation within a transaction." ;
129
142
130
143
/** Constructor. */
131
144
TcpClientCache (String name , ReliableChannel ch , ClientBinaryMarshaller marsh , TcpClientTransactions transactions ,
132
- ClientCacheEntryListenersRegistry lsnrsRegistry ) {
133
- this (name , ch , marsh , transactions , lsnrsRegistry , false , null );
145
+ ClientCacheEntryListenersRegistry lsnrsRegistry , IgniteLogger log ) {
146
+ this (name , ch , marsh , transactions , lsnrsRegistry , false , null , log );
134
147
}
135
148
136
149
/** Constructor. */
137
- TcpClientCache (String name , ReliableChannel ch , ClientBinaryMarshaller marsh , TcpClientTransactions transactions ,
138
- ClientCacheEntryListenersRegistry lsnrsRegistry , boolean keepBinary , ExpiryPolicy expiryPlc ) {
150
+ TcpClientCache (String name , ReliableChannel ch , ClientBinaryMarshaller marsh ,
151
+ TcpClientTransactions transactions , ClientCacheEntryListenersRegistry lsnrsRegistry , boolean keepBinary ,
152
+ ExpiryPolicy expiryPlc , IgniteLogger log ) {
139
153
this .name = name ;
140
154
this .cacheId = ClientUtils .cacheId (name );
141
155
this .ch = ch ;
@@ -151,6 +165,8 @@ public class TcpClientCache<K, V> implements ClientCache<K, V> {
151
165
jCacheAdapter = new ClientJCacheAdapter <>(this );
152
166
153
167
this .ch .registerCacheIfCustomAffinity (this .name );
168
+
169
+ this .log = log ;
154
170
}
155
171
156
172
/** {@inheritDoc} */
@@ -324,6 +340,8 @@ public class TcpClientCache<K, V> implements ClientCache<K, V> {
324
340
if (keys .isEmpty ())
325
341
return new HashMap <>();
326
342
343
+ warnIfUnordered (keys , true );
344
+
327
345
TcpClientTransaction tx = transactions .tx ();
328
346
329
347
return txAwareService (null , tx ,
@@ -340,6 +358,8 @@ public class TcpClientCache<K, V> implements ClientCache<K, V> {
340
358
if (keys .isEmpty ())
341
359
return IgniteClientFutureImpl .completedFuture (new HashMap <>());
342
360
361
+ warnIfUnordered (keys , true );
362
+
343
363
TcpClientTransaction tx = transactions .tx ();
344
364
345
365
return txAwareServiceAsync (null , tx ,
@@ -357,6 +377,8 @@ public class TcpClientCache<K, V> implements ClientCache<K, V> {
357
377
if (map .isEmpty ())
358
378
return ;
359
379
380
+ warnIfUnordered (map );
381
+
360
382
TcpClientTransaction tx = transactions .tx ();
361
383
362
384
txAwareService (null , tx ,
@@ -373,6 +395,8 @@ public class TcpClientCache<K, V> implements ClientCache<K, V> {
373
395
if (map .isEmpty ())
374
396
return IgniteClientFutureImpl .completedFuture (null );
375
397
398
+ warnIfUnordered (map );
399
+
376
400
TcpClientTransaction tx = transactions .tx ();
377
401
378
402
return txAwareServiceAsync (null , tx ,
@@ -523,6 +547,8 @@ public class TcpClientCache<K, V> implements ClientCache<K, V> {
523
547
if (keys .isEmpty ())
524
548
return ;
525
549
550
+ warnIfUnordered (keys , false );
551
+
526
552
TcpClientTransaction tx = transactions .tx ();
527
553
528
554
txAwareService (null , tx ,
@@ -542,6 +568,8 @@ public class TcpClientCache<K, V> implements ClientCache<K, V> {
542
568
if (keys .isEmpty ())
543
569
return IgniteClientFutureImpl .completedFuture (null );
544
570
571
+ warnIfUnordered (keys , false );
572
+
545
573
TcpClientTransaction tx = transactions .tx ();
546
574
547
575
return txAwareServiceAsync (null , tx ,
@@ -931,6 +959,8 @@ else if (err != null)
931
959
if (entryProc == null )
932
960
throw new NullPointerException ("entryProc" );
933
961
962
+ warnIfUnordered (keys , false );
963
+
934
964
TcpClientTransaction tx = transactions .tx ();
935
965
936
966
return txAwareService (null , tx ,
@@ -954,6 +984,8 @@ else if (err != null)
954
984
if (entryProc == null )
955
985
throw new NullPointerException ("entryProc" );
956
986
987
+ warnIfUnordered (keys , false );
988
+
957
989
TcpClientTransaction tx = transactions .tx ();
958
990
959
991
return txAwareServiceAsync (null , tx ,
@@ -1006,12 +1038,12 @@ private <T> Map<K, EntryProcessorResult<T>> readEntryProcessorResult(PayloadInpu
1006
1038
/** {@inheritDoc} */
1007
1039
@ Override public <K1 , V1 > ClientCache <K1 , V1 > withKeepBinary () {
1008
1040
return keepBinary ? (ClientCache <K1 , V1 >)this :
1009
- new TcpClientCache <>(name , ch , marsh , transactions , lsnrsRegistry , true , expiryPlc );
1041
+ new TcpClientCache <>(name , ch , marsh , transactions , lsnrsRegistry , true , expiryPlc , log );
1010
1042
}
1011
1043
1012
1044
/** {@inheritDoc} */
1013
1045
@ Override public <K1 , V1 > ClientCache <K1 , V1 > withExpirePolicy (ExpiryPolicy expirePlc ) {
1014
- return new TcpClientCache <>(name , ch , marsh , transactions , lsnrsRegistry , keepBinary , expirePlc );
1046
+ return new TcpClientCache <>(name , ch , marsh , transactions , lsnrsRegistry , keepBinary , expirePlc , log );
1015
1047
}
1016
1048
1017
1049
/** {@inheritDoc} */
@@ -1616,4 +1648,77 @@ private void checkDataReplicationSupported(ProtocolContext protocolCtx)
1616
1648
if (!protocolCtx .isFeatureSupported (ProtocolBitmaskFeature .DATA_REPLICATION_OPERATIONS ))
1617
1649
throw new ClientFeatureNotSupportedByServerException (ProtocolBitmaskFeature .DATA_REPLICATION_OPERATIONS );
1618
1650
}
1651
+
1652
+ /**
1653
+ * Warns if an unordered map is used in an operation that may lead to a distributed deadlock
1654
+ * during an explicit transaction.
1655
+ * <p>
1656
+ * This check is relevant only for explicit user-managed transactions. Implicit transactions
1657
+ * (such as those started automatically by the system) are not inspected by this method.
1658
+ * </p>
1659
+ *
1660
+ * @param m The map being used in the cache operation.
1661
+ */
1662
+ protected void warnIfUnordered (Map <?, ?> m ) {
1663
+ if (m == null || m .size () <= 1 )
1664
+ return ;
1665
+
1666
+ TcpClientTransaction tx = transactions .tx ();
1667
+
1668
+ // Only explicit transactions are checked.
1669
+ if (tx == null )
1670
+ return ;
1671
+
1672
+ if (m instanceof SortedMap )
1673
+ return ;
1674
+
1675
+ if (!canBlockTx (false , tx .concurrency (), tx .isolation ()))
1676
+ return ;
1677
+
1678
+ log .warning ("Unordered map " + m .getClass ().getName () + " is used for putAll operation on cache " +
1679
+ name + ". This can lead to a distributed deadlock. Switch to a sorted map like TreeMap instead." );
1680
+ }
1681
+
1682
+ /**
1683
+ * Warns if an unordered map is used in an operation that may lead to a distributed deadlock
1684
+ * during an explicit transaction.
1685
+ * <p>
1686
+ * This check is relevant only for explicit user-managed transactions. Implicit transactions
1687
+ * (such as those started automatically by the system) are not inspected by this method.
1688
+ * </p>
1689
+ *
1690
+ * @param coll The collection being used in the cache operation.
1691
+ * @param isGetOp {@code true} if the operation is a get (e.g., {@code getAll}).
1692
+ */
1693
+ protected void warnIfUnordered (Collection <?> coll , boolean isGetOp ) {
1694
+ if (coll == null || coll .size () <= 1 )
1695
+ return ;
1696
+
1697
+ TcpClientTransaction tx = transactions .tx ();
1698
+
1699
+ // Only explicit transactions are checked.
1700
+ if (tx == null )
1701
+ return ;
1702
+
1703
+ if (coll instanceof SortedSet )
1704
+ return ;
1705
+
1706
+ if (!canBlockTx (isGetOp , tx .concurrency (), tx .isolation ()))
1707
+ return ;
1708
+
1709
+ log .warning ("Unordered collection " + coll .getClass ().getName () +
1710
+ " is used for " + (isGetOp ? "getAll" : "" ) + " operation on cache " + name + ". " +
1711
+ "This can lead to a distributed deadlock. Switch to a sorted set like TreeSet instead." );
1712
+ }
1713
+
1714
+ /** */
1715
+ private boolean canBlockTx (boolean isGetOp , TransactionConcurrency concurrency , TransactionIsolation isolation ) {
1716
+ if (concurrency == OPTIMISTIC && isolation == SERIALIZABLE )
1717
+ return false ;
1718
+
1719
+ if (isGetOp && concurrency == PESSIMISTIC && isolation == READ_COMMITTED )
1720
+ return false ;
1721
+
1722
+ return true ;
1723
+ }
1619
1724
}
0 commit comments