11624Smax.romanov@nginx.com 
21624Smax.romanov@nginx.com /*
31624Smax.romanov@nginx.com  * Copyright (C) NGINX, Inc.
41624Smax.romanov@nginx.com  */
51624Smax.romanov@nginx.com 
61624Smax.romanov@nginx.com 
71624Smax.romanov@nginx.com #include <python/nxt_python.h>
81624Smax.romanov@nginx.com 
91624Smax.romanov@nginx.com #if (NXT_HAVE_ASGI)
101624Smax.romanov@nginx.com 
111624Smax.romanov@nginx.com #include <nxt_main.h>
121624Smax.romanov@nginx.com #include <python/nxt_python_asgi.h>
131624Smax.romanov@nginx.com #include <python/nxt_python_asgi_str.h>
141624Smax.romanov@nginx.com 
151624Smax.romanov@nginx.com 
161624Smax.romanov@nginx.com typedef struct  {
171624Smax.romanov@nginx.com     PyObject_HEAD
181681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
191681Smax.romanov@nginx.com     int                     disabled;
201681Smax.romanov@nginx.com     int                     startup_received;
211681Smax.romanov@nginx.com     int                     startup_sent;
221681Smax.romanov@nginx.com     int                     shutdown_received;
231681Smax.romanov@nginx.com     int                     shutdown_sent;
241681Smax.romanov@nginx.com     int                     shutdown_called;
251681Smax.romanov@nginx.com     PyObject                *startup_future;
261681Smax.romanov@nginx.com     PyObject                *shutdown_future;
271681Smax.romanov@nginx.com     PyObject                *receive_future;
281624Smax.romanov@nginx.com } nxt_py_asgi_lifespan_t;
291624Smax.romanov@nginx.com 
301624Smax.romanov@nginx.com 
311624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none);
321624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict);
331624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_lifespan_send_startup(
341624Smax.romanov@nginx.com     nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict);
351624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t *lifespan,
361624Smax.romanov@nginx.com     int v, int *sent, PyObject **future);
371624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_lifespan_send_shutdown(
381624Smax.romanov@nginx.com     nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict);
391624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan);
401624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future);
411624Smax.romanov@nginx.com 
421624Smax.romanov@nginx.com 
431624Smax.romanov@nginx.com static PyMethodDef nxt_py_asgi_lifespan_methods[] = {
441624Smax.romanov@nginx.com     { "receive",   nxt_py_asgi_lifespan_receive, METH_NOARGS, 0 },
451624Smax.romanov@nginx.com     { "send",      nxt_py_asgi_lifespan_send,    METH_O,      0 },
461624Smax.romanov@nginx.com     { "_done",     nxt_py_asgi_lifespan_done,    METH_O,      0 },
471624Smax.romanov@nginx.com     { NULL, NULL, 0, 0 }
481624Smax.romanov@nginx.com };
491624Smax.romanov@nginx.com 
501624Smax.romanov@nginx.com static PyAsyncMethods nxt_py_asgi_async_methods = {
511624Smax.romanov@nginx.com     .am_await = nxt_py_asgi_await,
521624Smax.romanov@nginx.com };
531624Smax.romanov@nginx.com 
541624Smax.romanov@nginx.com static PyTypeObject nxt_py_asgi_lifespan_type = {
551624Smax.romanov@nginx.com     PyVarObject_HEAD_INIT(NULL, 0)
561624Smax.romanov@nginx.com 
571624Smax.romanov@nginx.com     .tp_name      = "unit._asgi_lifespan",
581624Smax.romanov@nginx.com     .tp_basicsize = sizeof(nxt_py_asgi_lifespan_t),
591624Smax.romanov@nginx.com     .tp_dealloc   = nxt_py_asgi_dealloc,
601624Smax.romanov@nginx.com     .tp_as_async  = &nxt_py_asgi_async_methods,
611624Smax.romanov@nginx.com     .tp_flags     = Py_TPFLAGS_DEFAULT,
621624Smax.romanov@nginx.com     .tp_doc       = "unit ASGI Lifespan object",
631624Smax.romanov@nginx.com     .tp_iter      = nxt_py_asgi_iter,
641624Smax.romanov@nginx.com     .tp_iternext  = nxt_py_asgi_next,
651624Smax.romanov@nginx.com     .tp_methods   = nxt_py_asgi_lifespan_methods,
661624Smax.romanov@nginx.com };
671624Smax.romanov@nginx.com 
681624Smax.romanov@nginx.com 
691681Smax.romanov@nginx.com int
701681Smax.romanov@nginx.com nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data)
711624Smax.romanov@nginx.com {
721681Smax.romanov@nginx.com     int                     rc;
731624Smax.romanov@nginx.com     PyObject                *scope, *res, *py_task, *receive, *send, *done;
74*1697Smax.romanov@nginx.com     PyObject                *stage2;
751624Smax.romanov@nginx.com     nxt_py_asgi_lifespan_t  *lifespan;
761624Smax.romanov@nginx.com 
771624Smax.romanov@nginx.com     if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) {
781681Smax.romanov@nginx.com         nxt_unit_alert(NULL,
791624Smax.romanov@nginx.com                  "Python failed to initialize the 'asgi_lifespan' type object");
801681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
811624Smax.romanov@nginx.com     }
821624Smax.romanov@nginx.com 
831624Smax.romanov@nginx.com     lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type);
841624Smax.romanov@nginx.com     if (nxt_slow_path(lifespan == NULL)) {
851681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to create lifespan object");
861681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
871624Smax.romanov@nginx.com     }
881624Smax.romanov@nginx.com 
891681Smax.romanov@nginx.com     rc = NXT_UNIT_ERROR;
901624Smax.romanov@nginx.com 
911624Smax.romanov@nginx.com     receive = PyObject_GetAttrString((PyObject *) lifespan, "receive");
921624Smax.romanov@nginx.com     if (nxt_slow_path(receive == NULL)) {
931681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to get 'receive' method");
941624Smax.romanov@nginx.com         goto release_lifespan;
951624Smax.romanov@nginx.com     }
961624Smax.romanov@nginx.com 
971624Smax.romanov@nginx.com     send = PyObject_GetAttrString((PyObject *) lifespan, "send");
981624Smax.romanov@nginx.com     if (nxt_slow_path(receive == NULL)) {
991681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to get 'send' method");
1001624Smax.romanov@nginx.com         goto release_receive;
1011624Smax.romanov@nginx.com     }
1021624Smax.romanov@nginx.com 
1031624Smax.romanov@nginx.com     done = PyObject_GetAttrString((PyObject *) lifespan, "_done");
1041624Smax.romanov@nginx.com     if (nxt_slow_path(receive == NULL)) {
1051681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to get '_done' method");
1061624Smax.romanov@nginx.com         goto release_send;
1071624Smax.romanov@nginx.com     }
1081624Smax.romanov@nginx.com 
1091681Smax.romanov@nginx.com     lifespan->startup_future = PyObject_CallObject(ctx_data->loop_create_future,
1101624Smax.romanov@nginx.com                                                    NULL);
1111624Smax.romanov@nginx.com     if (nxt_slow_path(lifespan->startup_future == NULL)) {
1121624Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to create Future object");
1131624Smax.romanov@nginx.com         nxt_python_print_exception();
1141624Smax.romanov@nginx.com 
1151624Smax.romanov@nginx.com         goto release_done;
1161624Smax.romanov@nginx.com     }
1171624Smax.romanov@nginx.com 
1181681Smax.romanov@nginx.com     lifespan->ctx_data = ctx_data;
1191624Smax.romanov@nginx.com     lifespan->disabled = 0;
1201624Smax.romanov@nginx.com     lifespan->startup_received = 0;
1211624Smax.romanov@nginx.com     lifespan->startup_sent = 0;
1221624Smax.romanov@nginx.com     lifespan->shutdown_received = 0;
1231624Smax.romanov@nginx.com     lifespan->shutdown_sent = 0;
1241624Smax.romanov@nginx.com     lifespan->shutdown_called = 0;
1251624Smax.romanov@nginx.com     lifespan->shutdown_future = NULL;
1261624Smax.romanov@nginx.com     lifespan->receive_future = NULL;
1271624Smax.romanov@nginx.com 
1281624Smax.romanov@nginx.com     scope = nxt_py_asgi_new_scope(NULL, nxt_py_lifespan_str, nxt_py_2_0_str);
1291624Smax.romanov@nginx.com     if (nxt_slow_path(scope == NULL)) {
1301624Smax.romanov@nginx.com         goto release_future;
1311624Smax.romanov@nginx.com     }
1321624Smax.romanov@nginx.com 
133*1697Smax.romanov@nginx.com     if (!nxt_py_asgi_legacy) {
134*1697Smax.romanov@nginx.com         nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application");
135*1697Smax.romanov@nginx.com 
136*1697Smax.romanov@nginx.com         res = PyObject_CallFunctionObjArgs(nxt_py_application,
137*1697Smax.romanov@nginx.com                                            scope, receive, send, NULL);
138*1697Smax.romanov@nginx.com 
139*1697Smax.romanov@nginx.com     } else {
140*1697Smax.romanov@nginx.com         nxt_unit_req_debug(NULL, "Python call legacy application");
141*1697Smax.romanov@nginx.com 
142*1697Smax.romanov@nginx.com         res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL);
143*1697Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
144*1697Smax.romanov@nginx.com             nxt_unit_log(NULL, NXT_UNIT_LOG_INFO,
145*1697Smax.romanov@nginx.com                          "ASGI Lifespan processing exception");
146*1697Smax.romanov@nginx.com             nxt_python_print_exception();
147*1697Smax.romanov@nginx.com 
148*1697Smax.romanov@nginx.com             lifespan->disabled = 1;
149*1697Smax.romanov@nginx.com             rc = NXT_UNIT_OK;
150*1697Smax.romanov@nginx.com 
151*1697Smax.romanov@nginx.com             goto release_scope;
152*1697Smax.romanov@nginx.com         }
153*1697Smax.romanov@nginx.com 
154*1697Smax.romanov@nginx.com         if (nxt_slow_path(PyCallable_Check(res) == 0)) {
155*1697Smax.romanov@nginx.com             nxt_unit_req_error(NULL,
156*1697Smax.romanov@nginx.com                               "Legacy ASGI application returns not a callable");
157*1697Smax.romanov@nginx.com 
158*1697Smax.romanov@nginx.com             Py_DECREF(res);
159*1697Smax.romanov@nginx.com 
160*1697Smax.romanov@nginx.com             goto release_scope;
161*1697Smax.romanov@nginx.com         }
162*1697Smax.romanov@nginx.com 
163*1697Smax.romanov@nginx.com         stage2 = res;
164*1697Smax.romanov@nginx.com 
165*1697Smax.romanov@nginx.com         res = PyObject_CallFunctionObjArgs(stage2, receive, send, NULL);
166*1697Smax.romanov@nginx.com 
167*1697Smax.romanov@nginx.com         Py_DECREF(stage2);
168*1697Smax.romanov@nginx.com     }
169*1697Smax.romanov@nginx.com 
1701624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
1711681Smax.romanov@nginx.com         nxt_unit_error(NULL, "Python failed to call the application");
1721624Smax.romanov@nginx.com         nxt_python_print_exception();
1731624Smax.romanov@nginx.com         goto release_scope;
1741624Smax.romanov@nginx.com     }
1751624Smax.romanov@nginx.com 
1761624Smax.romanov@nginx.com     if (nxt_slow_path(!PyCoro_CheckExact(res))) {
1771681Smax.romanov@nginx.com         nxt_unit_error(NULL, "Application result type is not a coroutine");
1781624Smax.romanov@nginx.com         Py_DECREF(res);
1791624Smax.romanov@nginx.com         goto release_scope;
1801624Smax.romanov@nginx.com     }
1811624Smax.romanov@nginx.com 
182*1697Smax.romanov@nginx.com     py_task = PyObject_CallFunctionObjArgs(ctx_data->loop_create_task, res,
183*1697Smax.romanov@nginx.com                                            NULL);
1841624Smax.romanov@nginx.com     if (nxt_slow_path(py_task == NULL)) {
1851681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to call the create_task");
1861624Smax.romanov@nginx.com         nxt_python_print_exception();
1871624Smax.romanov@nginx.com         Py_DECREF(res);
1881624Smax.romanov@nginx.com         goto release_scope;
1891624Smax.romanov@nginx.com     }
1901624Smax.romanov@nginx.com 
1911624Smax.romanov@nginx.com     Py_DECREF(res);
1921624Smax.romanov@nginx.com 
1931624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(py_task, nxt_py_add_done_callback_str,
1941624Smax.romanov@nginx.com                                      done, NULL);
1951624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
1961681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to call 'task.add_done_callback'");
1971624Smax.romanov@nginx.com         nxt_python_print_exception();
1981624Smax.romanov@nginx.com         goto release_task;
1991624Smax.romanov@nginx.com     }
2001624Smax.romanov@nginx.com 
2011624Smax.romanov@nginx.com     Py_DECREF(res);
2021624Smax.romanov@nginx.com 
2031681Smax.romanov@nginx.com     res = PyObject_CallFunctionObjArgs(ctx_data->loop_run_until_complete,
2041624Smax.romanov@nginx.com                                        lifespan->startup_future, NULL);
2051624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
2061681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to call loop.run_until_complete");
2071624Smax.romanov@nginx.com         nxt_python_print_exception();
2081624Smax.romanov@nginx.com         goto release_task;
2091624Smax.romanov@nginx.com     }
2101624Smax.romanov@nginx.com 
2111624Smax.romanov@nginx.com     Py_DECREF(res);
2121624Smax.romanov@nginx.com 
2131624Smax.romanov@nginx.com     if (lifespan->startup_sent == 1 || lifespan->disabled) {
2141681Smax.romanov@nginx.com         ctx_data->lifespan = (PyObject *) lifespan;
2151681Smax.romanov@nginx.com         Py_INCREF(ctx_data->lifespan);
2161624Smax.romanov@nginx.com 
2171681Smax.romanov@nginx.com         rc = NXT_UNIT_OK;
2181624Smax.romanov@nginx.com     }
2191624Smax.romanov@nginx.com 
2201624Smax.romanov@nginx.com release_task:
2211624Smax.romanov@nginx.com     Py_DECREF(py_task);
2221624Smax.romanov@nginx.com release_scope:
2231624Smax.romanov@nginx.com     Py_DECREF(scope);
2241624Smax.romanov@nginx.com release_future:
2251624Smax.romanov@nginx.com     Py_CLEAR(lifespan->startup_future);
2261624Smax.romanov@nginx.com release_done:
2271624Smax.romanov@nginx.com     Py_DECREF(done);
2281624Smax.romanov@nginx.com release_send:
2291624Smax.romanov@nginx.com     Py_DECREF(send);
2301624Smax.romanov@nginx.com release_receive:
2311624Smax.romanov@nginx.com     Py_DECREF(receive);
2321624Smax.romanov@nginx.com release_lifespan:
2331624Smax.romanov@nginx.com     Py_DECREF(lifespan);
2341624Smax.romanov@nginx.com 
2351624Smax.romanov@nginx.com     return rc;
2361624Smax.romanov@nginx.com }
2371624Smax.romanov@nginx.com 
2381624Smax.romanov@nginx.com 
2391681Smax.romanov@nginx.com int
2401681Smax.romanov@nginx.com nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx)
2411624Smax.romanov@nginx.com {
2421624Smax.romanov@nginx.com     PyObject                *msg, *future, *res;
2431624Smax.romanov@nginx.com     nxt_py_asgi_lifespan_t  *lifespan;
2441681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
2451624Smax.romanov@nginx.com 
2461681Smax.romanov@nginx.com     ctx_data = ctx->data;
2471681Smax.romanov@nginx.com 
2481681Smax.romanov@nginx.com     lifespan = (nxt_py_asgi_lifespan_t *) ctx_data->lifespan;
2491681Smax.romanov@nginx.com 
2501681Smax.romanov@nginx.com     if (nxt_slow_path(lifespan == NULL || lifespan->disabled)) {
2511681Smax.romanov@nginx.com         return NXT_UNIT_OK;
2521624Smax.romanov@nginx.com     }
2531624Smax.romanov@nginx.com 
2541624Smax.romanov@nginx.com     lifespan->shutdown_called = 1;
2551624Smax.romanov@nginx.com 
2561624Smax.romanov@nginx.com     if (lifespan->receive_future != NULL) {
2571624Smax.romanov@nginx.com         future = lifespan->receive_future;
2581624Smax.romanov@nginx.com         lifespan->receive_future = NULL;
2591624Smax.romanov@nginx.com 
2601624Smax.romanov@nginx.com         msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_shutdown_str);
2611624Smax.romanov@nginx.com 
2621624Smax.romanov@nginx.com         if (nxt_fast_path(msg != NULL)) {
2631624Smax.romanov@nginx.com             res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str,
2641624Smax.romanov@nginx.com                                              msg, NULL);
2651624Smax.romanov@nginx.com             Py_XDECREF(res);
2661624Smax.romanov@nginx.com             Py_DECREF(msg);
2671624Smax.romanov@nginx.com         }
2681624Smax.romanov@nginx.com 
2691624Smax.romanov@nginx.com         Py_DECREF(future);
2701624Smax.romanov@nginx.com     }
2711624Smax.romanov@nginx.com 
2721624Smax.romanov@nginx.com     if (lifespan->shutdown_sent) {
2731681Smax.romanov@nginx.com         return NXT_UNIT_OK;
2741624Smax.romanov@nginx.com     }
2751624Smax.romanov@nginx.com 
2761681Smax.romanov@nginx.com     lifespan->shutdown_future = PyObject_CallObject(ctx_data->loop_create_future,
2771624Smax.romanov@nginx.com                                                     NULL);
2781624Smax.romanov@nginx.com     if (nxt_slow_path(lifespan->shutdown_future == NULL)) {
2791624Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to create Future object");
2801624Smax.romanov@nginx.com         nxt_python_print_exception();
2811681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
2821624Smax.romanov@nginx.com     }
2831624Smax.romanov@nginx.com 
2841681Smax.romanov@nginx.com     res = PyObject_CallFunctionObjArgs(ctx_data->loop_run_until_complete,
2851624Smax.romanov@nginx.com                                        lifespan->shutdown_future, NULL);
2861624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
2871624Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to call loop.run_until_complete");
2881624Smax.romanov@nginx.com         nxt_python_print_exception();
2891681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
2901624Smax.romanov@nginx.com     }
2911624Smax.romanov@nginx.com 
2921624Smax.romanov@nginx.com     Py_DECREF(res);
2931624Smax.romanov@nginx.com     Py_CLEAR(lifespan->shutdown_future);
2941624Smax.romanov@nginx.com 
2951681Smax.romanov@nginx.com     return NXT_UNIT_OK;
2961624Smax.romanov@nginx.com }
2971624Smax.romanov@nginx.com 
2981624Smax.romanov@nginx.com 
2991624Smax.romanov@nginx.com static PyObject *
3001624Smax.romanov@nginx.com nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none)
3011624Smax.romanov@nginx.com {
3021624Smax.romanov@nginx.com     PyObject                *msg, *future;
3031624Smax.romanov@nginx.com     nxt_py_asgi_lifespan_t  *lifespan;
3041681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
3051624Smax.romanov@nginx.com 
3061624Smax.romanov@nginx.com     lifespan = (nxt_py_asgi_lifespan_t *) self;
3071681Smax.romanov@nginx.com     ctx_data = lifespan->ctx_data;
3081624Smax.romanov@nginx.com 
3091624Smax.romanov@nginx.com     nxt_unit_debug(NULL, "asgi_lifespan_receive");
3101624Smax.romanov@nginx.com 
3111681Smax.romanov@nginx.com     future = PyObject_CallObject(ctx_data->loop_create_future, NULL);
3121624Smax.romanov@nginx.com     if (nxt_slow_path(future == NULL)) {
3131624Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to create Future object");
3141624Smax.romanov@nginx.com         nxt_python_print_exception();
3151624Smax.romanov@nginx.com 
3161624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
3171624Smax.romanov@nginx.com                             "failed to create Future object");
3181624Smax.romanov@nginx.com     }
3191624Smax.romanov@nginx.com 
3201624Smax.romanov@nginx.com     if (!lifespan->startup_received) {
3211624Smax.romanov@nginx.com         lifespan->startup_received = 1;
3221624Smax.romanov@nginx.com 
3231624Smax.romanov@nginx.com         msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_startup_str);
3241624Smax.romanov@nginx.com 
3251681Smax.romanov@nginx.com         return nxt_py_asgi_set_result_soon(NULL, ctx_data, future, msg);
3261624Smax.romanov@nginx.com     }
3271624Smax.romanov@nginx.com 
3281624Smax.romanov@nginx.com     if (lifespan->shutdown_called && !lifespan->shutdown_received) {
3291624Smax.romanov@nginx.com         lifespan->shutdown_received = 1;
3301624Smax.romanov@nginx.com 
3311624Smax.romanov@nginx.com         msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_shutdown_str);
3321624Smax.romanov@nginx.com 
3331681Smax.romanov@nginx.com         return nxt_py_asgi_set_result_soon(NULL, ctx_data, future, msg);
3341624Smax.romanov@nginx.com     }
3351624Smax.romanov@nginx.com 
3361624Smax.romanov@nginx.com     Py_INCREF(future);
3371624Smax.romanov@nginx.com     lifespan->receive_future = future;
3381624Smax.romanov@nginx.com 
3391624Smax.romanov@nginx.com     return future;
3401624Smax.romanov@nginx.com }
3411624Smax.romanov@nginx.com 
3421624Smax.romanov@nginx.com 
3431624Smax.romanov@nginx.com static PyObject *
3441624Smax.romanov@nginx.com nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict)
3451624Smax.romanov@nginx.com {
3461624Smax.romanov@nginx.com     PyObject                *type, *msg;
3471624Smax.romanov@nginx.com     const char              *type_str;
3481624Smax.romanov@nginx.com     Py_ssize_t              type_len;
3491624Smax.romanov@nginx.com     nxt_py_asgi_lifespan_t  *lifespan;
3501624Smax.romanov@nginx.com 
3511624Smax.romanov@nginx.com     static const nxt_str_t  startup_complete
3521624Smax.romanov@nginx.com                                 = nxt_string("lifespan.startup.complete");
3531624Smax.romanov@nginx.com     static const nxt_str_t  startup_failed
3541624Smax.romanov@nginx.com                                 = nxt_string("lifespan.startup.failed");
3551624Smax.romanov@nginx.com     static const nxt_str_t  shutdown_complete
3561624Smax.romanov@nginx.com                                 = nxt_string("lifespan.shutdown.complete");
3571624Smax.romanov@nginx.com     static const nxt_str_t  shutdown_failed
3581624Smax.romanov@nginx.com                                 = nxt_string("lifespan.shutdown.failed");
3591624Smax.romanov@nginx.com 
3601624Smax.romanov@nginx.com     lifespan = (nxt_py_asgi_lifespan_t *) self;
3611624Smax.romanov@nginx.com 
3621624Smax.romanov@nginx.com     type = PyDict_GetItem(dict, nxt_py_type_str);
3631624Smax.romanov@nginx.com     if (nxt_slow_path(type == NULL || !PyUnicode_Check(type))) {
3641624Smax.romanov@nginx.com         nxt_unit_error(NULL,
3651624Smax.romanov@nginx.com                        "asgi_lifespan_send: 'type' is not a unicode string");
3661624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError,
3671624Smax.romanov@nginx.com                             "'type' is not a unicode string");
3681624Smax.romanov@nginx.com     }
3691624Smax.romanov@nginx.com 
3701624Smax.romanov@nginx.com     type_str = PyUnicode_AsUTF8AndSize(type, &type_len);
3711624Smax.romanov@nginx.com 
3721624Smax.romanov@nginx.com     nxt_unit_debug(NULL, "asgi_lifespan_send type is '%.*s'",
3731624Smax.romanov@nginx.com                    (int) type_len, type_str);
3741624Smax.romanov@nginx.com 
3751624Smax.romanov@nginx.com     if (type_len == (Py_ssize_t) startup_complete.length
3761624Smax.romanov@nginx.com         && memcmp(type_str, startup_complete.start, type_len) == 0)
3771624Smax.romanov@nginx.com     {
3781624Smax.romanov@nginx.com         return nxt_py_asgi_lifespan_send_startup(lifespan, 0, NULL);
3791624Smax.romanov@nginx.com     }
3801624Smax.romanov@nginx.com 
3811624Smax.romanov@nginx.com     if (type_len == (Py_ssize_t) startup_failed.length
3821624Smax.romanov@nginx.com         && memcmp(type_str, startup_failed.start, type_len) == 0)
3831624Smax.romanov@nginx.com     {
3841624Smax.romanov@nginx.com         msg = PyDict_GetItem(dict, nxt_py_message_str);
3851624Smax.romanov@nginx.com         return nxt_py_asgi_lifespan_send_startup(lifespan, 1, msg);
3861624Smax.romanov@nginx.com     }
3871624Smax.romanov@nginx.com 
3881624Smax.romanov@nginx.com     if (type_len == (Py_ssize_t) shutdown_complete.length
3891624Smax.romanov@nginx.com         && memcmp(type_str, shutdown_complete.start, type_len) == 0)
3901624Smax.romanov@nginx.com     {
3911624Smax.romanov@nginx.com         return nxt_py_asgi_lifespan_send_shutdown(lifespan, 0, NULL);
3921624Smax.romanov@nginx.com     }
3931624Smax.romanov@nginx.com 
3941624Smax.romanov@nginx.com     if (type_len == (Py_ssize_t) shutdown_failed.length
3951624Smax.romanov@nginx.com         && memcmp(type_str, shutdown_failed.start, type_len) == 0)
3961624Smax.romanov@nginx.com     {
3971624Smax.romanov@nginx.com         msg = PyDict_GetItem(dict, nxt_py_message_str);
3981624Smax.romanov@nginx.com         return nxt_py_asgi_lifespan_send_shutdown(lifespan, 1, msg);
3991624Smax.romanov@nginx.com     }
4001624Smax.romanov@nginx.com 
4011624Smax.romanov@nginx.com     return nxt_py_asgi_lifespan_disable(lifespan);
4021624Smax.romanov@nginx.com }
4031624Smax.romanov@nginx.com 
4041624Smax.romanov@nginx.com 
4051624Smax.romanov@nginx.com static PyObject *
4061624Smax.romanov@nginx.com nxt_py_asgi_lifespan_send_startup(nxt_py_asgi_lifespan_t *lifespan, int v,
4071624Smax.romanov@nginx.com     PyObject *message)
4081624Smax.romanov@nginx.com {
4091624Smax.romanov@nginx.com     const char  *message_str;
4101624Smax.romanov@nginx.com     Py_ssize_t  message_len;
4111624Smax.romanov@nginx.com 
4121624Smax.romanov@nginx.com     if (nxt_slow_path(v != 0)) {
4131624Smax.romanov@nginx.com         nxt_unit_error(NULL, "Application startup failed");
4141624Smax.romanov@nginx.com 
4151624Smax.romanov@nginx.com         if (nxt_fast_path(message != NULL && PyUnicode_Check(message))) {
4161624Smax.romanov@nginx.com             message_str = PyUnicode_AsUTF8AndSize(message, &message_len);
4171624Smax.romanov@nginx.com 
4181624Smax.romanov@nginx.com             nxt_unit_error(NULL, "%.*s", (int) message_len, message_str);
4191624Smax.romanov@nginx.com         }
4201624Smax.romanov@nginx.com     }
4211624Smax.romanov@nginx.com 
4221624Smax.romanov@nginx.com     return nxt_py_asgi_lifespan_send_(lifespan, v,
4231624Smax.romanov@nginx.com                                       &lifespan->startup_sent,
4241624Smax.romanov@nginx.com                                       &lifespan->startup_future);
4251624Smax.romanov@nginx.com }
4261624Smax.romanov@nginx.com 
4271624Smax.romanov@nginx.com 
4281624Smax.romanov@nginx.com static PyObject *
4291624Smax.romanov@nginx.com nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t *lifespan, int v, int *sent,
4301624Smax.romanov@nginx.com     PyObject **pfuture)
4311624Smax.romanov@nginx.com {
4321624Smax.romanov@nginx.com     PyObject  *future, *res;
4331624Smax.romanov@nginx.com 
4341624Smax.romanov@nginx.com     if (*sent) {
4351624Smax.romanov@nginx.com         return nxt_py_asgi_lifespan_disable(lifespan);
4361624Smax.romanov@nginx.com     }
4371624Smax.romanov@nginx.com 
4381624Smax.romanov@nginx.com     *sent = 1 + v;
4391624Smax.romanov@nginx.com 
4401624Smax.romanov@nginx.com     if (*pfuture != NULL) {
4411624Smax.romanov@nginx.com         future = *pfuture;
4421624Smax.romanov@nginx.com         *pfuture = NULL;
4431624Smax.romanov@nginx.com 
4441624Smax.romanov@nginx.com         res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str,
4451624Smax.romanov@nginx.com                                          Py_None, NULL);
4461624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
4471624Smax.romanov@nginx.com             nxt_unit_alert(NULL, "Failed to call 'future.set_result'");
4481624Smax.romanov@nginx.com             nxt_python_print_exception();
4491624Smax.romanov@nginx.com 
4501624Smax.romanov@nginx.com             return nxt_py_asgi_lifespan_disable(lifespan);
4511624Smax.romanov@nginx.com         }
4521624Smax.romanov@nginx.com 
4531624Smax.romanov@nginx.com         Py_DECREF(res);
4541624Smax.romanov@nginx.com         Py_DECREF(future);
4551624Smax.romanov@nginx.com     }
4561624Smax.romanov@nginx.com 
4571624Smax.romanov@nginx.com     Py_INCREF(lifespan);
4581624Smax.romanov@nginx.com 
4591624Smax.romanov@nginx.com     return (PyObject *) lifespan;
4601624Smax.romanov@nginx.com }
4611624Smax.romanov@nginx.com 
4621624Smax.romanov@nginx.com 
4631624Smax.romanov@nginx.com static PyObject *
4641624Smax.romanov@nginx.com nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan)
4651624Smax.romanov@nginx.com {
4661624Smax.romanov@nginx.com     nxt_unit_warn(NULL, "Got invalid state transition on lifespan protocol");
4671624Smax.romanov@nginx.com 
4681624Smax.romanov@nginx.com     lifespan->disabled = 1;
4691624Smax.romanov@nginx.com 
4701624Smax.romanov@nginx.com     return PyErr_Format(PyExc_AssertionError,
4711624Smax.romanov@nginx.com                         "Got invalid state transition on lifespan protocol");
4721624Smax.romanov@nginx.com }
4731624Smax.romanov@nginx.com 
4741624Smax.romanov@nginx.com 
4751624Smax.romanov@nginx.com static PyObject *
4761624Smax.romanov@nginx.com nxt_py_asgi_lifespan_send_shutdown(nxt_py_asgi_lifespan_t *lifespan, int v,
4771624Smax.romanov@nginx.com     PyObject *message)
4781624Smax.romanov@nginx.com {
4791624Smax.romanov@nginx.com     return nxt_py_asgi_lifespan_send_(lifespan, v,
4801624Smax.romanov@nginx.com                                       &lifespan->shutdown_sent,
4811624Smax.romanov@nginx.com                                       &lifespan->shutdown_future);
4821624Smax.romanov@nginx.com }
4831624Smax.romanov@nginx.com 
4841624Smax.romanov@nginx.com 
4851624Smax.romanov@nginx.com static PyObject *
4861624Smax.romanov@nginx.com nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future)
4871624Smax.romanov@nginx.com {
4881624Smax.romanov@nginx.com     PyObject                *res;
4891624Smax.romanov@nginx.com     nxt_py_asgi_lifespan_t  *lifespan;
4901624Smax.romanov@nginx.com 
4911624Smax.romanov@nginx.com     nxt_unit_debug(NULL, "asgi_lifespan_done");
4921624Smax.romanov@nginx.com 
4931624Smax.romanov@nginx.com     lifespan = (nxt_py_asgi_lifespan_t *) self;
4941624Smax.romanov@nginx.com 
4951624Smax.romanov@nginx.com     if (lifespan->startup_sent == 0) {
4961624Smax.romanov@nginx.com         lifespan->disabled = 1;
4971624Smax.romanov@nginx.com     }
4981624Smax.romanov@nginx.com 
4991624Smax.romanov@nginx.com     /*
5001624Smax.romanov@nginx.com      * Get Future.result() and it raises an exception, if coroutine exited
5011624Smax.romanov@nginx.com      * with exception.
5021624Smax.romanov@nginx.com      */
5031624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_result_str, NULL);
5041624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
5051624Smax.romanov@nginx.com         nxt_unit_log(NULL, NXT_UNIT_LOG_INFO,
5061624Smax.romanov@nginx.com                      "ASGI Lifespan processing exception");
5071624Smax.romanov@nginx.com         nxt_python_print_exception();
5081624Smax.romanov@nginx.com     }
5091624Smax.romanov@nginx.com 
5101624Smax.romanov@nginx.com     Py_XDECREF(res);
5111624Smax.romanov@nginx.com 
5121624Smax.romanov@nginx.com     if (lifespan->startup_future != NULL) {
5131624Smax.romanov@nginx.com         future = lifespan->startup_future;
5141624Smax.romanov@nginx.com         lifespan->startup_future = NULL;
5151624Smax.romanov@nginx.com 
5161624Smax.romanov@nginx.com         res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str,
5171624Smax.romanov@nginx.com                                          Py_None, NULL);
5181624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
5191624Smax.romanov@nginx.com             nxt_unit_alert(NULL, "Failed to call 'future.set_result'");
5201624Smax.romanov@nginx.com             nxt_python_print_exception();
5211624Smax.romanov@nginx.com         }
5221624Smax.romanov@nginx.com 
5231624Smax.romanov@nginx.com         Py_XDECREF(res);
5241624Smax.romanov@nginx.com         Py_DECREF(future);
5251624Smax.romanov@nginx.com     }
5261624Smax.romanov@nginx.com 
5271624Smax.romanov@nginx.com     if (lifespan->shutdown_future != NULL) {
5281624Smax.romanov@nginx.com         future = lifespan->shutdown_future;
5291624Smax.romanov@nginx.com         lifespan->shutdown_future = NULL;
5301624Smax.romanov@nginx.com 
5311624Smax.romanov@nginx.com         res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str,
5321624Smax.romanov@nginx.com                                          Py_None, NULL);
5331624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
5341624Smax.romanov@nginx.com             nxt_unit_alert(NULL, "Failed to call 'future.set_result'");
5351624Smax.romanov@nginx.com             nxt_python_print_exception();
5361624Smax.romanov@nginx.com         }
5371624Smax.romanov@nginx.com 
5381624Smax.romanov@nginx.com         Py_XDECREF(res);
5391624Smax.romanov@nginx.com         Py_DECREF(future);
5401624Smax.romanov@nginx.com     }
5411624Smax.romanov@nginx.com 
5421624Smax.romanov@nginx.com     Py_RETURN_NONE;
5431624Smax.romanov@nginx.com }
5441624Smax.romanov@nginx.com 
5451624Smax.romanov@nginx.com 
5461624Smax.romanov@nginx.com #endif /* NXT_HAVE_ASGI */
547