summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/tests/unit/networking.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/tests/unit/networking.tcl')
-rw-r--r--examples/redis-unstable/tests/unit/networking.tcl446
1 files changed, 446 insertions, 0 deletions
diff --git a/examples/redis-unstable/tests/unit/networking.tcl b/examples/redis-unstable/tests/unit/networking.tcl
new file mode 100644
index 0000000..6a04c22
--- /dev/null
+++ b/examples/redis-unstable/tests/unit/networking.tcl
@@ -0,0 +1,446 @@
+#
+# 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
+ }
+}