diff options
author | Oisin Canty <o.canty@f5.com> | 2021-07-02 12:57:55 +0000 |
---|---|---|
committer | Oisin Canty <o.canty@f5.com> | 2021-07-02 12:57:55 +0000 |
commit | 655e321075c0beebe14eba83deeac1ba4c9e0b29 (patch) | |
tree | cb3fb16282bd571e6636f19e77199518264fdadd | |
parent | 7d2bc04e391f9216fb4e0464cb43c9c438f7e034 (diff) | |
download | unit-655e321075c0beebe14eba83deeac1ba4c9e0b29.tar.gz unit-655e321075c0beebe14eba83deeac1ba4c9e0b29.tar.bz2 |
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.
-rw-r--r-- | docs/changes.xml | 6 | ||||
-rw-r--r-- | src/nxt_application.h | 1 | ||||
-rw-r--r-- | src/nxt_conf_validation.c | 3 | ||||
-rw-r--r-- | src/nxt_main_process.c | 5 | ||||
-rw-r--r-- | src/ruby/nxt_ruby.c | 143 |
5 files changed, 157 insertions, 1 deletions
diff --git a/docs/changes.xml b/docs/changes.xml index 1d77aea7..5265d529 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,12 @@ NGINX Unit updated to 1.25.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> +<change type="feature"> +<para> +process and thread lifecycle hooks in Ruby. +</para> +</change> + <change type="bugfix"> <para> the router process could crash on TLS connection open when multiple listeners diff --git a/src/nxt_application.h b/src/nxt_application.h index 45e7fa48..6fbdc4be 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -74,6 +74,7 @@ typedef struct { typedef struct { nxt_str_t script; uint32_t threads; + nxt_str_t hooks; } nxt_ruby_app_conf_t; diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 06ae2847..a16c955c 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -732,6 +732,9 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_ruby_members[] = { .name = nxt_string("threads"), .type = NXT_CONF_VLDT_INTEGER, .validator = nxt_conf_vldt_threads, + }, { + .name = nxt_string("hooks"), + .type = NXT_CONF_VLDT_STRING }, NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members) diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 00f336f6..10bd2518 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -271,6 +271,11 @@ static nxt_conf_map_t nxt_ruby_app_conf[] = { NXT_CONF_MAP_INT32, 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), + } }; diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index 10935528..522869b5 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -29,6 +29,11 @@ typedef struct { static nxt_int_t nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data); 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_require_rubygems(VALUE arg); @@ -78,6 +83,7 @@ static uint32_t compat[] = { NXT_VERNUM, NXT_DEBUG, }; +static VALUE nxt_ruby_hook_procs; static VALUE nxt_ruby_rackup; 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_port_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[] = { { 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_PORT"), &nxt_rb_server_port_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 }, }; @@ -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 nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data) { int state, rc; - VALUE res; + VALUE res, path; nxt_ruby_ctx_t ruby_ctx; nxt_unit_ctx_t *unit_ctx; 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_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); 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; } + 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, 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); + 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_ruby_ctx_done(&ruby_ctx); @@ -1120,6 +1240,10 @@ nxt_ruby_atexit(void) 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(); ruby_cleanup(0); @@ -1182,6 +1306,7 @@ nxt_ruby_thread_create_gvl(void *rctx) static VALUE nxt_ruby_thread_func(VALUE arg) { + int state; nxt_unit_ctx_t *ctx; nxt_ruby_ctx_t *rctx; @@ -1194,9 +1319,25 @@ nxt_ruby_thread_func(VALUE arg) 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, 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); fail: |