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, 751 r->local_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