mirror of
https://gitee.com/iresty/apisix.git
synced 2024-12-05 21:47:36 +08:00
feat(ext-plugin): stop the runner with SIGTERM (#4367)
This commit is contained in:
parent
6c38fdff9c
commit
68711e443d
@ -291,6 +291,12 @@ http {
|
|||||||
apisix.http_init_worker()
|
apisix.http_init_worker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{% if not use_openresty_1_17 then %}
|
||||||
|
exit_worker_by_lua_block {
|
||||||
|
apisix.http_exit_worker()
|
||||||
|
}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
{% if enable_control then %}
|
{% if enable_control then %}
|
||||||
server {
|
server {
|
||||||
listen {* control_server_addr *};
|
listen {* control_server_addr *};
|
||||||
|
@ -65,7 +65,7 @@ version: print the version of apisix
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function check_version(cur_ver_s, need_ver_s)
|
local function version_greater_equal(cur_ver_s, need_ver_s)
|
||||||
local cur_vers = util.split(cur_ver_s, [[.]])
|
local cur_vers = util.split(cur_ver_s, [[.]])
|
||||||
local need_vers = util.split(need_ver_s, [[.]])
|
local need_vers = util.split(need_ver_s, [[.]])
|
||||||
local len = max(#cur_vers, #need_vers)
|
local len = max(#cur_vers, #need_vers)
|
||||||
@ -357,10 +357,15 @@ Please modify "admin_key" in conf/config.yaml .
|
|||||||
end
|
end
|
||||||
|
|
||||||
local need_ver = "1.17.3"
|
local need_ver = "1.17.3"
|
||||||
if not check_version(or_ver, need_ver) then
|
if not version_greater_equal(or_ver, need_ver) then
|
||||||
util.die("openresty version must >=", need_ver, " current ", or_ver, "\n")
|
util.die("openresty version must >=", need_ver, " current ", or_ver, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local use_openresty_1_17 = false
|
||||||
|
if not version_greater_equal(or_ver, "1.19.3") then
|
||||||
|
use_openresty_1_17 = true
|
||||||
|
end
|
||||||
|
|
||||||
local or_info = util.execute_cmd("openresty -V 2>&1")
|
local or_info = util.execute_cmd("openresty -V 2>&1")
|
||||||
local with_module_status = true
|
local with_module_status = true
|
||||||
if or_info and not or_info:find("http_stub_status_module", 1, true) then
|
if or_info and not or_info:find("http_stub_status_module", 1, true) then
|
||||||
@ -446,6 +451,7 @@ Please modify "admin_key" in conf/config.yaml .
|
|||||||
|
|
||||||
-- Using template.render
|
-- Using template.render
|
||||||
local sys_conf = {
|
local sys_conf = {
|
||||||
|
use_openresty_1_17 = use_openresty_1_17,
|
||||||
lua_path = env.pkg_path_org,
|
lua_path = env.pkg_path_org,
|
||||||
lua_cpath = env.pkg_cpath_org,
|
lua_cpath = env.pkg_cpath_org,
|
||||||
os_name = util.trim(util.execute_cmd("uname")),
|
os_name = util.trim(util.execute_cmd("uname")),
|
||||||
|
@ -23,11 +23,18 @@ local type = type
|
|||||||
|
|
||||||
|
|
||||||
local _M = {}
|
local _M = {}
|
||||||
|
local WNOHANG = 1
|
||||||
|
|
||||||
|
|
||||||
ffi.cdef[[
|
ffi.cdef[[
|
||||||
|
typedef int32_t pid_t;
|
||||||
|
typedef unsigned int useconds_t;
|
||||||
|
|
||||||
int setenv(const char *name, const char *value, int overwrite);
|
int setenv(const char *name, const char *value, int overwrite);
|
||||||
char *strerror(int errnum);
|
char *strerror(int errnum);
|
||||||
|
|
||||||
|
int usleep(useconds_t usec);
|
||||||
|
pid_t waitpid(pid_t pid, int *wstatus, int options);
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
|
||||||
@ -52,4 +59,31 @@ function _M.setenv(name, value)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function waitpid_nohang(pid)
|
||||||
|
local res = C.waitpid(pid, nil, WNOHANG)
|
||||||
|
if res == -1 then
|
||||||
|
return nil, err()
|
||||||
|
end
|
||||||
|
return res > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function _M.waitpid(pid, timeout)
|
||||||
|
local count = 0
|
||||||
|
local step = 1000 * 10
|
||||||
|
local total = timeout * 1000 * 1000
|
||||||
|
while step * count < total do
|
||||||
|
count = count + 1
|
||||||
|
C.usleep(step)
|
||||||
|
local ok, err = waitpid_nohang(pid)
|
||||||
|
if err then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
if ok then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
|
@ -134,6 +134,11 @@ function _M.http_init_worker()
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function _M.http_exit_worker()
|
||||||
|
require("apisix.plugins.ext-plugin.init").exit_worker()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function _M.http_ssl_phase()
|
function _M.http_ssl_phase()
|
||||||
local ngx_ctx = ngx.ctx
|
local ngx_ctx = ngx.ctx
|
||||||
local api_ctx = ngx_ctx.api_ctx
|
local api_ctx = ngx_ctx.api_ctx
|
||||||
|
@ -36,6 +36,7 @@ if is_http then
|
|||||||
ngx_pipe = require("ngx.pipe")
|
ngx_pipe = require("ngx.pipe")
|
||||||
events = require("resty.worker.events")
|
events = require("resty.worker.events")
|
||||||
end
|
end
|
||||||
|
local resty_signal = require "resty.signal"
|
||||||
local bit = require("bit")
|
local bit = require("bit")
|
||||||
local band = bit.band
|
local band = bit.band
|
||||||
local lshift = bit.lshift
|
local lshift = bit.lshift
|
||||||
@ -588,8 +589,10 @@ local function spawn_proc(cmd)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local runner
|
||||||
local function setup_runner(cmd)
|
local function setup_runner(cmd)
|
||||||
local proc = spawn_proc(cmd)
|
runner = spawn_proc(cmd)
|
||||||
|
|
||||||
ngx_timer_at(0, function(premature)
|
ngx_timer_at(0, function(premature)
|
||||||
if premature then
|
if premature then
|
||||||
return
|
return
|
||||||
@ -599,7 +602,7 @@ local function setup_runner(cmd)
|
|||||||
while true do
|
while true do
|
||||||
-- drain output
|
-- drain output
|
||||||
local max = 3800 -- smaller than Nginx error log length limit
|
local max = 3800 -- smaller than Nginx error log length limit
|
||||||
local data, err = proc:stdout_read_any(max)
|
local data, err = runner:stdout_read_any(max)
|
||||||
if not data then
|
if not data then
|
||||||
if exiting() then
|
if exiting() then
|
||||||
return
|
return
|
||||||
@ -615,11 +618,13 @@ local function setup_runner(cmd)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, reason, status = proc:wait()
|
local ok, reason, status = runner:wait()
|
||||||
if not ok then
|
if not ok then
|
||||||
core.log.warn("runner exited with reason: ", reason, ", status: ", status)
|
core.log.warn("runner exited with reason: ", reason, ", status: ", status)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
runner = nil
|
||||||
|
|
||||||
local ok, err = events.post(events_list._source, events_list.runner_exit)
|
local ok, err = events.post(events_list._source, events_list.runner_exit)
|
||||||
if not ok then
|
if not ok then
|
||||||
core.log.error("post event failure with ", events_list._source, ", error: ", err)
|
core.log.error("post event failure with ", events_list._source, ", error: ", err)
|
||||||
@ -628,7 +633,7 @@ local function setup_runner(cmd)
|
|||||||
core.log.warn("respawn runner 3 seconds later with cmd: ", core.json.encode(cmd))
|
core.log.warn("respawn runner 3 seconds later with cmd: ", core.json.encode(cmd))
|
||||||
core.utils.sleep(3)
|
core.utils.sleep(3)
|
||||||
core.log.warn("respawning new runner...")
|
core.log.warn("respawning new runner...")
|
||||||
proc = spawn_proc(cmd)
|
runner = spawn_proc(cmd)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@ -656,4 +661,21 @@ function _M.init_worker()
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function _M.exit_worker()
|
||||||
|
if process.type() == "privileged agent" and runner then
|
||||||
|
-- We need to send SIGTERM in the exit_worker phase, as:
|
||||||
|
-- 1. privileged agent doesn't support graceful exiting when I write this
|
||||||
|
-- 2. better to make it work without graceful exiting
|
||||||
|
local pid = runner:pid()
|
||||||
|
core.log.notice("terminate runner ", pid, " with SIGTERM")
|
||||||
|
local num = resty_signal.signum("TERM")
|
||||||
|
runner:kill(num)
|
||||||
|
|
||||||
|
-- give 1s to clean up the mess
|
||||||
|
core.os.waitpid(pid, 1)
|
||||||
|
-- then we KILL it via gc finalizer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
|
@ -103,3 +103,14 @@ nginx_config:
|
|||||||
envs:
|
envs:
|
||||||
- MY_ENV_VAR
|
- MY_ENV_VAR
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### APISIX terminates my runner with SIGKILL but not SIGTERM!
|
||||||
|
|
||||||
|
Since `v2.7`, APISIX will stop the runner with SIGTERM when it is running on
|
||||||
|
OpenResty 1.19+.
|
||||||
|
|
||||||
|
However, APISIX needs to wait the runner to quit so that we can ensure the resource
|
||||||
|
for the process group is freed.
|
||||||
|
|
||||||
|
Therefore, we send SIGTERM first. And then after 1 second, if the runner is still
|
||||||
|
running, we will send SIGKILL.
|
||||||
|
10
t/APISIX.pm
10
t/APISIX.pm
@ -402,7 +402,17 @@ _EOC_
|
|||||||
require("apisix").http_init_worker()
|
require("apisix").http_init_worker()
|
||||||
$extra_init_worker_by_lua
|
$extra_init_worker_by_lua
|
||||||
}
|
}
|
||||||
|
_EOC_
|
||||||
|
|
||||||
|
if ($version !~ m/\/1.17.8/) {
|
||||||
|
$http_config .= <<_EOC_;
|
||||||
|
exit_worker_by_lua_block {
|
||||||
|
require("apisix").http_exit_worker()
|
||||||
|
}
|
||||||
|
_EOC_
|
||||||
|
}
|
||||||
|
|
||||||
|
$http_config .= <<_EOC_;
|
||||||
log_format main escape=default '\$remote_addr - \$remote_user [\$time_local] \$http_host "\$request" \$status \$body_bytes_sent \$request_time "\$http_referer" "\$http_user_agent" \$upstream_addr \$upstream_status \$upstream_response_time "\$upstream_scheme://\$upstream_host\$upstream_uri"';
|
log_format main escape=default '\$remote_addr - \$remote_user [\$time_local] \$http_host "\$request" \$status \$body_bytes_sent \$request_time "\$http_referer" "\$http_user_agent" \$upstream_addr \$upstream_status \$upstream_response_time "\$upstream_scheme://\$upstream_host\$upstream_uri"';
|
||||||
|
|
||||||
# fake server, only for test
|
# fake server, only for test
|
||||||
|
23
t/plugin/ext-plugin/runner_can_not_terminated.sh
Executable file
23
t/plugin/ext-plugin/runner_can_not_terminated.sh
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
# contributor license agreements. See the NOTICE file distributed with
|
||||||
|
# this work for additional information regarding copyright ownership.
|
||||||
|
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
# (the "License"); you may not use this file except in compliance with
|
||||||
|
# the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
term() {
|
||||||
|
eval sleep 1800
|
||||||
|
}
|
||||||
|
trap term SIGTERM
|
||||||
|
sleep 1800
|
65
t/plugin/ext-plugin/sanity-openresty-1-19.t
vendored
Normal file
65
t/plugin/ext-plugin/sanity-openresty-1-19.t
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
# contributor license agreements. See the NOTICE file distributed with
|
||||||
|
# this work for additional information regarding copyright ownership.
|
||||||
|
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
# (the "License"); you may not use this file except in compliance with
|
||||||
|
# the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
use t::APISIX;
|
||||||
|
|
||||||
|
my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
|
||||||
|
my $version = eval { `$nginx_binary -V 2>&1` };
|
||||||
|
|
||||||
|
if ($version =~ m/\/1.17.8/) {
|
||||||
|
plan(skip_all => "require OpenResty 1.19+");
|
||||||
|
} else {
|
||||||
|
plan('no_plan');
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat_each(1);
|
||||||
|
no_long_string();
|
||||||
|
no_root_location();
|
||||||
|
no_shuffle();
|
||||||
|
log_level("info");
|
||||||
|
|
||||||
|
add_block_preprocessor(sub {
|
||||||
|
my ($block) = @_;
|
||||||
|
|
||||||
|
my $cmd = $block->ext_plugin_cmd // "['sleep', '5s']";
|
||||||
|
my $extra_yaml_config = <<_EOC_;
|
||||||
|
ext-plugin:
|
||||||
|
cmd: $cmd
|
||||||
|
_EOC_
|
||||||
|
$block->set_value("extra_yaml_config", $extra_yaml_config);
|
||||||
|
|
||||||
|
if (!$block->request) {
|
||||||
|
$block->set_value("request", "GET /t");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$block->error_log) {
|
||||||
|
$block->set_value("no_error_log", "[error]\n[alert]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
run_tests;
|
||||||
|
|
||||||
|
__DATA__
|
||||||
|
|
||||||
|
=== TEST 1: terminate spawn runner
|
||||||
|
--- ext_plugin_cmd
|
||||||
|
["t/plugin/ext-plugin/runner.sh", "3600"]
|
||||||
|
--- config
|
||||||
|
location /t {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
--- shutdown_error_log eval
|
||||||
|
qr/terminate runner \d+ with SIGTERM/
|
10
t/plugin/ext-plugin/sanity.t
vendored
10
t/plugin/ext-plugin/sanity.t
vendored
@ -453,3 +453,13 @@ MY_ENV_VAR foo
|
|||||||
{"error_msg":"failed to check the configuration of plugin ext-plugin-pre-req err: property \"conf\" validation failed: failed to validate item 1: property \"name\" is required"}
|
{"error_msg":"failed to check the configuration of plugin ext-plugin-pre-req err: property \"conf\" validation failed: failed to validate item 1: property \"name\" is required"}
|
||||||
|
|
||||||
{"error_msg":"failed to check the configuration of plugin ext-plugin-post-req err: property \"conf\" validation failed: failed to validate item 1: property \"value\" is required"}
|
{"error_msg":"failed to check the configuration of plugin ext-plugin-post-req err: property \"conf\" validation failed: failed to validate item 1: property \"value\" is required"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=== TEST 15: spawn runner which can't be terminated, ensure APISIX won't be blocked
|
||||||
|
--- ext_plugin_cmd
|
||||||
|
["t/plugin/ext-plugin/runner_can_not_terminated.sh"]
|
||||||
|
--- config
|
||||||
|
location /t {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user