├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── lib └── resty │ └── shell.lua ├── t ├── TestShell.pm ├── status.t ├── stderr.t ├── stdin.t └── stdout.t └── valgrind.suppress /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.swo 4 | t/servroot* 5 | /go 6 | /reindex 7 | /a.lua 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | branches: 5 | only: 6 | - "master" 7 | 8 | os: linux 9 | 10 | language: c 11 | 12 | compiler: 13 | - gcc 14 | 15 | env: 16 | global: 17 | - JOBS=3 18 | - NGX_BUILD_JOBS=$JOBS 19 | - LUAJIT_PREFIX=/opt/luajit21 20 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 21 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 22 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 23 | matrix: 24 | - NGINX_VERSION=1.27.1 25 | 26 | install: 27 | - sudo apt-get install -qq -y axel 28 | - cpanm --sudo --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 29 | - git clone https://github.com/openresty/openresty.git ../openresty 30 | - git clone https://github.com/openresty/nginx-devel-utils.git 31 | - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module 32 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 33 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 34 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 35 | - git clone https://github.com/openresty/lua-resty-signal.git ../lua-resty-signal 36 | - git clone https://github.com/openresty/lua-tablepool.git ../lua-tablepool 37 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 38 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 39 | 40 | script: 41 | - pushd ../lua-resty-signal/ 42 | - make 43 | - popd 44 | - cd luajit2/ 45 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1) 46 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 47 | - cd .. 48 | - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 49 | - export NGX_BUILD_CC=$CC 50 | - ngx-build $NGINX_VERSION --without-pcre2 --with-http_realip_module --add-module=../ndk-nginx-module --add-module=../lua-nginx-module --with-debug > build.log 2>&1 || (cat build.log && exit 1) 51 | - nginx -V 52 | - ldd `which nginx`|grep -E 'luajit|ssl|pcre' 53 | - prove -I. -r t 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty 2 | 3 | #LUA_VERSION := 5.1 4 | PREFIX ?= /usr/local 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-shell - Lua module for nonblocking system shell command executions 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Synopsis](#synopsis) 11 | * [Functions](#functions) 12 | * [run](#run) 13 | * [Dependencies](#dependencies) 14 | * [Author](#author) 15 | * [Copyright & Licenses](#copyright--licenses) 16 | 17 | Synopsis 18 | ======== 19 | 20 | ```lua 21 | local shell = require "resty.shell" 22 | 23 | local stdin = "hello" 24 | local timeout = 1000 -- ms 25 | local max_size = 4096 -- byte 26 | 27 | local ok, stdout, stderr, reason, status = 28 | shell.run([[perl -e 'warn "he\n"; print <>']], stdin, timeout, max_size) 29 | if not ok then 30 | -- ... 31 | end 32 | ``` 33 | 34 | Functions 35 | ========= 36 | 37 | run 38 | --- 39 | 40 | **syntax:** `ok, stdout, stderr, reason, status = shell.run(cmd, stdin?, timeout?, max_size?)` 41 | 42 | **context:** `all phases supporting yielding` 43 | 44 | Runs a shell command, `cmd`, with an optional stdin. 45 | 46 | The `cmd` argument can either be a single string value (e.g. `"echo 'hello, 47 | world'"`) or an array-like Lua table (e.g. `{"echo", "hello, world"}`). The 48 | former is equivalent to `{"/bin/sh", "-c", "echo 'hello, world'"}`, but simpler 49 | and slightly faster. 50 | 51 | When the `stdin` argument is `nil` or `""`, the stdin device will immediately 52 | be closed. 53 | 54 | The `timeout` argument specifies the timeout threshold (in ms) for 55 | stderr/stdout reading timeout, stdin writing timeout, and process waiting 56 | timeout. The default is 10 seconds as per https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/pipe.md#set_timeouts 57 | 58 | The `max_size` argument specifies the maximum size allowed for each output 59 | data stream of stdout and stderr. When exceeding the limit, the `run()` 60 | function will immediately stop reading any more data from the stream and return 61 | an error string in the `reason` return value: `"failed to read stdout: too much 62 | data"`. The default value of the `max_size` argument is 128 KB. 63 | 64 | Upon terminating successfully (with a zero exit status), `ok` will be `true`, 65 | `reason` will be `"exit"`, and `status` will hold the sub-process exit status. 66 | 67 | Upon terminating abnormally (non-zero exit status), `ok` will be `false`, 68 | `reason` will be `"exit"`, and `status` will hold the sub-process exit status. 69 | 70 | Upon exceeding a timeout threshold or any other unexpected error, `ok` will be 71 | `nil`, and `reason` will be a string describing the error. 72 | 73 | When a timeout threshold is exceeded, the sub-process will be terminated as 74 | such: 75 | 76 | 1. first, by receiving a `SIGTERM` signal from this library, 77 | 2. then, after 1ms, by receiving a `SIGKILL` signal from this library. 78 | 79 | Note that child processes of the sub-process (if any) will not be terminated. 80 | You may need to terminate these processes yourself. 81 | 82 | When the sub-process is terminated by a UNIX signal, the `reason` return value 83 | will be `"signal"` and the `status` return value will hold the signal number. 84 | 85 | [Back to TOC](#table-of-contents) 86 | 87 | Dependencies 88 | ============ 89 | 90 | This library depends on 91 | 92 | * the [lua-resty-signal](https://github.com/openresty/lua-resty-signal) library. 93 | * the [ngx.pipe](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/pipe.md#readme) 94 | API of OpenResty. 95 | * the [lua-tablepool](https://github.com/openresty/lua-tablepool) library. 96 | 97 | [Back to TOC](#table-of-contents) 98 | 99 | Author 100 | ====== 101 | 102 | Yichun Zhang (agentzh) 103 | 104 | [Back to TOC](#table-of-contents) 105 | 106 | Copyright & Licenses 107 | ==================== 108 | 109 | This module is licensed under the BSD license. 110 | 111 | Copyright (C) 2018-2019, [OpenResty Inc.](https://openresty.com) 112 | 113 | All rights reserved. 114 | 115 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 116 | 117 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 118 | 119 | * 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. 120 | 121 | * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 122 | 123 | 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. 124 | 125 | [Back to TOC](#table-of-contents) 126 | 127 | -------------------------------------------------------------------------------- /lib/resty/shell.lua: -------------------------------------------------------------------------------- 1 | local _M = { 2 | version = 0.03 3 | } 4 | 5 | 6 | local resty_sig = require "resty.signal" 7 | local ngx_pipe = require "ngx.pipe" 8 | local new_tab = require "table.new" 9 | local tablepool = require "tablepool" 10 | 11 | 12 | local kill = resty_sig.kill 13 | local pipe_spawn = ngx_pipe.spawn 14 | local tostring = tostring 15 | local spawn_thread = ngx.thread.spawn 16 | local wait_thread = ngx.thread.wait 17 | local concat = table.concat 18 | local fetch_tab = tablepool.fetch 19 | local release_tab = tablepool.release 20 | local sleep = ngx.sleep 21 | 22 | 23 | local spawn_opts = { 24 | buffer_size = 1024 * 32 -- 32K 25 | } 26 | 27 | 28 | local tab_pool_tag = "resty.shell" 29 | 30 | 31 | local function cleanup_proc(proc) 32 | local pid = proc:pid() 33 | if pid then 34 | local ok, err = kill(pid, "TERM") 35 | if not ok then 36 | return nil, "failed to kill process " .. pid 37 | .. ": " .. tostring(err) 38 | end 39 | sleep(0.001) -- only wait for 1 msec 40 | kill(pid, "KILL") 41 | end 42 | 43 | return true 44 | end 45 | 46 | 47 | local function concat_err(err1, err2) 48 | return tostring(err1) .. "; " .. tostring(err2) 49 | end 50 | 51 | 52 | local function read_stream(proc, buf, max_size, meth_name) 53 | local pos = 1 54 | local len = 0 55 | 56 | while len <= max_size do 57 | local data, err, partial = proc[meth_name](proc, max_size - len + 1) 58 | if not data then 59 | if partial then 60 | buf[pos] = partial 61 | pos = pos + 1 62 | len = len + #partial 63 | end 64 | 65 | if err == "closed" then 66 | return pos - 1 67 | end 68 | 69 | return pos - 1, err 70 | end 71 | 72 | buf[pos] = data 73 | pos = pos + 1 74 | len = len + #data 75 | end 76 | 77 | if len > max_size then 78 | return pos - 1, "too much data" 79 | end 80 | 81 | return pos - 1 82 | end 83 | 84 | 85 | function _M.run(cmd, stdin, timeout, max_size) 86 | if not max_size then 87 | max_size = 128 * 1024 -- 128KB 88 | end 89 | 90 | local proc, err = pipe_spawn(cmd, spawn_opts) 91 | if not proc then 92 | return nil, nil, nil, "failed to spawn: " .. tostring(err) 93 | end 94 | 95 | proc:set_timeouts(timeout, timeout, timeout, timeout) 96 | 97 | if stdin and stdin ~= "" then 98 | local bytes, err = proc:write(stdin) 99 | if not bytes then 100 | local ok2, err2 = cleanup_proc(proc) 101 | if not ok2 then 102 | err = concat_err(err, err2) 103 | end 104 | return nil, nil, nil, "failed to write to stdin: " .. tostring(err) 105 | end 106 | end 107 | 108 | local ok 109 | ok, err = proc:shutdown("stdin") 110 | if not ok then 111 | local ok2, err2 = cleanup_proc(proc) 112 | if not ok2 then 113 | err = concat_err(err, err2) 114 | end 115 | return nil, nil, nil, "failed to shutdown stdin: " .. tostring(err) 116 | end 117 | 118 | local stdout_tab = fetch_tab(tab_pool_tag, 4, 0) 119 | local stderr_tab = fetch_tab(tab_pool_tag, 4, 0) 120 | 121 | local thr_out = spawn_thread(read_stream, proc, stdout_tab, max_size, 122 | "stdout_read_any") 123 | local thr_err = spawn_thread(read_stream, proc, stderr_tab, max_size, 124 | "stderr_read_any") 125 | 126 | local reason, status 127 | ok, reason, status = proc:wait() 128 | 129 | if ok == nil and reason ~= "exited" then 130 | err = reason 131 | local ok2, err2 = cleanup_proc(proc) 132 | if not ok2 then 133 | err = concat_err(err, err2) 134 | end 135 | 136 | local stdout = concat(stdout_tab) 137 | release_tab(tab_pool_tag, stdout_tab) 138 | 139 | local stderr = concat(stderr_tab) 140 | release_tab(tab_pool_tag, stderr_tab) 141 | 142 | return nil, stdout, stderr, 143 | "failed to wait for process: " .. tostring(err) 144 | end 145 | 146 | local ok2, stdout_pos, err2 = wait_thread(thr_out) 147 | if not ok2 then 148 | local stdout = concat(stdout_tab) 149 | release_tab(tab_pool_tag, stdout_tab) 150 | 151 | local stderr = concat(stderr_tab) 152 | release_tab(tab_pool_tag, stderr_tab) 153 | 154 | return nil, stdout, stderr, "failed to wait stdout thread: " 155 | .. tostring(stdout_pos) 156 | end 157 | 158 | if err2 then 159 | local stdout = concat(stdout_tab, "", 1, stdout_pos) 160 | release_tab(tab_pool_tag, stdout_tab) 161 | 162 | local stderr = concat(stderr_tab) 163 | release_tab(tab_pool_tag, stderr_tab) 164 | 165 | return nil, stdout, stderr, "failed to read stdout: " .. tostring(err2) 166 | end 167 | 168 | local stderr_pos 169 | ok2, stderr_pos, err2 = wait_thread(thr_err) 170 | if not ok2 then 171 | local stdout = concat(stdout_tab, "", 1, stdout_pos) 172 | release_tab(tab_pool_tag, stdout_tab) 173 | 174 | local stderr = concat(stderr_tab) 175 | release_tab(tab_pool_tag, stderr_tab) 176 | 177 | return nil, stdout, stderr, "failed to wait stderr thread: " 178 | .. tostring(stderr_pos) 179 | end 180 | 181 | local stdout = concat(stdout_tab, "", 1, stdout_pos) 182 | release_tab(tab_pool_tag, stdout_tab) 183 | 184 | local stderr = concat(stderr_tab, "", 1, stderr_pos) 185 | release_tab(tab_pool_tag, stderr_tab) 186 | 187 | if err2 then 188 | return nil, stdout, stderr, "failed to read stderr: " .. tostring(err2) 189 | end 190 | 191 | return ok, stdout, stderr, reason, status 192 | end 193 | 194 | 195 | return _M 196 | -------------------------------------------------------------------------------- /t/TestShell.pm: -------------------------------------------------------------------------------- 1 | package t::TestShell; 2 | 3 | use v5.10.1; 4 | use Test::Nginx::Socket::Lua -Base; 5 | 6 | add_block_preprocessor(sub { 7 | my $block = shift; 8 | 9 | my $http_config = $block->http_config // ''; 10 | my $init_by_lua_block = $block->init_by_lua_block // 'require "resty.core"'; 11 | 12 | $http_config .= <<_EOC_; 13 | 14 | lua_package_path "./lib/?.lua;../lua-tablepool/lib/?.lua;../lua-resty-signal/lib/?.lua;../lua-resty-core/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; 15 | lua_package_cpath "../lua-resty-signal/?.so;;"; 16 | init_by_lua_block { 17 | $init_by_lua_block 18 | } 19 | _EOC_ 20 | 21 | $block->set_value("http_config", $http_config); 22 | 23 | if (!defined $block->error_log) { 24 | $block->set_value("no_error_log", "[error]"); 25 | } 26 | 27 | if (!defined $block->request) { 28 | $block->set_value("request", "GET /t"); 29 | } 30 | }); 31 | 32 | 1; 33 | -------------------------------------------------------------------------------- /t/status.t: -------------------------------------------------------------------------------- 1 | # vi:ft= 2 | 3 | use lib '.'; 4 | use t::TestShell; 5 | 6 | plan tests => 3 * blocks(); 7 | 8 | no_long_string(); 9 | #no_diff(); 10 | 11 | run_tests(); 12 | 13 | __DATA__ 14 | 15 | === TEST 1: exit 1 16 | --- config 17 | location = /t { 18 | content_by_lua_block { 19 | local say = ngx.say 20 | local shell = require "resty.shell" 21 | 22 | do 23 | local ok, stdout, stderr, reason, status = 24 | shell.run([[perl -e 'warn "he\n"; print "yes"; exit 1']], nil, 2000) 25 | say("ok: ", ok) 26 | say("stdout: ", stdout) 27 | say("stderr: ", stderr) 28 | say("reason: ", reason) 29 | say("status: ", status) 30 | end 31 | collectgarbage() 32 | } 33 | } 34 | --- response_body 35 | ok: false 36 | stdout: yes 37 | stderr: he 38 | 39 | reason: exit 40 | status: 1 41 | 42 | 43 | 44 | === TEST 2: exit 255 45 | --- config 46 | location = /t { 47 | content_by_lua_block { 48 | local say = ngx.say 49 | local shell = require "resty.shell" 50 | 51 | do 52 | local ok, stdout, stderr, reason, status = 53 | shell.run([[perl -e 'print "yes"; die;']], nil, 2000) 54 | say("ok: ", ok) 55 | say("stdout: ", stdout) 56 | say("stderr: ", stderr) 57 | say("reason: ", reason) 58 | say("status: ", status) 59 | end 60 | collectgarbage() 61 | } 62 | } 63 | --- response_body 64 | ok: false 65 | stdout: yes 66 | stderr: Died at -e line 1. 67 | 68 | reason: exit 69 | status: 255 70 | -------------------------------------------------------------------------------- /t/stderr.t: -------------------------------------------------------------------------------- 1 | # vi:ft= 2 | 3 | use lib '.'; 4 | use t::TestShell; 5 | 6 | plan tests => 3 * blocks(); 7 | 8 | no_long_string(); 9 | #no_diff(); 10 | 11 | run_tests(); 12 | 13 | __DATA__ 14 | 15 | === TEST 1: too much stderr data (1 byte more) 16 | --- config 17 | location = /t { 18 | content_by_lua_block { 19 | local say = ngx.say 20 | local shell = require "resty.shell" 21 | 22 | do 23 | local ok, stdout, stderr, reason, status = 24 | shell.run([[perl -e 'warn "hel\n"; print "yes"']], nil, 3000, 3) 25 | say("ok: ", ok) 26 | say("stdout: ", stdout) 27 | say("stderr: ", stderr) 28 | say("reason: ", reason) 29 | say("status: ", status) 30 | end 31 | collectgarbage() 32 | } 33 | } 34 | --- response_body 35 | ok: nil 36 | stdout: yes 37 | stderr: hel 38 | 39 | reason: failed to read stderr: too much data 40 | status: nil 41 | 42 | 43 | 44 | === TEST 2: too much stderr data (several bytes more) 45 | --- config 46 | location = /t { 47 | content_by_lua_block { 48 | local say = ngx.say 49 | local shell = require "resty.shell" 50 | 51 | do 52 | local ok, stdout, stderr, reason, status = 53 | shell.run([[perl -e 'warn "hello world\n"; print "yes"']], 54 | nil, 3000, 3) 55 | say("ok: ", ok) 56 | say("stdout: ", stdout) 57 | say("stderr: ", stderr) 58 | say("reason: ", reason) 59 | say("status: ", status) 60 | end 61 | collectgarbage() 62 | } 63 | } 64 | --- response_body 65 | ok: nil 66 | stdout: yes 67 | stderr: hell 68 | reason: failed to read stderr: too much data 69 | status: nil 70 | 71 | 72 | 73 | === TEST 3: stderr timeout 74 | --- config 75 | location = /t { 76 | content_by_lua_block { 77 | local say = ngx.say 78 | local shell = require "resty.shell" 79 | 80 | do 81 | local ok, stdout, stderr, reason, status = 82 | shell.run([[perl -e 'print "yes"; sleep 10; warn "he\n";']], 83 | nil, 1, 3) 84 | say("ok: ", ok) 85 | say("stdout: '", stdout, "'") 86 | say("stderr: '", stderr, "'") 87 | say("reason: ", reason) 88 | say("status: ", status) 89 | end 90 | collectgarbage() 91 | } 92 | } 93 | --- response_body 94 | ok: nil 95 | stdout: '' 96 | stderr: '' 97 | reason: failed to wait for process: timeout 98 | status: nil 99 | -------------------------------------------------------------------------------- /t/stdin.t: -------------------------------------------------------------------------------- 1 | # vi:ft= 2 | 3 | use lib '.'; 4 | use t::TestShell; 5 | 6 | plan tests => 3 * blocks(); 7 | 8 | no_long_string(); 9 | #no_diff(); 10 | 11 | run_tests(); 12 | 13 | __DATA__ 14 | 15 | === TEST 1: good case 16 | --- config 17 | location = /t { 18 | content_by_lua_block { 19 | local say = ngx.say 20 | local shell = require "resty.shell" 21 | 22 | do 23 | local ok, stdout, stderr, reason, status = 24 | shell.run([[perl -e 'my $ln = <>; print $ln']], "hello", 3000) 25 | say("ok: ", ok) 26 | say("stdout: ", stdout) 27 | say("stderr: ", stderr) 28 | say("reason: ", reason) 29 | say("status: ", status) 30 | end 31 | collectgarbage() 32 | } 33 | } 34 | --- response_body 35 | ok: true 36 | stdout: hello 37 | stderr: 38 | reason: exit 39 | status: 0 40 | -------------------------------------------------------------------------------- /t/stdout.t: -------------------------------------------------------------------------------- 1 | # vi:ft= 2 | 3 | use lib '.'; 4 | use t::TestShell; 5 | 6 | plan tests => 3 * blocks(); 7 | 8 | no_long_string(); 9 | #no_diff(); 10 | 11 | run_tests(); 12 | 13 | __DATA__ 14 | 15 | === TEST 1: good case (single shell cmd string) 16 | --- config 17 | location = /t { 18 | content_by_lua_block { 19 | local say = ngx.say 20 | local shell = require "resty.shell" 21 | 22 | do 23 | local ok, stdout, stderr, reason, status = 24 | shell.run([[perl -e 'warn "he\n"; print "yes"']], nil, 3000, 3) 25 | say("ok: ", ok) 26 | say("stdout: ", stdout) 27 | say("stderr: ", stderr) 28 | say("reason: ", reason) 29 | say("status: ", status) 30 | end 31 | collectgarbage() 32 | } 33 | } 34 | --- response_body 35 | ok: true 36 | stdout: yes 37 | stderr: he 38 | 39 | reason: exit 40 | status: 0 41 | 42 | 43 | 44 | === TEST 2: good case (shell cmd arg vector) 45 | --- config 46 | location = /t { 47 | content_by_lua_block { 48 | local say = ngx.say 49 | local shell = require "resty.shell" 50 | 51 | do 52 | local ok, stdout, stderr, reason, status = 53 | shell.run({'perl', '-e', [[warn "he\n"; print "yes"]]}, nil, 3000) 54 | say("ok: ", ok) 55 | say("stdout: ", stdout) 56 | say("stderr: ", stderr) 57 | say("reason: ", reason) 58 | say("status: ", status) 59 | end 60 | collectgarbage() 61 | } 62 | } 63 | --- response_body 64 | ok: true 65 | stdout: yes 66 | stderr: he 67 | 68 | reason: exit 69 | status: 0 70 | 71 | 72 | 73 | === TEST 3: too much stdout data (1 byte more) 74 | --- config 75 | location = /t { 76 | content_by_lua_block { 77 | local say = ngx.say 78 | local shell = require "resty.shell" 79 | 80 | do 81 | local ok, stdout, stderr, reason, status = 82 | shell.run([[perl -e 'warn "he\n"; print "yes!"']], nil, 3000, 3) 83 | say("ok: ", ok) 84 | say("stdout: ", stdout) 85 | say("stderr: ", stderr) 86 | say("reason: ", reason) 87 | say("status: ", status) 88 | end 89 | collectgarbage() 90 | } 91 | } 92 | --- response_body 93 | ok: nil 94 | stdout: yes! 95 | stderr: he 96 | 97 | reason: failed to read stdout: too much data 98 | status: nil 99 | 100 | 101 | 102 | === TEST 4: too much stdout data (several bytes more) 103 | --- config 104 | location = /t { 105 | content_by_lua_block { 106 | local say = ngx.say 107 | local shell = require "resty.shell" 108 | 109 | do 110 | local ok, stdout, stderr, reason, status = 111 | shell.run([[perl -e 'warn "he\n"; print "yes!!!yes~"']], 112 | nil, 3000, 3) 113 | say("ok: ", ok) 114 | say("stdout: ", stdout) 115 | say("stderr: ", stderr) 116 | say("reason: ", reason) 117 | say("status: ", status) 118 | end 119 | collectgarbage() 120 | } 121 | } 122 | --- response_body 123 | ok: nil 124 | stdout: yes! 125 | stderr: he 126 | 127 | reason: failed to read stdout: too much data 128 | status: nil 129 | 130 | 131 | 132 | === TEST 5: stdout timeout 133 | --- config 134 | location = /t { 135 | content_by_lua_block { 136 | local say = ngx.say 137 | local shell = require "resty.shell" 138 | 139 | do 140 | local ok, stdout, stderr, reason, status = 141 | shell.run([[perl -e 'warn "he\n"; sleep 10; print "yes"']], 142 | nil, 1, 3) 143 | say("ok: ", ok) 144 | say("stdout: '", stdout, "'") 145 | say("stderr: '", stderr, "'") 146 | say("reason: ", reason) 147 | say("status: ", status) 148 | end 149 | collectgarbage() 150 | } 151 | } 152 | --- response_body_like chomp 153 | \Aok: nil 154 | stdout: '' 155 | stderr: '(?:|he 156 | )' 157 | reason: failed to wait for process: timeout 158 | status: nil 159 | \z 160 | 161 | 162 | 163 | === TEST 6: clean up the sub-process when failed to wait 164 | --- config 165 | location = /t { 166 | content_by_lua_block { 167 | local say = ngx.say 168 | local shell = require "resty.shell" 169 | local ok, stdout, stderr, reason, status = 170 | shell.run([[echo aaaaa && sleep 10]], nil, 100, 3) 171 | say("ok: ", ok) 172 | say("stdout: '", stdout, "'") 173 | say("stderr: '", stderr, "'") 174 | say("reason: ", reason) 175 | say("status: ", status) 176 | } 177 | } 178 | --- response_body 179 | ok: nil 180 | stdout: 'aaaa' 181 | stderr: '' 182 | reason: failed to wait for process: timeout 183 | status: nil 184 | --- error_log 185 | lua pipe SIGCHLD fd read pid: 186 | --- wait: 0.2 187 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | epoll_ctl(event) 5 | fun:epoll_ctl 6 | fun:ngx_epoll_test_rdhup 7 | fun:ngx_epoll_init 8 | fun:ngx_event_process_init 9 | fun:ngx_single_process_cycle 10 | fun:main 11 | } 12 | { 13 | 14 | Memcheck:Leak 15 | match-leak-kinds: definite 16 | fun:malloc 17 | fun:ngx_alloc 18 | fun:ngx_set_environment 19 | fun:ngx_single_process_cycle 20 | fun:main 21 | } 22 | { 23 | 24 | Memcheck:Leak 25 | match-leak-kinds: definite 26 | fun:malloc 27 | fun:ngx_alloc 28 | fun:ngx_set_environment 29 | fun:ngx_worker_process_init 30 | } 31 | --------------------------------------------------------------------------------