1 2 /* 3 * Copyright (C) NGINX, Inc. 4 */ 5 6 7 #include <Python.h> 8 9 #include <nxt_main.h> 10 #include <nxt_router.h> 11 #include <nxt_unit.h> 12 13 #include <python/nxt_python.h> 14 15 #include NXT_PYTHON_MOUNTS_H 16 17 18 typedef struct { 19 pthread_t thread; 20 nxt_unit_ctx_t *ctx; 21 void *ctx_data; 22 } nxt_py_thread_info_t; 23 24 25 static nxt_int_t nxt_python_start(nxt_task_t *task, 26 nxt_process_data_t *data); 27 static nxt_int_t nxt_python_set_target(nxt_task_t *task, 28 nxt_python_target_t *target, nxt_conf_value_t *conf); 29 static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value); 30 static int nxt_python_init_threads(nxt_python_app_conf_t *c); 31 static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx); 32 static void *nxt_python_thread_func(void *main_ctx); 33 static void nxt_python_join_threads(nxt_unit_ctx_t *ctx, 34 nxt_python_app_conf_t *c); 35 static void nxt_python_atexit(void); 36 37 static uint32_t compat[] = { 38 NXT_VERNUM, NXT_DEBUG, 39 }; 40 41 42 NXT_EXPORT nxt_app_module_t nxt_app_module = { 43 sizeof(compat), 44 compat, 45 nxt_string("python"), 46 PY_VERSION, 47 nxt_python_mounts, 48 nxt_nitems(nxt_python_mounts), 49 NULL, 50 nxt_python_start, 51 }; 52 53 static PyObject *nxt_py_stderr_flush; 54 nxt_python_targets_t *nxt_py_targets; 55 56 #if PY_MAJOR_VERSION == 3 57 static wchar_t *nxt_py_home; 58 #else 59 static char *nxt_py_home; 60 #endif 61 62 static pthread_attr_t *nxt_py_thread_attr; 63 static nxt_py_thread_info_t *nxt_py_threads; 64 static nxt_python_proto_t nxt_py_proto; 65 66 67 static nxt_int_t 68 nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) 69 { 70 int rc; 71 size_t len, size; 72 uint32_t next; 73 PyObject *obj, *module; 74 nxt_str_t proto, probe_proto, name; 75 nxt_int_t ret, n, i; 76 nxt_unit_ctx_t *unit_ctx; 77 nxt_unit_init_t python_init; 78 nxt_conf_value_t *cv; 79 nxt_python_targets_t *targets; 80 nxt_common_app_conf_t *app_conf; 81 nxt_python_app_conf_t *c; 82 #if PY_MAJOR_VERSION == 3 83 char *path; 84 nxt_int_t pep405; 85 86 static const char pyvenv[] = "/pyvenv.cfg"; 87 static const char bin_python[] = "/bin/python"; 88 #endif 89 90 static const nxt_str_t wsgi = nxt_string("wsgi"); 91 static const nxt_str_t asgi = nxt_string("asgi"); 92 93 app_conf = data->app; 94 c = &app_conf->u.python; 95 96 if (c->home != NULL) { 97 len = nxt_strlen(c->home); 98 99 #if PY_MAJOR_VERSION == 3 100 101 path = nxt_malloc(len + sizeof(pyvenv)); 102 if (nxt_slow_path(path == NULL)) { 103 nxt_alert(task, "Failed to allocate memory"); 104 return NXT_ERROR; 105 } 106 107 nxt_memcpy(path, c->home, len); 108 nxt_memcpy(path + len, pyvenv, sizeof(pyvenv)); 109 110 pep405 = (access(path, R_OK) == 0); 111 112 nxt_free(path); 113 114 if (pep405) { 115 size = (len + sizeof(bin_python)) * sizeof(wchar_t); 116 117 } else { 118 size = (len + 1) * sizeof(wchar_t); 119 } 120 121 nxt_py_home = nxt_malloc(size); 122 if (nxt_slow_path(nxt_py_home == NULL)) { 123 nxt_alert(task, "Failed to allocate memory"); 124 return NXT_ERROR; 125 } 126 127 if (pep405) { 128 mbstowcs(nxt_py_home, c->home, len); 129 mbstowcs(nxt_py_home + len, bin_python, sizeof(bin_python)); 130 Py_SetProgramName(nxt_py_home); 131 132 } else { 133 mbstowcs(nxt_py_home, c->home, len + 1); 134 Py_SetPythonHome(nxt_py_home); 135 } 136 137 #else 138 nxt_py_home = nxt_malloc(len + 1); 139 if (nxt_slow_path(nxt_py_home == NULL)) { 140 nxt_alert(task, "Failed to allocate memory"); 141 return NXT_ERROR; 142 } 143 144 nxt_memcpy(nxt_py_home, c->home, len + 1); 145 Py_SetPythonHome(nxt_py_home); 146 #endif 147 } 148 149 Py_InitializeEx(0); 150 151 #if PY_VERSION_HEX < NXT_PYTHON_VER(3, 7) 152 if (c->threads > 1) { 153 PyEval_InitThreads(); 154 } 155 #endif 156 157 module = NULL; 158 obj = NULL; 159 160 python_init.ctx_data = NULL; 161 162 obj = PySys_GetObject((char *) "stderr"); 163 if (nxt_slow_path(obj == NULL)) { 164 nxt_alert(task, "Python failed to get \"sys.stderr\" object"); 165 goto fail; 166 } 167 168 nxt_py_stderr_flush = PyObject_GetAttrString(obj, "flush"); 169 170 /* obj is a Borrowed reference. */ 171 obj = NULL; 172 173 if (nxt_slow_path(nxt_py_stderr_flush == NULL)) { 174 nxt_alert(task, "Python failed to get \"flush\" attribute of " 175 "\"sys.stderr\" object"); 176 goto fail; 177 } 178 179 if (nxt_slow_path(nxt_python_set_path(task, c->path) != NXT_OK)) { 180 goto fail; 181 } 182 183 obj = Py_BuildValue("[s]", "unit"); 184 if (nxt_slow_path(obj == NULL)) { 185 nxt_alert(task, "Python failed to create the \"sys.argv\" list"); 186 goto fail; 187 } 188 189 if (nxt_slow_path(PySys_SetObject((char *) "argv", obj) != 0)) { 190 nxt_alert(task, "Python failed to set the \"sys.argv\" list"); 191 goto fail; 192 } 193 194 Py_CLEAR(obj); 195 196 n = (c->targets != NULL ? nxt_conf_object_members_count(c->targets) : 1); 197 198 size = sizeof(nxt_python_targets_t) + n * sizeof(nxt_python_target_t); 199 200 targets = nxt_unit_malloc(NULL, size); 201 if (nxt_slow_path(targets == NULL)) { 202 nxt_alert(task, "Could not allocate targets"); 203 goto fail; 204 } 205 206 memset(targets, 0, size); 207 208 targets->count = n; 209 nxt_py_targets = targets; 210 211 if (c->targets != NULL) { 212 next = 0; 213 214 for (i = 0; /* void */; i++) { 215 cv = nxt_conf_next_object_member(c->targets, &name, &next); 216 if (cv == NULL) { 217 break; 218 } 219 220 ret = nxt_python_set_target(task, &targets->target[i], cv); 221 if (nxt_slow_path(ret != NXT_OK)) { 222 goto fail; 223 } 224 } 225 226 } else { 227 ret = nxt_python_set_target(task, &targets->target[0], app_conf->self); 228 if (nxt_slow_path(ret != NXT_OK)) { 229 goto fail; 230 } 231 } 232 233 nxt_unit_default_init(task, &python_init); 234 235 python_init.data = c; 236 python_init.shm_limit = data->app->shm_limit; 237 python_init.callbacks.ready_handler = nxt_python_ready_handler; 238 239 proto = c->protocol; 240 241 if (proto.length == 0) { 242 proto = nxt_python_asgi_check(targets->target[0].application) 243 ? asgi : wsgi; 244 245 for (i = 1; i < targets->count; i++) { 246 probe_proto = nxt_python_asgi_check(targets->target[i].application) 247 ? asgi : wsgi; 248 if (probe_proto.start != proto.start) { 249 nxt_alert(task, "A mix of ASGI & WSGI targets is forbidden, " 250 "specify protocol in config if incorrect"); 251 goto fail; 252 } 253 } 254 } 255 256 if (nxt_strstr_eq(&proto, &asgi)) { 257 rc = nxt_python_asgi_init(&python_init, &nxt_py_proto); 258 259 } else { 260 rc = nxt_python_wsgi_init(&python_init, &nxt_py_proto); 261 } 262 263 if (nxt_slow_path(rc == NXT_UNIT_ERROR)) { 264 goto fail; 265 } 266 267 rc = nxt_py_proto.ctx_data_alloc(&python_init.ctx_data, 1); 268 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 269 goto fail; 270 } 271 272 rc = nxt_python_init_threads(c); 273 if (nxt_slow_path(rc == NXT_UNIT_ERROR)) { 274 goto fail; 275 } 276 277 if (nxt_py_proto.startup != NULL) { 278 if (nxt_py_proto.startup(python_init.ctx_data) != NXT_UNIT_OK) { 279 goto fail; 280 } 281 } 282 283 unit_ctx = nxt_unit_init(&python_init); 284 if (nxt_slow_path(unit_ctx == NULL)) { 285 goto fail; 286 } 287 288 rc = nxt_py_proto.run(unit_ctx); 289 290 nxt_python_join_threads(unit_ctx, c); 291 292 nxt_unit_done(unit_ctx); 293 294 nxt_py_proto.ctx_data_free(python_init.ctx_data); 295 296 nxt_python_atexit(); 297 298 exit(rc); 299 300 return NXT_OK; 301 302 fail: 303 304 nxt_python_join_threads(NULL, c); 305 306 if (python_init.ctx_data != NULL) { 307 nxt_py_proto.ctx_data_free(python_init.ctx_data); 308 } 309 310 Py_XDECREF(obj); 311 Py_XDECREF(module); 312 313 nxt_python_atexit(); 314 315 return NXT_ERROR; 316 } 317 318 319 static nxt_int_t 320 nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target, 321 nxt_conf_value_t *conf) 322 { 323 char *callable, *module_name; 324 PyObject *module, *obj; 325 nxt_str_t str; 326 nxt_conf_value_t *value; 327 328 static nxt_str_t module_str = nxt_string("module"); 329 static nxt_str_t callable_str = nxt_string("callable"); 330 331 module = obj = NULL; 332 333 value = nxt_conf_get_object_member(conf, &module_str, NULL); 334 if (nxt_slow_path(value == NULL)) { 335 goto fail; 336 } 337 338 nxt_conf_get_string(value, &str); 339 340 module_name = nxt_alloca(str.length + 1); 341 nxt_memcpy(module_name, str.start, str.length); 342 module_name[str.length] = '\0'; 343 344 module = PyImport_ImportModule(module_name); 345 if (nxt_slow_path(module == NULL)) { 346 nxt_alert(task, "Python failed to import module \"%s\"", module_name); 347 nxt_python_print_exception(); 348 goto fail; 349 } 350 351 value = nxt_conf_get_object_member(conf, &callable_str, NULL); 352 if (value == NULL) { 353 callable = nxt_alloca(12); 354 nxt_memcpy(callable, "application", 12); 355 356 } else { 357 nxt_conf_get_string(value, &str); 358 359 callable = nxt_alloca(str.length + 1); 360 nxt_memcpy(callable, str.start, str.length); 361 callable[str.length] = '\0'; 362 } 363 364 obj = PyDict_GetItemString(PyModule_GetDict(module), callable); 365 if (nxt_slow_path(obj == NULL)) { 366 nxt_alert(task, "Python failed to get \"%s\" from module \"%s\"", 367 callable, module_name); 368 goto fail; 369 } 370 371 if (nxt_slow_path(PyCallable_Check(obj) == 0)) { 372 nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object", 373 callable, module_name); 374 goto fail; 375 } 376 377 target->application = obj; 378 obj = NULL; 379 380 Py_INCREF(target->application); 381 Py_CLEAR(module); 382 383 return NXT_OK; 384 385 fail: 386 387 Py_XDECREF(obj); 388 Py_XDECREF(module); 389 390 return NXT_ERROR; 391 } 392 393 394 static nxt_int_t 395 nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value) 396 { 397 int ret; 398 PyObject *path, *sys; 399 nxt_str_t str; 400 nxt_uint_t n; 401 nxt_conf_value_t *array; 402 403 if (value == NULL) { 404 return NXT_OK; 405 } 406 407 sys = PySys_GetObject((char *) "path"); 408 if (nxt_slow_path(sys == NULL)) { 409 nxt_alert(task, "Python failed to get \"sys.path\" list"); 410 return NXT_ERROR; 411 } 412 413 /* sys is a Borrowed reference. */ 414 415 if (nxt_conf_type(value) == NXT_CONF_STRING) { 416 n = 0; 417 goto value_is_string; 418 } 419 420 /* NXT_CONF_ARRAY */ 421 array = value; 422 423 n = nxt_conf_array_elements_count(array); 424 425 while (n != 0) { 426 n--; 427 428 /* 429 * Insertion in front of existing paths starting from the last element 430 * to preserve original order while giving priority to the values 431 * specified in the "path" option. 432 */ 433 434 value = nxt_conf_get_array_element(array, n); 435 436 value_is_string: 437 438 nxt_conf_get_string(value, &str); 439 440 path = PyString_FromStringAndSize((char *) str.start, str.length); 441 if (nxt_slow_path(path == NULL)) { 442 nxt_alert(task, "Python failed to create string object \"%V\"", 443 &str); 444 return NXT_ERROR; 445 } 446 447 ret = PyList_Insert(sys, 0, path); 448 449 Py_DECREF(path); 450 451 if (nxt_slow_path(ret != 0)) { 452 nxt_alert(task, "Python failed to insert \"%V\" into \"sys.path\"", 453 &str); 454 return NXT_ERROR; 455 } 456 } 457 458 return NXT_OK; 459 } 460 461 462 static int 463 nxt_python_init_threads(nxt_python_app_conf_t *c) 464 { 465 int res; 466 uint32_t i; 467 nxt_py_thread_info_t *ti; 468 static pthread_attr_t attr; 469 470 if (c->threads <= 1) { 471 return NXT_UNIT_OK; 472 } 473 474 if (c->thread_stack_size > 0) { 475 res = pthread_attr_init(&attr); 476 if (nxt_slow_path(res != 0)) { 477 nxt_unit_alert(NULL, "thread attr init failed: %s (%d)", 478 strerror(res), res); 479 480 return NXT_UNIT_ERROR; 481 } 482 483 res = pthread_attr_setstacksize(&attr, c->thread_stack_size); 484 if (nxt_slow_path(res != 0)) { 485 nxt_unit_alert(NULL, "thread attr set stack size failed: %s (%d)", 486 strerror(res), res); 487 488 return NXT_UNIT_ERROR; 489 } 490 491 nxt_py_thread_attr = &attr; 492 } 493 494 nxt_py_threads = nxt_unit_malloc(NULL, sizeof(nxt_py_thread_info_t) 495 * (c->threads - 1)); 496 if (nxt_slow_path(nxt_py_threads == NULL)) { 497 nxt_unit_alert(NULL, "Failed to allocate thread info array"); 498 499 return NXT_UNIT_ERROR; 500 } 501 502 memset(nxt_py_threads, 0, sizeof(nxt_py_thread_info_t) * (c->threads - 1)); 503 504 for (i = 0; i < c->threads - 1; i++) { 505 ti = &nxt_py_threads[i]; 506 507 res = nxt_py_proto.ctx_data_alloc(&ti->ctx_data, 0); 508 if (nxt_slow_path(res != NXT_UNIT_OK)) { 509 return NXT_UNIT_ERROR; 510 } 511 } 512 513 return NXT_UNIT_OK; 514 } 515 516 517 static int 518 nxt_python_ready_handler(nxt_unit_ctx_t *ctx) 519 { 520 int res; 521 uint32_t i; 522 nxt_py_thread_info_t *ti; 523 nxt_python_app_conf_t *c; 524 525 if (nxt_py_proto.ready != NULL) { 526 res = nxt_py_proto.ready(ctx); 527 if (nxt_slow_path(res != NXT_UNIT_OK)) { 528 return NXT_UNIT_ERROR; 529 } 530 } 531 532 /* Worker thread context. */ 533 if (!nxt_unit_is_main_ctx(ctx)) { 534 return NXT_UNIT_OK; 535 } 536 537 c = ctx->unit->data; 538 539 if (c->threads <= 1) { 540 return NXT_UNIT_OK; 541 } 542 543 for (i = 0; i < c->threads - 1; i++) { 544 ti = &nxt_py_threads[i]; 545 546 ti->ctx = ctx; 547 548 res = pthread_create(&ti->thread, nxt_py_thread_attr, 549 nxt_python_thread_func, ti); 550 551 if (nxt_fast_path(res == 0)) { 552 nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1)); 553 554 } else { 555 nxt_unit_alert(ctx, "thread #%d create failed: %s (%d)", 556 (int) (i + 1), strerror(res), res); 557 } 558 } 559 560 return NXT_UNIT_OK; 561 } 562 563 564 static void * 565 nxt_python_thread_func(void *data) 566 { 567 nxt_unit_ctx_t *ctx; 568 PyGILState_STATE gstate; 569 nxt_py_thread_info_t *ti; 570 571 ti = data; 572 573 nxt_unit_debug(ti->ctx, "worker thread #%d start", 574 (int) (ti - nxt_py_threads + 1)); 575 576 gstate = PyGILState_Ensure(); 577 578 if (nxt_py_proto.startup != NULL) { 579 if (nxt_py_proto.startup(ti->ctx_data) != NXT_UNIT_OK) { 580 goto fail; 581 } 582 } 583 584 ctx = nxt_unit_ctx_alloc(ti->ctx, ti->ctx_data); 585 if (nxt_slow_path(ctx == NULL)) { 586 goto fail; 587 } 588 589 (void) nxt_py_proto.run(ctx); 590 591 nxt_unit_done(ctx); 592 593 fail: 594 595 PyGILState_Release(gstate); 596 597 nxt_unit_debug(NULL, "worker thread #%d end", 598 (int) (ti - nxt_py_threads + 1)); 599 600 return NULL; 601 } 602 603 604 static void 605 nxt_python_join_threads(nxt_unit_ctx_t *ctx, nxt_python_app_conf_t *c) 606 { 607 int res; 608 uint32_t i; 609 PyThreadState *thread_state; 610 nxt_py_thread_info_t *ti; 611 612 if (nxt_py_threads == NULL) { 613 return; 614 } 615 616 thread_state = PyEval_SaveThread(); 617 618 for (i = 0; i < c->threads - 1; i++) { 619 ti = &nxt_py_threads[i]; 620 621 if ((uintptr_t) ti->thread == 0) { 622 continue; 623 } 624 625 res = pthread_join(ti->thread, NULL); 626 627 if (nxt_fast_path(res == 0)) { 628 nxt_unit_debug(ctx, "thread #%d joined", (int) (i + 1)); 629 630 } else { 631 nxt_unit_alert(ctx, "thread #%d join failed: %s (%d)", 632 (int) (i + 1), strerror(res), res); 633 } 634 } 635 636 PyEval_RestoreThread(thread_state); 637 638 for (i = 0; i < c->threads - 1; i++) { 639 ti = &nxt_py_threads[i]; 640 641 if (ti->ctx_data != NULL) { 642 nxt_py_proto.ctx_data_free(ti->ctx_data); 643 } 644 } 645 646 nxt_unit_free(NULL, nxt_py_threads); 647 } 648 649 650 int 651 nxt_python_init_strings(nxt_python_string_t *pstr) 652 { 653 PyObject *obj; 654 655 while (pstr->string.start != NULL) { 656 obj = PyString_FromStringAndSize((char *) pstr->string.start, 657 pstr->string.length); 658 if (nxt_slow_path(obj == NULL)) { 659 return NXT_UNIT_ERROR; 660 } 661 662 PyUnicode_InternInPlace(&obj); 663 664 *pstr->object_p = obj; 665 666 pstr++; 667 } 668 669 return NXT_UNIT_OK; 670 } 671 672 673 void 674 nxt_python_done_strings(nxt_python_string_t *pstr) 675 { 676 PyObject *obj; 677 678 while (pstr->string.start != NULL) { 679 obj = *pstr->object_p; 680 681 Py_XDECREF(obj); 682 *pstr->object_p = NULL; 683 684 pstr++; 685 } 686 } 687 688 689 static void 690 nxt_python_atexit(void) 691 { 692 nxt_int_t i; 693 694 if (nxt_py_proto.done != NULL) { 695 nxt_py_proto.done(); 696 } 697 698 Py_XDECREF(nxt_py_stderr_flush); 699 700 if (nxt_py_targets != NULL) { 701 for (i = 0; i < nxt_py_targets->count; i++) { 702 Py_XDECREF(nxt_py_targets->target[i].application); 703 } 704 705 nxt_unit_free(NULL, nxt_py_targets); 706 } 707 708 Py_Finalize(); 709 710 if (nxt_py_home != NULL) { 711 nxt_free(nxt_py_home); 712 } 713 } 714 715 716 void 717 nxt_python_print_exception(void) 718 { 719 PyErr_Print(); 720 721 #if PY_MAJOR_VERSION == 3 722 /* The backtrace may be buffered in sys.stderr file object. */ 723 { 724 PyObject *result; 725 726 result = PyObject_CallFunction(nxt_py_stderr_flush, NULL); 727 if (nxt_slow_path(result == NULL)) { 728 PyErr_Clear(); 729 return; 730 } 731 732 Py_DECREF(result); 733 } 734 #endif 735 } 736