diff --git a/README.markdown b/README.markdown
index 28ba6e68c0..23f5f225dd 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1050,6 +1050,8 @@ Directives
* [init_worker_by_lua](#init_worker_by_lua)
* [init_worker_by_lua_block](#init_worker_by_lua_block)
* [init_worker_by_lua_file](#init_worker_by_lua_file)
+* [exit_worker_by_lua_block](#exit_worker_by_lua_block)
+* [exit_worker_by_lua_file](#exit_worker_by_lua_file)
* [set_by_lua](#set_by_lua)
* [set_by_lua_block](#set_by_lua_block)
* [set_by_lua_file](#set_by_lua_file)
@@ -1528,6 +1530,43 @@ This directive was first introduced in the `v0.9.5` release.
[Back to TOC](#directives)
+exit_worker_by_lua_block
+------------------------
+
+**syntax:** *exit_worker_by_lua_block { lua-script }*
+
+**context:** *http*
+
+**phase:** *exiting-worker*
+
+Runs the specified Lua code upon every Nginx worker process's exiting when the master process is enabled.
+
+This hook is often used to release resources when [init_worker_by_lua*](#init_worker_by_lua_block) create resources, To prevent the worker exit abnormal.
+
+For example,
+
+```nginx
+
+ exit_worker_by_lua_block {
+ print("log from exit_worker_by_lua_block")
+ }
+```
+
+[Back to TOC](#directives)
+
+exit_worker_by_lua_file
+-----------------------
+
+**syntax:** *exit_worker_by_lua_file { lua--file-path }*
+
+**context:** *http*
+
+**phase:** *exiting-worker*
+
+Similar to [exit_worker_by_lua_block](#exit_worker_by_lua_block), but accepts the file path to a Lua source file or Lua bytecode file.
+
+[Back to TOC](#directives)
+
set_by_lua
----------
diff --git a/config b/config
index 044deb974e..f7260b74ad 100644
--- a/config
+++ b/config
@@ -346,6 +346,7 @@ HTTP_LUA_SRCS=" \
$ngx_addon_dir/src/ngx_http_lua_bodyfilterby.c \
$ngx_addon_dir/src/ngx_http_lua_initby.c \
$ngx_addon_dir/src/ngx_http_lua_initworkerby.c \
+ $ngx_addon_dir/src/ngx_http_lua_exitworkerby.c \
$ngx_addon_dir/src/ngx_http_lua_socket_udp.c \
$ngx_addon_dir/src/ngx_http_lua_req_method.c \
$ngx_addon_dir/src/ngx_http_lua_phase.c \
@@ -407,6 +408,7 @@ HTTP_LUA_DEPS=" \
$ngx_addon_dir/src/ngx_http_lua_bodyfilterby.h \
$ngx_addon_dir/src/ngx_http_lua_initby.h \
$ngx_addon_dir/src/ngx_http_lua_initworkerby.h \
+ $ngx_addon_dir/src/ngx_http_lua_exitworkerby.h \
$ngx_addon_dir/src/ngx_http_lua_socket_udp.h \
$ngx_addon_dir/src/ngx_http_lua_req_method.h \
$ngx_addon_dir/src/ngx_http_lua_phase.h \
diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki
index 8ad2b2d4a9..73b5dfc977 100644
--- a/doc/HttpLuaModule.wiki
+++ b/doc/HttpLuaModule.wiki
@@ -1228,6 +1228,36 @@ Similar to [[#init_worker_by_lua|init_worker_by_lua]], but accepts the file path
This directive was first introduced in the v0.9.5
release.
+== exit_worker_by_lua_block ==
+
+'''syntax:''' ''exit_worker_by_lua_block { lua-script }''
+
+'''context:''' ''http''
+
+'''phase:''' ''exiting-worker''
+
+Runs the specified Lua code upon every Nginx worker process's exiting when the master process is enabled.
+
+This hook is often used to release resources when [[#init_worker_by_lua_block|init_worker_by_lua*]] create resources, To prevent the worker exit abnormal.
+
+For example,
+
+
+ exit_worker_by_lua_block {
+ print("log from exit_worker_by_lua_block")
+ }
+
+
+== exit_worker_by_lua_file ==
+
+'''syntax:''' ''exit_worker_by_lua_file { lua--file-path }''
+
+'''context:''' ''http''
+
+'''phase:''' ''exiting-worker''
+
+Similar to [[#exit_worker_by_lua_block|exit_worker_by_lua_block]], but accepts the file path to a Lua source file or Lua bytecode file.
+
== set_by_lua ==
'''syntax:''' ''set_by_lua $res [$arg1 $arg2 ...]''
diff --git a/src/ngx_http_lua_common.h b/src/ngx_http_lua_common.h
index e38978389c..0f0863e1b3 100644
--- a/src/ngx_http_lua_common.h
+++ b/src/ngx_http_lua_common.h
@@ -129,6 +129,7 @@ typedef struct {
#define NGX_HTTP_LUA_CONTEXT_SSL_CERT 0x0400
#define NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE 0x0800
#define NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH 0x1000
+#define NGX_HTTP_LUA_CONTEXT_EXIT_WORKER 0x2000
#ifndef NGX_LUA_NO_FFI_API
@@ -203,6 +204,9 @@ struct ngx_http_lua_main_conf_s {
ngx_http_lua_main_conf_handler_pt init_worker_handler;
ngx_str_t init_worker_src;
+ ngx_http_lua_main_conf_handler_pt exit_worker_handler;
+ ngx_str_t exit_worker_src;
+
ngx_http_lua_balancer_peer_data_t *balancer_peer_data;
/* balancer_by_lua does not support yielding and
* there cannot be any conflicts among concurrent requests,
diff --git a/src/ngx_http_lua_directive.c b/src/ngx_http_lua_directive.c
index 014a47237c..b2315ff706 100644
--- a/src/ngx_http_lua_directive.c
+++ b/src/ngx_http_lua_directive.c
@@ -23,6 +23,7 @@
#include "ngx_http_lua_bodyfilterby.h"
#include "ngx_http_lua_initby.h"
#include "ngx_http_lua_initworkerby.h"
+#include "ngx_http_lua_exitworkerby.h"
#include "ngx_http_lua_shdict.h"
#include "ngx_http_lua_ssl_certby.h"
#include "ngx_http_lua_lex.h"
@@ -1210,6 +1211,66 @@ ngx_http_lua_init_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd,
}
+char *
+ngx_http_lua_exit_worker_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ char *rv;
+ ngx_conf_t save;
+
+ save = *cf;
+ cf->handler = ngx_http_lua_exit_worker_by_lua;
+ cf->handler_conf = conf;
+
+ rv = ngx_http_lua_conf_lua_block_parse(cf, cmd);
+
+ *cf = save;
+
+ return rv;
+}
+
+
+char *
+ngx_http_lua_exit_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ u_char *name;
+ ngx_str_t *value;
+ ngx_http_lua_main_conf_t *lmcf = conf;
+
+ dd("enter");
+
+ /* must specify a content handler */
+ if (cmd->post == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (lmcf->exit_worker_handler) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ lmcf->exit_worker_handler = (ngx_http_lua_main_conf_handler_pt) cmd->post;
+
+ if (cmd->post == ngx_http_lua_exit_worker_by_file) {
+ name = ngx_http_lua_rebase_path(cf->pool, value[1].data,
+ value[1].len);
+ if (name == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ lmcf->exit_worker_src.data = name;
+ lmcf->exit_worker_src.len = ngx_strlen(name);
+
+ } else {
+ lmcf->exit_worker_src = value[1];
+ }
+
+ return NGX_CONF_OK;
+}
+
+
#if defined(NDK) && NDK
static ngx_int_t
ngx_http_lua_set_by_lua_init(ngx_http_request_t *r)
diff --git a/src/ngx_http_lua_directive.h b/src/ngx_http_lua_directive.h
index 5abfe4dde2..020da6f2af 100644
--- a/src/ngx_http_lua_directive.h
+++ b/src/ngx_http_lua_directive.h
@@ -49,6 +49,10 @@ char *ngx_http_lua_init_worker_by_lua_block(ngx_conf_t *cf,
ngx_command_t *cmd, void *conf);
char *ngx_http_lua_init_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
+char *ngx_http_lua_exit_worker_by_lua_block(ngx_conf_t *cf,
+ ngx_command_t *cmd, void *conf);
+char *ngx_http_lua_exit_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
char *ngx_http_lua_code_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
#if defined(NDK) && NDK
diff --git a/src/ngx_http_lua_exitworkerby.c b/src/ngx_http_lua_exitworkerby.c
new file mode 100644
index 0000000000..01cff24856
--- /dev/null
+++ b/src/ngx_http_lua_exitworkerby.c
@@ -0,0 +1,101 @@
+
+/*
+ * Copyright (C) Yichun Zhang (agentzh)
+ */
+
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+#include "ddebug.h"
+
+
+#include "ngx_http_lua_exitworkerby.h"
+#include "ngx_http_lua_util.h"
+
+
+void
+ngx_http_lua_exit_worker(ngx_cycle_t *cycle)
+{
+ ngx_http_lua_main_conf_t *lmcf;
+ ngx_connection_t *c = NULL;
+ ngx_http_request_t *r = NULL;
+ ngx_http_lua_ctx_t *ctx;
+ ngx_http_conf_ctx_t *conf_ctx;
+
+ lmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_lua_module);
+ if (lmcf == NULL
+ || lmcf->exit_worker_handler == NULL
+ || lmcf->lua == NULL)
+ {
+ return;
+ }
+
+ conf_ctx = ((ngx_http_conf_ctx_t *) cycle->conf_ctx[ngx_http_module.index]);
+
+ c = ngx_http_lua_create_fake_connection(NULL);
+ if (c == NULL) {
+ goto failed;
+ }
+
+ c->log = ngx_cycle->log;
+
+ r = ngx_http_lua_create_fake_request(c);
+ if (r == NULL) {
+ goto failed;
+ }
+
+ r->main_conf = conf_ctx->main_conf;
+ r->srv_conf = conf_ctx->srv_conf;
+ r->loc_conf = conf_ctx->loc_conf;
+
+ ctx = ngx_http_lua_create_ctx(r);
+ if (ctx == NULL) {
+ goto failed;
+ }
+
+ ctx->context = NGX_HTTP_LUA_CONTEXT_EXIT_WORKER;
+ ctx->cur_co_ctx = NULL;
+
+ ngx_http_lua_set_req(lmcf->lua, r);
+
+ (void) lmcf->exit_worker_handler(cycle->log, lmcf, lmcf->lua);
+
+ ngx_destroy_pool(c->pool);
+ return;
+
+failed:
+
+ if (c) {
+ ngx_http_lua_close_fake_connection(c);
+ }
+
+ return;
+}
+
+
+ngx_int_t
+ngx_http_lua_exit_worker_by_inline(ngx_log_t *log,
+ ngx_http_lua_main_conf_t *lmcf, lua_State *L)
+{
+ int status;
+
+ status = luaL_loadbuffer(L, (char *) lmcf->exit_worker_src.data,
+ lmcf->exit_worker_src.len, "=exit_worker_by_lua")
+ || ngx_http_lua_do_call(log, L);
+
+ return ngx_http_lua_report(log, L, status, "exit_worker_by_lua");
+}
+
+
+ngx_int_t
+ngx_http_lua_exit_worker_by_file(ngx_log_t *log,
+ ngx_http_lua_main_conf_t *lmcf, lua_State *L)
+{
+ int status;
+
+ status = luaL_loadfile(L, (char *) lmcf->exit_worker_src.data)
+ || ngx_http_lua_do_call(log, L);
+
+ return ngx_http_lua_report(log, L, status, "exit_worker_by_file");
+}
diff --git a/src/ngx_http_lua_exitworkerby.h b/src/ngx_http_lua_exitworkerby.h
new file mode 100644
index 0000000000..38d28fe1a0
--- /dev/null
+++ b/src/ngx_http_lua_exitworkerby.h
@@ -0,0 +1,25 @@
+
+/*
+ * Copyright (C) Yichun Zhang (agentzh)
+ */
+
+
+#ifndef _NGX_HTTP_LUA_EXITWORKERBY_H_INCLUDE_
+#define _NGX_HTTP_LUA_EXITWORKERBY_H_INCLUDE_
+
+
+#include "ngx_http_lua_common.h"
+
+
+ngx_int_t ngx_http_lua_exit_worker_by_inline(ngx_log_t *log,
+ ngx_http_lua_main_conf_t *lmcf, lua_State *L);
+
+ngx_int_t ngx_http_lua_exit_worker_by_file(ngx_log_t *log,
+ ngx_http_lua_main_conf_t *lmcf, lua_State *L);
+
+void ngx_http_lua_exit_worker(ngx_cycle_t *cycle);
+
+
+#endif /* _NGX_HTTP_LUA_EXITWORKERBY_H_INCLUDE_ */
+
+/* vi:set ft=c ts=4 sw=4 et fdm=marker: */
diff --git a/src/ngx_http_lua_module.c b/src/ngx_http_lua_module.c
index 9d914e86e8..b816d57731 100644
--- a/src/ngx_http_lua_module.c
+++ b/src/ngx_http_lua_module.c
@@ -22,6 +22,7 @@
#include "ngx_http_lua_bodyfilterby.h"
#include "ngx_http_lua_initby.h"
#include "ngx_http_lua_initworkerby.h"
+#include "ngx_http_lua_exitworkerby.h"
#include "ngx_http_lua_probe.h"
#include "ngx_http_lua_semaphore.h"
#include "ngx_http_lua_balancer.h"
@@ -205,6 +206,20 @@ static ngx_command_t ngx_http_lua_cmds[] = {
0,
(void *) ngx_http_lua_init_worker_by_file },
+ { ngx_string("exit_worker_by_lua_block"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_http_lua_exit_worker_by_lua_block,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ (void *) ngx_http_lua_exit_worker_by_inline },
+
+ { ngx_string("exit_worker_by_lua_file"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_http_lua_exit_worker_by_lua,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ (void *) ngx_http_lua_exit_worker_by_file },
+
#if defined(NDK) && NDK
/* set_by_lua $res { inline Lua code } [$arg1 [$arg2 [...]]] */
{ ngx_string("set_by_lua_block"),
@@ -616,7 +631,7 @@ ngx_module_t ngx_http_lua_module = {
ngx_http_lua_init_worker, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
- NULL, /* exit process */
+ ngx_http_lua_exit_worker, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
diff --git a/src/ngx_http_lua_phase.c b/src/ngx_http_lua_phase.c
index 50c53110b8..28704456fe 100644
--- a/src/ngx_http_lua_phase.c
+++ b/src/ngx_http_lua_phase.c
@@ -92,6 +92,10 @@ ngx_http_lua_ngx_get_phase(lua_State *L)
lua_pushliteral(L, "ssl_session_fetch");
break;
+ case NGX_HTTP_LUA_CONTEXT_EXIT_WORKER:
+ lua_pushliteral(L, "exit_worker");
+ break;
+
default:
return luaL_error(L, "unknown phase: %#x", (int) ctx->context);
}
diff --git a/src/ngx_http_lua_util.h b/src/ngx_http_lua_util.h
index 2f995e0455..53230a56dd 100644
--- a/src/ngx_http_lua_util.h
+++ b/src/ngx_http_lua_util.h
@@ -85,6 +85,7 @@ extern char ngx_http_lua_headers_metatable_key;
: (c) == NGX_HTTP_LUA_CONTEXT_BODY_FILTER ? "body_filter_by_lua*" \
: (c) == NGX_HTTP_LUA_CONTEXT_TIMER ? "ngx.timer" \
: (c) == NGX_HTTP_LUA_CONTEXT_INIT_WORKER ? "init_worker_by_lua*" \
+ : (c) == NGX_HTTP_LUA_CONTEXT_EXIT_WORKER ? "exit_worker_by_lua*" \
: (c) == NGX_HTTP_LUA_CONTEXT_BALANCER ? "balancer_by_lua*" \
: (c) == NGX_HTTP_LUA_CONTEXT_SSL_CERT ? "ssl_certificate_by_lua*" \
: (c) == NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE ? \
diff --git a/t/089-phase.t b/t/089-phase.t
index 94b7619ec9..8788d3bd73 100644
--- a/t/089-phase.t
+++ b/t/089-phase.t
@@ -176,3 +176,25 @@ GET /lua
init_worker
--- no_error_log
[error]
+
+
+
+=== TEST 11: get_phase in exit_worker_by_lua
+--- http_config
+ exit_worker_by_lua_block {
+ local phase = ngx.get_phase()
+ ngx.log(ngx.NOTICE, phase)
+ }
+--- config
+ location /lua {
+ content_by_lua_block {
+ ngx.say("ok")
+ }
+ }
+--- request
+GET /lua
+--- response_body
+ok
+--- shutdown_error_log
+exit_worker
+
diff --git a/t/151-exit-worker.t b/t/151-exit-worker.t
new file mode 100644
index 0000000000..38eaf92327
--- /dev/null
+++ b/t/151-exit-worker.t
@@ -0,0 +1,132 @@
+# vim:set ft= ts=4 sw=4 et fdm=marker:
+
+use Test::Nginx::Socket::Lua;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 2);
+
+#log_level("warn");
+no_long_string();
+our $HtmlDir = html_dir;
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: simple exit_worker_by_lua_block
+--- http_config
+ exit_worker_by_lua_block {
+ ngx.log(ngx.NOTICE, "log from exit_worker_by_lua_block")
+ }
+--- config
+ location /t {
+ echo "ok";
+ }
+--- request
+GET /t
+--- response_body
+ok
+--- shutdown_error_log
+log from exit_worker_by_lua_block
+
+
+
+=== TEST 2: simple exit_worker_by_lua_file
+--- http_config
+ exit_worker_by_lua_file html/exit_worker.lua;
+--- config
+ location /t {
+ echo "ok";
+ }
+--- user_files
+>>> exit_worker.lua
+ngx.log(ngx.NOTICE, "log from exit_worker_by_lua_file")
+--- request
+GET /t
+--- response_body
+ok
+--- shutdown_error_log
+log from exit_worker_by_lua_file
+
+
+
+=== TEST 3: exit_worker_by_lua (require a global table)
+--- http_config eval
+ qq{lua_package_path '$::HtmlDir/?.lua;;';
+ exit_worker_by_lua_block {
+ foo = require("foo")
+ ngx.log(ngx.NOTICE, foo.bar)
+ }}
+--- config
+ location /t {
+ content_by_lua_block {
+ foo = require("foo")
+ foo.bar = "hello, world"
+ ngx.say("ok")
+ }
+ }
+--- user_files
+>>> foo.lua
+return {}
+--- request
+GET /t
+--- response_body
+ok
+--- shutdown_error_log
+hello, world
+
+
+
+=== TEST 4: exit_worker_by_lua single process ngx.timer not work
+--- http_config
+ exit_worker_by_lua_block {
+ local function bar()
+ ngx.log(ngx.ERR, "run the timer!"
+ end
+
+ local ok, err = ngx.timer.at(0, bar)
+ if not ok then
+ ngx.log(ngx.ERR, "failed to create timer: " .. err)
+ else
+ ngx.log(ngx.NOTICE, "success")
+ end
+ }
+--- config
+ location /t {
+ echo "ok";
+ }
+--- request
+GET /t
+--- response_body
+ok
+--- no_error_log
+
+
+
+=== TEST 5: exit_worker_by_lua use shdict
+--- http_config
+ lua_shared_dict dog 10m;
+ exit_worker_by_lua_block {
+ local dog = ngx.shared.dog
+ local val, err = dog:get("foo")
+ if not val then
+ ngx.log(ngx.ERR, "failed get shdict: " .. err)
+ else
+ ngx.log(ngx.NOTICE, "get val: " .. val)
+ end
+ }
+--- config
+ location /t {
+ content_by_lua_block {
+ local dog = ngx.shared.dog
+ dog:set("foo", 100)
+ ngx.say("ok")
+ }
+ }
+--- request
+GET /t
+--- response_body
+ok
+--- shutdown_error_log
+get val: 100