server.tcl 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. set ::global_overrides {}
  2. set ::tags {}
  3. set ::valgrind_errors {}
  4. proc start_server_error {config_file error} {
  5. set err {}
  6. append err "Can't start the Redis server\n"
  7. append err "CONFIGURATION:"
  8. append err [exec cat $config_file]
  9. append err "\nERROR:"
  10. append err [string trim $error]
  11. send_data_packet $::test_server_fd err $err
  12. }
  13. proc check_valgrind_errors stderr {
  14. set fd [open $stderr]
  15. set buf [read $fd]
  16. close $fd
  17. if {[regexp -- { at 0x} $buf] ||
  18. (![regexp -- {definitely lost: 0 bytes} $buf] &&
  19. ![regexp -- {no leaks are possible} $buf])} {
  20. send_data_packet $::test_server_fd err "Valgrind error: $buf\n"
  21. }
  22. }
  23. proc kill_server config {
  24. # nothing to kill when running against external server
  25. if {$::external} return
  26. # nevermind if its already dead
  27. if {![is_alive $config]} { return }
  28. set pid [dict get $config pid]
  29. # check for leaks
  30. if {![dict exists $config "skipleaks"]} {
  31. catch {
  32. if {[string match {*Darwin*} [exec uname -a]]} {
  33. tags {"leaks"} {
  34. test "Check for memory leaks (pid $pid)" {
  35. set output {0 leaks}
  36. catch {exec leaks $pid} output
  37. if {[string match {*process does not exist*} $output] ||
  38. [string match {*cannot examine*} $output]} {
  39. # In a few tests we kill the server process.
  40. set output "0 leaks"
  41. }
  42. set output
  43. } {*0 leaks*}
  44. }
  45. }
  46. }
  47. }
  48. # kill server and wait for the process to be totally exited
  49. send_data_packet $::test_server_fd server-killing $pid
  50. catch {exec kill $pid}
  51. if {$::valgrind} {
  52. set max_wait 60000
  53. } else {
  54. set max_wait 10000
  55. }
  56. while {[is_alive $config]} {
  57. incr wait 10
  58. if {$wait >= $max_wait} {
  59. puts "Forcing process $pid to exit..."
  60. catch {exec kill -KILL $pid}
  61. } elseif {$wait % 1000 == 0} {
  62. puts "Waiting for process $pid to exit..."
  63. }
  64. after 10
  65. }
  66. # Check valgrind errors if needed
  67. if {$::valgrind} {
  68. check_valgrind_errors [dict get $config stderr]
  69. }
  70. # Remove this pid from the set of active pids in the test server.
  71. send_data_packet $::test_server_fd server-killed $pid
  72. }
  73. proc is_alive config {
  74. set pid [dict get $config pid]
  75. if {[catch {exec ps -p $pid} err]} {
  76. return 0
  77. } else {
  78. return 1
  79. }
  80. }
  81. proc ping_server {host port} {
  82. set retval 0
  83. if {[catch {
  84. if {$::tls} {
  85. set fd [::tls::socket $host $port]
  86. } else {
  87. set fd [socket $host $port]
  88. }
  89. fconfigure $fd -translation binary
  90. puts $fd "PING\r\n"
  91. flush $fd
  92. set reply [gets $fd]
  93. if {[string range $reply 0 0] eq {+} ||
  94. [string range $reply 0 0] eq {-}} {
  95. set retval 1
  96. }
  97. close $fd
  98. } e]} {
  99. if {$::verbose} {
  100. puts -nonewline "."
  101. }
  102. } else {
  103. if {$::verbose} {
  104. puts -nonewline "ok"
  105. }
  106. }
  107. return $retval
  108. }
  109. # Return 1 if the server at the specified addr is reachable by PING, otherwise
  110. # returns 0. Performs a try every 50 milliseconds for the specified number
  111. # of retries.
  112. proc server_is_up {host port retrynum} {
  113. after 10 ;# Use a small delay to make likely a first-try success.
  114. set retval 0
  115. while {[incr retrynum -1]} {
  116. if {[catch {ping_server $host $port} ping]} {
  117. set ping 0
  118. }
  119. if {$ping} {return 1}
  120. after 50
  121. }
  122. return 0
  123. }
  124. # doesn't really belong here, but highly coupled to code in start_server
  125. proc tags {tags code} {
  126. set ::tags [concat $::tags $tags]
  127. uplevel 1 $code
  128. set ::tags [lrange $::tags 0 end-[llength $tags]]
  129. }
  130. # Write the configuration in the dictionary 'config' in the specified
  131. # file name.
  132. proc create_server_config_file {filename config} {
  133. set fp [open $filename w+]
  134. foreach directive [dict keys $config] {
  135. puts -nonewline $fp "$directive "
  136. puts $fp [dict get $config $directive]
  137. }
  138. close $fp
  139. }
  140. proc start_server {options {code undefined}} {
  141. # If we are running against an external server, we just push the
  142. # host/port pair in the stack the first time
  143. if {$::external} {
  144. if {[llength $::servers] == 0} {
  145. set srv {}
  146. # In test_server_main(tests/test_helper.tcl:215~218), increase the value of start_port
  147. # and assign it to ::port through the `--port` option, so we need to reduce it.
  148. set baseport [expr {$::port-100}]
  149. dict set srv "host" $::host
  150. dict set srv "port" $baseport
  151. set client [redis $::host $baseport 0 $::tls]
  152. dict set srv "client" $client
  153. $client select 9
  154. # append the server to the stack
  155. lappend ::servers $srv
  156. }
  157. uplevel 1 $code
  158. return
  159. }
  160. # setup defaults
  161. set baseconfig "default.conf"
  162. set overrides {}
  163. set tags {}
  164. # parse options
  165. foreach {option value} $options {
  166. switch $option {
  167. "config" {
  168. set baseconfig $value }
  169. "overrides" {
  170. set overrides $value }
  171. "tags" {
  172. set tags $value
  173. set ::tags [concat $::tags $value] }
  174. default {
  175. error "Unknown option $option" }
  176. }
  177. }
  178. set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
  179. set config {}
  180. if {$::tls} {
  181. dict set config "tls-cert-file" [format "%s/tests/tls/redis.crt" [pwd]]
  182. dict set config "tls-key-file" [format "%s/tests/tls/redis.key" [pwd]]
  183. dict set config "tls-dh-params-file" [format "%s/tests/tls/redis.dh" [pwd]]
  184. dict set config "tls-ca-cert-file" [format "%s/tests/tls/ca.crt" [pwd]]
  185. dict set config "loglevel" "debug"
  186. }
  187. foreach line $data {
  188. if {[string length $line] > 0 && [string index $line 0] ne "#"} {
  189. set elements [split $line " "]
  190. set directive [lrange $elements 0 0]
  191. set arguments [lrange $elements 1 end]
  192. dict set config $directive $arguments
  193. }
  194. }
  195. # use a different directory every time a server is started
  196. dict set config dir [tmpdir server]
  197. # start every server on a different port
  198. set ::port [find_available_port [expr {$::port+1}]]
  199. if {$::tls} {
  200. dict set config "port" 0
  201. dict set config "tls-port" $::port
  202. dict set config "tls-cluster" "yes"
  203. dict set config "tls-replication" "yes"
  204. } else {
  205. dict set config port $::port
  206. }
  207. set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]]
  208. dict set config "unixsocket" $unixsocket
  209. # apply overrides from global space and arguments
  210. foreach {directive arguments} [concat $::global_overrides $overrides] {
  211. dict set config $directive $arguments
  212. }
  213. # write new configuration to temporary file
  214. set config_file [tmpfile redis.conf]
  215. create_server_config_file $config_file $config
  216. set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
  217. set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
  218. # We need a loop here to retry with different ports.
  219. set server_started 0
  220. while {$server_started == 0} {
  221. if {$::verbose} {
  222. puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
  223. }
  224. send_data_packet $::test_server_fd "server-spawning" "port $::port"
  225. if {$::valgrind} {
  226. set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &]
  227. } elseif ($::stack_logging) {
  228. set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/redis-server $config_file > $stdout 2> $stderr &]
  229. } else {
  230. set pid [exec src/redis-server $config_file > $stdout 2> $stderr &]
  231. }
  232. # Tell the test server about this new instance.
  233. send_data_packet $::test_server_fd server-spawned $pid
  234. # check that the server actually started
  235. # ugly but tries to be as fast as possible...
  236. if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
  237. # Wait for actual startup
  238. set checkperiod 100; # Milliseconds
  239. set maxiter [expr {120*1000/100}] ; # Wait up to 2 minutes.
  240. set port_busy 0
  241. while {![info exists _pid]} {
  242. regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid
  243. after $checkperiod
  244. incr maxiter -1
  245. if {$maxiter == 0} {
  246. start_server_error $config_file "No PID detected in log $stdout"
  247. puts "--- LOG CONTENT ---"
  248. puts [exec cat $stdout]
  249. puts "-------------------"
  250. break
  251. }
  252. # Check if the port is actually busy and the server failed
  253. # for this reason.
  254. if {[regexp {Could not create server TCP} [exec cat $stdout]]} {
  255. set port_busy 1
  256. break
  257. }
  258. }
  259. # Sometimes we have to try a different port, even if we checked
  260. # for availability. Other test clients may grab the port before we
  261. # are able to do it for example.
  262. if {$port_busy} {
  263. puts "Port $::port was already busy, trying another port..."
  264. set ::port [find_available_port [expr {$::port+1}]]
  265. if {$::tls} {
  266. dict set config "tls-port" $::port
  267. } else {
  268. dict set config port $::port
  269. }
  270. create_server_config_file $config_file $config
  271. continue; # Try again
  272. }
  273. if {$code ne "undefined"} {
  274. set serverisup [server_is_up $::host $::port $retrynum]
  275. } else {
  276. set serverisup 1
  277. }
  278. if {$::verbose} {
  279. puts ""
  280. }
  281. if {!$serverisup} {
  282. set err {}
  283. append err [exec cat $stdout] "\n" [exec cat $stderr]
  284. start_server_error $config_file $err
  285. return
  286. }
  287. set server_started 1
  288. }
  289. # setup properties to be able to initialize a client object
  290. set port_param [expr $::tls ? {"tls-port"} : {"port"}]
  291. set host $::host
  292. set port $::port
  293. if {[dict exists $config bind]} { set host [dict get $config bind] }
  294. if {[dict exists $config $port_param]} { set port [dict get $config $port_param] }
  295. # setup config dict
  296. dict set srv "config_file" $config_file
  297. dict set srv "config" $config
  298. dict set srv "pid" $pid
  299. dict set srv "host" $host
  300. dict set srv "port" $port
  301. dict set srv "stdout" $stdout
  302. dict set srv "stderr" $stderr
  303. dict set srv "unixsocket" $unixsocket
  304. # if a block of code is supplied, we wait for the server to become
  305. # available, create a client object and kill the server afterwards
  306. if {$code ne "undefined"} {
  307. set line [exec head -n1 $stdout]
  308. if {[string match {*already in use*} $line]} {
  309. error_and_quit $config_file $line
  310. }
  311. if {$::wait_server} {
  312. set msg "server started PID: [dict get $srv "pid"]. press any key to continue..."
  313. puts $msg
  314. read stdin 1
  315. }
  316. while 1 {
  317. # check that the server actually started and is ready for connections
  318. if {[exec grep -i "Ready to accept" | wc -l < $stdout] > 0} {
  319. break
  320. }
  321. after 10
  322. }
  323. # append the server to the stack
  324. lappend ::servers $srv
  325. # connect client (after server dict is put on the stack)
  326. reconnect
  327. # execute provided block
  328. set num_tests $::num_tests
  329. if {[catch { uplevel 1 $code } error]} {
  330. set backtrace $::errorInfo
  331. # Kill the server without checking for leaks
  332. dict set srv "skipleaks" 1
  333. kill_server $srv
  334. # Print warnings from log
  335. puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]]
  336. set warnings [warnings_from_file [dict get $srv "stdout"]]
  337. if {[string length $warnings] > 0} {
  338. puts "$warnings"
  339. } else {
  340. puts "(none)"
  341. }
  342. puts ""
  343. error $error $backtrace
  344. }
  345. # Don't do the leak check when no tests were run
  346. if {$num_tests == $::num_tests} {
  347. dict set srv "skipleaks" 1
  348. }
  349. # pop the server object
  350. set ::servers [lrange $::servers 0 end-1]
  351. set ::tags [lrange $::tags 0 end-[llength $tags]]
  352. kill_server $srv
  353. } else {
  354. set ::tags [lrange $::tags 0 end-[llength $tags]]
  355. set _ $srv
  356. }
  357. }