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