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 int disabled; 19 int startup_received; 20 int startup_sent; 21 int shutdown_received; 22 int shutdown_sent; 23 int shutdown_called; 24 PyObject *startup_future; 25 PyObject *shutdown_future; 26 PyObject *receive_future; 27 } nxt_py_asgi_lifespan_t; 28 29 30 static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none); 31 static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict); 32 static PyObject *nxt_py_asgi_lifespan_send_startup( 33 nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict); 34 static PyObject *nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t *lifespan, 35 int v, int *sent, PyObject **future); 36 static PyObject *nxt_py_asgi_lifespan_send_shutdown( 37 nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict); 38 static PyObject *nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan); 39 static PyObject *nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future); 40 41 42 static nxt_py_asgi_lifespan_t *nxt_py_lifespan; 43 44 static PyMethodDef nxt_py_asgi_lifespan_methods[] = { 45 { "receive", nxt_py_asgi_lifespan_receive, METH_NOARGS, 0 }, 46 { "send", nxt_py_asgi_lifespan_send, METH_O, 0 }, 47 { "_done", nxt_py_asgi_lifespan_done, METH_O, 0 }, 48 { NULL, NULL, 0, 0 } 49 }; 50 51 static PyAsyncMethods nxt_py_asgi_async_methods = { 52 .am_await = nxt_py_asgi_await, 53 }; 54 55 static PyTypeObject nxt_py_asgi_lifespan_type = { 56 PyVarObject_HEAD_INIT(NULL, 0) 57 58 .tp_name = "unit._asgi_lifespan", 59 .tp_basicsize = sizeof(nxt_py_asgi_lifespan_t), 60 .tp_dealloc = nxt_py_asgi_dealloc, 61 .tp_as_async = &nxt_py_asgi_async_methods, 62 .tp_flags = Py_TPFLAGS_DEFAULT, 63 .tp_doc = "unit ASGI Lifespan object", 64 .tp_iter = nxt_py_asgi_iter, 65 .tp_iternext = nxt_py_asgi_next, 66 .tp_methods = nxt_py_asgi_lifespan_methods, 67 }; 68 69 70 nxt_int_t 71 nxt_py_asgi_lifespan_startup(nxt_task_t *task) 72 { 73 PyObject *scope, *res, *py_task, *receive, *send, *done; 74 nxt_int_t rc; 75 nxt_py_asgi_lifespan_t *lifespan; 76 77 if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) { 78 nxt_alert(task, 79 "Python failed to initialize the 'asgi_lifespan' type object"); 80 return NXT_ERROR; 81 } 82 83 lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type); 84 if (nxt_slow_path(lifespan == NULL)) { 85 nxt_alert(task, "Python failed to create lifespan object"); 86 return NXT_ERROR; 87 } 88 89 rc = NXT_ERROR; 90 91 receive = PyObject_GetAttrString((PyObject *) lifespan, "receive"); 92 if (nxt_slow_path(receive == NULL)) { 93 nxt_alert(task, "Python failed to get 'receive' method"); 94 goto release_lifespan; 95 } 96 97 send = PyObject_GetAttrString((PyObject *) lifespan, "send"); 98 if (nxt_slow_path(receive == NULL)) { 99 nxt_alert(task, "Python failed to get 'send' method"); 100 goto release_receive; 101 } 102 103 done = PyObject_GetAttrString((PyObject *) lifespan, "_done"); 104 if (nxt_slow_path(receive == NULL)) { 105 nxt_alert(task, "Python failed to get '_done' method"); 106 goto release_send; 107 } 108 109 lifespan->startup_future = PyObject_CallObject(nxt_py_loop_create_future, 110 NULL); 111 if (nxt_slow_path(lifespan->startup_future == NULL)) { 112 nxt_unit_alert(NULL, "Python failed to create Future object"); 113 nxt_python_print_exception(); 114 115 goto release_done; 116 } 117 118 lifespan->disabled = 0; 119 lifespan->startup_received = 0; 120 lifespan->startup_sent = 0; 121 lifespan->shutdown_received = 0; 122 lifespan->shutdown_sent = 0; 123 lifespan->shutdown_called = 0; 124 lifespan->shutdown_future = NULL; 125 lifespan->receive_future = NULL; 126 127 scope = nxt_py_asgi_new_scope(NULL, nxt_py_lifespan_str, nxt_py_2_0_str); 128 if (nxt_slow_path(scope == NULL)) { 129 goto release_future; 130 } 131 132 res = PyObject_CallFunctionObjArgs(nxt_py_application, 133 scope, receive, send, NULL); 134 if (nxt_slow_path(res == NULL)) { 135 nxt_log(task, NXT_LOG_ERR, "Python failed to call the application"); 136 nxt_python_print_exception(); 137 goto release_scope; 138 } 139 140 if (nxt_slow_path(!PyCoro_CheckExact(res))) { 141 nxt_log(task, NXT_LOG_ERR, 142 "Application result type is not a coroutine"); 143 Py_DECREF(res); 144 goto release_scope; 145 } 146 147 py_task = PyObject_CallFunctionObjArgs(nxt_py_loop_create_task, res, NULL); 148 if (nxt_slow_path(py_task == NULL)) { 149 nxt_log(task, NXT_LOG_ERR, "Python failed to call the create_task"); 150 nxt_python_print_exception(); 151 Py_DECREF(res); 152 goto release_scope; 153 } 154 155 Py_DECREF(res); 156 157 res = PyObject_CallMethodObjArgs(py_task, nxt_py_add_done_callback_str, 158 done, NULL); 159 if (nxt_slow_path(res == NULL)) { 160 nxt_log(task, NXT_LOG_ERR, 161 "Python failed to call 'task.add_done_callback'"); 162 nxt_python_print_exception(); 163 goto release_task; 164 } 165 166 Py_DECREF(res); 167 168 res = PyObject_CallFunctionObjArgs(nxt_py_loop_run_until_complete, 169 lifespan->startup_future, NULL); 170 if (nxt_slow_path(res == NULL)) { 171 nxt_alert(task, "Python failed to call loop.run_until_complete"); 172 nxt_python_print_exception(); 173 goto release_task; 174 } 175 176 Py_DECREF(res); 177 178 if (lifespan->startup_sent == 1 || lifespan->disabled) { 179 nxt_py_lifespan = lifespan; 180 Py_INCREF(nxt_py_lifespan); 181 182 rc = NXT_OK; 183 } 184 185 release_task: 186 Py_DECREF(py_task); 187 release_scope: 188 Py_DECREF(scope); 189 release_future: 190 Py_CLEAR(lifespan->startup_future); 191 release_done: 192 Py_DECREF(done); 193 release_send: 194 Py_DECREF(send); 195 release_receive: 196 Py_DECREF(receive); 197 release_lifespan: 198 Py_DECREF(lifespan); 199 200 return rc; 201 } 202 203 204 nxt_int_t 205 nxt_py_asgi_lifespan_shutdown(void) 206 { 207 PyObject *msg, *future, *res; 208 nxt_py_asgi_lifespan_t *lifespan; 209 210 if (nxt_slow_path(nxt_py_lifespan == NULL || nxt_py_lifespan->disabled)) { 211 return NXT_OK; 212 } 213 214 lifespan = nxt_py_lifespan; 215 lifespan->shutdown_called = 1; 216 217 if (lifespan->receive_future != NULL) { 218 future = lifespan->receive_future; 219 lifespan->receive_future = NULL; 220 221 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_shutdown_str); 222 223 if (nxt_fast_path(msg != NULL)) { 224 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, 225 msg, NULL); 226 Py_XDECREF(res); 227 Py_DECREF(msg); 228 } 229 230 Py_DECREF(future); 231 } 232 233 if (lifespan->shutdown_sent) { 234 return NXT_OK; 235 } 236 237 lifespan->shutdown_future = PyObject_CallObject(nxt_py_loop_create_future, 238 NULL); 239 if (nxt_slow_path(lifespan->shutdown_future == NULL)) { 240 nxt_unit_alert(NULL, "Python failed to create Future object"); 241 nxt_python_print_exception(); 242 return NXT_ERROR; 243 } 244 245 res = PyObject_CallFunctionObjArgs(nxt_py_loop_run_until_complete, 246 lifespan->shutdown_future, NULL); 247 if (nxt_slow_path(res == NULL)) { 248 nxt_unit_alert(NULL, "Python failed to call loop.run_until_complete"); 249 nxt_python_print_exception(); 250 return NXT_ERROR; 251 } 252 253 Py_DECREF(res); 254 Py_CLEAR(lifespan->shutdown_future); 255 256 return NXT_OK; 257 } 258 259 260 static PyObject * 261 nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none) 262 { 263 PyObject *msg, *future; 264 nxt_py_asgi_lifespan_t *lifespan; 265 266 lifespan = (nxt_py_asgi_lifespan_t *) self; 267 268 nxt_unit_debug(NULL, "asgi_lifespan_receive"); 269 270 future = PyObject_CallObject(nxt_py_loop_create_future, NULL); 271 if (nxt_slow_path(future == NULL)) { 272 nxt_unit_alert(NULL, "Python failed to create Future object"); 273 nxt_python_print_exception(); 274 275 return PyErr_Format(PyExc_RuntimeError, 276 "failed to create Future object"); 277 } 278 279 if (!lifespan->startup_received) { 280 lifespan->startup_received = 1; 281 282 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_startup_str); 283 284 return nxt_py_asgi_set_result_soon(NULL, future, msg); 285 } 286 287 if (lifespan->shutdown_called && !lifespan->shutdown_received) { 288 lifespan->shutdown_received = 1; 289 290 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_shutdown_str); 291 292 return nxt_py_asgi_set_result_soon(NULL, future, msg); 293 } 294 295 Py_INCREF(future); 296 lifespan->receive_future = future; 297 298 return future; 299 } 300 301 302 static PyObject * 303 nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict) 304 { 305 PyObject *type, *msg; 306 const char *type_str; 307 Py_ssize_t type_len; 308 nxt_py_asgi_lifespan_t *lifespan; 309 310 static const nxt_str_t startup_complete 311 = nxt_string("lifespan.startup.complete"); 312 static const nxt_str_t startup_failed 313 = nxt_string("lifespan.startup.failed"); 314 static const nxt_str_t shutdown_complete 315 = nxt_string("lifespan.shutdown.complete"); 316 static const nxt_str_t shutdown_failed 317 = nxt_string("lifespan.shutdown.failed"); 318 319 lifespan = (nxt_py_asgi_lifespan_t *) self; 320 321 type = PyDict_GetItem(dict, nxt_py_type_str); 322 if (nxt_slow_path(type == NULL || !PyUnicode_Check(type))) { 323 nxt_unit_error(NULL, 324 "asgi_lifespan_send: 'type' is not a unicode string"); 325 return PyErr_Format(PyExc_TypeError, 326 "'type' is not a unicode string"); 327 } 328 329 type_str = PyUnicode_AsUTF8AndSize(type, &type_len); 330 331 nxt_unit_debug(NULL, "asgi_lifespan_send type is '%.*s'", 332 (int) type_len, type_str); 333 334 if (type_len == (Py_ssize_t) startup_complete.length 335 && memcmp(type_str, startup_complete.start, type_len) == 0) 336 { 337 return nxt_py_asgi_lifespan_send_startup(lifespan, 0, NULL); 338 } 339 340 if (type_len == (Py_ssize_t) startup_failed.length 341 && memcmp(type_str, startup_failed.start, type_len) == 0) 342 { 343 msg = PyDict_GetItem(dict, nxt_py_message_str); 344 return nxt_py_asgi_lifespan_send_startup(lifespan, 1, msg); 345 } 346 347 if (type_len == (Py_ssize_t) shutdown_complete.length 348 && memcmp(type_str, shutdown_complete.start, type_len) == 0) 349 { 350 return nxt_py_asgi_lifespan_send_shutdown(lifespan, 0, NULL); 351 } 352 353 if (type_len == (Py_ssize_t) shutdown_failed.length 354 && memcmp(type_str, shutdown_failed.start, type_len) == 0) 355 { 356 msg = PyDict_GetItem(dict, nxt_py_message_str); 357 return nxt_py_asgi_lifespan_send_shutdown(lifespan, 1, msg); 358 } 359 360 return nxt_py_asgi_lifespan_disable(lifespan); 361 } 362 363 364 static PyObject * 365 nxt_py_asgi_lifespan_send_startup(nxt_py_asgi_lifespan_t *lifespan, int v, 366 PyObject *message) 367 { 368 const char *message_str; 369 Py_ssize_t message_len; 370 371 if (nxt_slow_path(v != 0)) { 372 nxt_unit_error(NULL, "Application startup failed"); 373 374 if (nxt_fast_path(message != NULL && PyUnicode_Check(message))) { 375 message_str = PyUnicode_AsUTF8AndSize(message, &message_len); 376 377 nxt_unit_error(NULL, "%.*s", (int) message_len, message_str); 378 } 379 } 380 381 return nxt_py_asgi_lifespan_send_(lifespan, v, 382 &lifespan->startup_sent, 383 &lifespan->startup_future); 384 } 385 386 387 static PyObject * 388 nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t *lifespan, int v, int *sent, 389 PyObject **pfuture) 390 { 391 PyObject *future, *res; 392 393 if (*sent) { 394 return nxt_py_asgi_lifespan_disable(lifespan); 395 } 396 397 *sent = 1 + v; 398 399 if (*pfuture != NULL) { 400 future = *pfuture; 401 *pfuture = NULL; 402 403 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, 404 Py_None, NULL); 405 if (nxt_slow_path(res == NULL)) { 406 nxt_unit_alert(NULL, "Failed to call 'future.set_result'"); 407 nxt_python_print_exception(); 408 409 return nxt_py_asgi_lifespan_disable(lifespan); 410 } 411 412 Py_DECREF(res); 413 Py_DECREF(future); 414 } 415 416 Py_INCREF(lifespan); 417 418 return (PyObject *) lifespan; 419 } 420 421 422 static PyObject * 423 nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan) 424 { 425 nxt_unit_warn(NULL, "Got invalid state transition on lifespan protocol"); 426 427 lifespan->disabled = 1; 428 429 return PyErr_Format(PyExc_AssertionError, 430 "Got invalid state transition on lifespan protocol"); 431 } 432 433 434 static PyObject * 435 nxt_py_asgi_lifespan_send_shutdown(nxt_py_asgi_lifespan_t *lifespan, int v, 436 PyObject *message) 437 { 438 return nxt_py_asgi_lifespan_send_(lifespan, v, 439 &lifespan->shutdown_sent, 440 &lifespan->shutdown_future); 441 } 442 443 444 static PyObject * 445 nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future) 446 { 447 PyObject *res; 448 nxt_py_asgi_lifespan_t *lifespan; 449 450 nxt_unit_debug(NULL, "asgi_lifespan_done"); 451 452 lifespan = (nxt_py_asgi_lifespan_t *) self; 453 454 if (lifespan->startup_sent == 0) { 455 lifespan->disabled = 1; 456 } 457 458 /* 459 * Get Future.result() and it raises an exception, if coroutine exited 460 * with exception. 461 */ 462 res = PyObject_CallMethodObjArgs(future, nxt_py_result_str, NULL); 463 if (nxt_slow_path(res == NULL)) { 464 nxt_unit_log(NULL, NXT_UNIT_LOG_INFO, 465 "ASGI Lifespan processing exception"); 466 nxt_python_print_exception(); 467 } 468 469 Py_XDECREF(res); 470 471 if (lifespan->startup_future != NULL) { 472 future = lifespan->startup_future; 473 lifespan->startup_future = NULL; 474 475 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, 476 Py_None, NULL); 477 if (nxt_slow_path(res == NULL)) { 478 nxt_unit_alert(NULL, "Failed to call 'future.set_result'"); 479 nxt_python_print_exception(); 480 } 481 482 Py_XDECREF(res); 483 Py_DECREF(future); 484 } 485 486 if (lifespan->shutdown_future != NULL) { 487 future = lifespan->shutdown_future; 488 lifespan->shutdown_future = NULL; 489 490 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, 491 Py_None, NULL); 492 if (nxt_slow_path(res == NULL)) { 493 nxt_unit_alert(NULL, "Failed to call 'future.set_result'"); 494 nxt_python_print_exception(); 495 } 496 497 Py_XDECREF(res); 498 Py_DECREF(future); 499 } 500 501 Py_RETURN_NONE; 502 } 503 504 505 #endif /* NXT_HAVE_ASGI */ 506