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