Skip to content

Commit

Permalink
Ruby: process and thread lifecycle hooks.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ocanty committed Jul 2, 2021
1 parent 7d2bc04 commit 655e321
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 1 deletion.
6 changes: 6 additions & 0 deletions docs/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/nxt_application.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ typedef struct {
typedef struct {
nxt_str_t script;
uint32_t threads;
nxt_str_t hooks;
} nxt_ruby_app_conf_t;


Expand Down
3 changes: 3 additions & 0 deletions src/nxt_conf_validation.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions src/nxt_main_process.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
};


Expand Down
143 changes: 142 additions & 1 deletion src/ruby/nxt_ruby.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 },
Expand All @@ -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 },
};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand All @@ -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:
Expand Down

0 comments on commit 655e321

Please sign in to comment.