2
0

instances.tcl 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. # Multi-instance test framework.
  2. # This is used in order to test Sentinel and Redis Cluster, and provides
  3. # basic capabilities for spawning and handling N parallel Redis / Sentinel
  4. # instances.
  5. #
  6. # Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
  7. # This software is released under the BSD License. See the COPYING file for
  8. # more information.
  9. package require Tcl 8.5
  10. set tcl_precision 17
  11. source ../support/redis.tcl
  12. source ../support/util.tcl
  13. source ../support/server.tcl
  14. source ../support/test.tcl
  15. set ::verbose 0
  16. set ::valgrind 0
  17. set ::tls 0
  18. set ::pause_on_error 0
  19. set ::dont_clean 0
  20. set ::simulate_error 0
  21. set ::failed 0
  22. set ::sentinel_instances {}
  23. set ::redis_instances {}
  24. set ::global_config {}
  25. set ::sentinel_base_port 20000
  26. set ::redis_base_port 30000
  27. set ::redis_port_count 1024
  28. set ::host "127.0.0.1"
  29. set ::leaked_fds_file [file normalize "tmp/leaked_fds.txt"]
  30. set ::pids {} ; # We kill everything at exit
  31. set ::dirs {} ; # We remove all the temp dirs at exit
  32. set ::run_matching {} ; # If non empty, only tests matching pattern are run.
  33. if {[catch {cd tmp}]} {
  34. puts "tmp directory not found."
  35. puts "Please run this test from the Redis source root."
  36. exit 1
  37. }
  38. # Execute the specified instance of the server specified by 'type', using
  39. # the provided configuration file. Returns the PID of the process.
  40. proc exec_instance {type dirname cfgfile} {
  41. if {$type eq "redis"} {
  42. set prgname redis-server
  43. } elseif {$type eq "sentinel"} {
  44. set prgname redis-sentinel
  45. } else {
  46. error "Unknown instance type."
  47. }
  48. set errfile [file join $dirname err.txt]
  49. if {$::valgrind} {
  50. set pid [exec valgrind --track-origins=yes --suppressions=../../../src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full ../../../src/${prgname} $cfgfile 2>> $errfile &]
  51. } else {
  52. set pid [exec ../../../src/${prgname} $cfgfile 2>> $errfile &]
  53. }
  54. return $pid
  55. }
  56. # Spawn a redis or sentinel instance, depending on 'type'.
  57. proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
  58. for {set j 0} {$j < $count} {incr j} {
  59. set port [find_available_port $base_port $::redis_port_count]
  60. # plaintext port (only used for TLS cluster)
  61. set pport 0
  62. # Create a directory for this instance.
  63. set dirname "${type}_${j}"
  64. lappend ::dirs $dirname
  65. catch {exec rm -rf $dirname}
  66. file mkdir $dirname
  67. # Write the instance config file.
  68. set cfgfile [file join $dirname $type.conf]
  69. if {$base_conf_file ne ""} {
  70. file copy -- $base_conf_file $cfgfile
  71. set cfg [open $cfgfile a+]
  72. } else {
  73. set cfg [open $cfgfile w]
  74. }
  75. if {$::tls} {
  76. puts $cfg "tls-port $port"
  77. puts $cfg "tls-replication yes"
  78. puts $cfg "tls-cluster yes"
  79. # plaintext port, only used by plaintext clients in a TLS cluster
  80. set pport [find_available_port $base_port $::redis_port_count]
  81. puts $cfg "port $pport"
  82. puts $cfg [format "tls-cert-file %s/../../tls/server.crt" [pwd]]
  83. puts $cfg [format "tls-key-file %s/../../tls/server.key" [pwd]]
  84. puts $cfg [format "tls-client-cert-file %s/../../tls/client.crt" [pwd]]
  85. puts $cfg [format "tls-client-key-file %s/../../tls/client.key" [pwd]]
  86. puts $cfg [format "tls-dh-params-file %s/../../tls/redis.dh" [pwd]]
  87. puts $cfg [format "tls-ca-cert-file %s/../../tls/ca.crt" [pwd]]
  88. puts $cfg "loglevel debug"
  89. } else {
  90. puts $cfg "port $port"
  91. }
  92. puts $cfg "dir ./$dirname"
  93. puts $cfg "logfile log.txt"
  94. # Add additional config files
  95. foreach directive $conf {
  96. puts $cfg $directive
  97. }
  98. dict for {name val} $::global_config {
  99. puts $cfg "$name $val"
  100. }
  101. close $cfg
  102. # Finally exec it and remember the pid for later cleanup.
  103. set retry 100
  104. while {$retry} {
  105. set pid [exec_instance $type $dirname $cfgfile]
  106. # Check availability
  107. if {[server_is_up 127.0.0.1 $port 100] == 0} {
  108. puts "Starting $type #$j at port $port failed, try another"
  109. incr retry -1
  110. set port [find_available_port $base_port $::redis_port_count]
  111. set cfg [open $cfgfile a+]
  112. if {$::tls} {
  113. puts $cfg "tls-port $port"
  114. set pport [find_available_port $base_port $::redis_port_count]
  115. puts $cfg "port $pport"
  116. } else {
  117. puts $cfg "port $port"
  118. }
  119. close $cfg
  120. } else {
  121. puts "Starting $type #$j at port $port"
  122. lappend ::pids $pid
  123. break
  124. }
  125. }
  126. # Check availability finally
  127. if {[server_is_up $::host $port 100] == 0} {
  128. set logfile [file join $dirname log.txt]
  129. puts [exec tail $logfile]
  130. abort_sentinel_test "Problems starting $type #$j: ping timeout, maybe server start failed, check $logfile"
  131. }
  132. # Push the instance into the right list
  133. set link [redis $::host $port 0 $::tls]
  134. $link reconnect 1
  135. lappend ::${type}_instances [list \
  136. pid $pid \
  137. host $::host \
  138. port $port \
  139. plaintext-port $pport \
  140. link $link \
  141. ]
  142. }
  143. }
  144. proc log_crashes {} {
  145. set start_pattern {*REDIS BUG REPORT START*}
  146. set logs [glob */log.txt]
  147. foreach log $logs {
  148. set fd [open $log]
  149. set found 0
  150. while {[gets $fd line] >= 0} {
  151. if {[string match $start_pattern $line]} {
  152. puts "\n*** Crash report found in $log ***"
  153. set found 1
  154. }
  155. if {$found} {
  156. puts $line
  157. incr ::failed
  158. }
  159. }
  160. }
  161. set logs [glob */err.txt]
  162. foreach log $logs {
  163. set res [find_valgrind_errors $log true]
  164. if {$res != ""} {
  165. puts $res
  166. incr ::failed
  167. }
  168. }
  169. }
  170. proc is_alive pid {
  171. if {[catch {exec ps -p $pid} err]} {
  172. return 0
  173. } else {
  174. return 1
  175. }
  176. }
  177. proc stop_instance pid {
  178. catch {exec kill $pid}
  179. # Node might have been stopped in the test
  180. catch {exec kill -SIGCONT $pid}
  181. if {$::valgrind} {
  182. set max_wait 120000
  183. } else {
  184. set max_wait 10000
  185. }
  186. while {[is_alive $pid]} {
  187. incr wait 10
  188. if {$wait == $max_wait} {
  189. puts [colorstr red "Forcing process $pid to crash..."]
  190. catch {exec kill -SEGV $pid}
  191. } elseif {$wait >= $max_wait * 2} {
  192. puts [colorstr red "Forcing process $pid to exit..."]
  193. catch {exec kill -KILL $pid}
  194. } elseif {$wait % 1000 == 0} {
  195. puts "Waiting for process $pid to exit..."
  196. }
  197. after 10
  198. }
  199. }
  200. proc cleanup {} {
  201. puts "Cleaning up..."
  202. foreach pid $::pids {
  203. puts "killing stale instance $pid"
  204. stop_instance $pid
  205. }
  206. log_crashes
  207. if {$::dont_clean} {
  208. return
  209. }
  210. foreach dir $::dirs {
  211. catch {exec rm -rf $dir}
  212. }
  213. }
  214. proc abort_sentinel_test msg {
  215. incr ::failed
  216. puts "WARNING: Aborting the test."
  217. puts ">>>>>>>> $msg"
  218. if {$::pause_on_error} pause_on_error
  219. cleanup
  220. exit 1
  221. }
  222. proc parse_options {} {
  223. for {set j 0} {$j < [llength $::argv]} {incr j} {
  224. set opt [lindex $::argv $j]
  225. set val [lindex $::argv [expr $j+1]]
  226. if {$opt eq "--single"} {
  227. incr j
  228. set ::run_matching "*${val}*"
  229. } elseif {$opt eq "--pause-on-error"} {
  230. set ::pause_on_error 1
  231. } elseif {$opt eq {--dont-clean}} {
  232. set ::dont_clean 1
  233. } elseif {$opt eq "--fail"} {
  234. set ::simulate_error 1
  235. } elseif {$opt eq {--valgrind}} {
  236. set ::valgrind 1
  237. } elseif {$opt eq {--host}} {
  238. incr j
  239. set ::host ${val}
  240. } elseif {$opt eq {--tls}} {
  241. package require tls 1.6
  242. ::tls::init \
  243. -cafile "$::tlsdir/ca.crt" \
  244. -certfile "$::tlsdir/client.crt" \
  245. -keyfile "$::tlsdir/client.key"
  246. set ::tls 1
  247. } elseif {$opt eq {--config}} {
  248. set val2 [lindex $::argv [expr $j+2]]
  249. dict set ::global_config $val $val2
  250. incr j 2
  251. } elseif {$opt eq "--help"} {
  252. puts "--single <pattern> Only runs tests specified by pattern."
  253. puts "--dont-clean Keep log files on exit."
  254. puts "--pause-on-error Pause for manual inspection on error."
  255. puts "--fail Simulate a test failure."
  256. puts "--valgrind Run with valgrind."
  257. puts "--tls Run tests in TLS mode."
  258. puts "--host <host> Use hostname instead of 127.0.0.1."
  259. puts "--config <k> <v> Extra config argument(s)."
  260. puts "--help Shows this help."
  261. exit 0
  262. } else {
  263. puts "Unknown option $opt"
  264. exit 1
  265. }
  266. }
  267. }
  268. # If --pause-on-error option was passed at startup this function is called
  269. # on error in order to give the developer a chance to understand more about
  270. # the error condition while the instances are still running.
  271. proc pause_on_error {} {
  272. puts ""
  273. puts [colorstr yellow "*** Please inspect the error now ***"]
  274. puts "\nType \"continue\" to resume the test, \"help\" for help screen.\n"
  275. while 1 {
  276. puts -nonewline "> "
  277. flush stdout
  278. set line [gets stdin]
  279. set argv [split $line " "]
  280. set cmd [lindex $argv 0]
  281. if {$cmd eq {continue}} {
  282. break
  283. } elseif {$cmd eq {show-redis-logs}} {
  284. set count 10
  285. if {[lindex $argv 1] ne {}} {set count [lindex $argv 1]}
  286. foreach_redis_id id {
  287. puts "=== REDIS $id ===="
  288. puts [exec tail -$count redis_$id/log.txt]
  289. puts "---------------------\n"
  290. }
  291. } elseif {$cmd eq {show-sentinel-logs}} {
  292. set count 10
  293. if {[lindex $argv 1] ne {}} {set count [lindex $argv 1]}
  294. foreach_sentinel_id id {
  295. puts "=== SENTINEL $id ===="
  296. puts [exec tail -$count sentinel_$id/log.txt]
  297. puts "---------------------\n"
  298. }
  299. } elseif {$cmd eq {ls}} {
  300. foreach_redis_id id {
  301. puts -nonewline "Redis $id"
  302. set errcode [catch {
  303. set str {}
  304. append str "@[RI $id tcp_port]: "
  305. append str "[RI $id role] "
  306. if {[RI $id role] eq {slave}} {
  307. append str "[RI $id master_host]:[RI $id master_port]"
  308. }
  309. set str
  310. } retval]
  311. if {$errcode} {
  312. puts " -- $retval"
  313. } else {
  314. puts $retval
  315. }
  316. }
  317. foreach_sentinel_id id {
  318. puts -nonewline "Sentinel $id"
  319. set errcode [catch {
  320. set str {}
  321. append str "@[SI $id tcp_port]: "
  322. append str "[join [S $id sentinel get-master-addr-by-name mymaster]]"
  323. set str
  324. } retval]
  325. if {$errcode} {
  326. puts " -- $retval"
  327. } else {
  328. puts $retval
  329. }
  330. }
  331. } elseif {$cmd eq {help}} {
  332. puts "ls List Sentinel and Redis instances."
  333. puts "show-sentinel-logs \[N\] Show latest N lines of logs."
  334. puts "show-redis-logs \[N\] Show latest N lines of logs."
  335. puts "S <id> cmd ... arg Call command in Sentinel <id>."
  336. puts "R <id> cmd ... arg Call command in Redis <id>."
  337. puts "SI <id> <field> Show Sentinel <id> INFO <field>."
  338. puts "RI <id> <field> Show Redis <id> INFO <field>."
  339. puts "continue Resume test."
  340. } else {
  341. set errcode [catch {eval $line} retval]
  342. if {$retval ne {}} {puts "$retval"}
  343. }
  344. }
  345. }
  346. # We redefine 'test' as for Sentinel we don't use the server-client
  347. # architecture for the test, everything is sequential.
  348. proc test {descr code} {
  349. set ts [clock format [clock seconds] -format %H:%M:%S]
  350. puts -nonewline "$ts> $descr: "
  351. flush stdout
  352. if {[catch {set retval [uplevel 1 $code]} error]} {
  353. incr ::failed
  354. if {[string match "assertion:*" $error]} {
  355. set msg "FAILED: [string range $error 10 end]"
  356. puts [colorstr red $msg]
  357. if {$::pause_on_error} pause_on_error
  358. puts [colorstr red "(Jumping to next unit after error)"]
  359. return -code continue
  360. } else {
  361. # Re-raise, let handler up the stack take care of this.
  362. error $error $::errorInfo
  363. }
  364. } else {
  365. puts [colorstr green OK]
  366. }
  367. }
  368. # Check memory leaks when running on OSX using the "leaks" utility.
  369. proc check_leaks instance_types {
  370. if {[string match {*Darwin*} [exec uname -a]]} {
  371. puts -nonewline "Testing for memory leaks..."; flush stdout
  372. foreach type $instance_types {
  373. foreach_instance_id [set ::${type}_instances] id {
  374. if {[instance_is_killed $type $id]} continue
  375. set pid [get_instance_attrib $type $id pid]
  376. set output {0 leaks}
  377. catch {exec leaks $pid} output
  378. if {[string match {*process does not exist*} $output] ||
  379. [string match {*cannot examine*} $output]} {
  380. # In a few tests we kill the server process.
  381. set output "0 leaks"
  382. } else {
  383. puts -nonewline "$type/$pid "
  384. flush stdout
  385. }
  386. if {![string match {*0 leaks*} $output]} {
  387. puts [colorstr red "=== MEMORY LEAK DETECTED ==="]
  388. puts "Instance type $type, ID $id:"
  389. puts $output
  390. puts "==="
  391. incr ::failed
  392. }
  393. }
  394. }
  395. puts ""
  396. }
  397. }
  398. # Execute all the units inside the 'tests' directory.
  399. proc run_tests {} {
  400. set tests [lsort [glob ../tests/*]]
  401. foreach test $tests {
  402. # Remove leaked_fds file before starting
  403. if {$::leaked_fds_file != "" && [file exists $::leaked_fds_file]} {
  404. file delete $::leaked_fds_file
  405. }
  406. if {$::run_matching ne {} && [string match $::run_matching $test] == 0} {
  407. continue
  408. }
  409. if {[file isdirectory $test]} continue
  410. puts [colorstr yellow "Testing unit: [lindex [file split $test] end]"]
  411. source $test
  412. check_leaks {redis sentinel}
  413. # Check if a leaked fds file was created and abort the test.
  414. if {$::leaked_fds_file != "" && [file exists $::leaked_fds_file]} {
  415. puts [colorstr red "ERROR: Sentinel has leaked fds to scripts:"]
  416. puts [exec cat $::leaked_fds_file]
  417. puts "----"
  418. incr ::failed
  419. }
  420. }
  421. }
  422. # Print a message and exists with 0 / 1 according to zero or more failures.
  423. proc end_tests {} {
  424. if {$::failed == 0 } {
  425. puts [colorstr green "GOOD! No errors."]
  426. exit 0
  427. } else {
  428. puts [colorstr red "WARNING $::failed test(s) failed."]
  429. exit 1
  430. }
  431. }
  432. # The "S" command is used to interact with the N-th Sentinel.
  433. # The general form is:
  434. #
  435. # S <sentinel-id> command arg arg arg ...
  436. #
  437. # Example to ping the Sentinel 0 (first instance): S 0 PING
  438. proc S {n args} {
  439. set s [lindex $::sentinel_instances $n]
  440. [dict get $s link] {*}$args
  441. }
  442. # Returns a Redis instance by index.
  443. # Example:
  444. # [Rn 0] info
  445. proc Rn {n} {
  446. return [dict get [lindex $::redis_instances $n] link]
  447. }
  448. # Like R but to chat with Redis instances.
  449. proc R {n args} {
  450. [Rn $n] {*}$args
  451. }
  452. proc get_info_field {info field} {
  453. set fl [string length $field]
  454. append field :
  455. foreach line [split $info "\n"] {
  456. set line [string trim $line "\r\n "]
  457. if {[string range $line 0 $fl] eq $field} {
  458. return [string range $line [expr {$fl+1}] end]
  459. }
  460. }
  461. return {}
  462. }
  463. proc SI {n field} {
  464. get_info_field [S $n info] $field
  465. }
  466. proc RI {n field} {
  467. get_info_field [R $n info] $field
  468. }
  469. proc RPort {n} {
  470. if {$::tls} {
  471. return [lindex [R $n config get tls-port] 1]
  472. } else {
  473. return [lindex [R $n config get port] 1]
  474. }
  475. }
  476. # Iterate over IDs of sentinel or redis instances.
  477. proc foreach_instance_id {instances idvar code} {
  478. upvar 1 $idvar id
  479. for {set id 0} {$id < [llength $instances]} {incr id} {
  480. set errcode [catch {uplevel 1 $code} result]
  481. if {$errcode == 1} {
  482. error $result $::errorInfo $::errorCode
  483. } elseif {$errcode == 4} {
  484. continue
  485. } elseif {$errcode == 3} {
  486. break
  487. } elseif {$errcode != 0} {
  488. return -code $errcode $result
  489. }
  490. }
  491. }
  492. proc foreach_sentinel_id {idvar code} {
  493. set errcode [catch {uplevel 1 [list foreach_instance_id $::sentinel_instances $idvar $code]} result]
  494. return -code $errcode $result
  495. }
  496. proc foreach_redis_id {idvar code} {
  497. set errcode [catch {uplevel 1 [list foreach_instance_id $::redis_instances $idvar $code]} result]
  498. return -code $errcode $result
  499. }
  500. # Get the specific attribute of the specified instance type, id.
  501. proc get_instance_attrib {type id attrib} {
  502. dict get [lindex [set ::${type}_instances] $id] $attrib
  503. }
  504. # Set the specific attribute of the specified instance type, id.
  505. proc set_instance_attrib {type id attrib newval} {
  506. set d [lindex [set ::${type}_instances] $id]
  507. dict set d $attrib $newval
  508. lset ::${type}_instances $id $d
  509. }
  510. # Create a master-slave cluster of the given number of total instances.
  511. # The first instance "0" is the master, all others are configured as
  512. # slaves.
  513. proc create_redis_master_slave_cluster n {
  514. foreach_redis_id id {
  515. if {$id == 0} {
  516. # Our master.
  517. R $id slaveof no one
  518. R $id flushall
  519. } elseif {$id < $n} {
  520. R $id slaveof [get_instance_attrib redis 0 host] \
  521. [get_instance_attrib redis 0 port]
  522. } else {
  523. # Instances not part of the cluster.
  524. R $id slaveof no one
  525. }
  526. }
  527. # Wait for all the slaves to sync.
  528. wait_for_condition 1000 50 {
  529. [RI 0 connected_slaves] == ($n-1)
  530. } else {
  531. fail "Unable to create a master-slaves cluster."
  532. }
  533. }
  534. proc get_instance_id_by_port {type port} {
  535. foreach_${type}_id id {
  536. if {[get_instance_attrib $type $id port] == $port} {
  537. return $id
  538. }
  539. }
  540. fail "Instance $type port $port not found."
  541. }
  542. # Kill an instance of the specified type/id with SIGKILL.
  543. # This function will mark the instance PID as -1 to remember that this instance
  544. # is no longer running and will remove its PID from the list of pids that
  545. # we kill at cleanup.
  546. #
  547. # The instance can be restarted with restart-instance.
  548. proc kill_instance {type id} {
  549. set pid [get_instance_attrib $type $id pid]
  550. set port [get_instance_attrib $type $id port]
  551. if {$pid == -1} {
  552. error "You tried to kill $type $id twice."
  553. }
  554. stop_instance $pid
  555. set_instance_attrib $type $id pid -1
  556. set_instance_attrib $type $id link you_tried_to_talk_with_killed_instance
  557. # Remove the PID from the list of pids to kill at exit.
  558. set ::pids [lsearch -all -inline -not -exact $::pids $pid]
  559. # Wait for the port it was using to be available again, so that's not
  560. # an issue to start a new server ASAP with the same port.
  561. set retry 100
  562. while {[incr retry -1]} {
  563. set port_is_free [catch {set s [socket 127.0.0.1 $port]}]
  564. if {$port_is_free} break
  565. catch {close $s}
  566. after 100
  567. }
  568. if {$retry == 0} {
  569. error "Port $port does not return available after killing instance."
  570. }
  571. }
  572. # Return true of the instance of the specified type/id is killed.
  573. proc instance_is_killed {type id} {
  574. set pid [get_instance_attrib $type $id pid]
  575. expr {$pid == -1}
  576. }
  577. # Restart an instance previously killed by kill_instance
  578. proc restart_instance {type id} {
  579. set dirname "${type}_${id}"
  580. set cfgfile [file join $dirname $type.conf]
  581. set port [get_instance_attrib $type $id port]
  582. # Execute the instance with its old setup and append the new pid
  583. # file for cleanup.
  584. set pid [exec_instance $type $dirname $cfgfile]
  585. set_instance_attrib $type $id pid $pid
  586. lappend ::pids $pid
  587. # Check that the instance is running
  588. if {[server_is_up 127.0.0.1 $port 100] == 0} {
  589. set logfile [file join $dirname log.txt]
  590. puts [exec tail $logfile]
  591. abort_sentinel_test "Problems starting $type #$id: ping timeout, maybe server start failed, check $logfile"
  592. }
  593. # Connect with it with a fresh link
  594. set link [redis 127.0.0.1 $port 0 $::tls]
  595. $link reconnect 1
  596. set_instance_attrib $type $id link $link
  597. # Make sure the instance is not loading the dataset when this
  598. # function returns.
  599. while 1 {
  600. catch {[$link ping]} retval
  601. if {[string match {*LOADING*} $retval]} {
  602. after 100
  603. continue
  604. } else {
  605. break
  606. }
  607. }
  608. }
  609. proc redis_deferring_client {type id} {
  610. set port [get_instance_attrib $type $id port]
  611. set host [get_instance_attrib $type $id host]
  612. set client [redis $host $port 1 $::tls]
  613. return $client
  614. }
  615. proc redis_client {type id} {
  616. set port [get_instance_attrib $type $id port]
  617. set host [get_instance_attrib $type $id host]
  618. set client [redis $host $port 0 $::tls]
  619. return $client
  620. }