Ruby: process and thread lifecycle hooks.
This feature allows one to specify blocks of code that are called when certain
lifecycle events occur. A user configures a "hooks" property on the app
configuration that points to a script. This script will be evaluated on boot
and should contain blocks of code that will be called on specific events.
An example of configuration:
{
"type": "ruby",
"processes": 2,
"threads": 2,
"user": "vagrant",
"group": "vagrant",
"script": "config.ru",
"hooks": "hooks.rb",
"working_directory": "/home/vagrant/unit/rbhooks",
"environment": {
"GEM_HOME": "/home/vagrant/.ruby"
}
}
An example of a valid "hooks.rb" file follows:
File.write("./hooks.#{Process.pid}", "hooks evaluated")
on_worker_boot do
File.write("./worker_boot.#{Process.pid}", "worker booted")
end
on_thread_boot do
File.write("./thread_boot.#{Process.pid}.#{Thread.current.object_id}",
"thread booted")
end
on_thread_shutdown do
File.write("./thread_shutdown.#{Process.pid}.#{Thread.current.object_id}",
"thread shutdown")
end
on_worker_shutdown do
File.write("./worker_shutdown.#{Process.pid}", "worker shutdown")
end
This closes issue #535 on GitHub.
This commit is contained in:
@@ -31,6 +31,12 @@ NGINX Unit updated to 1.25.0.
|
|||||||
date="" time=""
|
date="" time=""
|
||||||
packager="Andrei Belov <defan@nginx.com>">
|
packager="Andrei Belov <defan@nginx.com>">
|
||||||
|
|
||||||
|
<change type="feature">
|
||||||
|
<para>
|
||||||
|
process and thread lifecycle hooks in Ruby.
|
||||||
|
</para>
|
||||||
|
</change>
|
||||||
|
|
||||||
<change type="bugfix">
|
<change type="bugfix">
|
||||||
<para>
|
<para>
|
||||||
the router process could crash on TLS connection open when multiple listeners
|
the router process could crash on TLS connection open when multiple listeners
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ typedef struct {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
nxt_str_t script;
|
nxt_str_t script;
|
||||||
uint32_t threads;
|
uint32_t threads;
|
||||||
|
nxt_str_t hooks;
|
||||||
} nxt_ruby_app_conf_t;
|
} nxt_ruby_app_conf_t;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -732,6 +732,9 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_ruby_members[] = {
|
|||||||
.name = nxt_string("threads"),
|
.name = nxt_string("threads"),
|
||||||
.type = NXT_CONF_VLDT_INTEGER,
|
.type = NXT_CONF_VLDT_INTEGER,
|
||||||
.validator = nxt_conf_vldt_threads,
|
.validator = nxt_conf_vldt_threads,
|
||||||
|
}, {
|
||||||
|
.name = nxt_string("hooks"),
|
||||||
|
.type = NXT_CONF_VLDT_STRING
|
||||||
},
|
},
|
||||||
|
|
||||||
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members)
|
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members)
|
||||||
|
|||||||
@@ -271,6 +271,11 @@ static nxt_conf_map_t nxt_ruby_app_conf[] = {
|
|||||||
NXT_CONF_MAP_INT32,
|
NXT_CONF_MAP_INT32,
|
||||||
offsetof(nxt_common_app_conf_t, u.ruby.threads),
|
offsetof(nxt_common_app_conf_t, u.ruby.threads),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
nxt_string("hooks"),
|
||||||
|
NXT_CONF_MAP_STR,
|
||||||
|
offsetof(nxt_common_app_conf_t, u.ruby.hooks),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ typedef struct {
|
|||||||
static nxt_int_t nxt_ruby_start(nxt_task_t *task,
|
static nxt_int_t nxt_ruby_start(nxt_task_t *task,
|
||||||
nxt_process_data_t *data);
|
nxt_process_data_t *data);
|
||||||
static VALUE nxt_ruby_init_basic(VALUE arg);
|
static VALUE nxt_ruby_init_basic(VALUE arg);
|
||||||
|
|
||||||
|
static VALUE nxt_ruby_hook_procs_load(VALUE path);
|
||||||
|
static VALUE nxt_ruby_hook_register(VALUE arg);
|
||||||
|
static VALUE nxt_ruby_hook_call(VALUE name);
|
||||||
|
|
||||||
static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init);
|
static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init);
|
||||||
|
|
||||||
static VALUE nxt_ruby_require_rubygems(VALUE arg);
|
static VALUE nxt_ruby_require_rubygems(VALUE arg);
|
||||||
@@ -78,6 +83,7 @@ static uint32_t compat[] = {
|
|||||||
NXT_VERNUM, NXT_DEBUG,
|
NXT_VERNUM, NXT_DEBUG,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static VALUE nxt_ruby_hook_procs;
|
||||||
static VALUE nxt_ruby_rackup;
|
static VALUE nxt_ruby_rackup;
|
||||||
static VALUE nxt_ruby_call;
|
static VALUE nxt_ruby_call;
|
||||||
|
|
||||||
@@ -115,6 +121,10 @@ static VALUE nxt_rb_server_addr_str;
|
|||||||
static VALUE nxt_rb_server_name_str;
|
static VALUE nxt_rb_server_name_str;
|
||||||
static VALUE nxt_rb_server_port_str;
|
static VALUE nxt_rb_server_port_str;
|
||||||
static VALUE nxt_rb_server_protocol_str;
|
static VALUE nxt_rb_server_protocol_str;
|
||||||
|
static VALUE nxt_rb_on_worker_boot;
|
||||||
|
static VALUE nxt_rb_on_worker_shutdown;
|
||||||
|
static VALUE nxt_rb_on_thread_boot;
|
||||||
|
static VALUE nxt_rb_on_thread_shutdown;
|
||||||
|
|
||||||
static nxt_ruby_string_t nxt_rb_strings[] = {
|
static nxt_ruby_string_t nxt_rb_strings[] = {
|
||||||
{ nxt_string("80"), &nxt_rb_80_str },
|
{ nxt_string("80"), &nxt_rb_80_str },
|
||||||
@@ -132,6 +142,10 @@ static nxt_ruby_string_t nxt_rb_strings[] = {
|
|||||||
{ nxt_string("SERVER_NAME"), &nxt_rb_server_name_str },
|
{ nxt_string("SERVER_NAME"), &nxt_rb_server_name_str },
|
||||||
{ nxt_string("SERVER_PORT"), &nxt_rb_server_port_str },
|
{ nxt_string("SERVER_PORT"), &nxt_rb_server_port_str },
|
||||||
{ nxt_string("SERVER_PROTOCOL"), &nxt_rb_server_protocol_str },
|
{ nxt_string("SERVER_PROTOCOL"), &nxt_rb_server_protocol_str },
|
||||||
|
{ nxt_string("on_worker_boot"), &nxt_rb_on_worker_boot },
|
||||||
|
{ nxt_string("on_worker_shutdown"), &nxt_rb_on_worker_shutdown },
|
||||||
|
{ nxt_string("on_thread_boot"), &nxt_rb_on_thread_boot },
|
||||||
|
{ nxt_string("on_thread_shutdown"), &nxt_rb_on_thread_shutdown },
|
||||||
{ nxt_null_string, NULL },
|
{ nxt_null_string, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,11 +197,70 @@ nxt_ruby_done_strings(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
nxt_ruby_hook_procs_load(VALUE path)
|
||||||
|
{
|
||||||
|
VALUE module, file, file_obj;
|
||||||
|
|
||||||
|
module = rb_define_module("Unit");
|
||||||
|
|
||||||
|
nxt_ruby_hook_procs = rb_hash_new();
|
||||||
|
|
||||||
|
rb_gc_register_address(&nxt_ruby_hook_procs);
|
||||||
|
|
||||||
|
rb_define_module_function(module, "on_worker_boot",
|
||||||
|
&nxt_ruby_hook_register, 0);
|
||||||
|
rb_define_module_function(module, "on_worker_shutdown",
|
||||||
|
&nxt_ruby_hook_register, 0);
|
||||||
|
rb_define_module_function(module, "on_thread_boot",
|
||||||
|
&nxt_ruby_hook_register, 0);
|
||||||
|
rb_define_module_function(module, "on_thread_shutdown",
|
||||||
|
&nxt_ruby_hook_register, 0);
|
||||||
|
|
||||||
|
file = rb_const_get(rb_cObject, rb_intern("File"));
|
||||||
|
file_obj = rb_funcall(file, rb_intern("read"), 1, path);
|
||||||
|
|
||||||
|
return rb_funcall(module, rb_intern("module_eval"), 3, file_obj, path,
|
||||||
|
INT2NUM(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
nxt_ruby_hook_register(VALUE arg)
|
||||||
|
{
|
||||||
|
VALUE kernel, callee, callee_str;
|
||||||
|
|
||||||
|
rb_need_block();
|
||||||
|
|
||||||
|
kernel = rb_const_get(rb_cObject, rb_intern("Kernel"));
|
||||||
|
callee = rb_funcall(kernel, rb_intern("__callee__"), 0);
|
||||||
|
callee_str = rb_funcall(callee, rb_intern("to_s"), 0);
|
||||||
|
|
||||||
|
rb_hash_aset(nxt_ruby_hook_procs, callee_str, rb_block_proc());
|
||||||
|
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
nxt_ruby_hook_call(VALUE name)
|
||||||
|
{
|
||||||
|
VALUE proc;
|
||||||
|
|
||||||
|
proc = rb_hash_lookup(nxt_ruby_hook_procs, name);
|
||||||
|
if (proc == Qnil) {
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rb_funcall(proc, rb_intern("call"), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static nxt_int_t
|
static nxt_int_t
|
||||||
nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data)
|
nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data)
|
||||||
{
|
{
|
||||||
int state, rc;
|
int state, rc;
|
||||||
VALUE res;
|
VALUE res, path;
|
||||||
nxt_ruby_ctx_t ruby_ctx;
|
nxt_ruby_ctx_t ruby_ctx;
|
||||||
nxt_unit_ctx_t *unit_ctx;
|
nxt_unit_ctx_t *unit_ctx;
|
||||||
nxt_unit_init_t ruby_unit_init;
|
nxt_unit_init_t ruby_unit_init;
|
||||||
@@ -231,6 +304,29 @@ nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
nxt_ruby_call = Qnil;
|
nxt_ruby_call = Qnil;
|
||||||
|
nxt_ruby_hook_procs = Qnil;
|
||||||
|
|
||||||
|
if (c->hooks.start != NULL) {
|
||||||
|
path = rb_str_new((const char *) c->hooks.start,
|
||||||
|
(long) c->hooks.length);
|
||||||
|
|
||||||
|
rb_protect(nxt_ruby_hook_procs_load, path, &state);
|
||||||
|
rb_str_free(path);
|
||||||
|
if (nxt_slow_path(state != 0)) {
|
||||||
|
nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
|
||||||
|
"Failed to setup hooks");
|
||||||
|
return NXT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nxt_ruby_hook_procs != Qnil) {
|
||||||
|
rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_boot, &state);
|
||||||
|
if (nxt_slow_path(state != 0)) {
|
||||||
|
nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
|
||||||
|
"Failed to call on_worker_boot()");
|
||||||
|
return NXT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init);
|
nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init);
|
||||||
if (nxt_slow_path(nxt_ruby_rackup == Qnil)) {
|
if (nxt_slow_path(nxt_ruby_rackup == Qnil)) {
|
||||||
@@ -274,11 +370,35 @@ nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data)
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nxt_ruby_hook_procs != Qnil) {
|
||||||
|
rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state);
|
||||||
|
if (nxt_slow_path(state != 0)) {
|
||||||
|
nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
|
||||||
|
"Failed to call on_thread_boot()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_unit_run, unit_ctx,
|
rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_unit_run, unit_ctx,
|
||||||
nxt_ruby_ubf, unit_ctx);
|
nxt_ruby_ubf, unit_ctx);
|
||||||
|
|
||||||
|
if (nxt_ruby_hook_procs != Qnil) {
|
||||||
|
rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state);
|
||||||
|
if (nxt_slow_path(state != 0)) {
|
||||||
|
nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
|
||||||
|
"Failed to call on_thread_shutdown()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nxt_ruby_join_threads(unit_ctx, c);
|
nxt_ruby_join_threads(unit_ctx, c);
|
||||||
|
|
||||||
|
if (nxt_ruby_hook_procs != Qnil) {
|
||||||
|
rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_shutdown, &state);
|
||||||
|
if (nxt_slow_path(state != 0)) {
|
||||||
|
nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
|
||||||
|
"Failed to call on_worker_shutdown()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nxt_unit_done(unit_ctx);
|
nxt_unit_done(unit_ctx);
|
||||||
|
|
||||||
nxt_ruby_ctx_done(&ruby_ctx);
|
nxt_ruby_ctx_done(&ruby_ctx);
|
||||||
@@ -1120,6 +1240,10 @@ nxt_ruby_atexit(void)
|
|||||||
rb_gc_unregister_address(&nxt_ruby_call);
|
rb_gc_unregister_address(&nxt_ruby_call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nxt_ruby_hook_procs != Qnil) {
|
||||||
|
rb_gc_unregister_address(&nxt_ruby_hook_procs);
|
||||||
|
}
|
||||||
|
|
||||||
nxt_ruby_done_strings();
|
nxt_ruby_done_strings();
|
||||||
|
|
||||||
ruby_cleanup(0);
|
ruby_cleanup(0);
|
||||||
@@ -1182,6 +1306,7 @@ nxt_ruby_thread_create_gvl(void *rctx)
|
|||||||
static VALUE
|
static VALUE
|
||||||
nxt_ruby_thread_func(VALUE arg)
|
nxt_ruby_thread_func(VALUE arg)
|
||||||
{
|
{
|
||||||
|
int state;
|
||||||
nxt_unit_ctx_t *ctx;
|
nxt_unit_ctx_t *ctx;
|
||||||
nxt_ruby_ctx_t *rctx;
|
nxt_ruby_ctx_t *rctx;
|
||||||
|
|
||||||
@@ -1194,9 +1319,25 @@ nxt_ruby_thread_func(VALUE arg)
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nxt_ruby_hook_procs != Qnil) {
|
||||||
|
rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state);
|
||||||
|
if (nxt_slow_path(state != 0)) {
|
||||||
|
nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
|
||||||
|
"Failed to call on_thread_boot()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(void) rb_thread_call_without_gvl(nxt_ruby_unit_run, ctx,
|
(void) rb_thread_call_without_gvl(nxt_ruby_unit_run, ctx,
|
||||||
nxt_ruby_ubf, ctx);
|
nxt_ruby_ubf, ctx);
|
||||||
|
|
||||||
|
if (nxt_ruby_hook_procs != Qnil) {
|
||||||
|
rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state);
|
||||||
|
if (nxt_slow_path(state != 0)) {
|
||||||
|
nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
|
||||||
|
"Failed to call on_thread_shutdown()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nxt_unit_done(ctx);
|
nxt_unit_done(ctx);
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
|||||||
Reference in New Issue
Block a user