1 2 /* 3 * Copyright (C) NGINX, Inc. 4 */ 5 6 7 #include <python/nxt_python.h> 8 9 #if (NXT_HAVE_ASGI) 10 11 #include <nxt_main.h> 12 #include <python/nxt_python_asgi.h> 13 #include <python/nxt_python_asgi_str.h> 14 15 16 typedef struct { 17 PyObject_HEAD 18 nxt_py_asgi_ctx_data_t *ctx_data; 19 int disabled; 20 int startup_received; 21 int startup_sent; 22 int shutdown_received; 23 int shutdown_sent; 24 int shutdown_called; 25 PyObject *startup_future; 26 PyObject *shutdown_future; 27 PyObject *receive_future; 28 } nxt_py_asgi_lifespan_t; 29 30 static PyObject *nxt_py_asgi_lifespan_target_startup( 31 nxt_py_asgi_ctx_data_t *ctx_data, nxt_python_target_t *target); 32 static int nxt_py_asgi_lifespan_target_shutdown( 33 nxt_py_asgi_lifespan_t *lifespan); 34 static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none); 35 static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict); 36 static PyObject *nxt_py_asgi_lifespan_send_startup( 37 nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict); 38 static PyObject *nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t *lifespan, 39 int v, int *sent, PyObject **future); 40 static PyObject *nxt_py_asgi_lifespan_send_shutdown( 41 nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict); 42 static PyObject *nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan); 43 static PyObject *nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future); 44 45 46 static PyMethodDef nxt_py_asgi_lifespan_methods[] = { 47 { "receive", nxt_py_asgi_lifespan_receive, METH_NOARGS, 0 }, 48 { "send", nxt_py_asgi_lifespan_send, METH_O, 0 }, 49 { "_done", nxt_py_asgi_lifespan_done, METH_O, 0 }, 50 { NULL, NULL, 0, 0 } 51 }; 52 53 static PyAsyncMethods nxt_py_asgi_async_methods = { 54 .am_await = nxt_py_asgi_await, 55 }; 56 57 static PyTypeObject nxt_py_asgi_lifespan_type = { 58 PyVarObject_HEAD_INIT(NULL, 0) 59 60 .tp_name = "unit._asgi_lifespan", 61 .tp_basicsize = sizeof(nxt_py_asgi_lifespan_t), 62 .tp_dealloc = nxt_py_asgi_dealloc, 63 .tp_as_async = &nxt_py_asgi_async_methods, 64 .tp_flags = Py_TPFLAGS_DEFAULT, 65 .tp_doc = "unit ASGI Lifespan object", 66 .tp_iter = nxt_py_asgi_iter, 67 .tp_iternext = nxt_py_asgi_next, 68 .tp_methods = nxt_py_asgi_lifespan_methods, 69 }; 70 71 72 int 73 nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data) 74 { 75 size_t size; 76 PyObject *lifespan; 77 PyObject **target_lifespans; 78 nxt_int_t i; 79 nxt_python_target_t *target; 80 81 size = nxt_py_targets->count * sizeof(PyObject*); 82 83 target_lifespans = nxt_unit_malloc(NULL, size); 84 if (nxt_slow_path(target_lifespans == NULL)) { 85 nxt_unit_alert(NULL, "Failed to allocate lifespan data"); 86 return NXT_UNIT_ERROR; 87 } 88 89 memset(target_lifespans, 0, size); 90 91 for (i = 0; i < nxt_py_targets->count; i++) { 92 target = &nxt_py_targets->target[i]; 93 94 lifespan = nxt_py_asgi_lifespan_target_startup(ctx_data, target); 95 if (nxt_slow_path(lifespan == NULL)) { 96 return NXT_UNIT_ERROR; 97 } 98 99 target_lifespans[i] = lifespan; 100 } 101 102 ctx_data->target_lifespans = target_lifespans; 103 104 return NXT_UNIT_OK; 105 } 106 107 108 static PyObject * 109 nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t *ctx_data, 110 nxt_python_target_t *target) 111 { 112 PyObject *scope, *res, *py_task, *receive, *send, *done; 113 PyObject *stage2; 114 nxt_py_asgi_lifespan_t *lifespan, *ret; 115 116 if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) { 117 nxt_unit_alert(NULL, 118 "Python failed to initialize the 'asgi_lifespan' type object"); 119 return NULL; 120 } 121 122 lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type); 123 if (nxt_slow_path(lifespan == NULL)) { 124 nxt_unit_alert(NULL, "Python failed to create lifespan object"); 125 return NULL; 126 } 127 128 ret = NULL; 129 130 receive = PyObject_GetAttrString((PyObject *) lifespan, "receive"); 131 if (nxt_slow_path(receive == NULL)) { 132 nxt_unit_alert(NULL, "Python failed to get 'receive' method"); 133 goto release_lifespan; 134 } 135 136 send = PyObject_GetAttrString((PyObject *) lifespan, "send"); 137 if (nxt_slow_path(receive == NULL)) { 138 nxt_unit_alert(NULL, "Python failed to get 'send' method"); 139 goto release_receive; 140 } 141 142 done = PyObject_GetAttrString((PyObject *) lifespan, "_done"); 143 if (nxt_slow_path(receive == NULL)) { 144 nxt_unit_alert(NULL, "Python failed to get '_done' method"); 145 goto release_send; 146 } 147 148 lifespan->startup_future = PyObject_CallObject(ctx_data->loop_create_future, 149 NULL); 150 if (nxt_slow_path(lifespan->startup_future == NULL)) { 151 nxt_unit_alert(NULL, "Python failed to create Future object"); 152 nxt_python_print_exception(); 153 154 goto release_done; 155 } 156 157 lifespan->ctx_data = ctx_data; 158 lifespan->disabled = 0; 159 lifespan->startup_received = 0; 160 lifespan->startup_sent = 0; 161 lifespan->shutdown_received = 0; 162 lifespan->shutdown_sent = 0; 163 lifespan->shutdown_called = 0; 164 lifespan->shutdown_future = NULL; 165 lifespan->receive_future = NULL; 166 167 scope = nxt_py_asgi_new_scope(NULL, nxt_py_lifespan_str, nxt_py_2_0_str); 168 if (nxt_slow_path(scope == NULL)) { 169 goto release_future; 170 } 171 172 if (!target->asgi_legacy) { 173 nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application"); 174 175 res = PyObject_CallFunctionObjArgs(target->application, 176 scope, receive, send, NULL); 177 178 } else { 179 nxt_unit_req_debug(NULL, "Python call legacy application"); 180 181 res = PyObject_CallFunctionObjArgs(target->application, scope, NULL); 182 if (nxt_slow_path(res == NULL)) { 183 nxt_unit_log(NULL, NXT_UNIT_LOG_INFO, 184 "ASGI Lifespan processing exception"); 185 nxt_python_print_exception(); 186 187 lifespan->disabled = 1; 188 189 Py_INCREF(lifespan); 190 ret = lifespan; 191 192 goto release_scope; 193 } 194 195 if (nxt_slow_path(PyCallable_Check(res) == 0)) { 196 nxt_unit_req_error(NULL, 197 "Legacy ASGI application returns not a callable"); 198 199 Py_DECREF(res); 200 201 goto release_scope; 202 } 203 204 stage2 = res; 205 206 res = PyObject_CallFunctionObjArgs(stage2, receive, send, NULL); 207 208 Py_DECREF(stage2); 209 } 210 211 if (nxt_slow_path(res == NULL)) { 212 nxt_unit_error(NULL, "Python failed to call the application"); 213 nxt_python_print_exception(); 214 goto release_scope; 215 } 216 217 if (nxt_slow_path(!PyCoro_CheckExact(res))) { 218 nxt_unit_error(NULL, "Application result type is not a coroutine"); 219 Py_DECREF(res); 220 goto release_scope; 221 } 222 223 py_task = PyObject_CallFunctionObjArgs(ctx_data->loop_create_task, res, 224 NULL); 225 if (nxt_slow_path(py_task == NULL)) { 226 nxt_unit_alert(NULL, "Python failed to call the create_task"); 227 nxt_python_print_exception(); 228 Py_DECREF(res); 229 goto release_scope; 230 } 231 232 Py_DECREF(res); 233 234 res = PyObject_CallMethodObjArgs(py_task, nxt_py_add_done_callback_str, 235 done, NULL); 236 if (nxt_slow_path(res == NULL)) { 237 nxt_unit_alert(NULL, "Python failed to call 'task.add_done_callback'"); 238 nxt_python_print_exception(); 239 goto release_task; 240 } 241 242 Py_DECREF(res); 243 244 res = PyObject_CallFunctionObjArgs(ctx_data->loop_run_until_complete, 245 lifespan->startup_future, NULL); 246 if (nxt_slow_path(res == NULL)) { 247 nxt_unit_alert(NULL, "Python failed to call loop.run_until_complete"); 248 nxt_python_print_exception(); 249 goto release_task; 250 } 251 252 Py_DECREF(res); 253 254 if (lifespan->startup_sent == 1 || lifespan->disabled) { 255 Py_INCREF(lifespan); 256 257 ret = lifespan; 258 } 259 260 release_task: 261 Py_DECREF(py_task); 262 release_scope: 263 Py_DECREF(scope); 264 release_future: 265 Py_CLEAR(lifespan->startup_future); 266 release_done: 267 Py_DECREF(done); 268 release_send: 269 Py_DECREF(send); 270 release_receive: 271 Py_DECREF(receive); 272 release_lifespan: 273 Py_DECREF(lifespan); 274 275 return (PyObject *) ret; 276 } 277 278 279 int 280 nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx) 281 { 282 nxt_int_t i, ret; 283 nxt_py_asgi_lifespan_t *lifespan; 284 nxt_py_asgi_ctx_data_t *ctx_data; 285 286 ctx_data = ctx->data; 287 288 for (i = 0; i < nxt_py_targets->count; i++) { 289 lifespan = (nxt_py_asgi_lifespan_t *)ctx_data->target_lifespans[i]; 290 291 ret = nxt_py_asgi_lifespan_target_shutdown(lifespan); 292 if (nxt_slow_path(ret != NXT_UNIT_OK)) { 293 return NXT_UNIT_ERROR; 294 } 295 } 296 297 nxt_unit_free(NULL, ctx_data->target_lifespans); 298 299 return NXT_UNIT_OK; 300 } 301 302 303 static int 304 nxt_py_asgi_lifespan_target_shutdown(nxt_py_asgi_lifespan_t *lifespan) 305 { 306 PyObject *msg, *future, *res; 307 nxt_py_asgi_ctx_data_t *ctx_data; 308 309 ctx_data = lifespan->ctx_data; 310 311 if (nxt_slow_path(lifespan == NULL || lifespan->disabled)) { 312 return NXT_UNIT_OK; 313 } 314 315 lifespan->shutdown_called = 1; 316 317 if (lifespan->receive_future != NULL) { 318 future = lifespan->receive_future; 319 lifespan->receive_future = NULL; 320 321 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_shutdown_str); 322 323 if (nxt_fast_path(msg != NULL)) { 324 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, 325 msg, NULL); 326 Py_XDECREF(res); 327 Py_DECREF(msg); 328 } 329 330 Py_DECREF(future); 331 } 332 333 if (lifespan->shutdown_sent) { 334 return NXT_UNIT_OK; 335 } 336 337 lifespan->shutdown_future = PyObject_CallObject(ctx_data->loop_create_future, 338 NULL); 339 if (nxt_slow_path(lifespan->shutdown_future == NULL)) { 340 nxt_unit_alert(NULL, "Python failed to create Future object"); 341 nxt_python_print_exception(); 342 return NXT_UNIT_ERROR; 343 } 344 345 res = PyObject_CallFunctionObjArgs(ctx_data->loop_run_until_complete, 346 lifespan->shutdown_future, NULL); 347 if (nxt_slow_path(res == NULL)) { 348 nxt_unit_alert(NULL, "Python failed to call loop.run_until_complete"); 349 nxt_python_print_exception(); 350 return NXT_UNIT_ERROR; 351 } 352 353 Py_DECREF(res); 354 Py_CLEAR(lifespan->shutdown_future); 355 356 return NXT_UNIT_OK; 357 } 358 359 360 static PyObject * 361 nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none) 362 { 363 PyObject *msg, *future; 364 nxt_py_asgi_lifespan_t *lifespan; 365 nxt_py_asgi_ctx_data_t *ctx_data; 366 367 lifespan = (nxt_py_asgi_lifespan_t *) self; 368 ctx_data = lifespan->ctx_data; 369 370 nxt_unit_debug(NULL, "asgi_lifespan_receive"); 371 372 future = PyObject_CallObject(ctx_data->loop_create_future, NULL); 373 if (nxt_slow_path(future == NULL)) { 374 nxt_unit_alert(NULL, "Python failed to create Future object"); 375 nxt_python_print_exception(); 376 377 return PyErr_Format(PyExc_RuntimeError, 378 "failed to create Future object"); 379 } 380 381 if (!lifespan->startup_received) { 382 lifespan->startup_received = 1; 383 384 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_startup_str); 385 386 return nxt_py_asgi_set_result_soon(NULL, ctx_data, future, msg); 387 } 388 389 if (lifespan->shutdown_called && !lifespan->shutdown_received) { 390 lifespan->shutdown_received = 1; 391 392 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_shutdown_str); 393 394 return nxt_py_asgi_set_result_soon(NULL, ctx_data, future, msg); 395 } 396 397 Py_INCREF(future); 398 lifespan->receive_future = future; 399 400 return future; 401 } 402 403 404 static PyObject * 405 nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict) 406 { 407 PyObject *type, *msg; 408 const char *type_str; 409 Py_ssize_t type_len; 410 nxt_py_asgi_lifespan_t *lifespan; 411 412 static const nxt_str_t startup_complete 413 = nxt_string("lifespan.startup.complete"); 414 static const nxt_str_t startup_failed 415 = nxt_string("lifespan.startup.failed"); 416 static const nxt_str_t shutdown_complete 417 = nxt_string("lifespan.shutdown.complete"); 418 static const nxt_str_t shutdown_failed 419 = nxt_string("lifespan.shutdown.failed"); 420 421 lifespan = (nxt_py_asgi_lifespan_t *) self; 422 423 type = PyDict_GetItem(dict, nxt_py_type_str); 424 if (nxt_slow_path(type == NULL || !PyUnicode_Check(type))) { 425 nxt_unit_error(NULL, 426 "asgi_lifespan_send: 'type' is not a unicode string"); 427 return PyErr_Format(PyExc_TypeError, 428 "'type' is not a unicode string"); 429 } 430 431 type_str = PyUnicode_AsUTF8AndSize(type, &type_len); 432 433 nxt_unit_debug(NULL, "asgi_lifespan_send type is '%.*s'", 434 (int) type_len, type_str); 435 436 if (type_len == (Py_ssize_t) startup_complete.length 437 && memcmp(type_str, startup_complete.start, type_len) == 0) 438 { 439 return nxt_py_asgi_lifespan_send_startup(lifespan, 0, NULL); 440 } 441 442 if (type_len == (Py_ssize_t) startup_failed.length 443 && memcmp(type_str, startup_failed.start, type_len) == 0) 444 { 445 msg = PyDict_GetItem(dict, nxt_py_message_str); 446 return nxt_py_asgi_lifespan_send_startup(lifespan, 1, msg); 447 } 448 449 if (type_len == (Py_ssize_t) shutdown_complete.length 450 && memcmp(type_str, shutdown_complete.start, type_len) == 0) 451 { 452 return nxt_py_asgi_lifespan_send_shutdown(lifespan, 0, NULL); 453 } 454 455 if (type_len == (Py_ssize_t) shutdown_failed.length 456 && memcmp(type_str, shutdown_failed.start, type_len) == 0) 457 { 458 msg = PyDict_GetItem(dict, nxt_py_message_str); 459 return nxt_py_asgi_lifespan_send_shutdown(lifespan, 1, msg); 460 } 461 462 return nxt_py_asgi_lifespan_disable(lifespan); 463 } 464 465 466 static PyObject * 467 nxt_py_asgi_lifespan_send_startup(nxt_py_asgi_lifespan_t *lifespan, int v, 468 PyObject *message) 469 { 470 const char *message_str; 471 Py_ssize_t message_len; 472 473 if (nxt_slow_path(v != 0)) { 474 nxt_unit_error(NULL, "Application startup failed"); 475 476 if (nxt_fast_path(message != NULL && PyUnicode_Check(message))) { 477 message_str = PyUnicode_AsUTF8AndSize(message, &message_len); 478 479 nxt_unit_error(NULL, "%.*s", (int) message_len, message_str); 480 } 481 } 482 483 return nxt_py_asgi_lifespan_send_(lifespan, v, 484 &lifespan->startup_sent, 485 &lifespan->startup_future); 486 } 487 488 489 static PyObject * 490 nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t *lifespan, int v, int *sent, 491 PyObject **pfuture) 492 { 493 PyObject *future, *res; 494 495 if (*sent) { 496 return nxt_py_asgi_lifespan_disable(lifespan); 497 } 498 499 *sent = 1 + v; 500 501 if (*pfuture != NULL) { 502 future = *pfuture; 503 *pfuture = NULL; 504 505 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, 506 Py_None, NULL); 507 if (nxt_slow_path(res == NULL)) { 508 nxt_unit_alert(NULL, "Failed to call 'future.set_result'"); 509 nxt_python_print_exception(); 510 511 return nxt_py_asgi_lifespan_disable(lifespan); 512 } 513 514 Py_DECREF(res); 515 Py_DECREF(future); 516 } 517 518 Py_INCREF(lifespan); 519 520 return (PyObject *) lifespan; 521 } 522 523 524 static PyObject * 525 nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan) 526 { 527 nxt_unit_warn(NULL, "Got invalid state transition on lifespan protocol"); 528 529 lifespan->disabled = 1; 530 531 return PyErr_Format(PyExc_AssertionError, 532 "Got invalid state transition on lifespan protocol"); 533 } 534 535 536 static PyObject * 537 nxt_py_asgi_lifespan_send_shutdown(nxt_py_asgi_lifespan_t *lifespan, int v, 538 PyObject *message) 539 { 540 return nxt_py_asgi_lifespan_send_(lifespan, v, 541 &lifespan->shutdown_sent, 542 &lifespan->shutdown_future); 543 } 544 545 546 static PyObject * 547 nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future) 548 { 549 PyObject *res; 550 nxt_py_asgi_lifespan_t *lifespan; 551 552 nxt_unit_debug(NULL, "asgi_lifespan_done"); 553 554 lifespan = (nxt_py_asgi_lifespan_t *) self; 555 556 if (lifespan->startup_sent == 0) { 557 lifespan->disabled = 1; 558 } 559 560 /* 561 * Get Future.result() and it raises an exception, if coroutine exited 562 * with exception. 563 */ 564 res = PyObject_CallMethodObjArgs(future, nxt_py_result_str, NULL); 565 if (nxt_slow_path(res == NULL)) { 566 nxt_unit_log(NULL, NXT_UNIT_LOG_INFO, 567 "ASGI Lifespan processing exception"); 568 nxt_python_print_exception(); 569 } 570 571 Py_XDECREF(res); 572 573 if (lifespan->startup_future != NULL) { 574 future = lifespan->startup_future; 575 lifespan->startup_future = NULL; 576 577 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, 578 Py_None, NULL); 579 if (nxt_slow_path(res == NULL)) { 580 nxt_unit_alert(NULL, "Failed to call 'future.set_result'"); 581 nxt_python_print_exception(); 582 } 583 584 Py_XDECREF(res); 585 Py_DECREF(future); 586 } 587 588 if (lifespan->shutdown_future != NULL) { 589 future = lifespan->shutdown_future; 590 lifespan->shutdown_future = NULL; 591 592 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, 593 Py_None, NULL); 594 if (nxt_slow_path(res == NULL)) { 595 nxt_unit_alert(NULL, "Failed to call 'future.set_result'"); 596 nxt_python_print_exception(); 597 } 598 599 Py_XDECREF(res); 600 Py_DECREF(future); 601 } 602 603 Py_RETURN_NONE; 604 } 605 606 607 #endif /* NXT_HAVE_ASGI */ 608