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