4646import org .slf4j .Logger ;
4747import org .slf4j .LoggerFactory ;
4848import org .tikv .common .key .Key ;
49+ import org .tikv .common .meta .TiTimestamp ;
4950import org .tikv .common .region .TiRegion ;
5051import org .tikv .kvproto .Coprocessor ;
5152import org .tikv .kvproto .Errorpb ;
@@ -67,6 +68,10 @@ public class KVMockServer extends TikvGrpc.TikvImplBase {
6768
6869 private final Map <Key , Supplier <Kvrpcpb .KeyError .Builder >> keyErrMap = new HashMap <>();
6970
71+ private final Map <Key , Supplier <Kvrpcpb .LockInfo .Builder >> lockMap = new HashMap <>();
72+ private final Map <Long , Supplier <Kvrpcpb .CheckTxnStatusResponse .Builder >> txnStatusMap =
73+ new HashMap <>();
74+
7075 // for KV error
7176 public static final int ABORT = 1 ;
7277 public static final int RETRY = 2 ;
@@ -117,9 +122,68 @@ public void putError(String key, Supplier<Errorpb.Error.Builder> builder) {
117122 regionErrMap .put (toRawKey (key .getBytes (StandardCharsets .UTF_8 )), builder );
118123 }
119124
125+ public void removeError (String key ) {
126+ regionErrMap .remove (toRawKey (key .getBytes (StandardCharsets .UTF_8 )));
127+ }
128+
129+ // putWithLock is used to "prewrite" key-value without "commit"
130+ public void putWithLock (
131+ ByteString key , ByteString value , ByteString primaryKey , Long startTs , Long ttl ) {
132+ put (key , value );
133+
134+ Kvrpcpb .LockInfo .Builder lock =
135+ Kvrpcpb .LockInfo .newBuilder ()
136+ .setPrimaryLock (primaryKey )
137+ .setLockVersion (startTs )
138+ .setKey (key )
139+ .setLockTtl (ttl );
140+ lockMap .put (toRawKey (key ), () -> lock );
141+ }
142+
143+ public void removeLock (ByteString key ) {
144+ lockMap .remove (toRawKey (key ));
145+ }
146+
147+ public boolean hasLock (ByteString key ) {
148+ return lockMap .containsKey (toRawKey (key ));
149+ }
150+
151+ // putTxnStatus is used to save transaction status
152+ // commitTs > 0: committed
153+ // commitTs == 0 && key is empty: rollback
154+ // commitTs == 0 && key not empty: locked by key
155+ public void putTxnStatus (Long startTs , Long commitTs , ByteString key ) {
156+ if (commitTs > 0 || (commitTs == 0 && key .isEmpty ())) { // committed || rollback
157+ Kvrpcpb .CheckTxnStatusResponse .Builder txnStatus =
158+ Kvrpcpb .CheckTxnStatusResponse .newBuilder ()
159+ .setCommitVersion (commitTs )
160+ .setLockTtl (0 )
161+ .setAction (Kvrpcpb .Action .NoAction );
162+ txnStatusMap .put (startTs , () -> txnStatus );
163+ } else { // locked
164+ Kvrpcpb .LockInfo .Builder lock = lockMap .get (toRawKey (key )).get ();
165+ Kvrpcpb .CheckTxnStatusResponse .Builder txnStatus =
166+ Kvrpcpb .CheckTxnStatusResponse .newBuilder ()
167+ .setCommitVersion (commitTs )
168+ .setLockTtl (lock .getLockTtl ())
169+ .setAction (Kvrpcpb .Action .NoAction )
170+ .setLockInfo (lock );
171+ txnStatusMap .put (startTs , () -> txnStatus );
172+ }
173+ }
174+
175+ // putTxnStatus is used to save transaction status
176+ // commitTs > 0: committed
177+ // commitTs == 0: rollback
178+ public void putTxnStatus (Long startTs , Long commitTs ) {
179+ putTxnStatus (startTs , commitTs , ByteString .EMPTY );
180+ }
181+
120182 public void clearAllMap () {
121183 dataMap .clear ();
122184 regionErrMap .clear ();
185+ lockMap .clear ();
186+ txnStatusMap .clear ();
123187 }
124188
125189 private Errorpb .Error verifyContext (Context context ) throws Exception {
@@ -255,9 +319,12 @@ public void kvGet(
255319 return ;
256320 }
257321
322+ Supplier <Kvrpcpb .LockInfo .Builder > lock = lockMap .get (key );
258323 Supplier <Kvrpcpb .KeyError .Builder > errProvider = keyErrMap .remove (key );
259324 if (errProvider != null ) {
260325 builder .setError (errProvider .get ().build ());
326+ } else if (lock != null ) {
327+ builder .setError (Kvrpcpb .KeyError .newBuilder ().setLocked (lock .get ()));
261328 } else {
262329 ByteString value = dataMap .get (key );
263330 builder .setValue (value );
@@ -299,11 +366,17 @@ public void kvScan(
299366 kvs .entrySet ()
300367 .stream ()
301368 .map (
302- kv ->
303- Kvrpcpb .KvPair .newBuilder ()
304- .setKey (kv .getKey ().toByteString ())
305- .setValue (kv .getValue ())
306- .build ())
369+ kv -> {
370+ Kvrpcpb .KvPair .Builder kvBuilder =
371+ Kvrpcpb .KvPair .newBuilder ()
372+ .setKey (kv .getKey ().toByteString ())
373+ .setValue (kv .getValue ());
374+ Supplier <Kvrpcpb .LockInfo .Builder > lock = lockMap .get (kv .getKey ());
375+ if (lock != null ) {
376+ kvBuilder .setError (Kvrpcpb .KeyError .newBuilder ().setLocked (lock .get ()));
377+ }
378+ return kvBuilder .build ();
379+ })
307380 .collect (Collectors .toList ()));
308381 }
309382 responseObserver .onNext (builder .build ());
@@ -354,6 +427,96 @@ public void kvBatchGet(
354427 }
355428 }
356429
430+ @ Override
431+ public void kvCheckTxnStatus (
432+ org .tikv .kvproto .Kvrpcpb .CheckTxnStatusRequest request ,
433+ io .grpc .stub .StreamObserver <org .tikv .kvproto .Kvrpcpb .CheckTxnStatusResponse >
434+ responseObserver ) {
435+ logger .info ("KVMockServer.kvCheckTxnStatus" );
436+ try {
437+ Long startTs = request .getLockTs ();
438+ Long currentTs = request .getCurrentTs ();
439+ logger .info ("kvCheckTxnStatus for txn: " + startTs );
440+ Kvrpcpb .CheckTxnStatusResponse .Builder builder = Kvrpcpb .CheckTxnStatusResponse .newBuilder ();
441+
442+ Error e = verifyContext (request .getContext ());
443+ if (e != null ) {
444+ responseObserver .onNext (builder .setRegionError (e ).build ());
445+ responseObserver .onCompleted ();
446+ return ;
447+ }
448+
449+ Supplier <Kvrpcpb .CheckTxnStatusResponse .Builder > txnStatus = txnStatusMap .get (startTs );
450+ if (txnStatus != null ) {
451+ Kvrpcpb .CheckTxnStatusResponse resp = txnStatus .get ().build ();
452+ if (resp .getCommitVersion () == 0
453+ && resp .getLockTtl () > 0
454+ && TiTimestamp .extractPhysical (startTs ) + resp .getLockInfo ().getLockTtl ()
455+ < TiTimestamp .extractPhysical (currentTs )) {
456+ ByteString key = resp .getLockInfo ().getKey ();
457+ logger .info (
458+ String .format (
459+ "kvCheckTxnStatus rollback expired txn: %d, remove lock: %s" ,
460+ startTs , key .toStringUtf8 ()));
461+ removeLock (key );
462+ putTxnStatus (startTs , 0L , ByteString .EMPTY );
463+ resp = txnStatusMap .get (startTs ).get ().build ();
464+ }
465+ logger .info ("kvCheckTxnStatus resp: " + resp );
466+ responseObserver .onNext (resp );
467+ } else {
468+ builder .setError (
469+ Kvrpcpb .KeyError .newBuilder ()
470+ .setTxnNotFound (
471+ Kvrpcpb .TxnNotFound .newBuilder ()
472+ .setPrimaryKey (request .getPrimaryKey ())
473+ .setStartTs (startTs )));
474+ logger .info ("kvCheckTxnStatus, TxnNotFound" );
475+ responseObserver .onNext (builder .build ());
476+ }
477+ responseObserver .onCompleted ();
478+ } catch (Exception e ) {
479+ logger .error ("kvCheckTxnStatus error: " + e );
480+ responseObserver .onError (Status .INTERNAL .asRuntimeException ());
481+ }
482+ }
483+
484+ @ Override
485+ public void kvResolveLock (
486+ org .tikv .kvproto .Kvrpcpb .ResolveLockRequest request ,
487+ io .grpc .stub .StreamObserver <org .tikv .kvproto .Kvrpcpb .ResolveLockResponse > responseObserver ) {
488+ logger .info ("KVMockServer.kvResolveLock" );
489+ try {
490+ Long startTs = request .getStartVersion ();
491+ Long commitTs = request .getCommitVersion ();
492+ logger .info (
493+ String .format (
494+ "kvResolveLock for txn: %d, commitTs: %d, keys: %d" ,
495+ startTs , commitTs , request .getKeysCount ()));
496+ Kvrpcpb .ResolveLockResponse .Builder builder = Kvrpcpb .ResolveLockResponse .newBuilder ();
497+
498+ Error e = verifyContext (request .getContext ());
499+ if (e != null ) {
500+ responseObserver .onNext (builder .setRegionError (e ).build ());
501+ responseObserver .onCompleted ();
502+ return ;
503+ }
504+
505+ if (request .getKeysCount () == 0 ) {
506+ lockMap .entrySet ().removeIf (entry -> entry .getValue ().get ().getLockVersion () == startTs );
507+ } else {
508+ for (int i = 0 ; i < request .getKeysCount (); i ++) {
509+ removeLock (request .getKeys (i ));
510+ }
511+ }
512+
513+ responseObserver .onNext (builder .build ());
514+ responseObserver .onCompleted ();
515+ } catch (Exception e ) {
516+ responseObserver .onError (Status .INTERNAL .asRuntimeException ());
517+ }
518+ }
519+
357520 @ Override
358521 public void coprocessor (
359522 org .tikv .kvproto .Coprocessor .Request requestWrap ,
0 commit comments