xref: /unit/src/python/nxt_python.c (revision 1932:ee35fc1dca33)
1 
2 /*
3  * Copyright (C) NGINX, Inc.
4  */
5 
6 
7 #include <Python.h>
8 
9 #include <nxt_main.h>
10 #include <nxt_router.h>
11 #include <nxt_unit.h>
12 
13 #include <python/nxt_python.h>
14 
15 #include NXT_PYTHON_MOUNTS_H
16 
17 
18 typedef struct {
19     pthread_t       thread;
20     nxt_unit_ctx_t  *ctx;
21     void            *ctx_data;
22 } nxt_py_thread_info_t;
23 
24 
25 static nxt_int_t nxt_python_start(nxt_task_t *task,
26     nxt_process_data_t *data);
27 static nxt_int_t nxt_python_set_target(nxt_task_t *task,
28     nxt_python_target_t *target, nxt_conf_value_t *conf);
29 static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value);
30 static int nxt_python_init_threads(nxt_python_app_conf_t *c);
31 static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx);
32 static void *nxt_python_thread_func(void *main_ctx);
33 static void nxt_python_join_threads(nxt_unit_ctx_t *ctx,
34     nxt_python_app_conf_t *c);
35 static void nxt_python_atexit(void);
36 
37 static uint32_t  compat[] = {
38     NXT_VERNUM, NXT_DEBUG,
39 };
40 
41 
42 NXT_EXPORT nxt_app_module_t  nxt_app_module = {
43     sizeof(compat),
44     compat,
45     nxt_string("python"),
46     PY_VERSION,
47     nxt_python_mounts,
48     nxt_nitems(nxt_python_mounts),
49     NULL,
50     nxt_python_start,
51 };
52 
53 static PyObject           *nxt_py_stderr_flush;
54 nxt_python_targets_t      *nxt_py_targets;
55 
56 #if PY_MAJOR_VERSION == 3
57 static wchar_t            *nxt_py_home;
58 #else
59 static char               *nxt_py_home;
60 #endif
61 
62 static pthread_attr_t        *nxt_py_thread_attr;
63 static nxt_py_thread_info_t  *nxt_py_threads;
64 static nxt_python_proto_t    nxt_py_proto;
65 
66 
67 static nxt_int_t
68 nxt_python_start(nxt_task_t *task, nxt_process_data_t *data)
69 {
70     int                    rc;
71     size_t                 len, size;
72     uint32_t               next;
73     PyObject               *obj, *module;
74     nxt_str_t              proto, probe_proto, name;
75     nxt_int_t              ret, n, i;
76     nxt_unit_ctx_t         *unit_ctx;
77     nxt_unit_init_t        python_init;
78     nxt_conf_value_t       *cv;
79     nxt_python_targets_t   *targets;
80     nxt_common_app_conf_t  *app_conf;
81     nxt_python_app_conf_t  *c;
82 #if PY_MAJOR_VERSION == 3
83     char                   *path;
84     nxt_int_t              pep405;
85 
86     static const char pyvenv[] = "/pyvenv.cfg";
87     static const char bin_python[] = "/bin/python";
88 #endif
89 
90     static const nxt_str_t  wsgi = nxt_string("wsgi");
91     static const nxt_str_t  asgi = nxt_string("asgi");
92 
93     app_conf = data->app;
94     c = &app_conf->u.python;
95 
96     if (c->home != NULL) {
97         len = nxt_strlen(c->home);
98 
99 #if PY_MAJOR_VERSION == 3
100 
101         path = nxt_malloc(len + sizeof(pyvenv));
102         if (nxt_slow_path(path == NULL)) {
103             nxt_alert(task, "Failed to allocate memory");
104             return NXT_ERROR;
105         }
106 
107         nxt_memcpy(path, c->home, len);
108         nxt_memcpy(path + len, pyvenv, sizeof(pyvenv));
109 
110         pep405 = (access(path, R_OK) == 0);
111 
112         nxt_free(path);
113 
114         if (pep405) {
115             size = (len + sizeof(bin_python)) * sizeof(wchar_t);
116 
117         } else {
118             size = (len + 1) * sizeof(wchar_t);
119         }
120 
121         nxt_py_home = nxt_malloc(size);
122         if (nxt_slow_path(nxt_py_home == NULL)) {
123             nxt_alert(task, "Failed to allocate memory");
124             return NXT_ERROR;
125         }
126 
127         if (pep405) {
128             mbstowcs(nxt_py_home, c->home, len);
129             mbstowcs(nxt_py_home + len, bin_python, sizeof(bin_python));
130             Py_SetProgramName(nxt_py_home);
131 
132         } else {
133             mbstowcs(nxt_py_home, c->home, len + 1);
134             Py_SetPythonHome(nxt_py_home);
135         }
136 
137 #else
138         nxt_py_home = nxt_malloc(len + 1);
139         if (nxt_slow_path(nxt_py_home == NULL)) {
140             nxt_alert(task, "Failed to allocate memory");
141             return NXT_ERROR;
142         }
143 
144         nxt_memcpy(nxt_py_home, c->home, len + 1);
145         Py_SetPythonHome(nxt_py_home);
146 #endif
147     }
148 
149     Py_InitializeEx(0);
150 
151 #if PY_VERSION_HEX < NXT_PYTHON_VER(3, 7)
152     if (c->threads > 1) {
153         PyEval_InitThreads();
154     }
155 #endif
156 
157     module = NULL;
158     obj = NULL;
159 
160     python_init.ctx_data = NULL;
161 
162     obj = PySys_GetObject((char *) "stderr");
163     if (nxt_slow_path(obj == NULL)) {
164         nxt_alert(task, "Python failed to get \"sys.stderr\" object");
165         goto fail;
166     }
167 
168     nxt_py_stderr_flush = PyObject_GetAttrString(obj, "flush");
169 
170     /* obj is a Borrowed reference. */
171     obj = NULL;
172 
173     if (nxt_slow_path(nxt_py_stderr_flush == NULL)) {
174         nxt_alert(task, "Python failed to get \"flush\" attribute of "
175                         "\"sys.stderr\" object");
176         goto fail;
177     }
178 
179     if (nxt_slow_path(nxt_python_set_path(task, c->path) != NXT_OK)) {
180         goto fail;
181     }
182 
183     obj = Py_BuildValue("[s]", "unit");
184     if (nxt_slow_path(obj == NULL)) {
185         nxt_alert(task, "Python failed to create the \"sys.argv\" list");
186         goto fail;
187     }
188 
189     if (nxt_slow_path(PySys_SetObject((char *) "argv", obj) != 0)) {
190         nxt_alert(task, "Python failed to set the \"sys.argv\" list");
191         goto fail;
192     }
193 
194     Py_CLEAR(obj);
195 
196     n = (c->targets != NULL ? nxt_conf_object_members_count(c->targets) : 1);
197 
198     size = sizeof(nxt_python_targets_t) + n * sizeof(nxt_python_target_t);
199 
200     targets = nxt_unit_malloc(NULL, size);
201     if (nxt_slow_path(targets == NULL)) {
202         nxt_alert(task, "Could not allocate targets");
203         goto fail;
204     }
205 
206     memset(targets, 0, size);
207 
208     targets->count = n;
209     nxt_py_targets = targets;
210 
211     if (c->targets != NULL) {
212         next = 0;
213 
214         for (i = 0; /* void */; i++) {
215             cv = nxt_conf_next_object_member(c->targets, &name, &next);
216             if (cv == NULL) {
217                 break;
218             }
219 
220             ret = nxt_python_set_target(task, &targets->target[i], cv);
221             if (nxt_slow_path(ret != NXT_OK)) {
222                 goto fail;
223             }
224         }
225 
226     } else {
227         ret = nxt_python_set_target(task, &targets->target[0], app_conf->self);
228         if (nxt_slow_path(ret != NXT_OK)) {
229             goto fail;
230         }
231     }
232 
233     nxt_unit_default_init(task, &python_init);
234 
235     python_init.data = c;
236     python_init.shm_limit = data->app->shm_limit;
237     python_init.callbacks.ready_handler = nxt_python_ready_handler;
238 
239     proto = c->protocol;
240 
241     if (proto.length == 0) {
242         proto = nxt_python_asgi_check(targets->target[0].application)
243                 ? asgi : wsgi;
244 
245         for (i = 1; i < targets->count; i++) {
246             probe_proto = nxt_python_asgi_check(targets->target[i].application)
247                           ? asgi : wsgi;
248             if (probe_proto.start != proto.start) {
249                 nxt_alert(task, "A mix of ASGI & WSGI targets is forbidden, "
250                                 "specify protocol in config if incorrect");
251                 goto fail;
252             }
253         }
254     }
255 
256     if (nxt_strstr_eq(&proto, &asgi)) {
257         rc = nxt_python_asgi_init(&python_init, &nxt_py_proto);
258 
259     } else {
260         rc = nxt_python_wsgi_init(&python_init, &nxt_py_proto);
261     }
262 
263     if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
264         goto fail;
265     }
266 
267     rc = nxt_py_proto.ctx_data_alloc(&python_init.ctx_data, 1);
268     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
269         goto fail;
270     }
271 
272     rc = nxt_python_init_threads(c);
273     if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
274         goto fail;
275     }
276 
277     if (nxt_py_proto.startup != NULL) {
278         if (nxt_py_proto.startup(python_init.ctx_data) != NXT_UNIT_OK) {
279             goto fail;
280         }
281     }
282 
283     unit_ctx = nxt_unit_init(&python_init);
284     if (nxt_slow_path(unit_ctx == NULL)) {
285         goto fail;
286     }
287 
288     rc = nxt_py_proto.run(unit_ctx);
289 
290     nxt_python_join_threads(unit_ctx, c);
291 
292     nxt_unit_done(unit_ctx);
293 
294     nxt_py_proto.ctx_data_free(python_init.ctx_data);
295 
296     nxt_python_atexit();
297 
298     exit(rc);
299 
300     return NXT_OK;
301 
302 fail:
303 
304     nxt_python_join_threads(NULL, c);
305 
306     if (python_init.ctx_data != NULL) {
307         nxt_py_proto.ctx_data_free(python_init.ctx_data);
308     }
309 
310     Py_XDECREF(obj);
311     Py_XDECREF(module);
312 
313     nxt_python_atexit();
314 
315     return NXT_ERROR;
316 }
317 
318 
319 static nxt_int_t
320 nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
321     nxt_conf_value_t *conf)
322 {
323     char              *callable, *module_name;
324     PyObject          *module, *obj;
325     nxt_str_t         str;
326     nxt_conf_value_t  *value;
327 
328     static nxt_str_t  module_str = nxt_string("module");
329     static nxt_str_t  callable_str = nxt_string("callable");
330 
331     module = obj = NULL;
332 
333     value = nxt_conf_get_object_member(conf, &module_str, NULL);
334     if (nxt_slow_path(value == NULL)) {
335         goto fail;
336     }
337 
338     nxt_conf_get_string(value, &str);
339 
340     module_name = nxt_alloca(str.length + 1);
341     nxt_memcpy(module_name, str.start, str.length);
342     module_name[str.length] = '\0';
343 
344     module = PyImport_ImportModule(module_name);
345     if (nxt_slow_path(module == NULL)) {
346         nxt_alert(task, "Python failed to import module \"%s\"", module_name);
347         nxt_python_print_exception();
348         goto fail;
349     }
350 
351     value = nxt_conf_get_object_member(conf, &callable_str, NULL);
352     if (value == NULL) {
353         callable = nxt_alloca(12);
354         nxt_memcpy(callable, "application", 12);
355 
356     } else {
357         nxt_conf_get_string(value, &str);
358 
359         callable = nxt_alloca(str.length + 1);
360         nxt_memcpy(callable, str.start, str.length);
361         callable[str.length] = '\0';
362     }
363 
364     obj = PyDict_GetItemString(PyModule_GetDict(module), callable);
365     if (nxt_slow_path(obj == NULL)) {
366         nxt_alert(task, "Python failed to get \"%s\" from module \"%s\"",
367                   callable, module_name);
368         goto fail;
369     }
370 
371     if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
372         nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object",
373                   callable, module_name);
374         goto fail;
375     }
376 
377     target->application = obj;
378     obj = NULL;
379 
380     Py_INCREF(target->application);
381     Py_CLEAR(module);
382 
383     return NXT_OK;
384 
385 fail:
386 
387     Py_XDECREF(obj);
388     Py_XDECREF(module);
389 
390     return NXT_ERROR;
391 }
392 
393 
394 static nxt_int_t
395 nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value)
396 {
397     int               ret;
398     PyObject          *path, *sys;
399     nxt_str_t         str;
400     nxt_uint_t        n;
401     nxt_conf_value_t  *array;
402 
403     if (value == NULL) {
404         return NXT_OK;
405     }
406 
407     sys = PySys_GetObject((char *) "path");
408     if (nxt_slow_path(sys == NULL)) {
409         nxt_alert(task, "Python failed to get \"sys.path\" list");
410         return NXT_ERROR;
411     }
412 
413     /* sys is a Borrowed reference. */
414 
415     if (nxt_conf_type(value) == NXT_CONF_STRING) {
416         n = 0;
417         goto value_is_string;
418     }
419 
420     /* NXT_CONF_ARRAY */
421     array = value;
422 
423     n = nxt_conf_array_elements_count(array);
424 
425     while (n != 0) {
426         n--;
427 
428         /*
429          * Insertion in front of existing paths starting from the last element
430          * to preserve original order while giving priority to the values
431          * specified in the "path" option.
432          */
433 
434         value = nxt_conf_get_array_element(array, n);
435 
436     value_is_string:
437 
438         nxt_conf_get_string(value, &str);
439 
440         path = PyString_FromStringAndSize((char *) str.start, str.length);
441         if (nxt_slow_path(path == NULL)) {
442             nxt_alert(task, "Python failed to create string object \"%V\"",
443                       &str);
444             return NXT_ERROR;
445         }
446 
447         ret = PyList_Insert(sys, 0, path);
448 
449         Py_DECREF(path);
450 
451         if (nxt_slow_path(ret != 0)) {
452             nxt_alert(task, "Python failed to insert \"%V\" into \"sys.path\"",
453                       &str);
454             return NXT_ERROR;
455         }
456     }
457 
458     return NXT_OK;
459 }
460 
461 
462 static int
463 nxt_python_init_threads(nxt_python_app_conf_t *c)
464 {
465     int                    res;
466     uint32_t               i;
467     nxt_py_thread_info_t   *ti;
468     static pthread_attr_t  attr;
469 
470     if (c->threads <= 1) {
471         return NXT_UNIT_OK;
472     }
473 
474     if (c->thread_stack_size > 0) {
475         res = pthread_attr_init(&attr);
476         if (nxt_slow_path(res != 0)) {
477             nxt_unit_alert(NULL, "thread attr init failed: %s (%d)",
478                            strerror(res), res);
479 
480             return NXT_UNIT_ERROR;
481         }
482 
483         res = pthread_attr_setstacksize(&attr, c->thread_stack_size);
484         if (nxt_slow_path(res != 0)) {
485             nxt_unit_alert(NULL, "thread attr set stack size failed: %s (%d)",
486                            strerror(res), res);
487 
488             return NXT_UNIT_ERROR;
489         }
490 
491         nxt_py_thread_attr = &attr;
492     }
493 
494     nxt_py_threads = nxt_unit_malloc(NULL, sizeof(nxt_py_thread_info_t)
495                                            * (c->threads - 1));
496     if (nxt_slow_path(nxt_py_threads == NULL)) {
497         nxt_unit_alert(NULL, "Failed to allocate thread info array");
498 
499         return NXT_UNIT_ERROR;
500     }
501 
502     memset(nxt_py_threads, 0, sizeof(nxt_py_thread_info_t) * (c->threads - 1));
503 
504     for (i = 0; i < c->threads - 1; i++) {
505         ti = &nxt_py_threads[i];
506 
507         res = nxt_py_proto.ctx_data_alloc(&ti->ctx_data, 0);
508         if (nxt_slow_path(res != NXT_UNIT_OK)) {
509             return NXT_UNIT_ERROR;
510         }
511     }
512 
513     return NXT_UNIT_OK;
514 }
515 
516 
517 static int
518 nxt_python_ready_handler(nxt_unit_ctx_t *ctx)
519 {
520     int                    res;
521     uint32_t               i;
522     nxt_py_thread_info_t   *ti;
523     nxt_python_app_conf_t  *c;
524 
525     if (nxt_py_proto.ready != NULL) {
526         res = nxt_py_proto.ready(ctx);
527         if (nxt_slow_path(res != NXT_UNIT_OK)) {
528             return NXT_UNIT_ERROR;
529         }
530     }
531 
532     /* Worker thread context. */
533     if (!nxt_unit_is_main_ctx(ctx)) {
534         return NXT_UNIT_OK;
535     }
536 
537     c = ctx->unit->data;
538 
539     if (c->threads <= 1) {
540         return NXT_UNIT_OK;
541     }
542 
543     for (i = 0; i < c->threads - 1; i++) {
544         ti = &nxt_py_threads[i];
545 
546         ti->ctx = ctx;
547 
548         res = pthread_create(&ti->thread, nxt_py_thread_attr,
549                              nxt_python_thread_func, ti);
550 
551         if (nxt_fast_path(res == 0)) {
552             nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1));
553 
554         } else {
555             nxt_unit_alert(ctx, "thread #%d create failed: %s (%d)",
556                            (int) (i + 1), strerror(res), res);
557         }
558     }
559 
560     return NXT_UNIT_OK;
561 }
562 
563 
564 static void *
565 nxt_python_thread_func(void *data)
566 {
567     nxt_unit_ctx_t        *ctx;
568     PyGILState_STATE      gstate;
569     nxt_py_thread_info_t  *ti;
570 
571     ti = data;
572 
573     nxt_unit_debug(ti->ctx, "worker thread #%d start",
574                    (int) (ti - nxt_py_threads + 1));
575 
576     gstate = PyGILState_Ensure();
577 
578     if (nxt_py_proto.startup != NULL) {
579         if (nxt_py_proto.startup(ti->ctx_data) != NXT_UNIT_OK) {
580             goto fail;
581         }
582     }
583 
584     ctx = nxt_unit_ctx_alloc(ti->ctx, ti->ctx_data);
585     if (nxt_slow_path(ctx == NULL)) {
586         goto fail;
587     }
588 
589     (void) nxt_py_proto.run(ctx);
590 
591     nxt_unit_done(ctx);
592 
593 fail:
594 
595     PyGILState_Release(gstate);
596 
597     nxt_unit_debug(NULL, "worker thread #%d end",
598                    (int) (ti - nxt_py_threads + 1));
599 
600     return NULL;
601 }
602 
603 
604 static void
605 nxt_python_join_threads(nxt_unit_ctx_t *ctx, nxt_python_app_conf_t *c)
606 {
607     int                   res;
608     uint32_t              i;
609     PyThreadState         *thread_state;
610     nxt_py_thread_info_t  *ti;
611 
612     if (nxt_py_threads == NULL) {
613         return;
614     }
615 
616     thread_state = PyEval_SaveThread();
617 
618     for (i = 0; i < c->threads - 1; i++) {
619         ti = &nxt_py_threads[i];
620 
621         if ((uintptr_t) ti->thread == 0) {
622             continue;
623         }
624 
625         res = pthread_join(ti->thread, NULL);
626 
627         if (nxt_fast_path(res == 0)) {
628             nxt_unit_debug(ctx, "thread #%d joined", (int) (i + 1));
629 
630         } else {
631             nxt_unit_alert(ctx, "thread #%d join failed: %s (%d)",
632                            (int) (i + 1), strerror(res), res);
633         }
634     }
635 
636     PyEval_RestoreThread(thread_state);
637 
638     for (i = 0; i < c->threads - 1; i++) {
639         ti = &nxt_py_threads[i];
640 
641         if (ti->ctx_data != NULL) {
642             nxt_py_proto.ctx_data_free(ti->ctx_data);
643         }
644     }
645 
646     nxt_unit_free(NULL, nxt_py_threads);
647 }
648 
649 
650 int
651 nxt_python_init_strings(nxt_python_string_t *pstr)
652 {
653     PyObject  *obj;
654 
655     while (pstr->string.start != NULL) {
656         obj = PyString_FromStringAndSize((char *) pstr->string.start,
657                                          pstr->string.length);
658         if (nxt_slow_path(obj == NULL)) {
659             return NXT_UNIT_ERROR;
660         }
661 
662         PyUnicode_InternInPlace(&obj);
663 
664         *pstr->object_p = obj;
665 
666         pstr++;
667     }
668 
669     return NXT_UNIT_OK;
670 }
671 
672 
673 void
674 nxt_python_done_strings(nxt_python_string_t *pstr)
675 {
676     PyObject  *obj;
677 
678     while (pstr->string.start != NULL) {
679         obj = *pstr->object_p;
680 
681         Py_XDECREF(obj);
682         *pstr->object_p = NULL;
683 
684         pstr++;
685     }
686 }
687 
688 
689 static void
690 nxt_python_atexit(void)
691 {
692     nxt_int_t  i;
693 
694     if (nxt_py_proto.done != NULL) {
695         nxt_py_proto.done();
696     }
697 
698     Py_XDECREF(nxt_py_stderr_flush);
699 
700     if (nxt_py_targets != NULL) {
701         for (i = 0; i < nxt_py_targets->count; i++) {
702             Py_XDECREF(nxt_py_targets->target[i].application);
703         }
704 
705         nxt_unit_free(NULL, nxt_py_targets);
706     }
707 
708     Py_Finalize();
709 
710     if (nxt_py_home != NULL) {
711         nxt_free(nxt_py_home);
712     }
713 }
714 
715 
716 void
717 nxt_python_print_exception(void)
718 {
719     PyErr_Print();
720 
721 #if PY_MAJOR_VERSION == 3
722     /* The backtrace may be buffered in sys.stderr file object. */
723     {
724         PyObject  *result;
725 
726         result = PyObject_CallFunction(nxt_py_stderr_flush, NULL);
727         if (nxt_slow_path(result == NULL)) {
728             PyErr_Clear();
729             return;
730         }
731 
732         Py_DECREF(result);
733     }
734 #endif
735 }
736