├── .gitignore ├── Makefile ├── README.markdown ├── lib └── resty │ ├── redis.lua │ └── redis_cluster.lua ├── t ├── bugs.t ├── count.t ├── hmset.t ├── mock.t ├── pipeline.t ├── pubsub.t ├── sanity.t ├── transaction.t ├── user-cmds.t └── version.t └── valgrind.suppress /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | *.t_ 8 | tags 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty-debug 2 | 3 | PREFIX ?= /usr/local 4 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 5 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 6 | INSTALL ?= install 7 | 8 | .PHONY: all test install 9 | 10 | all: ; 11 | 12 | install: all 13 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty 14 | $(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty 15 | 16 | test: all 17 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 18 | 19 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-redis - Lua redis client driver for the ngx_lua based on the cosocket API 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Description](#description) 12 | * [Synopsis](#synopsis) 13 | * [Methods](#methods) 14 | * [new](#new) 15 | * [connect](#connect) 16 | * [set_timeout](#set_timeout) 17 | * [set_keepalive](#set_keepalive) 18 | * [get_reused_times](#get_reused_times) 19 | * [close](#close) 20 | * [init_pipeline](#init_pipeline) 21 | * [commit_pipeline](#commit_pipeline) 22 | * [cancel_pipeline](#cancel_pipeline) 23 | * [hmset](#hmset) 24 | * [array_to_hash](#array_to_hash) 25 | * [read_reply](#read_reply) 26 | * [add_commands](#add_commands) 27 | * [Redis Authentication](#redis-authentication) 28 | * [Redis Transactions](#redis-transactions) 29 | * [Load Balancing and Failover](#load-balancing-and-failover) 30 | * [Debugging](#debugging) 31 | * [Automatic Error Logging](#automatic-error-logging) 32 | * [Check List for Issues](#check-list-for-issues) 33 | * [Limitations](#limitations) 34 | * [Installation](#installation) 35 | * [TODO](#todo) 36 | * [Community](#community) 37 | * [English Mailing List](#english-mailing-list) 38 | * [Chinese Mailing List](#chinese-mailing-list) 39 | * [Bugs and Patches](#bugs-and-patches) 40 | * [Author](#author) 41 | * [Copyright and License](#copyright-and-license) 42 | * [See Also](#see-also) 43 | 44 | Status 45 | ====== 46 | 47 | This library is considered production ready. 48 | 49 | Description 50 | =========== 51 | 52 | This Lua library is a Redis client driver for the ngx_lua nginx module: 53 | 54 | http://wiki.nginx.org/HttpLuaModule 55 | 56 | This Lua library takes advantage of ngx_lua's cosocket API, which ensures 57 | 100% nonblocking behavior. 58 | 59 | Note that at least [ngx_lua 0.5.14](https://github.com/chaoslawful/lua-nginx-module/tags) or [ngx_openresty 1.2.1.14](http://openresty.org/#Download) is required. 60 | 61 | Synopsis 62 | ======== 63 | 64 | ```lua 65 | # you do not need the following line if you are using 66 | # the ngx_openresty bundle: 67 | lua_package_path "/path/to/lua-resty-redis/lib/?.lua;;"; 68 | 69 | server { 70 | location /test { 71 | content_by_lua ' 72 | local redis = require "resty.redis" 73 | local red = redis:new() 74 | 75 | red:set_timeout(1000) -- 1 sec 76 | 77 | -- or connect to a unix domain socket file listened 78 | -- by a redis server: 79 | -- local ok, err = red:connect("unix:/path/to/redis.sock") 80 | 81 | local ok, err = red:connect("127.0.0.1", 6379) 82 | if not ok then 83 | ngx.say("failed to connect: ", err) 84 | return 85 | end 86 | 87 | ok, err = red:set("dog", "an animal") 88 | if not ok then 89 | ngx.say("failed to set dog: ", err) 90 | return 91 | end 92 | 93 | ngx.say("set result: ", ok) 94 | 95 | local res, err = red:get("dog") 96 | if not res then 97 | ngx.say("failed to get dog: ", err) 98 | return 99 | end 100 | 101 | if res == ngx.null then 102 | ngx.say("dog not found.") 103 | return 104 | end 105 | 106 | ngx.say("dog: ", res) 107 | 108 | red:init_pipeline() 109 | red:set("cat", "Marry") 110 | red:set("horse", "Bob") 111 | red:get("cat") 112 | red:get("horse") 113 | local results, err = red:commit_pipeline() 114 | if not results then 115 | ngx.say("failed to commit the pipelined requests: ", err) 116 | return 117 | end 118 | 119 | for i, res in ipairs(results) do 120 | if type(res) == "table" then 121 | if not res[1] then 122 | ngx.say("failed to run command ", i, ": ", res[2]) 123 | else 124 | -- process the table value 125 | end 126 | else 127 | -- process the scalar value 128 | end 129 | end 130 | 131 | -- put it into the connection pool of size 100, 132 | -- with 10 seconds max idle time 133 | local ok, err = red:set_keepalive(10000, 100) 134 | if not ok then 135 | ngx.say("failed to set keepalive: ", err) 136 | return 137 | end 138 | 139 | -- or just close the connection right away: 140 | -- local ok, err = red:close() 141 | -- if not ok then 142 | -- ngx.say("failed to close: ", err) 143 | -- return 144 | -- end 145 | '; 146 | } 147 | } 148 | ``` 149 | 150 | [Back to TOC](#table-of-contents) 151 | 152 | Methods 153 | ======= 154 | 155 | All of the Redis commands have their own methods with the same name except all in lower case. 156 | 157 | You can find the complete list of Redis commands here: 158 | 159 | http://redis.io/commands 160 | 161 | You need to check out this Redis command reference to see what Redis command accepts what arguments. 162 | 163 | The Redis command arguments can be directly fed into the corresponding method call. For example, the "GET" redis command accepts a single key argument, then you can just call the "get" method like this: 164 | 165 | ```lua 166 | local res, err = red:get("key") 167 | ``` 168 | 169 | Similarly, the "LRANGE" redis command accepts threee arguments, then you should call the "lrange" method like this: 170 | 171 | ```lua 172 | local res, err = red:lrange("nokey", 0, 1) 173 | ``` 174 | 175 | For example, "SET", "GET", "LRANGE", and "BLPOP" commands correspond to the methods "set", "get", "lrange", and "blpop". 176 | 177 | Here are some more examples: 178 | 179 | ```lua 180 | -- HMGET myhash field1 field2 nofield 181 | local res, err = red:hmget("myhash", "field1", "field2", "nofield") 182 | ``` 183 | 184 | ```lua 185 | -- HMSET myhash field1 "Hello" field2 "World" 186 | local res, err = red:hmset("myhash", "field1", "Hello", "field2", "World") 187 | ``` 188 | 189 | All these command methods returns a single result in success and `nil` otherwise. In case of errors or failures, it will also return a second value which is a string describing the error. 190 | 191 | A Redis "status reply" results in a string typed return value with the "+" prefix stripped. 192 | 193 | A Redis "integer reply" results in a Lua number typed return value. 194 | 195 | A Redis "error reply" results in a `false` value *and* a string describing the error. 196 | 197 | A non-nil Redis "bulk reply" results in a Lua string as the return value. A nil bulk reply results in a `ngx.null` return value. 198 | 199 | A non-nil Redis "multi-bulk reply" results in a Lua table holding all the composing values (if any). If any of the composing value is a valid redis error value, then it will be a two element table `{false, err}`. 200 | 201 | A nil multi-bulk reply returns in a `ngx.null` value. 202 | 203 | See http://redis.io/topics/protocol for details regarding various Redis reply types. 204 | 205 | In addition to all those redis command methods, the following methods are also provided: 206 | 207 | [Back to TOC](#table-of-contents) 208 | 209 | new 210 | --- 211 | `syntax: red, err = redis:new()` 212 | 213 | Creates a redis object. In case of failures, returns `nil` and a string describing the error. 214 | 215 | [Back to TOC](#table-of-contents) 216 | 217 | connect 218 | ------- 219 | `syntax: ok, err = red:connect(host, port, options_table?)` 220 | 221 | `syntax: ok, err = red:connect("unix:/path/to/unix.sock", options_table?)` 222 | 223 | Attempts to connect to the remote host and port that the redis server is listening to or a local unix domain socket file listened by the redis server. 224 | 225 | Before actually resolving the host name and connecting to the remote backend, this method will always look up the connection pool for matched idle connections created by previous calls of this method. 226 | 227 | An optional Lua table can be specified as the last argument to this method to specify various connect options: 228 | 229 | * `pool` 230 | 231 | Specifies a custom name for the connection pool being used. If omitted, then the connection pool name will be generated from the string template `:` or ``. 232 | 233 | [Back to TOC](#table-of-contents) 234 | 235 | set_timeout 236 | ---------- 237 | `syntax: red:set_timeout(time)` 238 | 239 | Sets the timeout (in ms) protection for subsequent operations, including the `connect` method. 240 | 241 | [Back to TOC](#table-of-contents) 242 | 243 | set_keepalive 244 | ------------ 245 | `syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size)` 246 | 247 | Puts the current Redis connection immediately into the ngx_lua cosocket connection pool. 248 | 249 | You can specify the max idle timeout (in ms) when the connection is in the pool and the maximal size of the pool every nginx worker process. 250 | 251 | In case of success, returns `1`. In case of errors, returns `nil` with a string describing the error. 252 | 253 | Only call this method in the place you would have called the `close` method instead. Calling this method will immediately turn the current redis object into the `closed` state. Any subsequent operations other than `connect()` on the current objet will return the `closed` error. 254 | 255 | [Back to TOC](#table-of-contents) 256 | 257 | get_reused_times 258 | ---------------- 259 | `syntax: times, err = red:get_reused_times()` 260 | 261 | This method returns the (successfully) reused times for the current connection. In case of error, it returns `nil` and a string describing the error. 262 | 263 | If the current connection does not come from the built-in connection pool, then this method always returns `0`, that is, the connection has never been reused (yet). If the connection comes from the connection pool, then the return value is always non-zero. So this method can also be used to determine if the current connection comes from the pool. 264 | 265 | [Back to TOC](#table-of-contents) 266 | 267 | close 268 | ----- 269 | `syntax: ok, err = red:close()` 270 | 271 | Closes the current redis connection and returns the status. 272 | 273 | In case of success, returns `1`. In case of errors, returns `nil` with a string describing the error. 274 | 275 | [Back to TOC](#table-of-contents) 276 | 277 | init_pipeline 278 | ------------- 279 | `syntax: red:init_pipeline()` 280 | 281 | `syntax: red:init_pipeline(n)` 282 | 283 | Enable the redis pipelining mode. All subsequent calls to Redis command methods will automatically get cached and will send to the server in one run when the `commit_pipeline` method is called or get cancelled by calling the `cancel_pipeline` method. 284 | 285 | This method always succeeds. 286 | 287 | If the redis object is already in the Redis pipelining mode, then calling this method will discard existing cached Redis queries. 288 | 289 | The optional `n` argument specifies the (approximate) number of commands that are going to add to this pipeline, which can make things a little faster. 290 | 291 | [Back to TOC](#table-of-contents) 292 | 293 | commit_pipeline 294 | --------------- 295 | `syntax: results, err = red:commit_pipeline()` 296 | 297 | Quits the pipelining mode by committing all the cached Redis queries to the remote server in a single run. All the replies for these queries will be collected automatically and are returned as if a big multi-bulk reply at the highest level. 298 | 299 | This method returns `nil` and a Lua string describing the error upon failures. 300 | 301 | [Back to TOC](#table-of-contents) 302 | 303 | cancel_pipeline 304 | --------------- 305 | `syntax: red:cancel_pipeline()` 306 | 307 | Quits the pipelining mode by discarding all existing cached Redis commands since the last call to the `init_pipeline` method. 308 | 309 | This method always succeeds. 310 | 311 | If the redis object is not in the Redis pipelining mode, then this method is a no-op. 312 | 313 | [Back to TOC](#table-of-contents) 314 | 315 | hmset 316 | ----- 317 | `syntax: red:hmset(myhash, field1, value1, field2, value2, ...)` 318 | 319 | `syntax: red:hmset(myhash, { field1 = value1, field2 = value2, ... })` 320 | 321 | Special wrapper for the Redis "hmset" command. 322 | 323 | When there are only three arguments (including the "red" object 324 | itself), then the last argument must be a Lua table holding all the field/value pairs. 325 | 326 | [Back to TOC](#table-of-contents) 327 | 328 | array_to_hash 329 | ------------- 330 | `syntax: hash = red:array_to_hash(array)` 331 | 332 | Auxiliary function that converts an array-like Lua table into a hash-like table. 333 | 334 | This method was first introduced in the `v0.11` release. 335 | 336 | [Back to TOC](#table-of-contents) 337 | 338 | read_reply 339 | ---------- 340 | `syntax: res, err = red:read_reply()` 341 | 342 | Reading a reply from the redis server. This method is mostly useful for the [Redis Pub/Sub API](http://redis.io/topics/pubsub/), for example, 343 | 344 | ```lua 345 | local cjson = require "cjson" 346 | local redis = require "resty.redis" 347 | 348 | local red = redis:new() 349 | local red2 = redis:new() 350 | 351 | red:set_timeout(1000) -- 1 sec 352 | red2:set_timeout(1000) -- 1 sec 353 | 354 | local ok, err = red:connect("127.0.0.1", 6379) 355 | if not ok then 356 | ngx.say("1: failed to connect: ", err) 357 | return 358 | end 359 | 360 | ok, err = red2:connect("127.0.0.1", 6379) 361 | if not ok then 362 | ngx.say("2: failed to connect: ", err) 363 | return 364 | end 365 | 366 | local res, err = red:subscribe("dog") 367 | if not res then 368 | ngx.say("1: failed to subscribe: ", err) 369 | return 370 | end 371 | 372 | ngx.say("1: subscribe: ", cjson.encode(res)) 373 | 374 | res, err = red2:publish("dog", "Hello") 375 | if not res then 376 | ngx.say("2: failed to publish: ", err) 377 | return 378 | end 379 | 380 | ngx.say("2: publish: ", cjson.encode(res)) 381 | 382 | res, err = red:read_reply() 383 | if not res then 384 | ngx.say("1: failed to read reply: ", err) 385 | return 386 | end 387 | 388 | ngx.say("1: receive: ", cjson.encode(res)) 389 | 390 | red:close() 391 | red2:close() 392 | ``` 393 | 394 | Running this example gives the output like this: 395 | 396 | 1: subscribe: ["subscribe","dog",1] 397 | 2: publish: 1 398 | 1: receive: ["message","dog","Hello"] 399 | 400 | The following class methods are provieded: 401 | 402 | [Back to TOC](#table-of-contents) 403 | 404 | add_commands 405 | ------------ 406 | `syntax: hash = redis.add_commands(cmd_name1, cmd_name2, ...)` 407 | 408 | Adds new redis commands to the `resty.redis` class. Here is an example: 409 | 410 | ```lua 411 | local redis = require "resty.redis" 412 | 413 | redis.add_commands("foo", "bar") 414 | 415 | local red = redis:new() 416 | 417 | red:set_timeout(1000) -- 1 sec 418 | 419 | local ok, err = red:connect("127.0.0.1", 6379) 420 | if not ok then 421 | ngx.say("failed to connect: ", err) 422 | return 423 | end 424 | 425 | local res, err = red:foo("a") 426 | if not res then 427 | ngx.say("failed to foo: ", err) 428 | end 429 | 430 | res, err = red:bar() 431 | if not res then 432 | ngx.say("failed to bar: ", err) 433 | end 434 | ``` 435 | 436 | [Back to TOC](#table-of-contents) 437 | 438 | Redis Authentication 439 | ==================== 440 | 441 | Redis uses the `AUTH` command to do authentication: http://redis.io/commands/auth 442 | 443 | There is nothing special for this command as compared to other Redis 444 | commands like `GET` and `SET`. So one can just invoke the `auth` method on your `resty.redis` instance. Here is an example: 445 | 446 | ```lua 447 | local redis = require "resty.redis" 448 | local red = redis:new() 449 | 450 | red:set_timeout(1000) -- 1 sec 451 | 452 | local ok, err = red:connect("127.0.0.1", 6379) 453 | if not ok then 454 | ngx.say("failed to connect: ", err) 455 | return 456 | end 457 | 458 | local res, err = red:auth("foobared") 459 | if not res then 460 | ngx.say("failed to authenticate: ", err) 461 | return 462 | end 463 | ``` 464 | 465 | where we assume that the Redis server is configured with the 466 | password `foobared` in the `redis.conf` file: 467 | 468 | requirepass foobared 469 | 470 | If the password specified is wrong, then the sample above will output the 471 | following to the HTTP client: 472 | 473 | failed to authenticate: ERR invalid password 474 | 475 | [Back to TOC](#table-of-contents) 476 | 477 | Redis Transactions 478 | ================== 479 | 480 | This library supports the [Redis transactions](http://redis.io/topics/transactions/). Here is an example: 481 | 482 | ```lua 483 | local cjson = require "cjson" 484 | local redis = require "resty.redis" 485 | local red = redis:new() 486 | 487 | red:set_timeout(1000) -- 1 sec 488 | 489 | local ok, err = red:connect("127.0.0.1", 6379) 490 | if not ok then 491 | ngx.say("failed to connect: ", err) 492 | return 493 | end 494 | 495 | local ok, err = red:multi() 496 | if not ok then 497 | ngx.say("failed to run multi: ", err) 498 | return 499 | end 500 | ngx.say("multi ans: ", cjson.encode(ok)) 501 | 502 | local ans, err = red:set("a", "abc") 503 | if not ans then 504 | ngx.say("failed to run sort: ", err) 505 | return 506 | end 507 | ngx.say("set ans: ", cjson.encode(ans)) 508 | 509 | local ans, err = red:lpop("a") 510 | if not ans then 511 | ngx.say("failed to run sort: ", err) 512 | return 513 | end 514 | ngx.say("set ans: ", cjson.encode(ans)) 515 | 516 | ans, err = red:exec() 517 | ngx.say("exec ans: ", cjson.encode(ans)) 518 | 519 | red:close() 520 | ``` 521 | 522 | Then the output will be 523 | 524 | multi ans: "OK" 525 | set ans: "QUEUED" 526 | set ans: "QUEUED" 527 | exec ans: ["OK",[false,"ERR Operation against a key holding the wrong kind of value"]] 528 | 529 | [Back to TOC](#table-of-contents) 530 | 531 | Load Balancing and Failover 532 | =========================== 533 | 534 | You can trivially implement your own Redis load balancing logic yourself in Lua. Just keep a Lua table of all available Redis backend information (like host name and port numbers) and pick one server according to some rule (like round-robin or key-based hashing) from the Lua table at every request. You can keep track of the current rule state in your own Lua module's data, see http://wiki.nginx.org/HttpLuaModule#Data_Sharing_within_an_Nginx_Worker 535 | 536 | Similarly, you can implement automatic failover logic in Lua at great flexibility. 537 | 538 | [Back to TOC](#table-of-contents) 539 | 540 | Debugging 541 | ========= 542 | 543 | It is usually convenient to use the [lua-cjson](http://www.kyne.com.au/~mark/software/lua-cjson.php) library to encode the return values of the redis command methods to JSON. For example, 544 | 545 | ```lua 546 | local cjson = require "cjson" 547 | ... 548 | local res, err = red:mget("h1234", "h5678") 549 | if res then 550 | print("res: ", cjson.encode(res)) 551 | end 552 | ``` 553 | 554 | [Back to TOC](#table-of-contents) 555 | 556 | Automatic Error Logging 557 | ======================= 558 | 559 | By default the underlying [ngx_lua](http://wiki.nginx.org/HttpLuaModule) module 560 | does error logging when socket errors happen. If you are already doing proper error 561 | handling in your own Lua code, then you are recommended to disable this automatic error logging by turning off [ngx_lua](http://wiki.nginx.org/HttpLuaModule)'s [lua_socket_log_errors](http://wiki.nginx.org/HttpLuaModule#lua_socket_log_errors) directive, that is, 562 | 563 | ```nginx 564 | lua_socket_log_errors off; 565 | ``` 566 | 567 | [Back to TOC](#table-of-contents) 568 | 569 | Check List for Issues 570 | ===================== 571 | 572 | 1. Ensure you configure the connection pool size properly in the [set_keepalive](#set_keepalive) . Basically if your NGINX handle `n` concurrent requests and your NGINX has `m` workers, then the connection pool size should be configured as `n/m`. For example, if your NGINX usually handles 1000 concurrent requests and you have 10 NGINX workers, then the connection pool size should be 100. 573 | 2. Ensure the backlog setting on the Redis side is large enough. For Redis 2.8+, you can directly tune the `tcp-backlog` parameter in the `redis.conf` file (and also tune the kernel parameter `SOMAXCONN` accordingly at least on Linux). You may also want to tune the `maxclients` parameter in `redis.conf`. 574 | 3. Ensure you are not using too short timeout setting in the [set_timeout](#set_timeout) method. If you have to, try redoing the operation upon timeout and turning off [automatic error logging](#automatic-error-logging) (because you are already doing proper error handling in your own Lua code). 575 | 4. If your NGINX worker processes' CPU usage is very high under load, then the NGINX event loop might be blocked by the CPU computation too much. Try sampling a [C-land on-CPU Flame Graph](https://github.com/agentzh/nginx-systemtap-toolkit#sample-bt) and [Lua-land on-CPU Flame Graph](https://github.com/agentzh/stapxx#ngx-lj-lua-stacks) for a typical NGINX worker process. You can optimize the CPU-bound things according to these Flame Graphs. 576 | 5. If your NGINX worker processes' CPU usage is very low under load, then the NGINX event loop might be blocked by some blocking system calls (like file IO system calls). You can confirm the issue by running the [epoll-loop-blocking-distr](https://github.com/agentzh/stapxx#epoll-loop-blocking-distr) tool against a typical NGINX worker process. If it is indeed the case, then you can further sample a [C-land off-CPU Flame Graph](https://github.com/agentzh/nginx-systemtap-toolkit#sample-bt-off-cpu) for a NGINX worker process to analyze the actual blockers. 577 | 6. If your `redis-server` process is running near 100% CPU usage, then you should consider scale your Redis backend by multiple nodes or use the [C-land on-CPU Flame Graph tool](https://github.com/agentzh/nginx-systemtap-toolkit#sample-bt) to analyze the internal bottlenecks within the Redis server process. 578 | 579 | [Back to TOC](#table-of-contents) 580 | 581 | Limitations 582 | =========== 583 | 584 | * This library cannot be used in code contexts like init_by_lua*, set_by_lua*, log_by_lua*, and 585 | header_filter_by_lua* where the ngx_lua cosocket API is not available. 586 | * The `resty.redis` object instance cannot be stored in a Lua variable at the Lua module level, 587 | because it will then be shared by all the concurrent requests handled by the same nginx 588 | worker process (see 589 | http://wiki.nginx.org/HttpLuaModule#Data_Sharing_within_an_Nginx_Worker ) and 590 | result in bad race conditions when concurrent requests are trying to use the same `resty.redis` instance 591 | (you would see the "bad request" or "socket busy" error to be returned from the method calls). 592 | You should always initiate `resty.redis` objects in function local 593 | variables or in the `ngx.ctx` table. These places all have their own data copies for 594 | each request. 595 | 596 | [Back to TOC](#table-of-contents) 597 | 598 | Installation 599 | ============ 600 | 601 | If you are using the ngx_openresty bundle (http://openresty.org ), then 602 | you do not need to do anything because it already includes and enables 603 | lua-resty-redis by default. And you can just use it in your Lua code, 604 | as in 605 | 606 | ```lua 607 | local redis = require "resty.redis" 608 | ... 609 | ``` 610 | 611 | If you are using your own nginx + ngx_lua build, then you need to configure 612 | the lua_package_path directive to add the path of your lua-resty-redis source 613 | tree to ngx_lua's LUA_PATH search path, as in 614 | 615 | ```nginx 616 | # nginx.conf 617 | http { 618 | lua_package_path "/path/to/lua-resty-redis/lib/?.lua;;"; 619 | ... 620 | } 621 | ``` 622 | 623 | Ensure that the system account running your Nginx ''worker'' proceses have 624 | enough permission to read the `.lua` file. 625 | 626 | [Back to TOC](#table-of-contents) 627 | 628 | TODO 629 | ==== 630 | 631 | [Back to TOC](#table-of-contents) 632 | 633 | Community 634 | ========= 635 | 636 | [Back to TOC](#table-of-contents) 637 | 638 | English Mailing List 639 | -------------------- 640 | 641 | The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. 642 | 643 | [Back to TOC](#table-of-contents) 644 | 645 | Chinese Mailing List 646 | -------------------- 647 | 648 | The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. 649 | 650 | [Back to TOC](#table-of-contents) 651 | 652 | Bugs and Patches 653 | ================ 654 | 655 | Please report bugs or submit patches by 656 | 657 | 1. creating a ticket on the [GitHub Issue Tracker](http://github.com/agentzh/lua-resty-redis/issues), 658 | 1. or posting to the [OpenResty community](#community). 659 | 660 | [Back to TOC](#table-of-contents) 661 | 662 | Author 663 | ====== 664 | 665 | Yichun "agentzh" Zhang (章亦春) , CloudFlare Inc. 666 | 667 | [Back to TOC](#table-of-contents) 668 | 669 | Copyright and License 670 | ===================== 671 | 672 | This module is licensed under the BSD license. 673 | 674 | Copyright (C) 2012-2014, by Yichun Zhang (agentzh) , CloudFlare Inc. 675 | 676 | All rights reserved. 677 | 678 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 679 | 680 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 681 | 682 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 683 | 684 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 685 | 686 | [Back to TOC](#table-of-contents) 687 | 688 | See Also 689 | ======== 690 | * the ngx_lua module: http://wiki.nginx.org/HttpLuaModule 691 | * the redis wired protocol specification: http://redis.io/topics/protocol 692 | * the [lua-resty-memcached](https://github.com/agentzh/lua-resty-memcached) library 693 | * the [lua-resty-mysql](https://github.com/agentzh/lua-resty-mysql) library 694 | 695 | [Back to TOC](#table-of-contents) 696 | 697 | -------------------------------------------------------------------------------- /lib/resty/redis.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh), CloudFlare Inc. 2 | 3 | 4 | local sub = string.sub 5 | local byte = string.byte 6 | local tcp = ngx.socket.tcp 7 | local concat = table.concat 8 | local null = ngx.null 9 | local pairs = pairs 10 | local unpack = unpack 11 | local setmetatable = setmetatable 12 | local tonumber = tonumber 13 | local error = error 14 | 15 | 16 | local ok, new_tab = pcall(require, "table.new") 17 | if not ok then 18 | new_tab = function (narr, nrec) return {} end 19 | end 20 | 21 | 22 | local _M = new_tab(0, 155) 23 | _M._VERSION = '0.20' 24 | 25 | 26 | local commands = { 27 | "append", "asking", "auth", 28 | "bgrewriteaof", "bgsave", "bitcount", 29 | "bitop", "blpop", "brpop", 30 | "brpoplpush", "client", "cluster", 31 | "config", "dbsize", 32 | "debug", "decr", "decrby", 33 | "del", "discard", "dump", 34 | "echo", 35 | "eval", "exec", "exists", 36 | "expire", "expireat", "flushall", 37 | "flushdb", "get", "getbit", 38 | "getrange", "getset", "hdel", 39 | "hexists", "hget", "hgetall", 40 | "hincrby", "hincrbyfloat", "hkeys", 41 | "hlen", 42 | "hmget", --[[ "hmset", ]] "hscan", 43 | "hset", 44 | "hsetnx", "hvals", "incr", 45 | "incrby", "incrbyfloat", "info", 46 | "keys", 47 | "lastsave", "lindex", "linsert", 48 | "llen", "lpop", "lpush", 49 | "lpushx", "lrange", "lrem", 50 | "lset", "ltrim", "mget", 51 | "migrate", 52 | "monitor", "move", "mset", 53 | "msetnx", "multi", "object", 54 | "persist", "pexpire", "pexpireat", 55 | "ping", "psetex", --[[ "psubscribe", ]] 56 | "pttl", 57 | "publish", --[[ "punsubscribe", ]] "pubsub", 58 | "quit", 59 | "randomkey", "rename", "renamenx", 60 | "restore", 61 | "rpop", "rpoplpush", "rpush", 62 | "rpushx", "sadd", "save", 63 | "scan", "scard", "script", 64 | "sdiff", "sdiffstore", 65 | "select", "set", "setbit", 66 | "setex", "setnx", "setrange", 67 | "shutdown", "sinter", "sinterstore", 68 | "sismember", "slaveof", "slowlog", 69 | "smembers", "smove", "sort", 70 | "spop", "srandmember", "srem", 71 | "sscan", 72 | "strlen", --[[ "subscribe", ]] "sunion", 73 | "sunionstore", "sync", "time", 74 | "ttl", 75 | "type", --[[ "unsubscribe", ]] "unwatch", 76 | "watch", "zadd", "zcard", 77 | "zcount", "zincrby", "zinterstore", 78 | "zrange", "zrangebyscore", "zrank", 79 | "zrem", "zremrangebyrank", "zremrangebyscore", 80 | "zrevrange", "zrevrangebyscore", "zrevrank", 81 | "zscan", 82 | "zscore", "zunionstore", "evalsha" 83 | } 84 | 85 | 86 | local sub_commands = { 87 | "subscribe", "psubscribe" 88 | } 89 | 90 | 91 | local unsub_commands = { 92 | "unsubscribe", "punsubscribe" 93 | } 94 | 95 | 96 | local mt = { __index = _M } 97 | 98 | 99 | function _M.new(self) 100 | local sock, err = tcp() 101 | if not sock then 102 | return nil, err 103 | end 104 | return setmetatable({ sock = sock }, mt) 105 | end 106 | 107 | 108 | function _M.set_timeout(self, timeout) 109 | local sock = self.sock 110 | if not sock then 111 | return nil, "not initialized" 112 | end 113 | 114 | return sock:settimeout(timeout) 115 | end 116 | 117 | 118 | function _M.connect(self, ...) 119 | local sock = self.sock 120 | if not sock then 121 | return nil, "not initialized" 122 | end 123 | 124 | self.subscribed = nil 125 | 126 | return sock:connect(...) 127 | end 128 | 129 | 130 | function _M.set_keepalive(self, ...) 131 | local sock = self.sock 132 | if not sock then 133 | return nil, "not initialized" 134 | end 135 | 136 | if self.subscribed then 137 | return nil, "subscribed state" 138 | end 139 | 140 | return sock:setkeepalive(...) 141 | end 142 | 143 | 144 | function _M.get_reused_times(self) 145 | local sock = self.sock 146 | if not sock then 147 | return nil, "not initialized" 148 | end 149 | 150 | return sock:getreusedtimes() 151 | end 152 | 153 | 154 | local function close(self) 155 | local sock = self.sock 156 | if not sock then 157 | return nil, "not initialized" 158 | end 159 | 160 | return sock:close() 161 | end 162 | _M.close = close 163 | 164 | 165 | local function _read_reply(self, sock) 166 | local line, err = sock:receive() 167 | if not line then 168 | if err == "timeout" and not self.subscribed then 169 | sock:close() 170 | end 171 | return nil, err 172 | end 173 | 174 | local prefix = byte(line) 175 | 176 | if prefix == 36 then -- char '$' 177 | -- print("bulk reply") 178 | 179 | local size = tonumber(sub(line, 2)) 180 | if size < 0 then 181 | return null 182 | end 183 | 184 | local data, err = sock:receive(size) 185 | if not data then 186 | if err == "timeout" then 187 | sock:close() 188 | end 189 | return nil, err 190 | end 191 | 192 | local dummy, err = sock:receive(2) -- ignore CRLF 193 | if not dummy then 194 | return nil, err 195 | end 196 | 197 | return data 198 | 199 | elseif prefix == 43 then -- char '+' 200 | -- print("status reply") 201 | 202 | return sub(line, 2) 203 | 204 | elseif prefix == 42 then -- char '*' 205 | local n = tonumber(sub(line, 2)) 206 | 207 | -- print("multi-bulk reply: ", n) 208 | if n < 0 then 209 | return null 210 | end 211 | 212 | local vals = new_tab(n, 0); 213 | local nvals = 0 214 | for i = 1, n do 215 | local res, err = _read_reply(self, sock) 216 | if res then 217 | nvals = nvals + 1 218 | vals[nvals] = res 219 | 220 | elseif res == nil then 221 | return nil, err 222 | 223 | else 224 | -- be a valid redis error value 225 | nvals = nvals + 1 226 | vals[nvals] = {false, err} 227 | end 228 | end 229 | 230 | return vals 231 | 232 | elseif prefix == 58 then -- char ':' 233 | -- print("integer reply") 234 | return tonumber(sub(line, 2)) 235 | 236 | elseif prefix == 45 then -- char '-' 237 | -- print("error reply: ", n) 238 | 239 | return false, sub(line, 2) 240 | 241 | else 242 | return nil, "unkown prefix: \"" .. prefix .. "\"" 243 | end 244 | end 245 | 246 | 247 | local function _gen_req(args) 248 | local nargs = #args 249 | 250 | local req = new_tab(nargs + 1, 0) 251 | req[1] = "*" .. nargs .. "\r\n" 252 | local nbits = 1 253 | 254 | for i = 1, nargs do 255 | local arg = args[i] 256 | nbits = nbits + 1 257 | 258 | if not arg then 259 | req[nbits] = "$-1\r\n" 260 | 261 | else 262 | if type(arg) ~= "string" then 263 | arg = tostring(arg) 264 | end 265 | req[nbits] = "$" .. #arg .. "\r\n" .. arg .. "\r\n" 266 | end 267 | end 268 | 269 | -- it is faster to do string concatenation on the Lua land 270 | return concat(req) 271 | end 272 | 273 | 274 | local function _do_cmd(self, ...) 275 | local args = {...} 276 | 277 | local sock = self.sock 278 | if not sock then 279 | return nil, "not initialized" 280 | end 281 | 282 | local req = _gen_req(args) 283 | 284 | local reqs = self._reqs 285 | if reqs then 286 | reqs[#reqs + 1] = req 287 | return 288 | end 289 | 290 | -- print("request: ", table.concat(req)) 291 | 292 | local bytes, err = sock:send(req) 293 | if not bytes then 294 | return nil, err 295 | end 296 | 297 | return _read_reply(self, sock) 298 | end 299 | 300 | 301 | local function _check_subscribed(self, res) 302 | if type(res) == "table" 303 | and (res[1] == "unsubscribe" or res[1] == "punsubscribe") 304 | and res[3] == 0 305 | then 306 | self.subscribed = nil 307 | end 308 | end 309 | 310 | 311 | function _M.read_reply(self) 312 | local sock = self.sock 313 | if not sock then 314 | return nil, "not initialized" 315 | end 316 | 317 | if not self.subscribed then 318 | return nil, "not subscribed" 319 | end 320 | 321 | local res, err = _read_reply(self, sock) 322 | _check_subscribed(self, res) 323 | 324 | return res, err 325 | end 326 | 327 | 328 | for i = 1, #commands do 329 | local cmd = commands[i] 330 | 331 | _M[cmd] = 332 | function (self, ...) 333 | return _do_cmd(self, cmd, ...) 334 | end 335 | end 336 | 337 | 338 | for i = 1, #sub_commands do 339 | local cmd = sub_commands[i] 340 | 341 | _M[cmd] = 342 | function (self, ...) 343 | self.subscribed = true 344 | return _do_cmd(self, cmd, ...) 345 | end 346 | end 347 | 348 | 349 | for i = 1, #unsub_commands do 350 | local cmd = unsub_commands[i] 351 | 352 | _M[cmd] = 353 | function (self, ...) 354 | local res, err = _do_cmd(self, cmd, ...) 355 | _check_subscribed(self, res) 356 | return res, err 357 | end 358 | end 359 | 360 | 361 | function _M.hmset(self, hashname, ...) 362 | local args = {...} 363 | if #args == 1 then 364 | local t = args[1] 365 | 366 | local n = 0 367 | for k, v in pairs(t) do 368 | n = n + 2 369 | end 370 | 371 | local array = new_tab(n, 0) 372 | 373 | local i = 0 374 | for k, v in pairs(t) do 375 | array[i + 1] = k 376 | array[i + 2] = v 377 | i = i + 2 378 | end 379 | -- print("key", hashname) 380 | return _do_cmd(self, "hmset", hashname, unpack(array)) 381 | end 382 | 383 | -- backwards compatibility 384 | return _do_cmd(self, "hmset", hashname, ...) 385 | end 386 | 387 | 388 | function _M.init_pipeline(self, n) 389 | self._reqs = new_tab(n or 4, 0) 390 | end 391 | 392 | 393 | function _M.cancel_pipeline(self) 394 | self._reqs = nil 395 | end 396 | 397 | 398 | function _M.commit_pipeline(self) 399 | local reqs = self._reqs 400 | if not reqs then 401 | return nil, "no pipeline" 402 | end 403 | 404 | self._reqs = nil 405 | 406 | local sock = self.sock 407 | if not sock then 408 | return nil, "not initialized" 409 | end 410 | 411 | local bytes, err = sock:send(reqs) 412 | if not bytes then 413 | return nil, err 414 | end 415 | 416 | local nvals = 0 417 | local nreqs = #reqs 418 | local vals = new_tab(nreqs, 0) 419 | for i = 1, nreqs do 420 | local res, err = _read_reply(self, sock) 421 | if res then 422 | nvals = nvals + 1 423 | vals[nvals] = res 424 | 425 | elseif res == nil then 426 | if err == "timeout" then 427 | close(self) 428 | end 429 | return nil, err 430 | 431 | else 432 | -- be a valid redis error value 433 | nvals = nvals + 1 434 | vals[nvals] = {false, err} 435 | end 436 | end 437 | 438 | return vals 439 | end 440 | 441 | 442 | function _M.array_to_hash(self, t) 443 | local n = #t 444 | -- print("n = ", n) 445 | local h = new_tab(0, n / 2) 446 | for i = 1, n, 2 do 447 | h[t[i]] = t[i + 1] 448 | end 449 | return h 450 | end 451 | 452 | 453 | function _M.add_commands(...) 454 | local cmds = {...} 455 | for i = 1, #cmds do 456 | local cmd = cmds[i] 457 | _M[cmd] = 458 | function (self, ...) 459 | return _do_cmd(self, cmd, ...) 460 | end 461 | end 462 | end 463 | 464 | 465 | return _M 466 | -------------------------------------------------------------------------------- /lib/resty/redis_cluster.lua: -------------------------------------------------------------------------------- 1 | local redis = require "resty.redis" 2 | local bit = require "bit" 3 | 4 | local setmetatable = setmetatable 5 | local pairs = pairs 6 | local sub = string.sub 7 | local find = string.find 8 | local byte = string.byte 9 | local char = string.char 10 | local tostring = tostring 11 | local tonumber = tonumber 12 | 13 | 14 | local ok, new_tab = pcall(require, "table.new") 15 | if not ok then 16 | new_tab = function (narr, nrec) return {} end 17 | end 18 | 19 | local _M = new_tab(0, 20) 20 | 21 | _M._VERSION = '0.01' 22 | 23 | local REDIS_CLUSTER_HASH_SLOTS = 16384 24 | local REDIS_CLUSTER_REQUEST_TTL = 16 25 | local REDIS_CLUSTER_DEFAULT_TIMEOUT = 1000 --1sec 26 | local REDIS_CLUSTER_DEFAULT_KEEPALIVE_DURATION = 10000 --10sec 27 | local REDIS_CLUSTER_DEFAULT_KEEPALIVE_SIZE = 100 --100 connections 28 | 29 | -- TODO: move to separate file 30 | local XMODEMCRC16Lookup = { 31 | 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 32 | 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, 33 | 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, 34 | 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, 35 | 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, 36 | 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, 37 | 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, 38 | 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, 39 | 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, 40 | 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, 41 | 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, 42 | 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, 43 | 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, 44 | 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, 45 | 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, 46 | 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, 47 | 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, 48 | 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, 49 | 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, 50 | 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, 51 | 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, 52 | 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, 53 | 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, 54 | 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, 55 | 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, 56 | 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, 57 | 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, 58 | 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, 59 | 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, 60 | 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, 61 | 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, 62 | 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 63 | } 64 | 65 | local cluster_invalid_cmds = { 66 | ["info"] = true, 67 | ["multi"] = true, 68 | ["exec"] = true, 69 | ["slaveof"] = true, 70 | ["config"] = true, 71 | ["shutdown"] = true 72 | } 73 | 74 | local band, bor, bxor = bit.band, bit.bor, bit.bxor 75 | local lshift, rshift = bit.lshift, bit.rshift 76 | 77 | -- Depends on luajit bitop extension. 78 | local function crc16(str) 79 | local crc = 0 80 | for i = 1, #str do 81 | local b = byte(str, i) 82 | crc = bxor(band(lshift(crc, 8), 0xffff), XMODEMCRC16Lookup[band(bxor(rshift(crc, 8), b), 0xff) + 1]) 83 | end 84 | return crc 85 | end 86 | 87 | local clusters = new_tab(0, 20) 88 | 89 | local function get_redis_link(host, port, timeout) 90 | local r = redis:new() 91 | 92 | r:set_timeout(timeout) 93 | r:connect(host, port) 94 | 95 | return r 96 | end 97 | 98 | local function set_node_name(n) 99 | if n["name"] == nil then 100 | n["name"] = n["host"] .. ":" .. n["port"] 101 | end 102 | end 103 | 104 | local function string_split(str, delim, max) 105 | if str == nil or delim == nil then 106 | return nil 107 | end 108 | 109 | if max == nil or max <= 0 then 110 | max = 1000 111 | end 112 | 113 | local t = new_tab(max, 0) 114 | local index = 1 115 | local start = 1 116 | for i = 1, max do 117 | local last, delim_last = find(str, delim, start, true) 118 | if last == nil or delim_last == nil then 119 | break 120 | end 121 | 122 | t[i] = sub(str, start, last - 1) 123 | start = delim_last + 1 124 | index = i + 1 125 | end 126 | t[index] = sub(str, start) 127 | return t 128 | end 129 | 130 | local mt = { __index = _M } 131 | 132 | function _M.new(self, cluster_id, startup_nodes, opt) 133 | if clusters[cluster_id] == nil then 134 | 135 | clusters[cluster_id] = { 136 | startup_nodes = startup_nodes, 137 | nodes = nil, 138 | slots = nil, 139 | timeout = (opt and opt.timeout) and opt.timeout or REDIS_CLUSTER_DEFAULT_TIMEOUT, 140 | keepalive_size = (opt and opt.keepalive_size) and opt.keepalive_size or REDIS_CLUSTER_DEFAULT_KEEPALIVE_SIZE, 141 | keepalive_duration = (opt and opt.keepalive_duration) and opt.keepalive_duration or REDIS_CLUSTER_DEFAULT_KEEPALIVE_DURATION, 142 | ttl = (opt and opt.ttl) and opt.ttl or REDIS_CLUSTER_REQUEST_TTL, 143 | refresh_table_asap = false, 144 | initialized = false 145 | } 146 | end 147 | 148 | local obj = { 149 | cluster_id = cluster_id 150 | } 151 | return setmetatable(obj, mt) 152 | end 153 | 154 | function _M.initialize(self) 155 | local cluster = clusters[self.cluster_id] 156 | 157 | if cluster == nil or cluster.startup_nodes == nil then 158 | return nil 159 | end 160 | 161 | if cluster.initialized == true and cluster.refresh_table_asap == false then 162 | return nil 163 | end 164 | 165 | local startup_nodes = cluster["startup_nodes"] 166 | 167 | for i = 1, #startup_nodes do 168 | local node = startup_nodes[i] 169 | local r = get_redis_link(node[1], node[2], cluster.timeout) 170 | local results, err = r:cluster("nodes") 171 | 172 | cluster.nodes = new_tab(500, 0) 173 | cluster.slots = new_tab(REDIS_CLUSTER_HASH_SLOTS, 0) 174 | 175 | if results then 176 | local lines = string_split(results, char(10), 1000) 177 | for line_index = 1, #lines do 178 | local line = lines[line_index] 179 | local fields = string_split(line, " ") 180 | if #fields > 1 then 181 | local addr_str = fields[2] 182 | local addr = nil 183 | 184 | if addr_str == ":0" then 185 | addr = { node[1], 186 | tonumber(node[2]), 187 | node[1] .. ":" .. tostring(node[2]) } 188 | else 189 | local host_port = string_split(addr_str, ":", 2) 190 | addr = { host_port[1], 191 | tonumber(host_port[2]), 192 | addr_str } 193 | end 194 | cluster.nodes[#(cluster.nodes) + 1] = addr 195 | 196 | local cluster_slots = cluster.slots 197 | 198 | for slot_index = 9, #fields do 199 | local slot = fields[slot_index] 200 | 201 | if not slot then 202 | break 203 | end 204 | 205 | if sub(slot, 1, 1) ~= "[" then 206 | local range = string_split(slot, "-", 2) 207 | local first = tonumber(range[1]) 208 | local last = first 209 | if #range >= 2 then 210 | last = tonumber(range[2]) 211 | end 212 | 213 | for ind = first + 1, last + 1 do 214 | cluster_slots[ind] = addr 215 | end 216 | end 217 | end 218 | end 219 | end 220 | 221 | self:populate_startup_nodes() 222 | cluster.initialized = true 223 | cluster.refresh_table_asap = false 224 | r:set_keepalive(cluster.keepalive_duration, cluster.keepalive_size) 225 | break 226 | else 227 | r:close() 228 | end 229 | end 230 | end 231 | 232 | function _M.populate_startup_nodes(self) 233 | local cluster = clusters[self.cluster_id] 234 | 235 | if cluster == nil or cluster.startup_nodes == nil then 236 | return nil 237 | end 238 | 239 | local startup_nodes = cluster.startup_nodes 240 | local nodes = cluster.nodes 241 | 242 | local startup_nodes_count = #startup_nodes 243 | local nodes_count = #nodes 244 | 245 | local unique_nodes = new_tab(0, nodes_count + startup_nodes_count) 246 | 247 | for i = 1, startup_nodes_count do 248 | local startup_node = startup_nodes[i] 249 | if startup_node[3] == nil then 250 | startup_node[3] = startup_node[1] .. ":" .. tostring(startup_node[2]) 251 | end 252 | 253 | unique_nodes[startup_node[3]] = startup_node 254 | end 255 | 256 | 257 | for i = 1, nodes_count do 258 | local node = nodes[i] 259 | unique_nodes[node[3]] = node 260 | end 261 | 262 | cluster.startup_nodes = new_tab(nodes_count + startup_nodes_count, 0) 263 | startup_nodes = cluster.startup_nodes 264 | 265 | for name, node in pairs(unique_nodes) do 266 | startup_nodes[#startup_nodes + 1] = node 267 | end 268 | end 269 | 270 | function _M.flush_slots_cache(self) 271 | clusters[self.cluster_id].slots = nil 272 | end 273 | 274 | function _M.keyslot(self, key) 275 | local s = find(key, "{") 276 | if s then 277 | local e = find(key, "}", s+1) 278 | if e and e ~= s+1 then 279 | key = sub(key, s+1, e-1) 280 | end 281 | end 282 | 283 | return (crc16(key) % REDIS_CLUSTER_HASH_SLOTS) + 1 284 | end 285 | 286 | function _M.get_random_connection(self) 287 | local cluster = clusters[self.cluster_id] 288 | 289 | if cluster == nil or cluster.startup_nodes == nil then 290 | return nil 291 | end 292 | 293 | local startup_nodes = cluster.startup_nodes 294 | 295 | for i = 1, #startup_nodes do 296 | local node = startup_nodes[i] 297 | local r = get_redis_link(node[1], node[2], cluster.timeout) 298 | local result, err = r:ping() 299 | if result == "PONG" then 300 | return r 301 | end 302 | r:close() 303 | end 304 | 305 | return nil 306 | end 307 | 308 | function _M.get_connection_by_slot(self, slot) 309 | local cluster = clusters[self.cluster_id] 310 | local node = cluster.slots[slot] 311 | 312 | if node == nil then 313 | return self:get_random_connection() 314 | end 315 | 316 | return get_redis_link(node[1], node[2], cluster.timeout) 317 | end 318 | 319 | function _M.send_cluster_command(self, cmd, ...) 320 | local cluster = clusters[self.cluster_id] 321 | 322 | if cluster.initialized == false then 323 | return nil, "Uninitialized cluster" 324 | end 325 | 326 | if cluster.refresh_table_asap == true then 327 | self:initialize() 328 | end 329 | 330 | local ttl = REDIS_CLUSTER_REQUEST_TTL 331 | local asking = false 332 | local try_random_node = false 333 | local argv = {...} 334 | local last_error = nil 335 | 336 | while ttl > 0 do 337 | ttl = ttl - 1 338 | 339 | if cluster_invalid_cmds[cmd] == true then 340 | last_error = "No way to dispatch this command to Redis cluster" 341 | break 342 | end 343 | 344 | local key = argv[1] 345 | local slot = self:keyslot(key) 346 | 347 | local r = nil 348 | 349 | if try_random_node == true then 350 | r = self:get_random_connection() 351 | try_random_node = false 352 | else 353 | r = self:get_connection_by_slot(slot) 354 | end 355 | 356 | if asking == true then 357 | -- TODO: pipeline 358 | r:asking() 359 | end 360 | 361 | asking = false 362 | 363 | local result, err = r[cmd](r, ...) 364 | r:set_keepalive(cluster.keepalive_duration, cluster.keepalive_size) 365 | 366 | if err == nil and result ~= nil then 367 | return result, err 368 | end 369 | 370 | last_error = err 371 | 372 | local err_split = string_split(err, " ") 373 | 374 | if err_split[1] == "ASK" then 375 | asking = true 376 | end 377 | 378 | if asking == true or err_split[1] == "MOVED" then 379 | if asking == false then 380 | cluster.refresh_table_asap = true 381 | end 382 | 383 | local newslot = tonumber(err_split[2]) + 1 384 | local node_ip_port = string_split(err_split[3], ":") 385 | 386 | local addr = { node_ip_port[1], tonumber(node_ip_port[2]), err_split[3]} 387 | 388 | cluster.slots[newslot] = addr 389 | else 390 | try_random_node = true 391 | end 392 | end 393 | return nil, last_error 394 | end 395 | 396 | local __M = setmetatable(_M, {__index = function (tab, cmd) 397 | return function (self, ...) 398 | return tab.send_cluster_command(self, cmd, ...) 399 | end 400 | end}) 401 | 402 | return __M 403 | -------------------------------------------------------------------------------- /t/bugs.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | my $HtmlDir = html_dir; 12 | 13 | our $HttpConfig = qq{ 14 | lua_package_path "$HtmlDir/?.lua;$pwd/lib/?.lua;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | log_level 'warn'; 24 | 25 | run_tests(); 26 | 27 | __DATA__ 28 | 29 | === TEST 1: github issue #108: ngx.locaiton.capture + redis.set_keepalive 30 | --- http_config eval: $::HttpConfig 31 | --- config 32 | location /r1 { 33 | default_type text/html; 34 | set $port $TEST_NGINX_REDIS_PORT; 35 | #lua_code_cache off; 36 | lua_need_request_body on; 37 | content_by_lua_file html/r1.lua; 38 | } 39 | 40 | location /r2 { 41 | default_type text/html; 42 | set $port $TEST_NGINX_REDIS_PORT; 43 | #lua_code_cache off; 44 | lua_need_request_body on; 45 | content_by_lua_file html/r2.lua; 46 | } 47 | 48 | location /anyurl { 49 | internal; 50 | proxy_pass http://127.0.0.1:$server_port/dummy; 51 | } 52 | 53 | location = /dummy { 54 | echo dummy; 55 | } 56 | --- user_files 57 | >>> r1.lua 58 | local redis = require "resty.redis" 59 | local red = redis:new() 60 | local ok, err = red:connect("127.0.0.1", ngx.var.port) 61 | if not ok then 62 | ngx.say("failed to connect: ", err) 63 | return 64 | end 65 | local ok, err = red:flushall() 66 | if not ok then 67 | ngx.say("failed to flushall: ", err) 68 | return 69 | end 70 | ok, err = red:set_keepalive() 71 | if not ok then 72 | ngx.say("failed to set keepalive: ", err) 73 | return 74 | end 75 | local http_ress = ngx.location.capture("/r2") -- 1 76 | ngx.say("ok") 77 | 78 | >>> r2.lua 79 | local redis = require "resty.redis" 80 | local red = redis:new() 81 | local ok, err = red:connect("127.0.0.1", ngx.var.port) --2 82 | if not ok then 83 | ngx.say("failed to connect: ", err) 84 | return 85 | end 86 | local res = ngx.location.capture("/anyurl") --3 87 | --- request 88 | GET /r1 89 | --- response_body 90 | ok 91 | --- no_error_log 92 | [error] 93 | 94 | 95 | 96 | === TEST 2: exit(404) after I/O (ngx_lua github issue #110 97 | https://github.com/chaoslawful/lua-nginx-module/issues/110 98 | --- http_config eval: $::HttpConfig 99 | --- config 100 | error_page 400 /400.html; 101 | error_page 404 /404.html; 102 | location /foo { 103 | access_by_lua ' 104 | local redis = require "resty.redis" 105 | local red = redis:new() 106 | 107 | red:set_timeout(2000) -- 2 sec 108 | 109 | -- ngx.log(ngx.ERR, "hello"); 110 | 111 | -- or connect to a unix domain socket file listened 112 | -- by a redis server: 113 | -- local ok, err = red:connect("unix:/path/to/redis.sock") 114 | 115 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 116 | if not ok then 117 | ngx.log(ngx.ERR, "failed to connect: ", err) 118 | return 119 | end 120 | 121 | res, err = red:set("dog", "an animal") 122 | if not res then 123 | ngx.log(ngx.ERR, "failed to set dog: ", err) 124 | return 125 | end 126 | 127 | -- ngx.say("set dog: ", res) 128 | 129 | local res, err = red:get("dog") 130 | if err then 131 | ngx.log(ngx.ERR, "failed to get dog: ", err) 132 | return 133 | end 134 | 135 | if not res then 136 | ngx.log(ngx.ERR, "dog not found.") 137 | return 138 | end 139 | 140 | -- ngx.say("dog: ", res) 141 | 142 | -- red:close() 143 | local ok, err = red:set_keepalive(0, 100) 144 | if not ok then 145 | ngx.log(ngx.ERR, "failed to set keepalive: ", err) 146 | return 147 | end 148 | 149 | ngx.exit(404) 150 | '; 151 | echo Hello; 152 | } 153 | --- user_files 154 | >>> 400.html 155 | Bad request, dear... 156 | >>> 404.html 157 | Not found, dear... 158 | --- request 159 | GET /foo 160 | --- response_body 161 | Not found, dear... 162 | --- error_code: 404 163 | --- no_error_log 164 | [error] 165 | 166 | 167 | 168 | === TEST 3: set and get an empty string 169 | --- http_config eval: $::HttpConfig 170 | --- config 171 | location /t { 172 | content_by_lua ' 173 | local redis = require "resty.redis" 174 | local red = redis:new() 175 | 176 | red:set_timeout(1000) -- 1 sec 177 | 178 | -- or connect to a unix domain socket file listened 179 | -- by a redis server: 180 | -- local ok, err = red:connect("unix:/path/to/redis.sock") 181 | 182 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 183 | if not ok then 184 | ngx.say("failed to connect: ", err) 185 | return 186 | end 187 | 188 | res, err = red:set("dog", "") 189 | if not res then 190 | ngx.say("failed to set dog: ", err) 191 | return 192 | end 193 | 194 | ngx.say("set dog: ", res) 195 | 196 | for i = 1, 2 do 197 | local res, err = red:get("dog") 198 | if err then 199 | ngx.say("failed to get dog: ", err) 200 | return 201 | end 202 | 203 | if not res then 204 | ngx.say("dog not found.") 205 | return 206 | end 207 | 208 | ngx.say("dog: ", res) 209 | end 210 | 211 | red:close() 212 | '; 213 | } 214 | --- request 215 | GET /t 216 | --- response_body 217 | set dog: OK 218 | dog: 219 | dog: 220 | --- no_error_log 221 | [error] 222 | 223 | 224 | 225 | === TEST 4: ngx.exec() after red:get() 226 | --- http_config eval: $::HttpConfig 227 | --- config 228 | location /t { 229 | content_by_lua ' 230 | local redis = require "resty.redis" 231 | local red = redis:new() 232 | 233 | red:set_timeout(1000) -- 1 sec 234 | 235 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 236 | if not ok then 237 | ngx.say("failed to connect: ", err) 238 | return 239 | end 240 | 241 | local res, err = red:get("dog") 242 | if err then 243 | ngx.say("failed to get dog: ", err) 244 | return 245 | end 246 | 247 | ngx.exec("/hello") 248 | '; 249 | } 250 | 251 | location = /hello { 252 | echo hello world; 253 | } 254 | 255 | --- request 256 | GET /t 257 | --- response_body 258 | hello world 259 | --- no_error_log 260 | [error] 261 | 262 | -------------------------------------------------------------------------------- /t/count.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: module size of resty.redis 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location = /t { 31 | content_by_lua ' 32 | local redis = require "resty.redis" 33 | n = 0 34 | for _, _ in pairs(redis) do 35 | n = n + 1 36 | end 37 | ngx.say("size: ", n) 38 | '; 39 | } 40 | --- request 41 | GET /t 42 | --- response_body 43 | size: 155 44 | --- no_error_log 45 | [error] 46 | 47 | -------------------------------------------------------------------------------- /t/hmset.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: hmset key-pairs 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location /t { 31 | content_by_lua ' 32 | local redis = require "resty.redis" 33 | local red = redis:new() 34 | 35 | red:set_timeout(1000) -- 1 sec 36 | 37 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 38 | if not ok then 39 | ngx.say("failed to connect: ", err) 40 | return 41 | end 42 | 43 | local res, err = red:hmset("animals", "dog", "bark", "cat", "meow") 44 | if not res then 45 | ngx.say("failed to set animals: ", err) 46 | return 47 | end 48 | ngx.say("hmset animals: ", res) 49 | 50 | local res, err = red:hmget("animals", "dog", "cat") 51 | if not res then 52 | ngx.say("failed to get animals: ", err) 53 | return 54 | end 55 | 56 | ngx.say("hmget animals: ", res) 57 | 58 | red:close() 59 | '; 60 | } 61 | --- request 62 | GET /t 63 | --- response_body 64 | hmset animals: OK 65 | hmget animals: barkmeow 66 | --- no_error_log 67 | [error] 68 | 69 | 70 | 71 | === TEST 2: hmset lua tables 72 | --- http_config eval: $::HttpConfig 73 | --- config 74 | location /t { 75 | content_by_lua ' 76 | local redis = require "resty.redis" 77 | local red = redis:new() 78 | 79 | red:set_timeout(1000) -- 1 sec 80 | 81 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 82 | if not ok then 83 | ngx.say("failed to connect: ", err) 84 | return 85 | end 86 | 87 | local t = { dog = "bark", cat = "meow", cow = "moo" } 88 | local res, err = red:hmset("animals", t) 89 | if not res then 90 | ngx.say("failed to set animals: ", err) 91 | return 92 | end 93 | ngx.say("hmset animals: ", res) 94 | 95 | local res, err = red:hmget("animals", "dog", "cat", "cow") 96 | if not res then 97 | ngx.say("failed to get animals: ", err) 98 | return 99 | end 100 | 101 | ngx.say("hmget animals: ", res) 102 | 103 | red:close() 104 | '; 105 | } 106 | --- request 107 | GET /t 108 | --- response_body 109 | hmset animals: OK 110 | hmget animals: barkmeowmoo 111 | --- no_error_log 112 | [error] 113 | 114 | 115 | 116 | === TEST 3: hmset a single scalar 117 | --- http_config eval: $::HttpConfig 118 | --- config 119 | location /t { 120 | content_by_lua ' 121 | local redis = require "resty.redis" 122 | local red = redis:new() 123 | 124 | red:set_timeout(1000) -- 1 sec 125 | 126 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 127 | if not ok then 128 | ngx.say("failed to connect: ", err) 129 | return 130 | end 131 | 132 | local res, err = red:hmset("animals", "cat") 133 | if not res then 134 | ngx.say("failed to set animals: ", err) 135 | return 136 | end 137 | ngx.say("hmset animals: ", res) 138 | 139 | local res, err = red:hmget("animals", "cat") 140 | if not res then 141 | ngx.say("failed to get animals: ", err) 142 | return 143 | end 144 | 145 | ngx.say("hmget animals: ", res) 146 | 147 | red:close() 148 | '; 149 | } 150 | --- request 151 | GET /t 152 | --- response_body_like: 500 Internal Server Error 153 | --- error_code: 500 154 | --- error_log 155 | table expected, got string 156 | 157 | -------------------------------------------------------------------------------- /t/mock.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (4 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: continue using the obj when read timeout happens 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location /t { 31 | content_by_lua ' 32 | local redis = require "resty.redis" 33 | local red = redis:new() 34 | 35 | local ok, err = red:connect("127.0.0.1", 1921); 36 | if not ok then 37 | ngx.say("failed to connect: ", err) 38 | return 39 | end 40 | 41 | red:set_timeout(100) -- 0.1 sec 42 | 43 | for i = 1, 2 do 44 | local data, err = red:get("foo") 45 | if not data then 46 | ngx.say("failed to get: ", err) 47 | else 48 | ngx.say("get: ", data); 49 | end 50 | ngx.sleep(0.1) 51 | end 52 | 53 | red:close() 54 | '; 55 | } 56 | --- request 57 | GET /t 58 | --- tcp_listen: 1921 59 | --- tcp_query eval 60 | "*2\r 61 | \$3\r 62 | get\r 63 | \$3\r 64 | foo\r 65 | " 66 | --- tcp_reply eval 67 | "\$5\r\nhello\r\n" 68 | --- tcp_reply_delay: 150ms 69 | --- response_body 70 | failed to get: timeout 71 | failed to get: closed 72 | --- error_log 73 | lua tcp socket read timed out 74 | 75 | -------------------------------------------------------------------------------- /t/pipeline.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: basic 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location /t { 31 | content_by_lua ' 32 | local redis = require "resty.redis" 33 | local red = redis:new() 34 | 35 | red:set_timeout(1000) -- 1 sec 36 | 37 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 38 | if not ok then 39 | ngx.say("failed to connect: ", err) 40 | return 41 | end 42 | 43 | for i = 1, 2 do 44 | red:init_pipeline() 45 | 46 | red:set("dog", "an animal") 47 | red:get("dog") 48 | red:set("dog", "hello") 49 | red:get("dog") 50 | 51 | local results = red:commit_pipeline() 52 | local cjson = require "cjson" 53 | ngx.say(cjson.encode(results)) 54 | end 55 | 56 | red:close() 57 | '; 58 | } 59 | --- request 60 | GET /t 61 | --- response_body 62 | ["OK","an animal","OK","hello"] 63 | ["OK","an animal","OK","hello"] 64 | --- no_error_log 65 | [error] 66 | 67 | 68 | 69 | === TEST 2: cancel automatically 70 | --- http_config eval: $::HttpConfig 71 | --- config 72 | location /t { 73 | content_by_lua ' 74 | local redis = require "resty.redis" 75 | local red = redis:new() 76 | 77 | red:set_timeout(1000) -- 1 sec 78 | 79 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 80 | if not ok then 81 | ngx.say("failed to connect: ", err) 82 | return 83 | end 84 | 85 | red:init_pipeline() 86 | 87 | red:set("dog", "an animal") 88 | red:get("dog") 89 | 90 | for i = 1, 2 do 91 | red:init_pipeline() 92 | 93 | red:set("dog", "an animal") 94 | red:get("dog") 95 | red:set("dog", "hello") 96 | red:get("dog") 97 | 98 | local results = red:commit_pipeline() 99 | local cjson = require "cjson" 100 | ngx.say(cjson.encode(results)) 101 | end 102 | 103 | red:close() 104 | '; 105 | } 106 | --- request 107 | GET /t 108 | --- response_body 109 | ["OK","an animal","OK","hello"] 110 | ["OK","an animal","OK","hello"] 111 | --- no_error_log 112 | [error] 113 | 114 | 115 | 116 | === TEST 3: cancel explicitly 117 | --- http_config eval: $::HttpConfig 118 | --- config 119 | location /t { 120 | content_by_lua ' 121 | local redis = require "resty.redis" 122 | local red = redis:new() 123 | 124 | red:set_timeout(1000) -- 1 sec 125 | 126 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 127 | if not ok then 128 | ngx.say("failed to connect: ", err) 129 | return 130 | end 131 | 132 | red:init_pipeline() 133 | 134 | red:set("dog", "an animal") 135 | red:get("dog") 136 | 137 | red:cancel_pipeline() 138 | 139 | local res, err = red:flushall() 140 | if not res then 141 | ngx.say("failed to flush all: ", err) 142 | return 143 | end 144 | 145 | ngx.say("flushall: ", res) 146 | 147 | for i = 1, 2 do 148 | red:init_pipeline() 149 | 150 | red:set("dog", "an animal") 151 | red:get("dog") 152 | red:set("dog", "hello") 153 | red:get("dog") 154 | 155 | local results = red:commit_pipeline() 156 | local cjson = require "cjson" 157 | ngx.say(cjson.encode(results)) 158 | end 159 | 160 | red:close() 161 | '; 162 | } 163 | --- request 164 | GET /t 165 | --- response_body 166 | flushall: OK 167 | ["OK","an animal","OK","hello"] 168 | ["OK","an animal","OK","hello"] 169 | --- no_error_log 170 | [error] 171 | 172 | 173 | 174 | === TEST 4: mixed 175 | --- http_config eval: $::HttpConfig 176 | --- config 177 | location /test { 178 | content_by_lua ' 179 | local redis = require "resty.redis" 180 | local red = redis:new() 181 | 182 | red:set_timeout(1000) -- 1 sec 183 | 184 | local ok, err = red:connect("127.0.0.1", 6379) 185 | if not ok then 186 | ngx.say("failed to connect: ", err) 187 | return 188 | end 189 | 190 | res, err = red:set("dog", "an aniaml") 191 | if not ok then 192 | ngx.say("failed to set dog: ", err) 193 | return 194 | end 195 | 196 | ngx.say("set result: ", res) 197 | 198 | local res, err = red:get("dog") 199 | if not res then 200 | ngx.say("failed to get dog: ", err) 201 | return 202 | end 203 | 204 | if res == ngx.null then 205 | ngx.say("dog not found.") 206 | return 207 | end 208 | 209 | ngx.say("dog: ", res) 210 | 211 | red:init_pipeline() 212 | red:set("cat", "Marry") 213 | red:set("horse", "Bob") 214 | red:get("cat") 215 | red:get("horse") 216 | local results, err = red:commit_pipeline() 217 | if not results then 218 | ngx.say("failed to commit the pipelined requests: ", err) 219 | return 220 | end 221 | 222 | for i, res in ipairs(results) do 223 | if type(res) == "table" then 224 | if not res[1] then 225 | ngx.say("failed to run command ", i, ": ", res[2]) 226 | else 227 | ngx.say("cmd ", i, ": ", res) 228 | end 229 | else 230 | -- process the scalar value 231 | ngx.say("cmd ", i, ": ", res) 232 | end 233 | end 234 | 235 | -- put it into the connection pool of size 100, 236 | -- with 0 idle timeout 237 | local ok, err = red:set_keepalive(0, 100) 238 | if not ok then 239 | ngx.say("failed to set keepalive: ", err) 240 | return 241 | end 242 | 243 | -- or just close the connection right away: 244 | -- local ok, err = red:close() 245 | -- if not ok then 246 | -- ngx.say("failed to close: ", err) 247 | -- return 248 | -- end 249 | '; 250 | } 251 | --- request 252 | GET /test 253 | --- response_body 254 | set result: OK 255 | dog: an aniaml 256 | cmd 1: OK 257 | cmd 2: OK 258 | cmd 3: Marry 259 | cmd 4: Bob 260 | --- no_error_log 261 | [error] 262 | 263 | -------------------------------------------------------------------------------- /t/pubsub.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: single channel 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location /t { 31 | content_by_lua ' 32 | local cjson = require "cjson" 33 | local redis = require "resty.redis" 34 | 35 | local red = redis:new() 36 | local red2 = redis:new() 37 | 38 | red:set_timeout(1000) -- 1 sec 39 | red2:set_timeout(1000) -- 1 sec 40 | 41 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 42 | if not ok then 43 | ngx.say("1: failed to connect: ", err) 44 | return 45 | end 46 | 47 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 48 | if not ok then 49 | ngx.say("2: failed to connect: ", err) 50 | return 51 | end 52 | 53 | res, err = red:subscribe("dog") 54 | if not res then 55 | ngx.say("1: failed to subscribe: ", err) 56 | return 57 | end 58 | 59 | ngx.say("1: subscribe: ", cjson.encode(res)) 60 | 61 | res, err = red2:publish("dog", "Hello") 62 | if not res then 63 | ngx.say("2: failed to publish: ", err) 64 | return 65 | end 66 | 67 | ngx.say("2: publish: ", cjson.encode(res)) 68 | 69 | res, err = red:read_reply() 70 | if not res then 71 | ngx.say("1: failed to read reply: ", err) 72 | return 73 | end 74 | 75 | ngx.say("1: receive: ", cjson.encode(res)) 76 | 77 | red:close() 78 | red2:close() 79 | '; 80 | } 81 | --- request 82 | GET /t 83 | --- response_body 84 | 1: subscribe: ["subscribe","dog",1] 85 | 2: publish: 1 86 | 1: receive: ["message","dog","Hello"] 87 | --- no_error_log 88 | [error] 89 | 90 | 91 | 92 | === TEST 2: single channel (retry read_reply() after timeout) 93 | --- http_config eval: $::HttpConfig 94 | --- config 95 | lua_socket_log_errors off; 96 | location /t { 97 | content_by_lua ' 98 | local cjson = require "cjson" 99 | local redis = require "resty.redis" 100 | 101 | local red = redis:new() 102 | local red2 = redis:new() 103 | 104 | red:set_timeout(1000) -- 1 sec 105 | red2:set_timeout(1000) -- 1 sec 106 | 107 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 108 | if not ok then 109 | ngx.say("1: failed to connect: ", err) 110 | return 111 | end 112 | 113 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 114 | if not ok then 115 | ngx.say("2: failed to connect: ", err) 116 | return 117 | end 118 | 119 | res, err = red:subscribe("dog") 120 | if not res then 121 | ngx.say("1: failed to subscribe: ", err) 122 | return 123 | end 124 | 125 | ngx.say("1: subscribe: ", cjson.encode(res)) 126 | 127 | red:set_timeout(1) 128 | for i = 1, 2 do 129 | res, err = red:read_reply() 130 | if not res then 131 | ngx.say("1: failed to read reply: ", err) 132 | if err ~= "timeout" then 133 | return 134 | end 135 | end 136 | end 137 | red:set_timeout(1000) 138 | 139 | res, err = red:unsubscribe("dog") 140 | if not res then 141 | ngx.say("1: failed to unsubscribe: ", err) 142 | else 143 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 144 | end 145 | 146 | res, err = red2:publish("dog", "Hello") 147 | if not res then 148 | ngx.say("2: failed to publish: ", err) 149 | return 150 | end 151 | 152 | ngx.say("2: publish: ", cjson.encode(res)) 153 | 154 | res, err = red:read_reply() 155 | if not res then 156 | ngx.say("1: failed to read reply: ", err) 157 | 158 | else 159 | ngx.say("1: receive: ", cjson.encode(res)) 160 | end 161 | 162 | res, err = red:unsubscribe("dog") 163 | if not res then 164 | ngx.say("1: failed to unsubscribe: ", err) 165 | return 166 | end 167 | 168 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 169 | 170 | red:close() 171 | red2:close() 172 | '; 173 | } 174 | --- request 175 | GET /t 176 | --- response_body 177 | 1: subscribe: ["subscribe","dog",1] 178 | 1: failed to read reply: timeout 179 | 1: failed to read reply: timeout 180 | 1: unsubscribe: ["unsubscribe","dog",0] 181 | 2: publish: 0 182 | 1: failed to read reply: not subscribed 183 | 1: unsubscribe: ["unsubscribe","dog",0] 184 | --- no_error_log 185 | [error] 186 | 187 | 188 | 189 | === TEST 3: multiple channels 190 | --- http_config eval: $::HttpConfig 191 | --- config 192 | location /t { 193 | lua_socket_log_errors off; 194 | content_by_lua ' 195 | local cjson = require "cjson" 196 | local redis = require "resty.redis" 197 | 198 | local red = redis:new() 199 | local red2 = redis:new() 200 | 201 | red:set_timeout(1000) -- 1 sec 202 | red2:set_timeout(1000) -- 1 sec 203 | 204 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 205 | if not ok then 206 | ngx.say("1: failed to connect: ", err) 207 | return 208 | end 209 | 210 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 211 | if not ok then 212 | ngx.say("2: failed to connect: ", err) 213 | return 214 | end 215 | 216 | res, err = red:subscribe("dog") 217 | if not res then 218 | ngx.say("1: failed to subscribe: ", err) 219 | return 220 | end 221 | 222 | ngx.say("1: subscribe dog: ", cjson.encode(res)) 223 | 224 | res, err = red:subscribe("cat") 225 | if not res then 226 | ngx.say("1: failed to subscribe: ", err) 227 | return 228 | end 229 | 230 | ngx.say("1: subscribe cat: ", cjson.encode(res)) 231 | 232 | res, err = red2:publish("dog", "Hello") 233 | if not res then 234 | ngx.say("2: failed to publish: ", err) 235 | return 236 | end 237 | 238 | ngx.say("2: publish: ", cjson.encode(res)) 239 | 240 | res, err = red:read_reply() 241 | if not res then 242 | ngx.say("1: failed to read reply: ", err) 243 | else 244 | ngx.say("1: receive: ", cjson.encode(res)) 245 | end 246 | 247 | red:set_timeout(10) -- 10ms 248 | res, err = red:read_reply() 249 | if not res then 250 | ngx.say("1: failed to read reply: ", err) 251 | else 252 | ngx.say("1: receive: ", cjson.encode(res)) 253 | end 254 | red:set_timeout(1000) -- 1s 255 | 256 | res, err = red:unsubscribe() 257 | if not res then 258 | ngx.say("1: failed to unscribe: ", err) 259 | else 260 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 261 | end 262 | 263 | res, err = red:read_reply() 264 | if not res then 265 | ngx.say("1: failed to read reply: ", err) 266 | else 267 | ngx.say("1: receive: ", cjson.encode(res)) 268 | end 269 | 270 | red:set_timeout(10) -- 10ms 271 | res, err = red:read_reply() 272 | if not res then 273 | ngx.say("1: failed to read reply: ", err) 274 | else 275 | ngx.say("1: receive: ", cjson.encode(res)) 276 | end 277 | red:set_timeout(1000) -- 1s 278 | 279 | red:close() 280 | red2:close() 281 | '; 282 | } 283 | --- request 284 | GET /t 285 | --- response_body_like chop 286 | ^1: subscribe dog: \["subscribe","dog",1\] 287 | 1: subscribe cat: \["subscribe","cat",2\] 288 | 2: publish: 1 289 | 1: receive: \["message","dog","Hello"\] 290 | 1: failed to read reply: timeout 291 | 1: unsubscribe: \["unsubscribe","(?:cat|dog)",1\] 292 | 1: receive: \["unsubscribe","(?:dog|cat)",0\] 293 | 1: failed to read reply: not subscribed$ 294 | 295 | --- no_error_log 296 | [error] 297 | 298 | 299 | 300 | === TEST 4: call subscribe after read_reply() times out 301 | --- http_config eval: $::HttpConfig 302 | --- config 303 | lua_socket_log_errors off; 304 | location /t { 305 | content_by_lua ' 306 | local cjson = require "cjson" 307 | local redis = require "resty.redis" 308 | 309 | local red = redis:new() 310 | local red2 = redis:new() 311 | 312 | red:set_timeout(1000) -- 1 sec 313 | red2:set_timeout(1000) -- 1 sec 314 | 315 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 316 | if not ok then 317 | ngx.say("1: failed to connect: ", err) 318 | return 319 | end 320 | 321 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 322 | if not ok then 323 | ngx.say("2: failed to connect: ", err) 324 | return 325 | end 326 | 327 | res, err = red:subscribe("dog") 328 | if not res then 329 | ngx.say("1: failed to subscribe: ", err) 330 | return 331 | end 332 | 333 | ngx.say("1: subscribe: ", cjson.encode(res)) 334 | 335 | red:set_timeout(1) 336 | for i = 1, 2 do 337 | res, err = red:read_reply() 338 | if not res then 339 | ngx.say("1: failed to read reply: ", err) 340 | if err ~= "timeout" then 341 | return 342 | end 343 | end 344 | end 345 | red:set_timeout(1000) 346 | 347 | res, err = red:subscribe("cat") 348 | if not res then 349 | ngx.say("1: failed to subscribe to cat: ", err) 350 | else 351 | ngx.say("1: subscribe: ", cjson.encode(res)) 352 | end 353 | 354 | red:close() 355 | red2:close() 356 | '; 357 | } 358 | --- request 359 | GET /t 360 | --- response_body 361 | 1: subscribe: ["subscribe","dog",1] 362 | 1: failed to read reply: timeout 363 | 1: failed to read reply: timeout 364 | 1: subscribe: ["subscribe","cat",2] 365 | --- no_error_log 366 | [error] 367 | 368 | 369 | 370 | === TEST 5: call set_keepalive in subscribed mode (previous read_reply calls timed out) 371 | --- http_config eval: $::HttpConfig 372 | --- config 373 | lua_socket_log_errors off; 374 | location /t { 375 | content_by_lua ' 376 | local cjson = require "cjson" 377 | local redis = require "resty.redis" 378 | 379 | local red = redis:new() 380 | local red2 = redis:new() 381 | 382 | red:set_timeout(1000) -- 1 sec 383 | red2:set_timeout(1000) -- 1 sec 384 | 385 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 386 | if not ok then 387 | ngx.say("1: failed to connect: ", err) 388 | return 389 | end 390 | 391 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 392 | if not ok then 393 | ngx.say("2: failed to connect: ", err) 394 | return 395 | end 396 | 397 | res, err = red:subscribe("dog") 398 | if not res then 399 | ngx.say("1: failed to subscribe: ", err) 400 | return 401 | end 402 | 403 | ngx.say("1: subscribe: ", cjson.encode(res)) 404 | 405 | red:set_timeout(1) 406 | for i = 1, 2 do 407 | res, err = red:read_reply() 408 | if not res then 409 | ngx.say("1: failed to read reply: ", err) 410 | if err ~= "timeout" then 411 | return 412 | end 413 | end 414 | end 415 | red:set_timeout(1000) 416 | 417 | res, err = red:set_keepalive() 418 | if not res then 419 | ngx.say("1: failed to set keepalive: ", err) 420 | else 421 | ngx.say("1: set keepalive: ", cjson.encode(res)) 422 | end 423 | 424 | red:close() 425 | red2:close() 426 | '; 427 | } 428 | --- request 429 | GET /t 430 | --- response_body 431 | 1: subscribe: ["subscribe","dog",1] 432 | 1: failed to read reply: timeout 433 | 1: failed to read reply: timeout 434 | 1: failed to set keepalive: subscribed state 435 | --- no_error_log 436 | [error] 437 | 438 | 439 | 440 | === TEST 6: call set_keepalive in subscribed mode 441 | --- http_config eval: $::HttpConfig 442 | --- config 443 | lua_socket_log_errors off; 444 | location /t { 445 | content_by_lua ' 446 | local cjson = require "cjson" 447 | local redis = require "resty.redis" 448 | 449 | local red = redis:new() 450 | local red2 = redis:new() 451 | 452 | red:set_timeout(1000) -- 1 sec 453 | red2:set_timeout(1000) -- 1 sec 454 | 455 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 456 | if not ok then 457 | ngx.say("1: failed to connect: ", err) 458 | return 459 | end 460 | 461 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 462 | if not ok then 463 | ngx.say("2: failed to connect: ", err) 464 | return 465 | end 466 | 467 | res, err = red:subscribe("dog") 468 | if not res then 469 | ngx.say("1: failed to subscribe: ", err) 470 | return 471 | end 472 | 473 | ngx.say("1: subscribe: ", cjson.encode(res)) 474 | 475 | res, err = red:set_keepalive() 476 | if not res then 477 | ngx.say("1: failed to set keepalive: ", err) 478 | else 479 | ngx.say("1: set keepalive: ", cjson.encode(res)) 480 | end 481 | 482 | red:close() 483 | red2:close() 484 | '; 485 | } 486 | --- request 487 | GET /t 488 | --- response_body 489 | 1: subscribe: ["subscribe","dog",1] 490 | 1: failed to set keepalive: subscribed state 491 | --- no_error_log 492 | [error] 493 | 494 | 495 | 496 | === TEST 7: call set_keepalive in unsubscribed mode 497 | --- http_config eval: $::HttpConfig 498 | --- config 499 | lua_socket_log_errors off; 500 | location /t { 501 | content_by_lua ' 502 | local cjson = require "cjson" 503 | local redis = require "resty.redis" 504 | 505 | local red = redis:new() 506 | local red2 = redis:new() 507 | 508 | red:set_timeout(1000) -- 1 sec 509 | red2:set_timeout(1000) -- 1 sec 510 | 511 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 512 | if not ok then 513 | ngx.say("1: failed to connect: ", err) 514 | return 515 | end 516 | 517 | ok, err = red2:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 518 | if not ok then 519 | ngx.say("2: failed to connect: ", err) 520 | return 521 | end 522 | 523 | res, err = red:subscribe("dog") 524 | if not res then 525 | ngx.say("1: failed to subscribe: ", err) 526 | return 527 | end 528 | 529 | ngx.say("1: subscribe: ", cjson.encode(res)) 530 | 531 | res, err = red:unsubscribe() 532 | if not res then 533 | ngx.say("1: failed to unsubscribe: ", err) 534 | return 535 | end 536 | 537 | ngx.say("1: unsubscribe: ", cjson.encode(res)) 538 | 539 | res, err = red:set_keepalive() 540 | if not res then 541 | ngx.say("1: failed to set keepalive: ", err) 542 | else 543 | ngx.say("1: set keepalive: ", cjson.encode(res)) 544 | end 545 | 546 | red:close() 547 | red2:close() 548 | '; 549 | } 550 | --- request 551 | GET /t 552 | --- response_body 553 | 1: subscribe: ["subscribe","dog",1] 554 | 1: unsubscribe: ["unsubscribe","dog",0] 555 | 1: set keepalive: 1 556 | 557 | --- no_error_log 558 | [error] 559 | 560 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: set and get 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location /t { 31 | content_by_lua ' 32 | local redis = require "resty.redis" 33 | local red = redis:new() 34 | 35 | red:set_timeout(1000) -- 1 sec 36 | 37 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 38 | if not ok then 39 | ngx.say("failed to connect: ", err) 40 | return 41 | end 42 | 43 | ok, err = red:select(1) 44 | if not ok then 45 | ngx.say("failed to select: ", err) 46 | return 47 | end 48 | 49 | res, err = red:set("dog", "an animal") 50 | if not res then 51 | ngx.say("failed to set dog: ", err) 52 | return 53 | end 54 | 55 | ngx.say("set dog: ", res) 56 | 57 | for i = 1, 2 do 58 | local res, err = red:get("dog") 59 | if err then 60 | ngx.say("failed to get dog: ", err) 61 | return 62 | end 63 | 64 | if not res then 65 | ngx.say("dog not found.") 66 | return 67 | end 68 | 69 | ngx.say("dog: ", res) 70 | end 71 | 72 | red:close() 73 | '; 74 | } 75 | --- request 76 | GET /t 77 | --- response_body 78 | set dog: OK 79 | dog: an animal 80 | dog: an animal 81 | --- no_error_log 82 | [error] 83 | 84 | 85 | 86 | === TEST 2: flushall 87 | --- http_config eval: $::HttpConfig 88 | --- config 89 | location /t { 90 | content_by_lua ' 91 | local redis = require "resty.redis" 92 | local red = redis:new() 93 | 94 | red:set_timeout(1000) -- 1 sec 95 | 96 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 97 | if not ok then 98 | ngx.say("failed to connect: ", err) 99 | return 100 | end 101 | 102 | local res, err = red:flushall() 103 | if not res then 104 | ngx.say("failed to flushall: ", err) 105 | return 106 | end 107 | ngx.say("flushall: ", res) 108 | 109 | red:close() 110 | '; 111 | } 112 | --- request 113 | GET /t 114 | --- response_body 115 | flushall: OK 116 | --- no_error_log 117 | [error] 118 | 119 | 120 | 121 | === TEST 3: get nil bulk value 122 | --- http_config eval: $::HttpConfig 123 | --- config 124 | location /t { 125 | content_by_lua ' 126 | local redis = require "resty.redis" 127 | local red = redis:new() 128 | 129 | red:set_timeout(1000) -- 1 sec 130 | 131 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 132 | if not ok then 133 | ngx.say("failed to connect: ", err) 134 | return 135 | end 136 | 137 | local res, err = red:flushall() 138 | if not res then 139 | ngx.say("failed to flushall: ", err) 140 | return 141 | end 142 | 143 | ngx.say("flushall: ", res) 144 | 145 | for i = 1, 2 do 146 | res, err = red:get("not_found") 147 | if err then 148 | ngx.say("failed to get: ", err) 149 | return 150 | end 151 | 152 | if res == ngx.null then 153 | ngx.say("not_found not found.") 154 | return 155 | end 156 | 157 | ngx.say("get not_found: ", res) 158 | end 159 | 160 | red:close() 161 | '; 162 | } 163 | --- request 164 | GET /t 165 | --- response_body 166 | flushall: OK 167 | not_found not found. 168 | --- no_error_log 169 | [error] 170 | 171 | 172 | 173 | === TEST 4: get nil list 174 | --- http_config eval: $::HttpConfig 175 | --- config 176 | location /t { 177 | content_by_lua ' 178 | local redis = require "resty.redis" 179 | local red = redis:new() 180 | 181 | red:set_timeout(1000) -- 1 sec 182 | 183 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 184 | if not ok then 185 | ngx.say("failed to connect: ", err) 186 | return 187 | end 188 | 189 | local res, err = red:flushall() 190 | if not res then 191 | ngx.say("failed to flushall: ", err) 192 | return 193 | end 194 | 195 | ngx.say("flushall: ", res) 196 | 197 | for i = 1, 2 do 198 | res, err = red:lrange("nokey", 0, 1) 199 | if err then 200 | ngx.say("failed to get: ", err) 201 | return 202 | end 203 | 204 | if res == ngx.null then 205 | ngx.say("nokey not found.") 206 | return 207 | end 208 | 209 | ngx.say("get nokey: ", #res, " (", type(res), ")") 210 | end 211 | 212 | red:close() 213 | '; 214 | } 215 | --- request 216 | GET /t 217 | --- response_body 218 | flushall: OK 219 | get nokey: 0 (table) 220 | get nokey: 0 (table) 221 | --- no_error_log 222 | [error] 223 | 224 | 225 | 226 | === TEST 5: incr and decr 227 | --- http_config eval: $::HttpConfig 228 | --- config 229 | location /t { 230 | content_by_lua ' 231 | local redis = require "resty.redis" 232 | local red = redis:new() 233 | 234 | red:set_timeout(1000) -- 1 sec 235 | 236 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 237 | if not ok then 238 | ngx.say("failed to connect: ", err) 239 | return 240 | end 241 | 242 | res, err = red:set("connections", 10) 243 | if not res then 244 | ngx.say("failed to set connections: ", err) 245 | return 246 | end 247 | 248 | ngx.say("set connections: ", res) 249 | 250 | res, err = red:incr("connections") 251 | if not res then 252 | ngx.say("failed to set connections: ", err) 253 | return 254 | end 255 | 256 | ngx.say("incr connections: ", res) 257 | 258 | local res, err = red:get("connections") 259 | if err then 260 | ngx.say("failed to get connections: ", err) 261 | return 262 | end 263 | 264 | res, err = red:incr("connections") 265 | if not res then 266 | ngx.say("failed to incr connections: ", err) 267 | return 268 | end 269 | 270 | ngx.say("incr connections: ", res) 271 | 272 | res, err = red:decr("connections") 273 | if not res then 274 | ngx.say("failed to decr connections: ", err) 275 | return 276 | end 277 | 278 | ngx.say("decr connections: ", res) 279 | 280 | res, err = red:get("connections") 281 | if not res then 282 | ngx.say("connections not found.") 283 | return 284 | end 285 | 286 | ngx.say("connections: ", res) 287 | 288 | res, err = red:del("connections") 289 | if not res then 290 | ngx.say("failed to del connections: ", err) 291 | return 292 | end 293 | 294 | ngx.say("del connections: ", res) 295 | 296 | res, err = red:incr("connections") 297 | if not res then 298 | ngx.say("failed to set connections: ", err) 299 | return 300 | end 301 | 302 | ngx.say("incr connections: ", res) 303 | 304 | res, err = red:get("connections") 305 | if not res then 306 | ngx.say("connections not found.") 307 | return 308 | end 309 | 310 | ngx.say("connections: ", res) 311 | 312 | red:close() 313 | '; 314 | } 315 | --- request 316 | GET /t 317 | --- response_body 318 | set connections: OK 319 | incr connections: 11 320 | incr connections: 12 321 | decr connections: 11 322 | connections: 11 323 | del connections: 1 324 | incr connections: 1 325 | connections: 1 326 | --- no_error_log 327 | [error] 328 | 329 | 330 | 331 | === TEST 6: bad incr command format 332 | --- http_config eval: $::HttpConfig 333 | --- config 334 | location /t { 335 | content_by_lua ' 336 | local redis = require "resty.redis" 337 | local red = redis:new() 338 | 339 | red:set_timeout(1000) -- 1 sec 340 | 341 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 342 | if not ok then 343 | ngx.say("failed to connect: ", err) 344 | return 345 | end 346 | 347 | res, err = red:incr("connections", 12) 348 | if not res then 349 | ngx.say("failed to set connections: ", res, ": ", err) 350 | return 351 | end 352 | 353 | ngx.say("incr connections: ", res) 354 | 355 | red:close() 356 | '; 357 | } 358 | --- request 359 | GET /t 360 | --- response_body 361 | failed to set connections: false: ERR wrong number of arguments for 'incr' command 362 | --- no_error_log 363 | [error] 364 | 365 | 366 | 367 | === TEST 7: lpush and lrange 368 | --- http_config eval: $::HttpConfig 369 | --- config 370 | location /t { 371 | content_by_lua ' 372 | local redis = require "resty.redis" 373 | local red = redis:new() 374 | 375 | red:set_timeout(1000) -- 1 sec 376 | 377 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 378 | if not ok then 379 | ngx.say("failed to connect: ", err) 380 | return 381 | end 382 | 383 | local res, err = red:flushall() 384 | if not res then 385 | ngx.say("failed to flushall: ", err) 386 | return 387 | end 388 | ngx.say("flushall: ", res) 389 | 390 | local res, err = red:lpush("mylist", "world") 391 | if not res then 392 | ngx.say("failed to lpush: ", err) 393 | return 394 | end 395 | ngx.say("lpush result: ", res) 396 | 397 | res, err = red:lpush("mylist", "hello") 398 | if not res then 399 | ngx.say("failed to lpush: ", err) 400 | return 401 | end 402 | ngx.say("lpush result: ", res) 403 | 404 | res, err = red:lrange("mylist", 0, -1) 405 | if not res then 406 | ngx.say("failed to lrange: ", err) 407 | return 408 | end 409 | local cjson = require "cjson" 410 | ngx.say("lrange result: ", cjson.encode(res)) 411 | 412 | red:close() 413 | '; 414 | } 415 | --- request 416 | GET /t 417 | --- response_body 418 | flushall: OK 419 | lpush result: 1 420 | lpush result: 2 421 | lrange result: ["hello","world"] 422 | --- no_error_log 423 | [error] 424 | 425 | 426 | 427 | === TEST 8: blpop expires its own timeout 428 | --- http_config eval: $::HttpConfig 429 | --- config 430 | location /t { 431 | content_by_lua ' 432 | local redis = require "resty.redis" 433 | local red = redis:new() 434 | 435 | red:set_timeout(2500) -- 2.5 sec 436 | 437 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 438 | if not ok then 439 | ngx.say("failed to connect: ", err) 440 | return 441 | end 442 | 443 | local res, err = red:flushall() 444 | if not res then 445 | ngx.say("failed to flushall: ", err) 446 | return 447 | end 448 | ngx.say("flushall: ", res) 449 | 450 | local res, err = red:blpop("key", 1) 451 | if err then 452 | ngx.say("failed to blpop: ", err) 453 | return 454 | end 455 | 456 | if res == ngx.null then 457 | ngx.say("no element popped.") 458 | return 459 | end 460 | 461 | local cjson = require "cjson" 462 | ngx.say("blpop result: ", cjson.encode(res)) 463 | 464 | red:close() 465 | '; 466 | } 467 | --- request 468 | GET /t 469 | --- response_body 470 | flushall: OK 471 | no element popped. 472 | --- no_error_log 473 | [error] 474 | --- timeout: 3 475 | 476 | 477 | 478 | === TEST 9: blpop expires cosocket timeout 479 | --- http_config eval: $::HttpConfig 480 | --- config 481 | location /t { 482 | content_by_lua ' 483 | local redis = require "resty.redis" 484 | local red = redis:new() 485 | 486 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 487 | if not ok then 488 | ngx.say("failed to connect: ", err) 489 | return 490 | end 491 | 492 | local res, err = red:flushall() 493 | if not res then 494 | ngx.say("failed to flushall: ", err) 495 | return 496 | end 497 | ngx.say("flushall: ", res) 498 | 499 | red:set_timeout(200) -- 200 ms 500 | 501 | local res, err = red:blpop("key", 1) 502 | if err then 503 | ngx.say("failed to blpop: ", err) 504 | return 505 | end 506 | 507 | if not res then 508 | ngx.say("no element popped.") 509 | return 510 | end 511 | 512 | local cjson = require "cjson" 513 | ngx.say("blpop result: ", cjson.encode(res)) 514 | 515 | red:close() 516 | '; 517 | } 518 | --- request 519 | GET /t 520 | --- response_body 521 | flushall: OK 522 | failed to blpop: timeout 523 | --- error_log 524 | lua tcp socket read timed out 525 | 526 | 527 | 528 | === TEST 10: set keepalive and get reused times 529 | --- http_config eval: $::HttpConfig 530 | --- config 531 | resolver $TEST_NGINX_RESOLVER; 532 | location /t { 533 | content_by_lua ' 534 | local redis = require "resty.redis" 535 | local red = redis:new() 536 | 537 | red:set_timeout(1000) -- 1 sec 538 | 539 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 540 | if not ok then 541 | ngx.say("failed to connect: ", err) 542 | return 543 | end 544 | 545 | local times = red:get_reused_times() 546 | ngx.say("reused times: ", times) 547 | 548 | local ok, err = red:set_keepalive() 549 | if not ok then 550 | ngx.say("failed to set keepalive: ", err) 551 | return 552 | end 553 | 554 | ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 555 | if not ok then 556 | ngx.say("failed to connect: ", err) 557 | return 558 | end 559 | 560 | times = red:get_reused_times() 561 | ngx.say("reused times: ", times) 562 | '; 563 | } 564 | --- request 565 | GET /t 566 | --- response_body 567 | reused times: 0 568 | reused times: 1 569 | --- no_error_log 570 | [error] 571 | 572 | 573 | 574 | === TEST 11: mget 575 | --- http_config eval: $::HttpConfig 576 | --- config 577 | location /t { 578 | content_by_lua ' 579 | local redis = require "resty.redis" 580 | local red = redis:new() 581 | 582 | red:set_timeout(1000) -- 1 sec 583 | 584 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 585 | if not ok then 586 | ngx.say("failed to connect: ", err) 587 | return 588 | end 589 | 590 | ok, err = red:flushall() 591 | if not ok then 592 | ngx.say("failed to flush all: ", err) 593 | return 594 | end 595 | 596 | res, err = red:set("dog", "an animal") 597 | if not res then 598 | ngx.say("failed to set dog: ", err) 599 | return 600 | end 601 | 602 | ngx.say("set dog: ", res) 603 | 604 | for i = 1, 2 do 605 | local res, err = red:mget("dog", "cat", "dog") 606 | if err then 607 | ngx.say("failed to get dog: ", err) 608 | return 609 | end 610 | 611 | if not res then 612 | ngx.say("dog not found.") 613 | return 614 | end 615 | 616 | local cjson = require "cjson" 617 | ngx.say("res: ", cjson.encode(res)) 618 | end 619 | 620 | red:close() 621 | '; 622 | } 623 | --- request 624 | GET /t 625 | --- response_body 626 | set dog: OK 627 | res: ["an animal",null,"an animal"] 628 | res: ["an animal",null,"an animal"] 629 | --- no_error_log 630 | [error] 631 | 632 | 633 | 634 | === TEST 12: hmget array_to_hash 635 | --- http_config eval: $::HttpConfig 636 | --- config 637 | location /t { 638 | content_by_lua ' 639 | local redis = require "resty.redis" 640 | local red = redis:new() 641 | 642 | red:set_timeout(1000) -- 1 sec 643 | 644 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 645 | if not ok then 646 | ngx.say("failed to connect: ", err) 647 | return 648 | end 649 | 650 | ok, err = red:flushall() 651 | if not ok then 652 | ngx.say("failed to flush all: ", err) 653 | return 654 | end 655 | 656 | res, err = red:hmset("animals", { dog = "bark", cat = "meow", cow = "moo" }) 657 | if not res then 658 | ngx.say("failed to set animals: ", err) 659 | return 660 | end 661 | 662 | ngx.say("hmset animals: ", res) 663 | 664 | local res, err = red:hmget("animals", "dog", "cat", "cow") 665 | if not res then 666 | ngx.say("failed to get animals: ", err) 667 | return 668 | end 669 | 670 | ngx.say("hmget animals: ", res) 671 | 672 | local res, err = red:hgetall("animals") 673 | if err then 674 | ngx.say("failed to get animals: ", err) 675 | return 676 | end 677 | 678 | if not res then 679 | ngx.say("animals not found.") 680 | return 681 | end 682 | 683 | local h = red:array_to_hash(res) 684 | 685 | ngx.say("dog: ", h.dog) 686 | ngx.say("cat: ", h.cat) 687 | ngx.say("cow: ", h.cow) 688 | 689 | red:close() 690 | '; 691 | } 692 | --- request 693 | GET /t 694 | --- response_body 695 | hmset animals: OK 696 | hmget animals: barkmeowmoo 697 | dog: bark 698 | cat: meow 699 | cow: moo 700 | --- no_error_log 701 | [error] 702 | 703 | -------------------------------------------------------------------------------- /t/transaction.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: sanity 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location /t { 31 | content_by_lua ' 32 | local cjson = require "cjson" 33 | local redis = require "resty.redis" 34 | local red = redis:new() 35 | 36 | red:set_timeout(1000) -- 1 sec 37 | 38 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 39 | if not ok then 40 | ngx.say("failed to connect: ", err) 41 | return 42 | end 43 | 44 | local redis_key = "foo" 45 | 46 | local ok, err = red:multi() 47 | if not ok then 48 | ngx.say("failed to run multi: ", err) 49 | return 50 | end 51 | 52 | ngx.say("multi ans: ", cjson.encode(ok)) 53 | 54 | local ans, err = red:sort("log", "by", redis_key .. ":*->timestamp") 55 | if not ans then 56 | ngx.say("failed to run sort: ", err) 57 | return 58 | end 59 | 60 | ngx.say("sort ans: ", cjson.encode(ans)) 61 | 62 | ans, err = red:exec() 63 | 64 | ngx.say("exec ans: ", cjson.encode(ans)) 65 | 66 | local ok, err = red:set_keepalive(0, 1024) 67 | if not ok then 68 | ngx.say("failed to put the current redis connection into pool: ", err) 69 | return 70 | end 71 | '; 72 | } 73 | --- request 74 | GET /t 75 | --- response_body 76 | multi ans: "OK" 77 | sort ans: "QUEUED" 78 | exec ans: [{}] 79 | --- no_error_log 80 | [error] 81 | 82 | 83 | 84 | === TEST 2: redis cmd reference sample: redis does not halt on errors 85 | --- http_config eval: $::HttpConfig 86 | --- config 87 | location /t { 88 | content_by_lua ' 89 | local cjson = require "cjson" 90 | local redis = require "resty.redis" 91 | local red = redis:new() 92 | 93 | red:set_timeout(1000) -- 1 sec 94 | 95 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 96 | if not ok then 97 | ngx.say("failed to connect: ", err) 98 | return 99 | end 100 | 101 | local ok, err = red:multi() 102 | if not ok then 103 | ngx.say("failed to run multi: ", err) 104 | return 105 | end 106 | 107 | ngx.say("multi ans: ", cjson.encode(ok)) 108 | 109 | local ans, err = red:set("a", "abc") 110 | if not ans then 111 | ngx.say("failed to run sort: ", err) 112 | return 113 | end 114 | 115 | ngx.say("set ans: ", cjson.encode(ans)) 116 | 117 | local ans, err = red:lpop("a") 118 | if not ans then 119 | ngx.say("failed to run sort: ", err) 120 | return 121 | end 122 | 123 | ngx.say("set ans: ", cjson.encode(ans)) 124 | 125 | ans, err = red:exec() 126 | 127 | ngx.say("exec ans: ", cjson.encode(ans)) 128 | 129 | red:close() 130 | '; 131 | } 132 | --- request 133 | GET /t 134 | --- response_body_like chop 135 | ^multi ans: "OK" 136 | set ans: "QUEUED" 137 | set ans: "QUEUED" 138 | exec ans: \["OK",\[false,"(?:ERR|WRONGTYPE) Operation against a key holding the wrong kind of value"\]\] 139 | $ 140 | --- no_error_log 141 | [error] 142 | 143 | -------------------------------------------------------------------------------- /t/user-cmds.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; 19 | 20 | no_long_string(); 21 | #no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: single channel 28 | --- http_config eval: $::HttpConfig 29 | --- config 30 | location /t { 31 | content_by_lua ' 32 | local cjson = require "cjson" 33 | local redis = require "resty.redis" 34 | 35 | redis.add_commands("foo", "bar") 36 | 37 | local red = redis:new() 38 | 39 | red:set_timeout(1000) -- 1 sec 40 | 41 | local ok, err = red:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) 42 | if not ok then 43 | ngx.say("failed to connect: ", err) 44 | return 45 | end 46 | 47 | local res, err = red:foo("a") 48 | if not res then 49 | ngx.say("failed to foo: ", err) 50 | end 51 | 52 | res, err = red:bar() 53 | if not res then 54 | ngx.say("failed to bar: ", err) 55 | end 56 | '; 57 | } 58 | --- request 59 | GET /t 60 | --- response_body 61 | failed to foo: ERR unknown command 'foo' 62 | failed to bar: ERR unknown command 'bar' 63 | --- no_error_log 64 | [error] 65 | 66 | -------------------------------------------------------------------------------- /t/version.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | }; 15 | 16 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 17 | 18 | no_long_string(); 19 | #no_diff(); 20 | 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: basic 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | content_by_lua ' 30 | local redis = require "resty.redis" 31 | ngx.say(redis._VERSION) 32 | '; 33 | } 34 | --- request 35 | GET /t 36 | --- response_body_like chop 37 | ^\d+\.\d+$ 38 | --- no_error_log 39 | [error] 40 | 41 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | write(buf) 5 | fun:__write_nocancel 6 | fun:ngx_log_error_core 7 | fun:ngx_resolver_read_response 8 | } 9 | { 10 | 11 | Memcheck:Cond 12 | fun:ngx_sprintf_num 13 | fun:ngx_vslprintf 14 | fun:ngx_log_error_core 15 | fun:ngx_resolver_read_response 16 | fun:ngx_epoll_process_events 17 | fun:ngx_process_events_and_timers 18 | fun:ngx_single_process_cycle 19 | fun:main 20 | } 21 | { 22 | 23 | Memcheck:Addr1 24 | fun:ngx_vslprintf 25 | fun:ngx_snprintf 26 | fun:ngx_sock_ntop 27 | fun:ngx_event_accept 28 | } 29 | { 30 | 31 | Memcheck:Param 32 | write(buf) 33 | fun:__write_nocancel 34 | fun:ngx_log_error_core 35 | fun:ngx_resolver_read_response 36 | fun:ngx_event_process_posted 37 | fun:ngx_process_events_and_timers 38 | fun:ngx_single_process_cycle 39 | fun:main 40 | } 41 | { 42 | 43 | Memcheck:Cond 44 | fun:ngx_sprintf_num 45 | fun:ngx_vslprintf 46 | fun:ngx_log_error_core 47 | fun:ngx_resolver_read_response 48 | fun:ngx_event_process_posted 49 | fun:ngx_process_events_and_timers 50 | fun:ngx_single_process_cycle 51 | fun:main 52 | } 53 | { 54 | 55 | Memcheck:Leak 56 | fun:malloc 57 | fun:ngx_alloc 58 | obj:* 59 | } 60 | { 61 | 62 | exp-sgcheck:SorG 63 | fun:ngx_http_lua_ndk_set_var_get 64 | } 65 | { 66 | 67 | exp-sgcheck:SorG 68 | fun:ngx_http_variables_init_vars 69 | fun:ngx_http_block 70 | } 71 | { 72 | 73 | exp-sgcheck:SorG 74 | fun:ngx_conf_parse 75 | } 76 | { 77 | 78 | exp-sgcheck:SorG 79 | fun:ngx_vslprintf 80 | fun:ngx_log_error_core 81 | } 82 | { 83 | 84 | Memcheck:Leak 85 | fun:malloc 86 | fun:ngx_alloc 87 | fun:ngx_calloc 88 | fun:ngx_event_process_init 89 | } 90 | { 91 | 92 | Memcheck:Leak 93 | fun:malloc 94 | fun:ngx_alloc 95 | fun:ngx_malloc 96 | fun:ngx_pcalloc 97 | } 98 | { 99 | 100 | Memcheck:Leak 101 | fun:malloc 102 | fun:ngx_alloc 103 | fun:(below main) 104 | } 105 | { 106 | 107 | Memcheck:Param 108 | epoll_ctl(event) 109 | fun:epoll_ctl 110 | } 111 | { 112 | 113 | Memcheck:Leak 114 | fun:malloc 115 | fun:ngx_alloc 116 | fun:ngx_event_process_init 117 | } 118 | { 119 | 120 | Memcheck:Cond 121 | fun:ngx_conf_flush_files 122 | fun:ngx_single_process_cycle 123 | } 124 | { 125 | 126 | Memcheck:Cond 127 | fun:memcpy 128 | fun:ngx_vslprintf 129 | fun:ngx_log_error_core 130 | fun:ngx_http_charset_header_filter 131 | } 132 | { 133 | 134 | Memcheck:Leak 135 | fun:memalign 136 | fun:posix_memalign 137 | fun:ngx_memalign 138 | fun:ngx_pcalloc 139 | } 140 | { 141 | 142 | Memcheck:Param 143 | socketcall.setsockopt(optval) 144 | fun:setsockopt 145 | fun:drizzle_state_connect 146 | } 147 | { 148 | 149 | Memcheck:Leak 150 | fun:malloc 151 | fun:ngx_alloc 152 | fun:ngx_palloc_large 153 | } 154 | { 155 | 156 | Memcheck:Leak 157 | fun:malloc 158 | fun:ngx_alloc 159 | fun:ngx_pool_cleanup_add 160 | } 161 | { 162 | 163 | Memcheck:Leak 164 | fun:malloc 165 | fun:ngx_alloc 166 | fun:ngx_pnalloc 167 | } 168 | { 169 | 170 | Memcheck:Cond 171 | fun:ngx_conf_flush_files 172 | fun:ngx_single_process_cycle 173 | fun:main 174 | } 175 | 176 | { 177 | 178 | Memcheck:Leak 179 | fun:malloc 180 | fun:ngx_alloc 181 | fun:ngx_palloc 182 | } 183 | { 184 | 185 | Memcheck:Leak 186 | fun:malloc 187 | fun:ngx_alloc 188 | fun:ngx_pcalloc 189 | } 190 | { 191 | 192 | Memcheck:Leak 193 | fun:malloc 194 | fun:ngx_alloc 195 | fun:ngx_malloc 196 | fun:ngx_palloc_large 197 | } 198 | { 199 | 200 | Memcheck:Leak 201 | fun:malloc 202 | fun:ngx_alloc 203 | fun:ngx_create_pool 204 | } 205 | { 206 | 207 | Memcheck:Leak 208 | fun:malloc 209 | fun:ngx_alloc 210 | fun:ngx_malloc 211 | fun:ngx_palloc 212 | } 213 | { 214 | 215 | Memcheck:Leak 216 | fun:malloc 217 | fun:ngx_alloc 218 | fun:ngx_malloc 219 | fun:ngx_pnalloc 220 | } 221 | 222 | { 223 | 224 | Memcheck:Leak 225 | fun:malloc 226 | fun:ngx_alloc 227 | fun:ngx_palloc_large 228 | fun:ngx_palloc 229 | fun:ngx_array_push 230 | fun:ngx_http_get_variable_index 231 | fun:ngx_http_memc_add_variable 232 | fun:ngx_http_memc_init 233 | fun:ngx_http_block 234 | fun:ngx_conf_parse 235 | fun:ngx_init_cycle 236 | fun:main 237 | } 238 | 239 | { 240 | 241 | Memcheck:Leak 242 | fun:malloc 243 | fun:ngx_alloc 244 | fun:ngx_event_process_init 245 | fun:ngx_single_process_cycle 246 | fun:main 247 | } 248 | { 249 | 250 | Memcheck:Leak 251 | fun:malloc 252 | fun:ngx_alloc 253 | fun:ngx_crc32_table_init 254 | fun:main 255 | } 256 | { 257 | 258 | Memcheck:Leak 259 | fun:malloc 260 | fun:ngx_alloc 261 | fun:ngx_event_process_init 262 | fun:ngx_worker_process_init 263 | fun:ngx_worker_process_cycle 264 | fun:ngx_spawn_process 265 | fun:ngx_start_worker_processes 266 | fun:ngx_master_process_cycle 267 | fun:main 268 | } 269 | { 270 | 271 | Memcheck:Leak 272 | fun:malloc 273 | fun:ngx_alloc 274 | fun:ngx_palloc_large 275 | fun:ngx_palloc 276 | fun:ngx_pcalloc 277 | fun:ngx_hash_init 278 | fun:ngx_http_variables_init_vars 279 | fun:ngx_http_block 280 | fun:ngx_conf_parse 281 | fun:ngx_init_cycle 282 | fun:main 283 | } 284 | { 285 | 286 | Memcheck:Leak 287 | fun:malloc 288 | fun:ngx_alloc 289 | fun:ngx_palloc_large 290 | fun:ngx_palloc 291 | fun:ngx_pcalloc 292 | fun:ngx_http_upstream_drizzle_create_srv_conf 293 | fun:ngx_http_upstream 294 | fun:ngx_conf_parse 295 | fun:ngx_http_block 296 | fun:ngx_conf_parse 297 | fun:ngx_init_cycle 298 | fun:main 299 | } 300 | { 301 | 302 | Memcheck:Leak 303 | fun:malloc 304 | fun:ngx_alloc 305 | fun:ngx_palloc_large 306 | fun:ngx_palloc 307 | fun:ngx_pcalloc 308 | fun:ngx_hash_keys_array_init 309 | fun:ngx_http_variables_add_core_vars 310 | fun:ngx_http_core_preconfiguration 311 | fun:ngx_http_block 312 | fun:ngx_conf_parse 313 | fun:ngx_init_cycle 314 | fun:main 315 | } 316 | { 317 | 318 | Memcheck:Leak 319 | fun:malloc 320 | fun:ngx_alloc 321 | fun:ngx_palloc_large 322 | fun:ngx_palloc 323 | fun:ngx_array_push 324 | fun:ngx_hash_add_key 325 | fun:ngx_http_add_variable 326 | fun:ngx_http_echo_add_variables 327 | fun:ngx_http_echo_handler_init 328 | fun:ngx_http_block 329 | fun:ngx_conf_parse 330 | fun:ngx_init_cycle 331 | } 332 | { 333 | 334 | Memcheck:Leak 335 | fun:malloc 336 | fun:ngx_alloc 337 | fun:ngx_palloc_large 338 | fun:ngx_palloc 339 | fun:ngx_pcalloc 340 | fun:ngx_http_upstream_drizzle_create_srv_conf 341 | fun:ngx_http_core_server 342 | fun:ngx_conf_parse 343 | fun:ngx_http_block 344 | fun:ngx_conf_parse 345 | fun:ngx_init_cycle 346 | fun:main 347 | } 348 | { 349 | 350 | Memcheck:Leak 351 | fun:malloc 352 | fun:ngx_alloc 353 | fun:ngx_palloc_large 354 | fun:ngx_palloc 355 | fun:ngx_pcalloc 356 | fun:ngx_http_upstream_drizzle_create_srv_conf 357 | fun:ngx_http_block 358 | fun:ngx_conf_parse 359 | fun:ngx_init_cycle 360 | fun:main 361 | } 362 | { 363 | 364 | Memcheck:Leak 365 | fun:malloc 366 | fun:ngx_alloc 367 | fun:ngx_palloc_large 368 | fun:ngx_palloc 369 | fun:ngx_array_push 370 | fun:ngx_hash_add_key 371 | fun:ngx_http_variables_add_core_vars 372 | fun:ngx_http_core_preconfiguration 373 | fun:ngx_http_block 374 | fun:ngx_conf_parse 375 | fun:ngx_init_cycle 376 | fun:main 377 | } 378 | { 379 | 380 | Memcheck:Leak 381 | fun:malloc 382 | fun:ngx_alloc 383 | fun:ngx_palloc_large 384 | fun:ngx_palloc 385 | fun:ngx_pcalloc 386 | fun:ngx_init_cycle 387 | fun:main 388 | } 389 | { 390 | 391 | Memcheck:Leak 392 | fun:malloc 393 | fun:ngx_alloc 394 | fun:ngx_palloc_large 395 | fun:ngx_palloc 396 | fun:ngx_hash_init 397 | fun:ngx_http_upstream_init_main_conf 398 | fun:ngx_http_block 399 | fun:ngx_conf_parse 400 | fun:ngx_init_cycle 401 | fun:main 402 | } 403 | { 404 | 405 | Memcheck:Leak 406 | fun:malloc 407 | fun:ngx_alloc 408 | fun:ngx_palloc_large 409 | fun:ngx_palloc 410 | fun:ngx_pcalloc 411 | fun:ngx_http_drizzle_keepalive_init 412 | fun:ngx_http_upstream_drizzle_init 413 | fun:ngx_http_upstream_init_main_conf 414 | fun:ngx_http_block 415 | fun:ngx_conf_parse 416 | fun:ngx_init_cycle 417 | fun:main 418 | } 419 | { 420 | 421 | Memcheck:Leak 422 | fun:malloc 423 | fun:ngx_alloc 424 | fun:ngx_palloc_large 425 | fun:ngx_palloc 426 | fun:ngx_hash_init 427 | fun:ngx_http_variables_init_vars 428 | fun:ngx_http_block 429 | fun:ngx_conf_parse 430 | fun:ngx_init_cycle 431 | fun:main 432 | } 433 | { 434 | 435 | Memcheck:Leak 436 | fun:memalign 437 | fun:posix_memalign 438 | fun:ngx_memalign 439 | fun:ngx_create_pool 440 | } 441 | { 442 | 443 | Memcheck:Leak 444 | fun:memalign 445 | fun:posix_memalign 446 | fun:ngx_memalign 447 | fun:ngx_palloc_block 448 | fun:ngx_palloc 449 | } 450 | { 451 | 452 | Memcheck:Cond 453 | fun:index 454 | fun:expand_dynamic_string_token 455 | fun:_dl_map_object 456 | fun:map_doit 457 | fun:_dl_catch_error 458 | fun:do_preload 459 | fun:dl_main 460 | fun:_dl_sysdep_start 461 | fun:_dl_start 462 | } 463 | --------------------------------------------------------------------------------