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