xref: /unit/src/python/nxt_python_asgi_lifespan.c (revision 1624:e46b1b422545)
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