summaryrefslogtreecommitdiffstats
path: root/tests/unit/tracking.tcl
blob: bea8508b1e43499718dbc22ab342c7fc265962f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
# logreqres:skip because it seems many of these tests rely heavily on RESP2
start_server {tags {"tracking network logreqres:skip"}} {
    # Create a deferred client we'll use to redirect invalidation
    # messages to.
    set rd_redirection [redis_deferring_client]
    $rd_redirection client id
    set redir_id [$rd_redirection read]
    $rd_redirection subscribe __redis__:invalidate
    $rd_redirection read ; # Consume the SUBSCRIBE reply.

    # Create another client that's not used as a redirection client
    # We should always keep this client's buffer clean
    set rd [redis_deferring_client]

    # Client to be used for SET and GET commands
    # We don't read this client's buffer
    set rd_sg [redis_client] 

    proc clean_all {} {
        uplevel {
            # We should make r TRACKING off first. If r is in RESP3,
            # r FLUSH ALL will send us tracking-redir-broken or other
            # info which will not be consumed.
            r CLIENT TRACKING off
            $rd QUIT
            $rd_redirection QUIT
            set rd [redis_deferring_client]
            set rd_redirection [redis_deferring_client]
            $rd_redirection client id
            set redir_id [$rd_redirection read]
            $rd_redirection subscribe __redis__:invalidate
            $rd_redirection read ; # Consume the SUBSCRIBE reply.
            r FLUSHALL
            r HELLO 2
            r config set tracking-table-max-keys 1000000
        }
    }

    test {Clients are able to enable tracking and redirect it} {
        r CLIENT TRACKING on REDIRECT $redir_id
    } {*OK}

    test {The other connection is able to get invalidations} {
        r SET a{t} 1
        r SET b{t} 1
        r GET a{t}
        r INCR b{t} ; # This key should not be notified, since it wasn't fetched.
        r INCR a{t}
        set keys [lindex [$rd_redirection read] 2]
        assert {[llength $keys] == 1}
        assert {[lindex $keys 0] eq {a{t}}}
    }

    test {The client is now able to disable tracking} {
        # Make sure to add a few more keys in the tracking list
        # so that we can check for leaks, as a side effect.
        r MGET a{t} b{t} c{t} d{t} e{t} f{t} g{t}
        r CLIENT TRACKING off
    } {*OK}

    test {Clients can enable the BCAST mode with the empty prefix} {
        r CLIENT TRACKING on BCAST REDIRECT $redir_id
    } {*OK*}

    test {The connection gets invalidation messages about all the keys} {
        r MSET a{t} 1 b{t} 2 c{t} 3
        set keys [lsort [lindex [$rd_redirection read] 2]]
        assert {$keys eq {a{t} b{t} c{t}}}
    }

    test {Clients can enable the BCAST mode with prefixes} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on BCAST REDIRECT $redir_id PREFIX a: PREFIX b:
        r MULTI
        r INCR a:1{t}
        r INCR a:2{t}
        r INCR b:1{t}
        r INCR b:2{t}
        # we should not get this key
        r INCR c:1{t}
        r EXEC
        # Because of the internals, we know we are going to receive
        # two separated notifications for the two different prefixes.
        set keys1 [lsort [lindex [$rd_redirection read] 2]]
        set keys2 [lsort [lindex [$rd_redirection read] 2]]
        set keys [lsort [list {*}$keys1 {*}$keys2]]
        assert {$keys eq {a:1{t} a:2{t} b:1{t} b:2{t}}}
    }

    test {Adding prefixes to BCAST mode works} {
        r CLIENT TRACKING on BCAST REDIRECT $redir_id PREFIX c:
        r INCR c:1234
        set keys [lsort [lindex [$rd_redirection read] 2]]
        assert {$keys eq {c:1234}}
    }

    test {Tracking NOLOOP mode in standard mode works} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on REDIRECT $redir_id NOLOOP
        r MGET otherkey1{t} loopkey{t} otherkey2{t}
        $rd_sg SET otherkey1{t} 1; # We should get this
        r SET loopkey{t} 1 ; # We should not get this
        $rd_sg SET otherkey2{t} 1; # We should get this
        # Because of the internals, we know we are going to receive
        # two separated notifications for the two different keys.
        set keys1 [lsort [lindex [$rd_redirection read] 2]]
        set keys2 [lsort [lindex [$rd_redirection read] 2]]
        set keys [lsort [list {*}$keys1 {*}$keys2]]
        assert {$keys eq {otherkey1{t} otherkey2{t}}}
    }

    test {Tracking NOLOOP mode in BCAST mode works} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP
        $rd_sg SET otherkey1 1; # We should get this
        r SET loopkey 1 ; # We should not get this
        $rd_sg SET otherkey2 1; # We should get this
        # Because $rd_sg send command synchronously, we know we are
        # going to receive two separated notifications.
        set keys1 [lsort [lindex [$rd_redirection read] 2]]
        set keys2 [lsort [lindex [$rd_redirection read] 2]]
        set keys [lsort [list {*}$keys1 {*}$keys2]]
        assert {$keys eq {otherkey1 otherkey2}}
    }

    test {Tracking gets notification of expired keys} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP
        r SET mykey myval px 1
        r SET mykeyotherkey myval ; # We should not get it
        after 1000
        set keys [lsort [lindex [$rd_redirection read] 2]]
        assert {$keys eq {mykey}}
    }

    test {Tracking gets notification of lazy expired keys} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on BCAST REDIRECT $redir_id NOLOOP
        # Use multi-exec to expose a race where the key gets an two invalidations
        # in the same event loop, once by the client so filtered by NOLOOP, and
        # the second one by the lazy expire
        r MULTI
        r SET mykey{t} myval px 1
        r SET mykeyotherkey{t} myval ; # We should not get it
        r DEBUG SLEEP 0.1
        r GET mykey{t}
        r EXEC
        set keys [lsort [lindex [$rd_redirection read] 2]]
        assert {$keys eq {mykey{t}}}
    } {} {needs:debug}

    test {HELLO 3 reply is correct} {
        set reply [r HELLO 3]
        assert_equal [dict get $reply proto] 3
    }

    test {HELLO without protover} {
        set reply [r HELLO 3]
        assert_equal [dict get $reply proto] 3

        set reply [r HELLO]
        assert_equal [dict get $reply proto] 3

        set reply [r HELLO 2]
        assert_equal [dict get $reply proto] 2

        set reply [r HELLO]
        assert_equal [dict get $reply proto] 2

        # restore RESP3 for next test
        r HELLO 3
    }

    test {RESP3 based basic invalidation} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on
        $rd_sg SET key1 1
        r GET key1
        $rd_sg SET key1 2
        r read
    } {invalidate key1}

    test {RESP3 tracking redirection} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd_sg SET key1 1
        r GET key1
        $rd_sg SET key1 2
        set res [lindex [$rd_redirection read] 2]
        assert {$res eq {key1}}
    }

    test {Invalidations of previous keys can be redirected after switching to RESP3} {
        r HELLO 2
        $rd_sg SET key1 1
        r GET key1
        r HELLO 3
        $rd_sg SET key1 2
        set res [lindex [$rd_redirection read] 2]
        assert {$res eq {key1}}
    }

    test {Invalidations of new keys can be redirected after switching to RESP3} {
        r HELLO 3
        $rd_sg SET key1 1
        r GET key1
        $rd_sg SET key1 2
        set res [lindex [$rd_redirection read] 2]
        assert {$res eq {key1}}
    }

    test {Invalid keys should not be tracked for scripts in NOLOOP mode} {
        $rd_sg CLIENT TRACKING off
        $rd_sg CLIENT TRACKING on NOLOOP
        $rd_sg HELLO 3
        $rd_sg SET key1 1
        assert_equal "1" [$rd_sg GET key1]

        # For write command in script, invalid key should not be tracked with NOLOOP flag
        $rd_sg eval "return redis.call('set', 'key1', '2')" 1 key1
        assert_equal "2" [$rd_sg GET key1]
        $rd_sg CLIENT TRACKING off
    }

    test {Tracking only occurs for scripts when a command calls a read-only command} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on
        $rd_sg MSET key2{t} 1 key2{t} 1

        # If a script doesn't call any read command, don't track any keys
        r EVAL "redis.call('set', 'key3{t}', 'bar')" 2 key1{t} key2{t} 
        $rd_sg MSET key2{t} 2 key1{t} 2
        assert_equal "PONG" [r ping]

        # If a script calls a read command, just the read keys
        r EVAL "redis.call('get', 'key2{t}')" 2 key1{t} key2{t}
        $rd_sg MSET key2{t} 2 key3{t} 2
        assert_equal {invalidate key2{t}} [r read]
        assert_equal "PONG" [r ping]

        # RO variants work like the normal variants

        # If a RO script doesn't call any read command, don't track any keys
        r EVAL_RO "redis.call('ping')" 2 key1{t} key2{t}
        $rd_sg MSET key2{t} 2 key1{t} 2
        assert_equal "PONG" [r ping]

        # If a RO script calls a read command, just the read keys
        r EVAL_RO "redis.call('get', 'key2{t}')" 2 key1{t} key2{t}
        $rd_sg MSET key2{t} 2 key3{t} 2
        assert_equal {invalidate key2{t}} [r read]
        assert_equal "PONG" [r ping]
    }

    test {RESP3 Client gets tracking-redir-broken push message after cached key changed when rediretion client is terminated} {
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd_sg SET key1 1
        r GET key1
        $rd_redirection QUIT
        assert_equal OK [$rd_redirection read]
        $rd_sg SET key1 2
        set MAX_TRIES 100
        set res -1
        for {set i 0} {$i <= $MAX_TRIES && $res < 0} {incr i} {
            set res [lsearch -exact [r PING] "tracking-redir-broken"]
        }
        assert {$res >= 0}
        # Consume PING reply
        assert_equal PONG [r read]

        # Reinstantiating after QUIT
        set rd_redirection [redis_deferring_client]
        $rd_redirection CLIENT ID
        set redir_id [$rd_redirection read]
        $rd_redirection SUBSCRIBE __redis__:invalidate
        $rd_redirection read ; # Consume the SUBSCRIBE reply
    }

    test {Different clients can redirect to the same connection} {
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd CLIENT TRACKING on REDIRECT $redir_id 
        assert_equal OK [$rd read] ; # Consume the TRACKING reply
        $rd_sg MSET key1{t} 1 key2{t} 1
        r GET key1{t}
        $rd GET key2{t} 
        assert_equal 1 [$rd read] ; # Consume the GET reply
        $rd_sg INCR key1{t}
        $rd_sg INCR key2{t}
        set res1 [lindex [$rd_redirection read] 2]
        set res2 [lindex [$rd_redirection read] 2]
        assert {$res1 eq {key1{t}}}
        assert {$res2 eq {key2{t}}}
    }

    test {Different clients using different protocols can track the same key} {
        $rd HELLO 3 
        set reply [$rd read] ; # Consume the HELLO reply
        assert_equal 3 [dict get $reply proto]
        $rd CLIENT TRACKING on 
        assert_equal OK [$rd read] ; # Consume the TRACKING reply
        $rd_sg set key1 1
        r GET key1
        $rd GET key1 
        assert_equal 1 [$rd read] ; # Consume the GET reply
        $rd_sg INCR key1
        set res1 [lindex [$rd_redirection read] 2]
        $rd PING ; # Non redirecting client has to talk to the server in order to get invalidation message
        set res2 [lindex [split [$rd read] " "] 1] 
        assert_equal PONG [$rd read] ; # Consume the PING reply, which comes together with the invalidation message
        assert {$res1 eq {key1}}
        assert {$res2 eq {key1}}
    }

    test {No invalidation message when using OPTIN option} {
        r CLIENT TRACKING on OPTIN REDIRECT $redir_id
        $rd_sg SET key1 1
        r GET key1 ; # This key should not be notified, since OPTIN is on and CLIENT CACHING yes wasn't called
        $rd_sg SET key1 2
        # Preparing some message to consume on $rd_redirection so we don't get blocked
        r CLIENT TRACKING off
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd_sg SET key2 1
        r GET key2 ; # This key should be notified
        $rd_sg SET key2 2
        set res [lindex [$rd_redirection read] 2]
        assert {$res eq {key2}}
    }

    test {Invalidation message sent when using OPTIN option with CLIENT CACHING yes} {
        r CLIENT TRACKING on OPTIN REDIRECT $redir_id
        $rd_sg SET key1 3
        r CLIENT CACHING yes
        r GET key1
        $rd_sg SET key1 4
        set res [lindex [$rd_redirection read] 2]
        assert {$res eq {key1}}
    }

    test {Invalidation message sent when using OPTOUT option} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on OPTOUT REDIRECT $redir_id
        $rd_sg SET key1 1
        r GET key1 
        $rd_sg SET key1 2
        set res [lindex [$rd_redirection read] 2]
        assert {$res eq {key1}}
    }

    test {No invalidation message when using OPTOUT option with CLIENT CACHING no} {
        $rd_sg SET key1 1
        r CLIENT CACHING no
        r GET key1 ; # This key should not be notified, since OPTOUT is on and CLIENT CACHING no was called
        $rd_sg SET key1 2
        # Preparing some message to consume on $rd_redirection so we don't get blocked
        $rd_sg SET key2 1
        r GET key2 ; # This key should be notified
        $rd_sg SET key2 2
        set res [lindex [$rd_redirection read] 2]
        assert {$res eq {key2}}
    }

    test {Able to redirect to a RESP3 client} {
        $rd_redirection UNSUBSCRIBE __redis__:invalidate ; # Need to unsub first before we can do HELLO 3
        set res [$rd_redirection read] ; # Consume the UNSUBSCRIBE reply
        assert_equal {__redis__:invalidate} [lindex $res 1]
        $rd_redirection HELLO 3
        set res [$rd_redirection read] ; # Consume the HELLO reply
        assert_equal [dict get $reply proto] 3
        $rd_redirection SUBSCRIBE __redis__:invalidate
        set res [$rd_redirection read] ; # Consume the SUBSCRIBE reply
        assert_equal {__redis__:invalidate} [lindex $res 1]
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd_sg SET key1 1
        r GET key1
        $rd_sg INCR key1
        set res [lindex [$rd_redirection read] 1]
        assert {$res eq {key1}}
        $rd_redirection HELLO 2
        set res [$rd_redirection read] ; # Consume the HELLO reply
        assert_equal [dict get $res proto] 2
    }

    test {After switching from normal tracking to BCAST mode, no invalidation message is produced for pre-BCAST keys} {
        r CLIENT TRACKING off
        r HELLO 3
        r CLIENT TRACKING on
        $rd_sg SET key1 1
        r GET key1
        r CLIENT TRACKING off 
        r CLIENT TRACKING on BCAST
        $rd_sg INCR key1
        set inv_msg [r PING]
        set ping_reply [r read]
        assert {$inv_msg eq {invalidate key1}}
        assert {$ping_reply eq {PONG}}
    }

    test {BCAST with prefix collisions throw errors} {
        set r [redis_client] 
        catch {$r CLIENT TRACKING ON BCAST PREFIX FOOBAR PREFIX FOO} output
        assert_match {ERR Prefix 'FOOBAR'*'FOO'*} $output

        catch {$r CLIENT TRACKING ON BCAST PREFIX FOO PREFIX FOOBAR} output
        assert_match {ERR Prefix 'FOO'*'FOOBAR'*} $output

        $r CLIENT TRACKING ON BCAST PREFIX FOO PREFIX BAR
        catch {$r CLIENT TRACKING ON BCAST PREFIX FO} output
        assert_match {ERR Prefix 'FO'*'FOO'*} $output

        catch {$r CLIENT TRACKING ON BCAST PREFIX BARB} output
        assert_match {ERR Prefix 'BARB'*'BAR'*} $output

        $r CLIENT TRACKING OFF
    }

    test {hdel deliver invalidate message after response in the same connection} {
        r CLIENT TRACKING off
        r HELLO 3
        r CLIENT TRACKING on
        r HSET myhash f 1
        r HGET myhash f
        set res [r HDEL myhash f]
        assert_equal $res 1
        set res [r read]
        assert_equal $res {invalidate myhash}
    }

    test {Tracking invalidation message is not interleaved with multiple keys response} {
        r CLIENT TRACKING off
        r HELLO 3
        r CLIENT TRACKING on
        # We need disable active expire, so we can trigger lazy expire
        r DEBUG SET-ACTIVE-EXPIRE 0
        r MULTI
        r MSET x{t} 1 y{t} 2
        r PEXPIRE y{t} 100
        r GET y{t}
        r EXEC
        after 110
        # Read expired key y{t}, generate invalidate message about this key
        set res [r MGET x{t} y{t}]
        assert_equal $res {1 {}}
        # Consume the invalidate message which is after command response
        set res [r read]
        assert_equal $res {invalidate y{t}}
        r DEBUG SET-ACTIVE-EXPIRE 1
    } {OK} {needs:debug}

    test {Tracking invalidation message is not interleaved with transaction response} {
        r CLIENT TRACKING off
        r HELLO 3
        r CLIENT TRACKING on
        r MSET a{t} 1 b{t} 2
        r GET a{t}
        # Start a transaction, make a{t} generate an invalidate message
        r MULTI
        r INCR a{t}
        r GET b{t}
        set res [r EXEC]
        assert_equal $res {2 2}
        set res [r read]
        # Consume the invalidate message which is after command response
        assert_equal $res {invalidate a{t}}
    }

    test {Tracking invalidation message of eviction keys should be before response} {
        # Get the current memory limit and calculate a new limit.
        r CLIENT TRACKING off
        r HELLO 3
        r CLIENT TRACKING on

        # make the previous test is really done before sampling used_memory
        wait_lazyfree_done r

        set used [expr {[s used_memory] - [s mem_not_counted_for_evict]}]
        set limit [expr {$used+100*1024}]
        set old_policy [lindex [r config get maxmemory-policy] 1]
        r config set maxmemory $limit
        # We set policy volatile-random, so only keys with ttl will be evicted
        r config set maxmemory-policy volatile-random
        # Add a volatile key and tracking it.
        r setex volatile-key 10000 x
        r get volatile-key
        # We use SETBIT here, so we can set a big key and get the used_memory
        # bigger than maxmemory. Next command will evict volatile keys. We
        # can't use SET, as SET uses big input buffer, so it will fail.
        r setbit big-key 1600000 0 ;# this will consume 200kb
        # volatile-key is evicted before response.
        set res [r getbit big-key 0]
        assert_equal $res {invalidate volatile-key}
        set res [r read]
        assert_equal $res 0
        r config set maxmemory-policy $old_policy
        r config set maxmemory 0
    }

    test {Unblocked BLMOVE gets notification after response} {
        r RPUSH list2{t} a
        $rd HELLO 3
        $rd read
        $rd CLIENT TRACKING on
        $rd read
        # Tracking key list2{t}
        $rd LRANGE list2{t} 0 -1
        $rd read
        # We block on list1{t}
        $rd BLMOVE list1{t} list2{t} left left 0
        wait_for_blocked_clients_count 1
        # unblock $rd, list2{t} gets element and generate invalidation message
        r rpush list1{t} foo
        assert_equal [$rd read] {foo}
        assert_equal [$rd read] {invalidate list2{t}}
    }

    test {Tracking gets notification on tracking table key eviction} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on REDIRECT $redir_id NOLOOP
        r MSET key1{t} 1 key2{t} 2
        # Let the server track the two keys for us
        r MGET key1{t} key2{t}
        # Force the eviction of all the keys but one:
        r config set tracking-table-max-keys 1
        # Note that we may have other keys in the table for this client,
        # since we disabled/enabled tracking multiple time with the same
        # ID, and tracking does not do ID cleanups for performance reasons.
        # So we check that eventually we'll receive one or the other key,
        # otherwise the test will die for timeout.
        while 1 {
            set keys [lindex [$rd_redirection read] 2]
            if {$keys eq {key1{t}} || $keys eq {key2{t}}} break
        }
        # We should receive an expire notification for one of
        # the two keys (only one must remain)
        assert {$keys eq {key1{t}} || $keys eq {key2{t}}}
    }

    test {Invalidation message received for flushall} {
        clean_all
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd_sg SET key1 1
        r GET key1
        $rd_sg FLUSHALL
        set msg [$rd_redirection read]
        assert {[lindex msg 2] eq {} }
    }

    test {Invalidation message received for flushdb} {
        clean_all
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd_sg SET key1 1
        r GET key1
        $rd_sg FLUSHDB
        set msg [$rd_redirection read]
        assert {[lindex msg 2] eq {} }
    }

    test {Test ASYNC flushall} {
        clean_all
        r CLIENT TRACKING on REDIRECT $redir_id
        r GET key1
        r GET key2
        assert_equal [s 0 tracking_total_keys] 2
        $rd_sg FLUSHALL ASYNC
        assert_equal [s 0 tracking_total_keys] 0
        assert_equal [lindex [$rd_redirection read] 2] {}
    }

    test {flushdb tracking invalidation message is not interleaved with transaction response} {
        clean_all
        r HELLO 3
        r CLIENT TRACKING on
        r SET a{t} 1
        r GET a{t}
        r MULTI
        r FLUSHDB
        set res [r EXEC]
        assert_equal $res {OK}
        # Consume the invalidate message which is after command response
        r read
    } {invalidate {}}

    # Keys are defined to be evicted 100 at a time by default.
    # If after eviction the number of keys still surpasses the limit
    # defined in tracking-table-max-keys, we increases eviction 
    # effort to 200, and then 300, etc. 
    # This test tests this effort incrementation. 
    test {Server is able to evacuate enough keys when num of keys surpasses limit by more than defined initial effort} {
        clean_all
        set NUM_OF_KEYS_TO_TEST 250
        set TRACKING_TABLE_MAX_KEYS 1
        r CLIENT TRACKING on REDIRECT $redir_id
        for {set i 0} {$i < $NUM_OF_KEYS_TO_TEST} {incr i} {
            $rd_sg SET key$i $i
            r GET key$i
        }
        r config set tracking-table-max-keys $TRACKING_TABLE_MAX_KEYS
        # If not enough keys are evicted, we won't get enough invalidation
        # messages, and "$rd_redirection read" will block.
        # If too many keys are evicted, we will get too many invalidation
        # messages, and the assert will fail.
        for {set i 0} {$i < $NUM_OF_KEYS_TO_TEST - $TRACKING_TABLE_MAX_KEYS} {incr i} {
            $rd_redirection read
        }
        $rd_redirection PING
        assert {[$rd_redirection read] eq {pong {}}}
    }

    test {Tracking info is correct} {
        clean_all
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd_sg SET key1 1
        $rd_sg SET key2 2
        r GET key1 
        r GET key2
        $rd CLIENT TRACKING on BCAST PREFIX prefix:
        assert [string match *OK* [$rd read]]
        $rd_sg SET prefix:key1 1 
        $rd_sg SET prefix:key2 2
        set info [r info]
        regexp "\r\ntracking_total_items:(.*?)\r\n" $info _ total_items
        regexp "\r\ntracking_total_keys:(.*?)\r\n" $info _ total_keys
        regexp "\r\ntracking_total_prefixes:(.*?)\r\n" $info _ total_prefixes
        regexp "\r\ntracking_clients:(.*?)\r\n" $info _ tracking_clients
        assert {$total_items == 2}
        assert {$total_keys == 2}
        assert {$total_prefixes == 1}
        assert {$tracking_clients == 2}
    }

    test {CLIENT GETREDIR provides correct client id} {
        set res [r CLIENT GETREDIR]
        assert_equal $redir_id $res
        r CLIENT TRACKING off
        set res [r CLIENT GETREDIR]
        assert_equal -1 $res
        r CLIENT TRACKING on
        set res [r CLIENT GETREDIR]
        assert_equal 0 $res
    }

    test {CLIENT TRACKINGINFO provides reasonable results when tracking off} {
        r CLIENT TRACKING off
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {off} $flags
        set redirect [dict get $res redirect]
        assert_equal {-1} $redirect
        set prefixes [dict get $res prefixes]
        assert_equal {} $prefixes
    }

    test {CLIENT TRACKINGINFO provides reasonable results when tracking on} {
        r CLIENT TRACKING on
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {on} $flags
        set redirect [dict get $res redirect]
        assert_equal {0} $redirect
        set prefixes [dict get $res prefixes]
        assert_equal {} $prefixes
    }

    test {CLIENT TRACKINGINFO provides reasonable results when tracking on with options} {
        r CLIENT TRACKING on REDIRECT $redir_id noloop
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {on noloop} $flags
        set redirect [dict get $res redirect]
        assert_equal $redir_id $redirect
        set prefixes [dict get $res prefixes]
        assert_equal {} $prefixes
    }

    test {CLIENT TRACKINGINFO provides reasonable results when tracking optin} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on optin
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {on optin} $flags
        set redirect [dict get $res redirect]
        assert_equal {0} $redirect
        set prefixes [dict get $res prefixes]
        assert_equal {} $prefixes

        r CLIENT CACHING yes
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {on optin caching-yes} $flags
    }

    test {CLIENT TRACKINGINFO provides reasonable results when tracking optout} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on optout
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {on optout} $flags
        set redirect [dict get $res redirect]
        assert_equal {0} $redirect
        set prefixes [dict get $res prefixes]
        assert_equal {} $prefixes

        r CLIENT CACHING no
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {on optout caching-no} $flags
    }

    test {CLIENT TRACKINGINFO provides reasonable results when tracking bcast mode} {
        r CLIENT TRACKING off
        r CLIENT TRACKING on BCAST PREFIX foo PREFIX bar
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {on bcast} $flags
        set redirect [dict get $res redirect]
        assert_equal {0} $redirect
        set prefixes [lsort [dict get $res prefixes]]
        assert_equal {bar foo} $prefixes

        r CLIENT TRACKING off
        r CLIENT TRACKING on BCAST
        set res [r client trackinginfo]
        set prefixes [dict get $res prefixes]
        assert_equal {{}} $prefixes
    }

    test {CLIENT TRACKINGINFO provides reasonable results when tracking redir broken} {
        clean_all
        r HELLO 3
        r CLIENT TRACKING on REDIRECT $redir_id
        $rd_sg SET key1 1
        r GET key1
        $rd_redirection QUIT
        assert_equal OK [$rd_redirection read]
        $rd_sg SET key1 2
        set res [lsearch -exact [r read] "tracking-redir-broken"]
        assert {$res >= 0}
        set res [r client trackinginfo]
        set flags [dict get $res flags]
        assert_equal {on broken_redirect} $flags
        set redirect [dict get $res redirect]
        assert_equal $redir_id $redirect
        set prefixes [dict get $res prefixes]
        assert_equal {} $prefixes
    }

    test {Regression test for #11715} {
        # This issue manifests when a client invalidates keys through the max key
        # limit, which invalidates keys to get Redis below the limit, but no command is
        # then executed. This can occur in several ways but the simplest is through 
        # multi-exec which queues commands.
        clean_all
        r config set tracking-table-max-keys 2

        # The cron will invalidate keys if we're above the limit, so disable it.
        r debug pause-cron 1

        # Set up a client that has listened to 2 keys and start a multi, this
        # sets up the crash for later.
        $rd HELLO 3
        $rd read
        $rd CLIENT TRACKING on
        assert_match "OK" [$rd read]
        $rd mget "1{tag}" "2{tag}"
        assert_match "{} {}" [$rd read]
        $rd multi
        assert_match "OK" [$rd read]

        # Reduce the tracking table keys to 1, this doesn't immediately take affect, but
        # instead will apply on the next command.
        r config set tracking-table-max-keys 1

        # This command will get queued, so make sure this command doesn't crash.
        $rd ping
        $rd exec

        # Validate we got some invalidation message and then the command was queued.
        assert_match "invalidate *{tag}" [$rd read]
        assert_match "QUEUED" [$rd read]
        assert_match "PONG" [$rd read]

        r debug pause-cron 0
    } {OK} {needs:debug}

    foreach resp {3 2} {
        test "RESP$resp based basic invalidation with client reply off" {
            # This entire test is mostly irrelevant for RESP2, but we run it anyway just for some extra coverage.
            clean_all

            $rd hello $resp
            $rd read
            $rd client tracking on
            $rd read

            $rd_sg set foo bar
            $rd get foo
            $rd read

            $rd client reply off

            $rd_sg set foo bar2

            if {$resp == 3} {
                assert_equal {invalidate foo} [$rd read]
            } elseif {$resp == 2} { } ;# Just coverage

            # Verify things didn't get messed up and no unexpected reply was pushed to the client.
            $rd client reply on
            assert_equal {OK} [$rd read]
            $rd ping
            assert_equal {PONG} [$rd read]
        }
    }

    test {RESP3 based basic redirect invalidation with client reply off} {
        clean_all

        set rd_redir [redis_deferring_client]
        $rd_redir hello 3
        $rd_redir read

        $rd_redir client id
        set rd_redir_id [$rd_redir read]

        $rd client tracking on redirect $rd_redir_id
        $rd read

        $rd_sg set foo bar
        $rd get foo
        $rd read

        $rd_redir client reply off

        $rd_sg set foo bar2
        assert_equal {invalidate foo} [$rd_redir read]

        # Verify things didn't get messed up and no unexpected reply was pushed to the client.
        $rd_redir client reply on
        assert_equal {OK} [$rd_redir read]
        $rd_redir ping
        assert_equal {PONG} [$rd_redir read]

        $rd_redir close
    }

    test {RESP3 based basic tracking-redir-broken with client reply off} {
        clean_all

        $rd hello 3
        $rd read
        $rd client tracking on redirect $redir_id
        $rd read

        $rd_sg set foo bar
        $rd get foo
        $rd read

        $rd client reply off

        $rd_redirection quit
        $rd_redirection read

        $rd_sg set foo bar2

        set res [lsearch -exact [$rd read] "tracking-redir-broken"]
        assert_morethan_equal $res 0

        # Verify things didn't get messed up and no unexpected reply was pushed to the client.
        $rd client reply on
        assert_equal {OK} [$rd read]
        $rd ping
        assert_equal {PONG} [$rd read]
    }

    $rd_redirection close
    $rd_sg close
    $rd close
}

# Just some extra covergae for --log-req-res, because we do not
# run the full tracking unit in that mode
start_server {tags {"tracking network"}} {
    test {Coverage: Basic CLIENT CACHING} {
        set rd_redirection [redis_deferring_client]
        $rd_redirection client id
        set redir_id [$rd_redirection read]
        assert_equal {OK} [r CLIENT TRACKING on OPTIN REDIRECT $redir_id]
        assert_equal {OK} [r CLIENT CACHING yes]
        r CLIENT TRACKING off
    } {OK}

    test {Coverage: Basic CLIENT REPLY} {
        r CLIENT REPLY on
    } {OK}

    test {Coverage: Basic CLIENT TRACKINGINFO} {
        r CLIENT TRACKINGINFO
    } {flags off redirect -1 prefixes {}}

    test {Coverage: Basic CLIENT GETREDIR} {
        r CLIENT GETREDIR
    } {-1}
}