xref: /unit/src/ruby/nxt_ruby.c (revision 2164:52fbf5e84a42)
1 /*
2  * Copyright (C) Alexander Borisov
3  * Copyright (C) NGINX, Inc.
4  */
5 
6 #include <ruby/nxt_ruby.h>
7 
8 #include <nxt_unit.h>
9 #include <nxt_unit_request.h>
10 
11 #include <ruby/thread.h>
12 
13 #include NXT_RUBY_MOUNTS_H
14 
15 #include <locale.h>
16 
17 
18 #define NXT_RUBY_RACK_API_VERSION_MAJOR  1
19 #define NXT_RUBY_RACK_API_VERSION_MINOR  3
20 
21 
22 typedef struct {
23     nxt_task_t      *task;
24     nxt_str_t       *script;
25     nxt_ruby_ctx_t  *rctx;
26 } nxt_ruby_rack_init_t;
27 
28 
29 static nxt_int_t nxt_ruby_start(nxt_task_t *task,
30     nxt_process_data_t *data);
31 static VALUE nxt_ruby_init_basic(VALUE arg);
32 
33 static VALUE nxt_ruby_hook_procs_load(VALUE path);
34 static VALUE nxt_ruby_hook_register(VALUE arg);
35 static VALUE nxt_ruby_hook_call(VALUE name);
36 
37 static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init);
38 
39 static VALUE nxt_ruby_require_rubygems(VALUE arg);
40 static VALUE nxt_ruby_bundler_setup(VALUE arg);
41 static VALUE nxt_ruby_require_rack(VALUE arg);
42 static VALUE nxt_ruby_rack_parse_script(VALUE ctx);
43 static VALUE nxt_ruby_rack_env_create(VALUE arg);
44 static int nxt_ruby_init_io(nxt_ruby_ctx_t *rctx);
45 static void nxt_ruby_request_handler(nxt_unit_request_info_t *req);
46 static void *nxt_ruby_request_handler_gvl(void *req);
47 static int nxt_ruby_ready_handler(nxt_unit_ctx_t *ctx);
48 static void *nxt_ruby_thread_create_gvl(void *rctx);
49 static VALUE nxt_ruby_thread_func(VALUE arg);
50 static void *nxt_ruby_unit_run(void *ctx);
51 static void nxt_ruby_ubf(void *ctx);
52 static int nxt_ruby_init_threads(nxt_ruby_app_conf_t *c);
53 static void nxt_ruby_join_threads(nxt_unit_ctx_t *ctx,
54     nxt_ruby_app_conf_t *c);
55 
56 static VALUE nxt_ruby_rack_app_run(VALUE arg);
57 static int nxt_ruby_read_request(nxt_unit_request_info_t *req, VALUE hash_env);
58 nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, VALUE name,
59     nxt_unit_sptr_t *sptr, uint32_t len);
60 static nxt_int_t nxt_ruby_rack_result_status(nxt_unit_request_info_t *req,
61     VALUE result);
62 static int nxt_ruby_rack_result_headers(nxt_unit_request_info_t *req,
63     VALUE result, nxt_int_t status);
64 static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg);
65 static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg);
66 static int nxt_ruby_rack_result_body(nxt_unit_request_info_t *req,
67     VALUE result);
68 static int nxt_ruby_rack_result_body_file_write(nxt_unit_request_info_t *req,
69     VALUE filepath);
70 static void *nxt_ruby_response_write_cb(void *read_info);
71 static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg,
72     int argc, const VALUE *argv, VALUE blockarg);
73 static void *nxt_ruby_response_write(void *body);
74 
75 static void nxt_ruby_exception_log(nxt_unit_request_info_t *req,
76     uint32_t level, const char *desc);
77 
78 static void nxt_ruby_ctx_done(nxt_ruby_ctx_t *rctx);
79 static void nxt_ruby_atexit(void);
80 
81 
82 static uint32_t  compat[] = {
83     NXT_VERNUM, NXT_DEBUG,
84 };
85 
86 static VALUE  nxt_ruby_hook_procs;
87 static VALUE  nxt_ruby_rackup;
88 static VALUE  nxt_ruby_call;
89 
90 static uint32_t        nxt_ruby_threads;
91 static nxt_ruby_ctx_t  *nxt_ruby_ctxs;
92 
93 NXT_EXPORT nxt_app_module_t  nxt_app_module = {
94     sizeof(compat),
95     compat,
96     nxt_string("ruby"),
97     ruby_version,
98     nxt_ruby_mounts,
99     nxt_nitems(nxt_ruby_mounts),
100     NULL,
101     nxt_ruby_start,
102 };
103 
104 typedef struct {
105     nxt_str_t  string;
106     VALUE      *v;
107 } nxt_ruby_string_t;
108 
109 static VALUE  nxt_rb_80_str;
110 static VALUE  nxt_rb_content_length_str;
111 static VALUE  nxt_rb_content_type_str;
112 static VALUE  nxt_rb_http_str;
113 static VALUE  nxt_rb_https_str;
114 static VALUE  nxt_rb_path_info_str;
115 static VALUE  nxt_rb_query_string_str;
116 static VALUE  nxt_rb_rack_url_scheme_str;
117 static VALUE  nxt_rb_remote_addr_str;
118 static VALUE  nxt_rb_request_method_str;
119 static VALUE  nxt_rb_request_uri_str;
120 static VALUE  nxt_rb_server_addr_str;
121 static VALUE  nxt_rb_server_name_str;
122 static VALUE  nxt_rb_server_port_str;
123 static VALUE  nxt_rb_server_protocol_str;
124 static VALUE  nxt_rb_on_worker_boot;
125 static VALUE  nxt_rb_on_worker_shutdown;
126 static VALUE  nxt_rb_on_thread_boot;
127 static VALUE  nxt_rb_on_thread_shutdown;
128 
129 static nxt_ruby_string_t nxt_rb_strings[] = {
130     { nxt_string("80"), &nxt_rb_80_str },
131     { nxt_string("CONTENT_LENGTH"), &nxt_rb_content_length_str },
132     { nxt_string("CONTENT_TYPE"), &nxt_rb_content_type_str },
133     { nxt_string("http"), &nxt_rb_http_str },
134     { nxt_string("https"), &nxt_rb_https_str },
135     { nxt_string("PATH_INFO"), &nxt_rb_path_info_str },
136     { nxt_string("QUERY_STRING"), &nxt_rb_query_string_str },
137     { nxt_string("rack.url_scheme"), &nxt_rb_rack_url_scheme_str },
138     { nxt_string("REMOTE_ADDR"), &nxt_rb_remote_addr_str },
139     { nxt_string("REQUEST_METHOD"), &nxt_rb_request_method_str },
140     { nxt_string("REQUEST_URI"), &nxt_rb_request_uri_str },
141     { nxt_string("SERVER_ADDR"), &nxt_rb_server_addr_str },
142     { nxt_string("SERVER_NAME"), &nxt_rb_server_name_str },
143     { nxt_string("SERVER_PORT"), &nxt_rb_server_port_str },
144     { nxt_string("SERVER_PROTOCOL"), &nxt_rb_server_protocol_str },
145     { nxt_string("on_worker_boot"), &nxt_rb_on_worker_boot },
146     { nxt_string("on_worker_shutdown"), &nxt_rb_on_worker_shutdown },
147     { nxt_string("on_thread_boot"), &nxt_rb_on_thread_boot },
148     { nxt_string("on_thread_shutdown"), &nxt_rb_on_thread_shutdown },
149     { nxt_null_string, NULL },
150 };
151 
152 
153 static int
154 nxt_ruby_init_strings(void)
155 {
156     VALUE              v;
157     nxt_ruby_string_t  *pstr;
158 
159     pstr = nxt_rb_strings;
160 
161     while (pstr->string.start != NULL) {
162         v = rb_str_new_static((char *) pstr->string.start, pstr->string.length);
163 
164         if (nxt_slow_path(v == Qnil)) {
165             nxt_unit_alert(NULL, "Ruby: failed to create const string '%.*s'",
166                            (int) pstr->string.length,
167                            (char *) pstr->string.start);
168 
169             return NXT_UNIT_ERROR;
170         }
171 
172         *pstr->v = v;
173 
174         rb_gc_register_address(pstr->v);
175 
176         pstr++;
177     }
178 
179     return NXT_UNIT_OK;
180 }
181 
182 
183 static void
184 nxt_ruby_done_strings(void)
185 {
186     nxt_ruby_string_t  *pstr;
187 
188     pstr = nxt_rb_strings;
189 
190     while (pstr->string.start != NULL) {
191         rb_gc_unregister_address(pstr->v);
192 
193         *pstr->v = Qnil;
194 
195         pstr++;
196     }
197 }
198 
199 
200 static VALUE
201 nxt_ruby_hook_procs_load(VALUE path)
202 {
203     VALUE  module, file, file_obj;
204 
205     module = rb_define_module("Unit");
206 
207     nxt_ruby_hook_procs = rb_hash_new();
208 
209     rb_gc_register_address(&nxt_ruby_hook_procs);
210 
211     rb_define_module_function(module, "on_worker_boot",
212                               &nxt_ruby_hook_register, 0);
213     rb_define_module_function(module, "on_worker_shutdown",
214                               &nxt_ruby_hook_register, 0);
215     rb_define_module_function(module, "on_thread_boot",
216                               &nxt_ruby_hook_register, 0);
217     rb_define_module_function(module, "on_thread_shutdown",
218                               &nxt_ruby_hook_register, 0);
219 
220     file = rb_const_get(rb_cObject, rb_intern("File"));
221     file_obj = rb_funcall(file, rb_intern("read"), 1, path);
222 
223     return rb_funcall(module, rb_intern("module_eval"), 3, file_obj, path,
224                       INT2NUM(1));
225 }
226 
227 
228 static VALUE
229 nxt_ruby_hook_register(VALUE arg)
230 {
231     VALUE  kernel, callee, callee_str;
232 
233     rb_need_block();
234 
235     kernel = rb_const_get(rb_cObject, rb_intern("Kernel"));
236     callee = rb_funcall(kernel, rb_intern("__callee__"), 0);
237     callee_str = rb_funcall(callee, rb_intern("to_s"), 0);
238 
239     rb_hash_aset(nxt_ruby_hook_procs, callee_str, rb_block_proc());
240 
241     return Qnil;
242 }
243 
244 
245 static VALUE
246 nxt_ruby_hook_call(VALUE name)
247 {
248     VALUE  proc;
249 
250     proc = rb_hash_lookup(nxt_ruby_hook_procs, name);
251     if (proc == Qnil) {
252         return Qnil;
253     }
254 
255     return rb_funcall(proc, rb_intern("call"), 0);
256 }
257 
258 
259 static nxt_int_t
260 nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data)
261 {
262     int                    state, rc;
263     VALUE                  res, path;
264     nxt_ruby_ctx_t         ruby_ctx;
265     nxt_unit_ctx_t         *unit_ctx;
266     nxt_unit_init_t        ruby_unit_init;
267     nxt_ruby_app_conf_t    *c;
268     nxt_ruby_rack_init_t   rack_init;
269     nxt_common_app_conf_t  *conf;
270 
271     static char  *argv[2] = { (char *) "NGINX_Unit", (char *) "-e0" };
272 
273     conf = data->app;
274     c = &conf->u.ruby;
275 
276     nxt_ruby_threads = c->threads;
277 
278     setlocale(LC_CTYPE, "");
279 
280     RUBY_INIT_STACK
281     ruby_init();
282     ruby_options(2, argv);
283     ruby_script("NGINX_Unit");
284 
285     ruby_ctx.env = Qnil;
286     ruby_ctx.io_input = Qnil;
287     ruby_ctx.io_error = Qnil;
288     ruby_ctx.thread = Qnil;
289     ruby_ctx.ctx = NULL;
290     ruby_ctx.req = NULL;
291 
292     rack_init.task = task;
293     rack_init.script = &c->script;
294     rack_init.rctx = &ruby_ctx;
295 
296     nxt_ruby_init_strings();
297 
298     res = rb_protect(nxt_ruby_init_basic,
299                      (VALUE) (uintptr_t) &rack_init, &state);
300     if (nxt_slow_path(res == Qnil || state != 0)) {
301         nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
302                                "Failed to init basic variables");
303         return NXT_ERROR;
304     }
305 
306     nxt_ruby_call = Qnil;
307     nxt_ruby_hook_procs = Qnil;
308 
309     if (c->hooks.start != NULL) {
310         path = rb_str_new((const char *) c->hooks.start,
311                           (long) c->hooks.length);
312 
313         rb_protect(nxt_ruby_hook_procs_load, path, &state);
314         rb_str_free(path);
315         if (nxt_slow_path(state != 0)) {
316             nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
317                                    "Failed to setup hooks");
318             return NXT_ERROR;
319         }
320     }
321 
322     if (nxt_ruby_hook_procs != Qnil) {
323         rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_boot, &state);
324         if (nxt_slow_path(state != 0)) {
325             nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
326                                    "Failed to call on_worker_boot()");
327             return NXT_ERROR;
328         }
329     }
330 
331     nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init);
332     if (nxt_slow_path(nxt_ruby_rackup == Qnil)) {
333         return NXT_ERROR;
334     }
335 
336     rb_gc_register_address(&nxt_ruby_rackup);
337 
338     nxt_ruby_call = rb_intern("call");
339     if (nxt_slow_path(nxt_ruby_call == Qnil)) {
340         nxt_alert(task, "Ruby: Unable to find rack entry point");
341 
342         goto fail;
343     }
344 
345     rb_gc_register_address(&nxt_ruby_call);
346 
347     ruby_ctx.env = rb_protect(nxt_ruby_rack_env_create,
348                               (VALUE) (uintptr_t) &ruby_ctx, &state);
349     if (nxt_slow_path(ruby_ctx.env == Qnil || state != 0)) {
350         nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
351                                "Failed to create 'environ' variable");
352         goto fail;
353     }
354 
355     rc = nxt_ruby_init_threads(c);
356     if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
357         goto fail;
358     }
359 
360     nxt_unit_default_init(task, &ruby_unit_init, conf);
361 
362     ruby_unit_init.callbacks.request_handler = nxt_ruby_request_handler;
363     ruby_unit_init.callbacks.ready_handler = nxt_ruby_ready_handler;
364     ruby_unit_init.data = c;
365     ruby_unit_init.ctx_data = &ruby_ctx;
366 
367     unit_ctx = nxt_unit_init(&ruby_unit_init);
368     if (nxt_slow_path(unit_ctx == NULL)) {
369         goto fail;
370     }
371 
372     if (nxt_ruby_hook_procs != Qnil) {
373         rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state);
374         if (nxt_slow_path(state != 0)) {
375             nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
376                                    "Failed to call on_thread_boot()");
377         }
378     }
379 
380     rc = (intptr_t) rb_thread_call_without_gvl2(nxt_ruby_unit_run, unit_ctx,
381                                                 nxt_ruby_ubf, unit_ctx);
382 
383     if (nxt_ruby_hook_procs != Qnil) {
384         rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state);
385         if (nxt_slow_path(state != 0)) {
386             nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
387                                    "Failed to call on_thread_shutdown()");
388         }
389     }
390 
391     nxt_ruby_join_threads(unit_ctx, c);
392 
393     if (nxt_ruby_hook_procs != Qnil) {
394         rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_shutdown, &state);
395         if (nxt_slow_path(state != 0)) {
396             nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
397                                    "Failed to call on_worker_shutdown()");
398         }
399     }
400 
401     nxt_unit_done(unit_ctx);
402 
403     nxt_ruby_ctx_done(&ruby_ctx);
404 
405     nxt_ruby_atexit();
406 
407     exit(rc);
408 
409     return NXT_OK;
410 
411 fail:
412 
413     nxt_ruby_join_threads(NULL, c);
414 
415     nxt_ruby_ctx_done(&ruby_ctx);
416 
417     nxt_ruby_atexit();
418 
419     return NXT_ERROR;
420 }
421 
422 
423 static VALUE
424 nxt_ruby_init_basic(VALUE arg)
425 {
426     int                   state;
427     nxt_ruby_rack_init_t  *rack_init;
428 
429     rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) arg;
430 
431     state = rb_enc_find_index("encdb");
432     if (nxt_slow_path(state == 0)) {
433         nxt_alert(rack_init->task,
434                   "Ruby: Failed to find encoding index 'encdb'");
435 
436         return Qnil;
437     }
438 
439     rb_funcall(rb_cObject, rb_intern("require"), 1,
440                rb_str_new2("enc/trans/transdb"));
441 
442     return arg;
443 }
444 
445 
446 static VALUE
447 nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init)
448 {
449     int    state;
450     VALUE  rackup, err;
451 
452     rb_protect(nxt_ruby_require_rubygems, Qnil, &state);
453     if (nxt_slow_path(state != 0)) {
454         nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
455                                "Failed to require 'rubygems' package");
456         return Qnil;
457     }
458 
459     rb_protect(nxt_ruby_bundler_setup, Qnil, &state);
460     if (state != 0) {
461         err = rb_errinfo();
462 
463         if (rb_obj_is_kind_of(err, rb_eLoadError) == Qfalse) {
464             nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
465                                    "Failed to require 'bundler/setup' package");
466             return Qnil;
467         }
468 
469         rb_set_errinfo(Qnil);
470     }
471 
472     rb_protect(nxt_ruby_require_rack, Qnil, &state);
473     if (nxt_slow_path(state != 0)) {
474         nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
475                                "Failed to require 'rack' package");
476         return Qnil;
477     }
478 
479     rackup = rb_protect(nxt_ruby_rack_parse_script,
480                         (VALUE) (uintptr_t) rack_init, &state);
481     if (nxt_slow_path(TYPE(rackup) != T_ARRAY || state != 0)) {
482         nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
483                                "Failed to parse rack script");
484         return Qnil;
485     }
486 
487     if (nxt_slow_path(RARRAY_LEN(rackup) < 1)) {
488         nxt_alert(rack_init->task, "Ruby: Invalid rack config file");
489         return Qnil;
490     }
491 
492     return RARRAY_PTR(rackup)[0];
493 }
494 
495 
496 static VALUE
497 nxt_ruby_require_rubygems(VALUE arg)
498 {
499     return rb_funcall(rb_cObject, rb_intern("require"), 1,
500                       rb_str_new2("rubygems"));
501 }
502 
503 
504 static VALUE
505 nxt_ruby_bundler_setup(VALUE arg)
506 {
507     return rb_funcall(rb_cObject, rb_intern("require"), 1,
508                       rb_str_new2("bundler/setup"));
509 }
510 
511 
512 static VALUE
513 nxt_ruby_require_rack(VALUE arg)
514 {
515     return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack"));
516 }
517 
518 
519 static VALUE
520 nxt_ruby_rack_parse_script(VALUE ctx)
521 {
522     VALUE                 script, res, rack, builder;
523     nxt_ruby_rack_init_t  *rack_init;
524 
525     rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) ctx;
526 
527     rack = rb_const_get(rb_cObject, rb_intern("Rack"));
528     builder = rb_const_get(rack, rb_intern("Builder"));
529 
530     script = rb_str_new((const char *) rack_init->script->start,
531                         (long) rack_init->script->length);
532 
533     res = rb_funcall(builder, rb_intern("parse_file"), 1, script);
534 
535     rb_str_free(script);
536 
537     return res;
538 }
539 
540 
541 static VALUE
542 nxt_ruby_rack_env_create(VALUE arg)
543 {
544     int             rc;
545     VALUE           hash_env, version;
546     nxt_ruby_ctx_t  *rctx;
547 
548     rctx = (nxt_ruby_ctx_t *) (uintptr_t) arg;
549 
550     rc = nxt_ruby_init_io(rctx);
551     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
552         return Qnil;
553     }
554 
555     hash_env = rb_hash_new();
556 
557     rb_hash_aset(hash_env, rb_str_new2("SERVER_SOFTWARE"),
558                  rb_str_new((const char *) nxt_server.start,
559                             (long) nxt_server.length));
560 
561     version = rb_ary_new();
562 
563     rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MAJOR));
564     rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR));
565 
566     rb_hash_aset(hash_env, rb_str_new2("SCRIPT_NAME"), rb_str_new("", 0));
567     rb_hash_aset(hash_env, rb_str_new2("rack.version"), version);
568     rb_hash_aset(hash_env, rb_str_new2("rack.input"), rctx->io_input);
569     rb_hash_aset(hash_env, rb_str_new2("rack.errors"), rctx->io_error);
570     rb_hash_aset(hash_env, rb_str_new2("rack.multithread"),
571                  nxt_ruby_threads > 1 ? Qtrue : Qfalse);
572     rb_hash_aset(hash_env, rb_str_new2("rack.multiprocess"), Qtrue);
573     rb_hash_aset(hash_env, rb_str_new2("rack.run_once"), Qfalse);
574     rb_hash_aset(hash_env, rb_str_new2("rack.hijack?"), Qfalse);
575     rb_hash_aset(hash_env, rb_str_new2("rack.hijack"), Qnil);
576     rb_hash_aset(hash_env, rb_str_new2("rack.hijack_io"), Qnil);
577 
578     rctx->env = hash_env;
579 
580     rb_gc_register_address(&rctx->env);
581 
582     return hash_env;
583 }
584 
585 
586 static int
587 nxt_ruby_init_io(nxt_ruby_ctx_t *rctx)
588 {
589     VALUE  io_input, io_error;
590 
591     io_input = nxt_ruby_stream_io_input_init();
592 
593     rctx->io_input = rb_funcall(io_input, rb_intern("new"), 1,
594                                    (VALUE) (uintptr_t) rctx);
595     if (nxt_slow_path(rctx->io_input == Qnil)) {
596         nxt_unit_alert(NULL,
597                        "Ruby: Failed to create environment 'rack.input' var");
598 
599         return NXT_UNIT_ERROR;
600     }
601 
602     rb_gc_register_address(&rctx->io_input);
603 
604     io_error = nxt_ruby_stream_io_error_init();
605 
606     rctx->io_error = rb_funcall(io_error, rb_intern("new"), 1,
607                                    (VALUE) (uintptr_t) rctx);
608     if (nxt_slow_path(rctx->io_error == Qnil)) {
609         nxt_unit_alert(NULL,
610                        "Ruby: Failed to create environment 'rack.error' var");
611 
612         return NXT_UNIT_ERROR;
613     }
614 
615     rb_gc_register_address(&rctx->io_error);
616 
617     return NXT_UNIT_OK;
618 }
619 
620 
621 static void
622 nxt_ruby_request_handler(nxt_unit_request_info_t *req)
623 {
624     (void) rb_thread_call_with_gvl(nxt_ruby_request_handler_gvl, req);
625 }
626 
627 
628 static void *
629 nxt_ruby_request_handler_gvl(void *data)
630 {
631     int                      state;
632     VALUE                    res;
633     nxt_ruby_ctx_t           *rctx;
634     nxt_unit_request_info_t  *req;
635 
636     req = data;
637 
638     rctx = req->ctx->data;
639     rctx->req = req;
640 
641     res = rb_protect(nxt_ruby_rack_app_run, (VALUE) (uintptr_t) req, &state);
642     if (nxt_slow_path(res == Qnil || state != 0)) {
643         nxt_ruby_exception_log(req, NXT_LOG_ERR,
644                                "Failed to run ruby script");
645 
646         nxt_unit_request_done(req, NXT_UNIT_ERROR);
647 
648     } else {
649         nxt_unit_request_done(req, NXT_UNIT_OK);
650     }
651 
652     rctx->req = NULL;
653 
654     return NULL;
655 }
656 
657 
658 static VALUE
659 nxt_ruby_rack_app_run(VALUE arg)
660 {
661     int                      rc;
662     VALUE                    env, result;
663     nxt_int_t                status;
664     nxt_ruby_ctx_t           *rctx;
665     nxt_unit_request_info_t  *req;
666 
667     req = (nxt_unit_request_info_t *) arg;
668 
669     rctx = req->ctx->data;
670 
671     env = rb_hash_dup(rctx->env);
672 
673     rc = nxt_ruby_read_request(req, env);
674     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
675         nxt_unit_req_alert(req,
676                            "Ruby: Failed to process incoming request");
677 
678         goto fail;
679     }
680 
681     result = rb_funcall(nxt_ruby_rackup, nxt_ruby_call, 1, env);
682     if (nxt_slow_path(TYPE(result) != T_ARRAY)) {
683         nxt_unit_req_error(req,
684                            "Ruby: Invalid response format from application");
685 
686         goto fail;
687     }
688 
689     if (nxt_slow_path(RARRAY_LEN(result) != 3)) {
690         nxt_unit_req_error(req,
691                            "Ruby: Invalid response format from application. "
692                            "Need 3 entries [Status, Headers, Body]");
693 
694         goto fail;
695     }
696 
697     status = nxt_ruby_rack_result_status(req, result);
698     if (nxt_slow_path(status < 0)) {
699         nxt_unit_req_error(req,
700                            "Ruby: Invalid response status from application.");
701 
702         goto fail;
703     }
704 
705     rc = nxt_ruby_rack_result_headers(req, result, status);
706     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
707         goto fail;
708     }
709 
710     rc = nxt_ruby_rack_result_body(req, result);
711     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
712         goto fail;
713     }
714 
715     rb_hash_delete(env, rb_obj_id(env));
716 
717     return result;
718 
719 fail:
720 
721     rb_hash_delete(env, rb_obj_id(env));
722 
723     return Qnil;
724 }
725 
726 
727 static int
728 nxt_ruby_read_request(nxt_unit_request_info_t *req, VALUE hash_env)
729 {
730     VALUE               name;
731     uint32_t            i;
732     nxt_unit_field_t    *f;
733     nxt_unit_request_t  *r;
734 
735     r = req->request;
736 
737     nxt_ruby_add_sptr(hash_env, nxt_rb_request_method_str, &r->method,
738                       r->method_length);
739     nxt_ruby_add_sptr(hash_env, nxt_rb_request_uri_str, &r->target,
740                       r->target_length);
741     nxt_ruby_add_sptr(hash_env, nxt_rb_path_info_str, &r->path, r->path_length);
742     nxt_ruby_add_sptr(hash_env, nxt_rb_query_string_str, &r->query,
743                       r->query_length);
744     nxt_ruby_add_sptr(hash_env, nxt_rb_server_protocol_str, &r->version,
745                       r->version_length);
746     nxt_ruby_add_sptr(hash_env, nxt_rb_remote_addr_str, &r->remote,
747                       r->remote_length);
748     nxt_ruby_add_sptr(hash_env, nxt_rb_server_addr_str, &r->local,
749                       r->local_length);
750     nxt_ruby_add_sptr(hash_env, nxt_rb_server_name_str, &r->server_name,
751                       r->server_name_length);
752 
753     rb_hash_aset(hash_env, nxt_rb_server_port_str, nxt_rb_80_str);
754 
755     rb_hash_aset(hash_env, nxt_rb_rack_url_scheme_str,
756                  r->tls ? nxt_rb_https_str : nxt_rb_http_str);
757 
758     for (i = 0; i < r->fields_count; i++) {
759         f = r->fields + i;
760 
761         name = rb_str_new(nxt_unit_sptr_get(&f->name), f->name_length);
762 
763         nxt_ruby_add_sptr(hash_env, name, &f->value, f->value_length);
764     }
765 
766     if (r->content_length_field != NXT_UNIT_NONE_FIELD) {
767         f = r->fields + r->content_length_field;
768 
769         nxt_ruby_add_sptr(hash_env, nxt_rb_content_length_str,
770                           &f->value, f->value_length);
771     }
772 
773     if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
774         f = r->fields + r->content_type_field;
775 
776         nxt_ruby_add_sptr(hash_env, nxt_rb_content_type_str,
777                           &f->value, f->value_length);
778     }
779 
780     return NXT_UNIT_OK;
781 }
782 
783 
784 nxt_inline void
785 nxt_ruby_add_sptr(VALUE hash_env, VALUE name,
786     nxt_unit_sptr_t *sptr, uint32_t len)
787 {
788     char  *str;
789 
790     str = nxt_unit_sptr_get(sptr);
791 
792     rb_hash_aset(hash_env, name, rb_str_new(str, len));
793 }
794 
795 
796 static nxt_int_t
797 nxt_ruby_rack_result_status(nxt_unit_request_info_t *req, VALUE result)
798 {
799     VALUE   status;
800 
801     status = rb_ary_entry(result, 0);
802 
803     if (TYPE(status) == T_FIXNUM) {
804         return FIX2INT(status);
805     }
806 
807     if (TYPE(status) == T_STRING) {
808         return nxt_int_parse((u_char *) RSTRING_PTR(status),
809                              RSTRING_LEN(status));
810     }
811 
812     nxt_unit_req_error(req, "Ruby: Invalid response 'status' "
813                        "format from application");
814 
815     return -2;
816 }
817 
818 
819 typedef struct {
820     int                      rc;
821     uint32_t                 fields;
822     uint32_t                 size;
823     nxt_unit_request_info_t  *req;
824 } nxt_ruby_headers_info_t;
825 
826 
827 static int
828 nxt_ruby_rack_result_headers(nxt_unit_request_info_t *req, VALUE result,
829     nxt_int_t status)
830 {
831     int                      rc;
832     VALUE                    headers;
833     nxt_ruby_headers_info_t  headers_info;
834 
835     headers = rb_ary_entry(result, 1);
836     if (nxt_slow_path(TYPE(headers) != T_HASH)) {
837         nxt_unit_req_error(req,
838                            "Ruby: Invalid response 'headers' format from "
839                            "application");
840 
841         return NXT_UNIT_ERROR;
842     }
843 
844     rc = NXT_UNIT_OK;
845 
846     headers_info.rc = NXT_UNIT_OK;
847     headers_info.fields = 0;
848     headers_info.size = 0;
849     headers_info.req = req;
850 
851     rb_hash_foreach(headers, nxt_ruby_hash_info,
852                     (VALUE) (uintptr_t) &headers_info);
853     if (nxt_slow_path(headers_info.rc != NXT_UNIT_OK)) {
854         return headers_info.rc;
855     }
856 
857     rc = nxt_unit_response_init(req, status,
858                                 headers_info.fields, headers_info.size);
859     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
860         return rc;
861     }
862 
863     rb_hash_foreach(headers, nxt_ruby_hash_add,
864                     (VALUE) (uintptr_t) &headers_info);
865 
866     return rc;
867 }
868 
869 
870 static int
871 nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg)
872 {
873     const char               *value, *value_end, *pos;
874     nxt_ruby_headers_info_t  *headers_info;
875 
876     headers_info = (void *) (uintptr_t) arg;
877 
878     if (nxt_slow_path(TYPE(r_key) != T_STRING)) {
879         nxt_unit_req_error(headers_info->req,
880                            "Ruby: Wrong header entry 'key' from application");
881 
882         goto fail;
883     }
884 
885     if (nxt_slow_path(TYPE(r_value) != T_STRING)) {
886         nxt_unit_req_error(headers_info->req,
887                            "Ruby: Wrong header entry 'value' from application");
888 
889         goto fail;
890     }
891 
892     value = RSTRING_PTR(r_value);
893     value_end = value + RSTRING_LEN(r_value);
894 
895     pos = value;
896 
897     for ( ;; ) {
898         pos = strchr(pos, '\n');
899 
900         if (pos == NULL) {
901             break;
902         }
903 
904         headers_info->fields++;
905         headers_info->size += RSTRING_LEN(r_key) + (pos - value);
906 
907         pos++;
908         value = pos;
909     }
910 
911     if (value <= value_end) {
912         headers_info->fields++;
913         headers_info->size += RSTRING_LEN(r_key) + (value_end - value);
914     }
915 
916     return ST_CONTINUE;
917 
918 fail:
919 
920     headers_info->rc = NXT_UNIT_ERROR;
921 
922     return ST_STOP;
923 }
924 
925 
926 static int
927 nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg)
928 {
929     int                      *rc;
930     uint32_t                 key_len;
931     const char               *value, *value_end, *pos;
932     nxt_ruby_headers_info_t  *headers_info;
933 
934     headers_info = (void *) (uintptr_t) arg;
935     rc = &headers_info->rc;
936 
937     value = RSTRING_PTR(r_value);
938     value_end = value + RSTRING_LEN(r_value);
939 
940     key_len = RSTRING_LEN(r_key);
941 
942     pos = value;
943 
944     for ( ;; ) {
945         pos = strchr(pos, '\n');
946 
947         if (pos == NULL) {
948             break;
949         }
950 
951         *rc = nxt_unit_response_add_field(headers_info->req,
952                                           RSTRING_PTR(r_key), key_len,
953                                           value, pos - value);
954         if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
955             goto fail;
956         }
957 
958         pos++;
959         value = pos;
960     }
961 
962     if (value <= value_end) {
963         *rc = nxt_unit_response_add_field(headers_info->req,
964                                           RSTRING_PTR(r_key), key_len,
965                                           value, value_end - value);
966         if (nxt_slow_path(*rc != NXT_UNIT_OK)) {
967             goto fail;
968         }
969     }
970 
971     return ST_CONTINUE;
972 
973 fail:
974 
975     *rc = NXT_UNIT_ERROR;
976 
977     return ST_STOP;
978 }
979 
980 
981 static int
982 nxt_ruby_rack_result_body(nxt_unit_request_info_t *req, VALUE result)
983 {
984     int    rc;
985     VALUE  fn, body;
986 
987     body = rb_ary_entry(result, 2);
988 
989     if (rb_respond_to(body, rb_intern("to_path"))) {
990 
991         fn = rb_funcall(body, rb_intern("to_path"), 0);
992         if (nxt_slow_path(TYPE(fn) != T_STRING)) {
993             nxt_unit_req_error(req,
994                                "Ruby: Failed to get 'body' file path from "
995                                "application");
996 
997             return NXT_UNIT_ERROR;
998         }
999 
1000         rc = nxt_ruby_rack_result_body_file_write(req, fn);
1001         if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1002             return rc;
1003         }
1004 
1005     } else if (rb_respond_to(body, rb_intern("each"))) {
1006         rb_block_call(body, rb_intern("each"), 0, 0,
1007                       nxt_ruby_rack_result_body_each, (VALUE) (uintptr_t) req);
1008 
1009     } else {
1010         nxt_unit_req_error(req,
1011                            "Ruby: Invalid response 'body' format "
1012                            "from application");
1013 
1014         return NXT_UNIT_ERROR;
1015     }
1016 
1017     if (rb_respond_to(body, rb_intern("close"))) {
1018         rb_funcall(body, rb_intern("close"), 0);
1019     }
1020 
1021     return NXT_UNIT_OK;
1022 }
1023 
1024 
1025 typedef struct {
1026     int    fd;
1027     off_t  pos;
1028     off_t  rest;
1029 } nxt_ruby_rack_file_t;
1030 
1031 
1032 static ssize_t
1033 nxt_ruby_rack_file_read(nxt_unit_read_info_t *read_info, void *dst, size_t size)
1034 {
1035     ssize_t               res;
1036     nxt_ruby_rack_file_t  *file;
1037 
1038     file = read_info->data;
1039 
1040     size = nxt_min(size, (size_t) file->rest);
1041 
1042     res = pread(file->fd, dst, size, file->pos);
1043 
1044     if (res >= 0) {
1045         file->pos += res;
1046         file->rest -= res;
1047 
1048         if (size > (size_t) res) {
1049             file->rest = 0;
1050         }
1051     }
1052 
1053     read_info->eof = file->rest == 0;
1054 
1055     return res;
1056 }
1057 
1058 
1059 typedef struct {
1060     nxt_unit_read_info_t     read_info;
1061     nxt_unit_request_info_t  *req;
1062 } nxt_ruby_read_info_t;
1063 
1064 
1065 static int
1066 nxt_ruby_rack_result_body_file_write(nxt_unit_request_info_t *req,
1067     VALUE filepath)
1068 {
1069     int                   fd, rc;
1070     struct stat           finfo;
1071     nxt_ruby_rack_file_t  ruby_file;
1072     nxt_ruby_read_info_t  ri;
1073 
1074     fd = open(RSTRING_PTR(filepath), O_RDONLY, 0);
1075     if (nxt_slow_path(fd == -1)) {
1076         nxt_unit_req_error(req,
1077                            "Ruby: Failed to open content file \"%s\": %s (%d)",
1078                            RSTRING_PTR(filepath), strerror(errno), errno);
1079 
1080         return NXT_UNIT_ERROR;
1081     }
1082 
1083     rc = fstat(fd, &finfo);
1084     if (nxt_slow_path(rc == -1)) {
1085         nxt_unit_req_error(req,
1086                            "Ruby: Content file fstat(\"%s\") failed: %s (%d)",
1087                            RSTRING_PTR(filepath), strerror(errno), errno);
1088 
1089         close(fd);
1090 
1091         return NXT_UNIT_ERROR;
1092     }
1093 
1094     ruby_file.fd = fd;
1095     ruby_file.pos = 0;
1096     ruby_file.rest = finfo.st_size;
1097 
1098     ri.read_info.read = nxt_ruby_rack_file_read;
1099     ri.read_info.eof = ruby_file.rest == 0;
1100     ri.read_info.buf_size = ruby_file.rest;
1101     ri.read_info.data = &ruby_file;
1102     ri.req = req;
1103 
1104     rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_response_write_cb,
1105                                                &ri,
1106                                                nxt_ruby_ubf,
1107                                                req->ctx);
1108 
1109     close(fd);
1110 
1111     return rc;
1112 }
1113 
1114 
1115 static void *
1116 nxt_ruby_response_write_cb(void *data)
1117 {
1118     int                   rc;
1119     nxt_ruby_read_info_t  *ri;
1120 
1121     ri = data;
1122 
1123     rc = nxt_unit_response_write_cb(ri->req, &ri->read_info);
1124     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1125         nxt_unit_req_error(ri->req, "Ruby: Failed to write content file.");
1126     }
1127 
1128     return (void *) (intptr_t) rc;
1129 }
1130 
1131 
1132 typedef struct {
1133     VALUE                    body;
1134     nxt_unit_request_info_t  *req;
1135 } nxt_ruby_write_info_t;
1136 
1137 
1138 static VALUE
1139 nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, int argc,
1140     const VALUE *argv, VALUE blockarg)
1141 {
1142     nxt_ruby_write_info_t  wi;
1143 
1144     if (TYPE(body) != T_STRING) {
1145         return Qnil;
1146     }
1147 
1148     wi.body = body;
1149     wi.req = (void *) (uintptr_t) arg;
1150 
1151     (void) rb_thread_call_without_gvl(nxt_ruby_response_write,
1152                                       (void *) (uintptr_t) &wi,
1153                                       nxt_ruby_ubf, wi.req->ctx);
1154 
1155     return Qnil;
1156 }
1157 
1158 
1159 static void *
1160 nxt_ruby_response_write(void *data)
1161 {
1162     int                    rc;
1163     nxt_ruby_write_info_t  *wi;
1164 
1165     wi = data;
1166 
1167     rc = nxt_unit_response_write(wi->req, RSTRING_PTR(wi->body),
1168                                  RSTRING_LEN(wi->body));
1169     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1170         nxt_unit_req_error(wi->req,
1171                            "Ruby: Failed to write 'body' from application");
1172     }
1173 
1174     return (void *) (intptr_t) rc;
1175 }
1176 
1177 
1178 static void
1179 nxt_ruby_exception_log(nxt_unit_request_info_t *req, uint32_t level,
1180     const char *desc)
1181 {
1182     int    i;
1183     VALUE  err, ary, eclass, msg;
1184 
1185     nxt_unit_req_log(req, level, "Ruby: %s", desc);
1186 
1187     err = rb_errinfo();
1188     if (nxt_slow_path(err == Qnil)) {
1189         return;
1190     }
1191 
1192     eclass = rb_class_name(rb_class_of(err));
1193 
1194     msg = rb_funcall(err, rb_intern("message"), 0);
1195     ary = rb_funcall(err, rb_intern("backtrace"), 0);
1196 
1197     if (RARRAY_LEN(ary) == 0) {
1198         nxt_unit_req_log(req, level, "Ruby: %s (%s)", RSTRING_PTR(msg),
1199                          RSTRING_PTR(eclass));
1200 
1201         return;
1202     }
1203 
1204     nxt_unit_req_log(req, level, "Ruby: %s: %s (%s)",
1205                      RSTRING_PTR(RARRAY_PTR(ary)[0]),
1206                      RSTRING_PTR(msg), RSTRING_PTR(eclass));
1207 
1208     for (i = 1; i < RARRAY_LEN(ary); i++) {
1209         nxt_unit_req_log(req, level, "from %s",
1210                          RSTRING_PTR(RARRAY_PTR(ary)[i]));
1211     }
1212 }
1213 
1214 
1215 static void
1216 nxt_ruby_ctx_done(nxt_ruby_ctx_t *rctx)
1217 {
1218     if (rctx->io_input != Qnil) {
1219         rb_gc_unregister_address(&rctx->io_input);
1220     }
1221 
1222     if (rctx->io_error != Qnil) {
1223         rb_gc_unregister_address(&rctx->io_error);
1224     }
1225 
1226     if (rctx->env != Qnil) {
1227         rb_gc_unregister_address(&rctx->env);
1228     }
1229 }
1230 
1231 
1232 static void
1233 nxt_ruby_atexit(void)
1234 {
1235     if (nxt_ruby_rackup != Qnil) {
1236         rb_gc_unregister_address(&nxt_ruby_rackup);
1237     }
1238 
1239     if (nxt_ruby_call != Qnil) {
1240         rb_gc_unregister_address(&nxt_ruby_call);
1241     }
1242 
1243     if (nxt_ruby_hook_procs != Qnil) {
1244         rb_gc_unregister_address(&nxt_ruby_hook_procs);
1245     }
1246 
1247     nxt_ruby_done_strings();
1248 
1249     ruby_cleanup(0);
1250 }
1251 
1252 
1253 static int
1254 nxt_ruby_ready_handler(nxt_unit_ctx_t *ctx)
1255 {
1256     VALUE                res;
1257     uint32_t             i;
1258     nxt_ruby_ctx_t       *rctx;
1259     nxt_ruby_app_conf_t  *c;
1260 
1261     c = ctx->unit->data;
1262 
1263     if (c->threads <= 1) {
1264         return NXT_UNIT_OK;
1265     }
1266 
1267     for (i = 0; i < c->threads - 1; i++) {
1268         rctx = &nxt_ruby_ctxs[i];
1269 
1270         rctx->ctx = ctx;
1271 
1272         res = (VALUE) rb_thread_call_with_gvl(nxt_ruby_thread_create_gvl, rctx);
1273 
1274         if (nxt_fast_path(res != Qnil)) {
1275             nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1));
1276 
1277             rctx->thread = res;
1278 
1279         } else {
1280             nxt_unit_alert(ctx, "thread #%d create failed", (int) (i + 1));
1281 
1282             return NXT_UNIT_ERROR;
1283         }
1284     }
1285 
1286     return NXT_UNIT_OK;
1287 }
1288 
1289 
1290 static void *
1291 nxt_ruby_thread_create_gvl(void *rctx)
1292 {
1293     VALUE  res;
1294 
1295     res = rb_thread_create(RUBY_METHOD_FUNC(nxt_ruby_thread_func), rctx);
1296 
1297     return (void *) (uintptr_t) res;
1298 }
1299 
1300 
1301 static VALUE
1302 nxt_ruby_thread_func(VALUE arg)
1303 {
1304     int             state;
1305     nxt_unit_ctx_t  *ctx;
1306     nxt_ruby_ctx_t  *rctx;
1307 
1308     rctx = (nxt_ruby_ctx_t *) (uintptr_t) arg;
1309 
1310     nxt_unit_debug(rctx->ctx, "worker thread start");
1311 
1312     ctx = nxt_unit_ctx_alloc(rctx->ctx, rctx);
1313     if (nxt_slow_path(ctx == NULL)) {
1314         goto fail;
1315     }
1316 
1317     if (nxt_ruby_hook_procs != Qnil) {
1318         rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state);
1319         if (nxt_slow_path(state != 0)) {
1320             nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
1321                                    "Failed to call on_thread_boot()");
1322         }
1323     }
1324 
1325     (void) rb_thread_call_without_gvl(nxt_ruby_unit_run, ctx,
1326                                       nxt_ruby_ubf, ctx);
1327 
1328     if (nxt_ruby_hook_procs != Qnil) {
1329         rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state);
1330         if (nxt_slow_path(state != 0)) {
1331             nxt_ruby_exception_log(NULL, NXT_LOG_ERR,
1332                                    "Failed to call on_thread_shutdown()");
1333         }
1334     }
1335 
1336     nxt_unit_done(ctx);
1337 
1338 fail:
1339 
1340     nxt_unit_debug(NULL, "worker thread end");
1341 
1342     return Qnil;
1343 }
1344 
1345 
1346 static void *
1347 nxt_ruby_unit_run(void *ctx)
1348 {
1349     return (void *) (intptr_t) nxt_unit_run(ctx);
1350 }
1351 
1352 
1353 static void
1354 nxt_ruby_ubf(void *ctx)
1355 {
1356     nxt_unit_warn(ctx, "Ruby: UBF");
1357 }
1358 
1359 
1360 static int
1361 nxt_ruby_init_threads(nxt_ruby_app_conf_t *c)
1362 {
1363     int             state;
1364     uint32_t        i;
1365     nxt_ruby_ctx_t  *rctx;
1366 
1367     if (c->threads <= 1) {
1368         return NXT_UNIT_OK;
1369     }
1370 
1371     nxt_ruby_ctxs = nxt_unit_malloc(NULL, sizeof(nxt_ruby_ctx_t)
1372                                           * (c->threads - 1));
1373     if (nxt_slow_path(nxt_ruby_ctxs == NULL)) {
1374         nxt_unit_alert(NULL, "Failed to allocate run contexts array");
1375 
1376         return NXT_UNIT_ERROR;
1377     }
1378 
1379     for (i = 0; i < c->threads - 1; i++) {
1380         rctx = &nxt_ruby_ctxs[i];
1381 
1382         rctx->env = Qnil;
1383         rctx->io_input = Qnil;
1384         rctx->io_error = Qnil;
1385         rctx->thread = Qnil;
1386     }
1387 
1388     for (i = 0; i < c->threads - 1; i++) {
1389         rctx = &nxt_ruby_ctxs[i];
1390 
1391         rctx->env = rb_protect(nxt_ruby_rack_env_create,
1392                                (VALUE) (uintptr_t) rctx, &state);
1393         if (nxt_slow_path(rctx->env == Qnil || state != 0)) {
1394             nxt_ruby_exception_log(NULL, NXT_LOG_ALERT,
1395                                    "Failed to create 'environ' variable");
1396             return NXT_UNIT_ERROR;
1397         }
1398     }
1399 
1400     return NXT_UNIT_OK;
1401 }
1402 
1403 
1404 static void
1405 nxt_ruby_join_threads(nxt_unit_ctx_t *ctx, nxt_ruby_app_conf_t *c)
1406 {
1407     uint32_t        i;
1408     nxt_ruby_ctx_t  *rctx;
1409 
1410     if (nxt_ruby_ctxs == NULL) {
1411         return;
1412     }
1413 
1414     for (i = 0; i < c->threads - 1; i++) {
1415         rctx = &nxt_ruby_ctxs[i];
1416 
1417         if (rctx->thread != Qnil) {
1418             rb_funcall(rctx->thread, rb_intern("join"), 0);
1419 
1420             nxt_unit_debug(ctx, "thread #%d joined", (int) (i + 1));
1421 
1422         } else {
1423             nxt_unit_debug(ctx, "thread #%d not started", (int) (i + 1));
1424         }
1425     }
1426 
1427     for (i = 0; i < c->threads - 1; i++) {
1428         nxt_ruby_ctx_done(&nxt_ruby_ctxs[i]);
1429     }
1430 
1431     nxt_unit_free(ctx, nxt_ruby_ctxs);
1432 }
1433