# # Copyright (c) 2009-Present, Redis Ltd. # All rights reserved. # # Copyright (c) 2025-present, Valkey contributors. # All rights reserved. # # Licensed under your choice of (a) the Redis Source Available License 2.0 # (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the # GNU Affero General Public License v3 (AGPLv3). # # Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. # source tests/support/cli.tcl test {CONFIG SET port number} { start_server {} { if {$::tls} { set port_cfg tls-port} else { set port_cfg port } # available port set avail_port [find_available_port $::baseport $::portcount] set rd [redis [srv 0 host] [srv 0 port] 0 $::tls] $rd CONFIG SET $port_cfg $avail_port $rd close set rd [redis [srv 0 host] $avail_port 0 $::tls] $rd PING # already inuse port catch {$rd CONFIG SET $port_cfg $::test_server_port} e assert_match {*Unable to listen on this port*} $e $rd close # make sure server still listening on the previous port set rd [redis [srv 0 host] $avail_port 0 $::tls] $rd PING $rd close } } {} {external:skip} test {CONFIG SET bind address} { start_server {} { # non-valid address catch {r CONFIG SET bind "999.999.999.999"} e assert_match {*Failed to bind to specified addresses*} $e # make sure server still bound to the previous address set rd [redis [srv 0 host] [srv 0 port] 0 $::tls] $rd PING $rd close } } {} {external:skip} # Attempt to connect to host using a client bound to bindaddr, # and return a non-zero value if successful within specified # millisecond timeout, or zero otherwise. proc test_loopback {host bindaddr timeout} { if {[exec uname] != {Linux}} { return 0 } after $timeout set ::test_loopback_state timeout if {[catch { set server_sock [socket -server accept 0] set port [lindex [fconfigure $server_sock -sockname] 2] } err]} { return 0 } proc accept {channel clientaddr clientport} { set ::test_loopback_state "connected" close $channel } if {[catch {set client_sock [socket -async -myaddr $bindaddr $host $port]} err]} { puts "test_loopback: Client connect failed: $err" } else { close $client_sock } vwait ::test_loopback_state close $server_sock return [expr {$::test_loopback_state == {connected}}] } test {CONFIG SET bind-source-addr} { if {[test_loopback 127.0.0.1 127.0.0.2 1000]} { start_server {} { start_server {} { set replica [srv 0 client] set master [srv -1 client] $master config set protected-mode no $replica config set bind-source-addr 127.0.0.2 $replica replicaof [srv -1 host] [srv -1 port] wait_for_condition 50 100 { [s 0 master_link_status] eq {up} } else { fail "Replication not started." } assert_match {*ip=127.0.0.2*} [s -1 slave0] } } } else { if {$::verbose} { puts "Skipping bind-source-addr test." } } } {} {external:skip} start_server {config "minimal.conf" tags {"external:skip"}} { test {Default bind address configuration handling} { # Default is explicit and sane assert_equal "* -::*" [lindex [r CONFIG GET bind] 1] # CONFIG REWRITE acknowledges this as a default r CONFIG REWRITE assert_equal 0 [count_message_lines [srv 0 config_file] bind] # Removing the bind address works r CONFIG SET bind "" assert_equal "" [lindex [r CONFIG GET bind] 1] # No additional clients can connect catch {redis_client} err assert_match {*connection refused*} $err # CONFIG REWRITE handles empty bindaddr r CONFIG REWRITE assert_equal 1 [count_message_lines [srv 0 config_file] bind] # Make sure we're able to restart restart_server 0 0 0 0 # Make sure bind parameter is as expected and server handles binding # accordingly. # (it seems that rediscli_exec behaves differently in RESP3, possibly # because CONFIG GET returns a dict instead of a list so redis-cli emits # it in a single line) if {$::force_resp3} { assert_equal {{bind }} [rediscli_exec 0 config get bind] } else { assert_equal {bind {}} [rediscli_exec 0 config get bind] } catch {reconnect 0} err assert_match {*connection refused*} $err assert_equal {OK} [rediscli_exec 0 config set bind *] reconnect 0 r ping } {PONG} test {Protected mode works as expected} { # Get a non-loopback address of this instance for this test. set myaddr [get_nonloopback_addr] if {$myaddr != "" && ![string match {127.*} $myaddr]} { # Non-loopback client should fail by default set r2 [get_nonloopback_client] catch {$r2 ping} err assert_match {*DENIED*} $err # Bind configuration should not matter assert_equal {OK} [r config set bind "*"] set r2 [get_nonloopback_client] catch {$r2 ping} err assert_match {*DENIED*} $err # Setting a password should disable protected mode assert_equal {OK} [r config set requirepass "secret"] set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] assert_equal {OK} [$r2 auth secret] assert_equal {PONG} [$r2 ping] # Clearing the password re-enables protected mode assert_equal {OK} [r config set requirepass ""] set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] assert_match {*DENIED*} $err # Explicitly disabling protected-mode works assert_equal {OK} [r config set protected-mode no] set r2 [redis $myaddr [srv 0 "port"] 0 $::tls] assert_equal {PONG} [$r2 ping] } } } start_server {config "minimal.conf" tags {"external:skip"} overrides {enable-debug-command {yes} io-threads 2}} { set server_pid [s process_id] # Since each thread may perform memory prefetch independently, this test is # only run when the number of IO threads is 2 to ensure deterministic results. if {[r config get io-threads] eq "io-threads 2"} { test {prefetch works as expected when killing a client from the middle of prefetch commands batch} { # Create 16 (prefetch batch size) +1 clients for {set i 0} {$i < 16} {incr i} { set rd$i [redis_deferring_client] } # set a key that will be later be prefetch r set a 0 # Get the client ID of rd4 $rd4 client id set rd4_id [$rd4 read] # Create a batch of commands by suspending the server for a while # before responding to the first command pause_process $server_pid # The first client will kill the fourth client $rd0 client kill id $rd4_id # Send set commands for all clients except the first for {set i 1} {$i < 16} {incr i} { [set rd$i] set $i $i [set rd$i] flush } # Resume the server resume_process $server_pid # Read the results assert_equal {1} [$rd0 read] catch {$rd4 read} res if {$res eq "OK"} { # maybe OK then err, we can not control the order of execution catch {$rd4 read} err } else { set err $res } assert_match {I/O error reading reply} $err # verify the prefetch stats are as expected set info [r info stats] set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] assert_range $prefetch_entries 2 15; # With slower machines, the number of prefetch entries can be lower set prefetch_batches [getInfoProperty $info io_threaded_total_prefetch_batches] assert_range $prefetch_batches 1 7; # With slower machines, the number of batches can be higher # verify other clients are working as expected for {set i 1} {$i < 16} {incr i} { if {$i != 4} { ;# 4th client was killed [set rd$i] get $i assert_equal {OK} [[set rd$i] read] assert_equal $i [[set rd$i] read] } } } test {prefetch works as expected when changing the batch size while executing the commands batch} { # Create 16 (default prefetch batch size) clients for {set i 0} {$i < 16} {incr i} { set rd$i [redis_deferring_client] } # Create a batch of commands by suspending the server for a while # before responding to the first command pause_process $server_pid # Send set commands for all clients the 5th client will change the prefetch batch size for {set i 0} {$i < 16} {incr i} { if {$i == 4} { [set rd$i] config set prefetch-batch-max-size 1 } [set rd$i] set a $i [set rd$i] flush } # Resume the server resume_process $server_pid # Read the results for {set i 0} {$i < 16} {incr i} { assert_equal {OK} [[set rd$i] read] [set rd$i] close } # assert the configured prefetch batch size was changed assert {[r config get prefetch-batch-max-size] eq "prefetch-batch-max-size 1"} } proc do_prefetch_batch {server_pid batch_size} { # Create clients for {set i 0} {$i < $batch_size} {incr i} { set rd$i [redis_deferring_client] } # Suspend the server to batch the commands pause_process $server_pid # Send commands from all clients for {set i 0} {$i < $batch_size} {incr i} { [set rd$i] set a $i [set rd$i] flush } # Resume the server to process the batch resume_process $server_pid # Verify responses for {set i 0} {$i < $batch_size} {incr i} { assert_equal {OK} [[set rd$i] read] [set rd$i] close } } test {no prefetch when the batch size is set to 0} { # set the batch size to 0 r config set prefetch-batch-max-size 0 # save the current value of prefetch entries set info [r info stats] set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] do_prefetch_batch $server_pid 16 # assert the prefetch entries did not change set info [r info stats] set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] assert_equal $prefetch_entries $new_prefetch_entries } test {Prefetch can resume working when the configuration option is set to a non-zero value} { # save the current value of prefetch entries set info [r info stats] set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] # set the batch size to 0 r config set prefetch-batch-max-size 16 do_prefetch_batch $server_pid 16 # assert the prefetch entries did not change set info [r info stats] set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] # With slower machines, the number of prefetch entries can be lower assert_range $new_prefetch_entries [expr {$prefetch_entries + 2}] [expr {$prefetch_entries + 16}] } test {Prefetch works with batch size greater than 16 (buffer overflow regression test)} { # save the current value of prefetch entries set info [r info stats] set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] # set the batch size to a value greater than the old hardcoded limit of 16 r config set prefetch-batch-max-size 64 # Create a batch with more than 16 clients to trigger the old buffer overflow do_prefetch_batch $server_pid 64 # verify the prefetch entries increased set info [r info stats] set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] # With slower machines, the number of prefetch entries can be lower assert_range $new_prefetch_entries [expr {$prefetch_entries + 2}] [expr {$prefetch_entries + 64}] } test {Prefetch works with maximum batch size of 128 and client number larger than batch size} { # save the current value of prefetch entries set info [r info stats] set prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] # set the batch size to the maximum allowed value r config set prefetch-batch-max-size 128 # Create a batch with 300 clients to test the maximum limit do_prefetch_batch $server_pid 300 # verify the prefetch entries increased set info [r info stats] set new_prefetch_entries [getInfoProperty $info io_threaded_total_prefetch_entries] # With slower machines, the number of prefetch entries can be lower assert_range $new_prefetch_entries [expr {$prefetch_entries + 2}] [expr {$prefetch_entries + 300}] } } } start_server {tags {"timeout external:skip"}} { test {Multiple clients idle timeout test} { # set client timeout to 1 second r config set timeout 1 # create multiple client connections set clients {} set num_clients 10 for {set i 0} {$i < $num_clients} {incr i} { set client [redis_deferring_client] $client ping assert_equal "PONG" [$client read] lappend clients $client } assert_equal [llength $clients] $num_clients # wait for 2.5 seconds after 2500 # try to send commands to all clients - they should all fail due to timeout set disconnected_count 0 foreach client $clients { $client ping if {[catch {$client read} err]} { incr disconnected_count # expected error patterns for connection timeout assert_match {*I/O error*} $err } catch {$client close} } # all clients should have been disconnected due to timeout assert_equal $disconnected_count $num_clients # redis server still works well reconnect assert_equal "PONG" [r ping] } } test {Pending command pool expansion and shrinking} { start_server {overrides {loglevel debug io-threads 1} tags {external:skip}} { set rd1 [redis_deferring_client] set rd2 [redis_deferring_client] # Client1 sends 16 commands in pipeline, and was blocked at the first command set buf "" append buf "blpop mylist 0\r\n" for {set i 1} {$i < 16} {incr i} { append buf "set key$i value$i\r\n" } $rd1 write $buf $rd1 flush wait_for_blocked_clients_count 1 # Client2 sends 1 command, this will trigger pending command pool expansion # from 16 to 32 since A client has used up all 16 commands in the command pool. $rd2 set bkey bvalue assert_equal {OK} [$rd2 read] # Unblock client1, allowing it to return all pending commands back to the pool. r lpush mylist unblock_value assert_equal {mylist unblock_value} [$rd1 read] for {set i 1} {$i < 16} {incr i} { assert_equal {OK} [$rd1 read] } # Wait for the pending command pool to shrink back to 16 due to low utilization. wait_for_log_messages 0 {"*Shrunk pending command pool: capacity 32->16*"} 0 10 1000 $rd1 close $rd2 close } }