aof.tcl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. set defaults { appendonly {yes} appendfilename {appendonly.aof} }
  2. set server_path [tmpdir server.aof]
  3. set aof_path "$server_path/appendonly.aof"
  4. proc append_to_aof {str} {
  5. upvar fp fp
  6. puts -nonewline $fp $str
  7. }
  8. proc create_aof {code} {
  9. upvar fp fp aof_path aof_path
  10. set fp [open $aof_path w+]
  11. uplevel 1 $code
  12. close $fp
  13. }
  14. proc start_server_aof {overrides code} {
  15. upvar defaults defaults srv srv server_path server_path
  16. set config [concat $defaults $overrides]
  17. set srv [start_server [list overrides $config]]
  18. uplevel 1 $code
  19. kill_server $srv
  20. }
  21. tags {"aof"} {
  22. ## Server can start when aof-load-truncated is set to yes and AOF
  23. ## is truncated, with an incomplete MULTI block.
  24. create_aof {
  25. append_to_aof [formatCommand set foo hello]
  26. append_to_aof [formatCommand multi]
  27. append_to_aof [formatCommand set bar world]
  28. }
  29. start_server_aof [list dir $server_path aof-load-truncated yes] {
  30. test "Unfinished MULTI: Server should start if load-truncated is yes" {
  31. assert_equal 1 [is_alive $srv]
  32. }
  33. }
  34. ## Should also start with truncated AOF without incomplete MULTI block.
  35. create_aof {
  36. append_to_aof [formatCommand incr foo]
  37. append_to_aof [formatCommand incr foo]
  38. append_to_aof [formatCommand incr foo]
  39. append_to_aof [formatCommand incr foo]
  40. append_to_aof [formatCommand incr foo]
  41. append_to_aof [string range [formatCommand incr foo] 0 end-1]
  42. }
  43. start_server_aof [list dir $server_path aof-load-truncated yes] {
  44. test "Short read: Server should start if load-truncated is yes" {
  45. assert_equal 1 [is_alive $srv]
  46. }
  47. set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
  48. wait_for_condition 50 100 {
  49. [catch {$client ping} e] == 0
  50. } else {
  51. fail "Loading DB is taking too much time."
  52. }
  53. test "Truncated AOF loaded: we expect foo to be equal to 5" {
  54. assert {[$client get foo] eq "5"}
  55. }
  56. test "Append a new command after loading an incomplete AOF" {
  57. $client incr foo
  58. }
  59. }
  60. # Now the AOF file is expected to be correct
  61. start_server_aof [list dir $server_path aof-load-truncated yes] {
  62. test "Short read + command: Server should start" {
  63. assert_equal 1 [is_alive $srv]
  64. }
  65. set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
  66. wait_for_condition 50 100 {
  67. [catch {$client ping} e] == 0
  68. } else {
  69. fail "Loading DB is taking too much time."
  70. }
  71. test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
  72. assert {[$client get foo] eq "6"}
  73. }
  74. }
  75. ## Test that the server exits when the AOF contains a format error
  76. create_aof {
  77. append_to_aof [formatCommand set foo hello]
  78. append_to_aof "!!!"
  79. append_to_aof [formatCommand set foo hello]
  80. }
  81. start_server_aof [list dir $server_path aof-load-truncated yes] {
  82. test "Bad format: Server should have logged an error" {
  83. set pattern "*Bad file format reading the append only file*"
  84. set retry 10
  85. while {$retry} {
  86. set result [exec tail -1 < [dict get $srv stdout]]
  87. if {[string match $pattern $result]} {
  88. break
  89. }
  90. incr retry -1
  91. after 1000
  92. }
  93. if {$retry == 0} {
  94. error "assertion:expected error not found on config file"
  95. }
  96. }
  97. }
  98. ## Test the server doesn't start when the AOF contains an unfinished MULTI
  99. create_aof {
  100. append_to_aof [formatCommand set foo hello]
  101. append_to_aof [formatCommand multi]
  102. append_to_aof [formatCommand set bar world]
  103. }
  104. start_server_aof [list dir $server_path aof-load-truncated no] {
  105. test "Unfinished MULTI: Server should have logged an error" {
  106. set pattern "*Unexpected end of file reading the append only file*"
  107. set retry 10
  108. while {$retry} {
  109. set result [exec tail -1 < [dict get $srv stdout]]
  110. if {[string match $pattern $result]} {
  111. break
  112. }
  113. incr retry -1
  114. after 1000
  115. }
  116. if {$retry == 0} {
  117. error "assertion:expected error not found on config file"
  118. }
  119. }
  120. }
  121. ## Test that the server exits when the AOF contains a short read
  122. create_aof {
  123. append_to_aof [formatCommand set foo hello]
  124. append_to_aof [string range [formatCommand set bar world] 0 end-1]
  125. }
  126. start_server_aof [list dir $server_path aof-load-truncated no] {
  127. test "Short read: Server should have logged an error" {
  128. set pattern "*Unexpected end of file reading the append only file*"
  129. set retry 10
  130. while {$retry} {
  131. set result [exec tail -1 < [dict get $srv stdout]]
  132. if {[string match $pattern $result]} {
  133. break
  134. }
  135. incr retry -1
  136. after 1000
  137. }
  138. if {$retry == 0} {
  139. error "assertion:expected error not found on config file"
  140. }
  141. }
  142. }
  143. ## Test that redis-check-aof indeed sees this AOF is not valid
  144. test "Short read: Utility should confirm the AOF is not valid" {
  145. catch {
  146. exec src/redis-check-aof $aof_path
  147. } result
  148. assert_match "*not valid*" $result
  149. }
  150. test "Short read: Utility should be able to fix the AOF" {
  151. set result [exec src/redis-check-aof --fix $aof_path << "y\n"]
  152. assert_match "*Successfully truncated AOF*" $result
  153. }
  154. ## Test that the server can be started using the truncated AOF
  155. start_server_aof [list dir $server_path aof-load-truncated no] {
  156. test "Fixed AOF: Server should have been started" {
  157. assert_equal 1 [is_alive $srv]
  158. }
  159. test "Fixed AOF: Keyspace should contain values that were parseable" {
  160. set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
  161. wait_for_condition 50 100 {
  162. [catch {$client ping} e] == 0
  163. } else {
  164. fail "Loading DB is taking too much time."
  165. }
  166. assert_equal "hello" [$client get foo]
  167. assert_equal "" [$client get bar]
  168. }
  169. }
  170. ## Test that SPOP (that modifies the client's argc/argv) is correctly free'd
  171. create_aof {
  172. append_to_aof [formatCommand sadd set foo]
  173. append_to_aof [formatCommand sadd set bar]
  174. append_to_aof [formatCommand spop set]
  175. }
  176. start_server_aof [list dir $server_path aof-load-truncated no] {
  177. test "AOF+SPOP: Server should have been started" {
  178. assert_equal 1 [is_alive $srv]
  179. }
  180. test "AOF+SPOP: Set should have 1 member" {
  181. set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
  182. wait_for_condition 50 100 {
  183. [catch {$client ping} e] == 0
  184. } else {
  185. fail "Loading DB is taking too much time."
  186. }
  187. assert_equal 1 [$client scard set]
  188. }
  189. }
  190. ## Uses the alsoPropagate() API.
  191. create_aof {
  192. append_to_aof [formatCommand sadd set foo]
  193. append_to_aof [formatCommand sadd set bar]
  194. append_to_aof [formatCommand sadd set gah]
  195. append_to_aof [formatCommand spop set 2]
  196. }
  197. start_server_aof [list dir $server_path] {
  198. test "AOF+SPOP: Server should have been started" {
  199. assert_equal 1 [is_alive $srv]
  200. }
  201. test "AOF+SPOP: Set should have 1 member" {
  202. set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
  203. wait_for_condition 50 100 {
  204. [catch {$client ping} e] == 0
  205. } else {
  206. fail "Loading DB is taking too much time."
  207. }
  208. assert_equal 1 [$client scard set]
  209. }
  210. }
  211. ## Test that EXPIREAT is loaded correctly
  212. create_aof {
  213. append_to_aof [formatCommand rpush list foo]
  214. append_to_aof [formatCommand expireat list 1000]
  215. append_to_aof [formatCommand rpush list bar]
  216. }
  217. start_server_aof [list dir $server_path aof-load-truncated no] {
  218. test "AOF+EXPIRE: Server should have been started" {
  219. assert_equal 1 [is_alive $srv]
  220. }
  221. test "AOF+EXPIRE: List should be empty" {
  222. set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
  223. wait_for_condition 50 100 {
  224. [catch {$client ping} e] == 0
  225. } else {
  226. fail "Loading DB is taking too much time."
  227. }
  228. assert_equal 0 [$client llen list]
  229. }
  230. }
  231. start_server {overrides {appendonly {yes} appendfilename {appendonly.aof}}} {
  232. test {Redis should not try to convert DEL into EXPIREAT for EXPIRE -1} {
  233. r set x 10
  234. r expire x -1
  235. }
  236. }
  237. start_server {overrides {appendonly {yes} appendfilename {appendonly.aof} appendfsync always}} {
  238. test {AOF fsync always barrier issue} {
  239. set rd [redis_deferring_client]
  240. # Set a sleep when aof is flushed, so that we have a chance to look
  241. # at the aof size and detect if the response of an incr command
  242. # arrives before the data was written (and hopefully fsynced)
  243. # We create a big reply, which will hopefully not have room in the
  244. # socket buffers, and will install a write handler, then we sleep
  245. # a big and issue the incr command, hoping that the last portion of
  246. # the output buffer write, and the processing of the incr will happen
  247. # in the same event loop cycle.
  248. # Since the socket buffers and timing are unpredictable, we fuzz this
  249. # test with slightly different sizes and sleeps a few times.
  250. for {set i 0} {$i < 10} {incr i} {
  251. r debug aof-flush-sleep 0
  252. r del x
  253. r setrange x [expr {int(rand()*5000000)+10000000}] x
  254. r debug aof-flush-sleep 500000
  255. set aof [file join [lindex [r config get dir] 1] appendonly.aof]
  256. set size1 [file size $aof]
  257. $rd get x
  258. after [expr {int(rand()*30)}]
  259. $rd incr new_value
  260. $rd read
  261. $rd read
  262. set size2 [file size $aof]
  263. assert {$size1 != $size2}
  264. }
  265. }
  266. }
  267. }