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
nxt_python_start(nxt_task_t * task,nxt_process_data_t * data)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, data->app);
234 
235     python_init.data = c;
236     python_init.callbacks.ready_handler = nxt_python_ready_handler;
237 
238     proto = c->protocol;
239 
240     if (proto.length == 0) {
241         proto = nxt_python_asgi_check(targets->target[0].application)
242                 ? asgi : wsgi;
243 
244         for (i = 1; i < targets->count; i++) {
245             probe_proto = nxt_python_asgi_check(targets->target[i].application)
246                           ? asgi : wsgi;
247             if (probe_proto.start != proto.start) {
248                 nxt_alert(task, "A mix of ASGI & WSGI targets is forbidden, "
249                                 "specify protocol in config if incorrect");
250                 goto fail;
251             }
252         }
253     }
254 
255     if (nxt_strstr_eq(&proto, &asgi)) {
256         rc = nxt_python_asgi_init(&python_init, &nxt_py_proto);
257 
258     } else {
259         rc = nxt_python_wsgi_init(&python_init, &nxt_py_proto);
260     }
261 
262     if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
263         goto fail;
264     }
265 
266     rc = nxt_py_proto.ctx_data_alloc(&python_init.ctx_data, 1);
267     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
268         goto fail;
269     }
270 
271     rc = nxt_python_init_threads(c);
272     if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
273         goto fail;
274     }
275 
276     if (nxt_py_proto.startup != NULL) {
277         if (nxt_py_proto.startup(python_init.ctx_data) != NXT_UNIT_OK) {
278             goto fail;
279         }
280     }
281 
282     unit_ctx = nxt_unit_init(&python_init);
283     if (nxt_slow_path(unit_ctx == NULL)) {
284         goto fail;
285     }
286 
287     rc = nxt_py_proto.run(unit_ctx);
288 
289     nxt_python_join_threads(unit_ctx, c);
290 
291     nxt_unit_done(unit_ctx);
292 
293     nxt_py_proto.ctx_data_free(python_init.ctx_data);
294 
295     nxt_python_atexit();
296 
297     exit(rc);
298 
299     return NXT_OK;
300 
301 fail:
302 
303     nxt_python_join_threads(NULL, c);
304 
305     if (python_init.ctx_data != NULL) {
306         nxt_py_proto.ctx_data_free(python_init.ctx_data);
307     }
308 
309     Py_XDECREF(obj);
310     Py_XDECREF(module);
311 
312     nxt_python_atexit();
313 
314     return NXT_ERROR;
315 }
316 
317 
318 static nxt_int_t
nxt_python_set_target(nxt_task_t * task,nxt_python_target_t * target,nxt_conf_value_t * conf)319 nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
320     nxt_conf_value_t *conf)
321 {
322     char              *callable, *module_name;
323     PyObject          *module, *obj;
324     nxt_str_t         str;
325     nxt_conf_value_t  *value;
326 
327     static nxt_str_t  module_str = nxt_string("module");
328     static nxt_str_t  callable_str = nxt_string("callable");
329 
330     module = obj = NULL;
331 
332     value = nxt_conf_get_object_member(conf, &module_str, NULL);
333     if (nxt_slow_path(value == NULL)) {
334         goto fail;
335     }
336 
337     nxt_conf_get_string(value, &str);
338 
339     module_name = nxt_alloca(str.length + 1);
340     nxt_memcpy(module_name, str.start, str.length);
341     module_name[str.length] = '\0';
342 
343     module = PyImport_ImportModule(module_name);
344     if (nxt_slow_path(module == NULL)) {
345         nxt_alert(task, "Python failed to import module \"%s\"", module_name);
346         nxt_python_print_exception();
347         goto fail;
348     }
349 
350     value = nxt_conf_get_object_member(conf, &callable_str, NULL);
351     if (value == NULL) {
352         callable = nxt_alloca(12);
353         nxt_memcpy(callable, "application", 12);
354 
355     } else {
356         nxt_conf_get_string(value, &str);
357 
358         callable = nxt_alloca(str.length + 1);
359         nxt_memcpy(callable, str.start, str.length);
360         callable[str.length] = '\0';
361     }
362 
363     obj = PyDict_GetItemString(PyModule_GetDict(module), callable);
364     if (nxt_slow_path(obj == NULL)) {
365         nxt_alert(task, "Python failed to get \"%s\" from module \"%s\"",
366                   callable, module_name);
367         goto fail;
368     }
369 
370     if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
371         nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object",
372                   callable, module_name);
373         goto fail;
374     }
375 
376     target->application = obj;
377     obj = NULL;
378 
379     Py_INCREF(target->application);
380     Py_CLEAR(module);
381 
382     return NXT_OK;
383 
384 fail:
385 
386     Py_XDECREF(obj);
387     Py_XDECREF(module);
388 
389     return NXT_ERROR;
390 }
391 
392 
393 static nxt_int_t
nxt_python_set_path(nxt_task_t * task,nxt_conf_value_t * value)394 nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value)
395 {
396     int               ret;
397     PyObject          *path, *sys;
398     nxt_str_t         str;
399     nxt_uint_t        n;
400     nxt_conf_value_t  *array;
401 
402     if (value == NULL) {
403         return NXT_OK;
404     }
405 
406     sys = PySys_GetObject((char *) "path");
407     if (nxt_slow_path(sys == NULL)) {
408         nxt_alert(task, "Python failed to get \"sys.path\" list");
409         return NXT_ERROR;
410     }
411 
412     /* sys is a Borrowed reference. */
413 
414     if (nxt_conf_type(value) == NXT_CONF_STRING) {
415         n = 0;
416         goto value_is_string;
417     }
418 
419     /* NXT_CONF_ARRAY */
420     array = value;
421 
422     n = nxt_conf_array_elements_count(array);
423 
424     while (n != 0) {
425         n--;
426 
427         /*
428          * Insertion in front of existing paths starting from the last element
429          * to preserve original order while giving priority to the values
430          * specified in the "path" option.
431          */
432 
433         value = nxt_conf_get_array_element(array, n);
434 
435     value_is_string:
436 
437         nxt_conf_get_string(value, &str);
438 
439         path = PyString_FromStringAndSize((char *) str.start, str.length);
440         if (nxt_slow_path(path == NULL)) {
441             nxt_alert(task, "Python failed to create string object \"%V\"",
442                       &str);
443             return NXT_ERROR;
444         }
445 
446         ret = PyList_Insert(sys, 0, path);
447 
448         Py_DECREF(path);
449 
450         if (nxt_slow_path(ret != 0)) {
451             nxt_alert(task, "Python failed to insert \"%V\" into \"sys.path\"",
452                       &str);
453             return NXT_ERROR;
454         }
455     }
456 
457     return NXT_OK;
458 }
459 
460 
461 static int
nxt_python_init_threads(nxt_python_app_conf_t * c)462 nxt_python_init_threads(nxt_python_app_conf_t *c)
463 {
464     int                    res;
465     uint32_t               i;
466     nxt_py_thread_info_t   *ti;
467     static pthread_attr_t  attr;
468 
469     if (c->threads <= 1) {
470         return NXT_UNIT_OK;
471     }
472 
473     if (c->thread_stack_size > 0) {
474         res = pthread_attr_init(&attr);
475         if (nxt_slow_path(res != 0)) {
476             nxt_unit_alert(NULL, "thread attr init failed: %s (%d)",
477                            strerror(res), res);
478 
479             return NXT_UNIT_ERROR;
480         }
481 
482         res = pthread_attr_setstacksize(&attr, c->thread_stack_size);
483         if (nxt_slow_path(res != 0)) {
484             nxt_unit_alert(NULL, "thread attr set stack size failed: %s (%d)",
485                            strerror(res), res);
486 
487             return NXT_UNIT_ERROR;
488         }
489 
490         nxt_py_thread_attr = &attr;
491     }
492 
493     nxt_py_threads = nxt_unit_malloc(NULL, sizeof(nxt_py_thread_info_t)
494                                            * (c->threads - 1));
495     if (nxt_slow_path(nxt_py_threads == NULL)) {
496         nxt_unit_alert(NULL, "Failed to allocate thread info array");
497 
498         return NXT_UNIT_ERROR;
499     }
500 
501     memset(nxt_py_threads, 0, sizeof(nxt_py_thread_info_t) * (c->threads - 1));
502 
503     for (i = 0; i < c->threads - 1; i++) {
504         ti = &nxt_py_threads[i];
505 
506         res = nxt_py_proto.ctx_data_alloc(&ti->ctx_data, 0);
507         if (nxt_slow_path(res != NXT_UNIT_OK)) {
508             return NXT_UNIT_ERROR;
509         }
510     }
511 
512     return NXT_UNIT_OK;
513 }
514 
515 
516 static int
nxt_python_ready_handler(nxt_unit_ctx_t * ctx)517 nxt_python_ready_handler(nxt_unit_ctx_t *ctx)
518 {
519     int                    res;
520     uint32_t               i;
521     nxt_py_thread_info_t   *ti;
522     nxt_python_app_conf_t  *c;
523 
524     c = ctx->unit->data;
525 
526     if (c->threads <= 1) {
527         return NXT_UNIT_OK;
528     }
529 
530     for (i = 0; i < c->threads - 1; i++) {
531         ti = &nxt_py_threads[i];
532 
533         ti->ctx = ctx;
534 
535         res = pthread_create(&ti->thread, nxt_py_thread_attr,
536                              nxt_python_thread_func, ti);
537 
538         if (nxt_fast_path(res == 0)) {
539             nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1));
540 
541         } else {
542             nxt_unit_alert(ctx, "thread #%d create failed: %s (%d)",
543                            (int) (i + 1), strerror(res), res);
544         }
545     }
546 
547     return NXT_UNIT_OK;
548 }
549 
550 
551 static void *
nxt_python_thread_func(void * data)552 nxt_python_thread_func(void *data)
553 {
554     nxt_unit_ctx_t        *ctx;
555     PyGILState_STATE      gstate;
556     nxt_py_thread_info_t  *ti;
557 
558     ti = data;
559 
560     nxt_unit_debug(ti->ctx, "worker thread #%d start",
561                    (int) (ti - nxt_py_threads + 1));
562 
563     gstate = PyGILState_Ensure();
564 
565     if (nxt_py_proto.startup != NULL) {
566         if (nxt_py_proto.startup(ti->ctx_data) != NXT_UNIT_OK) {
567             goto fail;
568         }
569     }
570 
571     ctx = nxt_unit_ctx_alloc(ti->ctx, ti->ctx_data);
572     if (nxt_slow_path(ctx == NULL)) {
573         goto fail;
574     }
575 
576     (void) nxt_py_proto.run(ctx);
577 
578     nxt_unit_done(ctx);
579 
580 fail:
581 
582     PyGILState_Release(gstate);
583 
584     nxt_unit_debug(NULL, "worker thread #%d end",
585                    (int) (ti - nxt_py_threads + 1));
586 
587     return NULL;
588 }
589 
590 
591 static void
nxt_python_join_threads(nxt_unit_ctx_t * ctx,nxt_python_app_conf_t * c)592 nxt_python_join_threads(nxt_unit_ctx_t *ctx, nxt_python_app_conf_t *c)
593 {
594     int                   res;
595     uint32_t              i;
596     PyThreadState         *thread_state;
597     nxt_py_thread_info_t  *ti;
598 
599     if (nxt_py_threads == NULL) {
600         return;
601     }
602 
603     thread_state = PyEval_SaveThread();
604 
605     for (i = 0; i < c->threads - 1; i++) {
606         ti = &nxt_py_threads[i];
607 
608         if ((uintptr_t) ti->thread == 0) {
609             continue;
610         }
611 
612         res = pthread_join(ti->thread, NULL);
613 
614         if (nxt_fast_path(res == 0)) {
615             nxt_unit_debug(ctx, "thread #%d joined", (int) (i + 1));
616 
617         } else {
618             nxt_unit_alert(ctx, "thread #%d join failed: %s (%d)",
619                            (int) (i + 1), strerror(res), res);
620         }
621     }
622 
623     PyEval_RestoreThread(thread_state);
624 
625     for (i = 0; i < c->threads - 1; i++) {
626         ti = &nxt_py_threads[i];
627 
628         if (ti->ctx_data != NULL) {
629             nxt_py_proto.ctx_data_free(ti->ctx_data);
630         }
631     }
632 
633     nxt_unit_free(NULL, nxt_py_threads);
634 }
635 
636 
637 int
nxt_python_init_strings(nxt_python_string_t * pstr)638 nxt_python_init_strings(nxt_python_string_t *pstr)
639 {
640     PyObject  *obj;
641 
642     while (pstr->string.start != NULL) {
643         obj = PyString_FromStringAndSize((char *) pstr->string.start,
644                                          pstr->string.length);
645         if (nxt_slow_path(obj == NULL)) {
646             return NXT_UNIT_ERROR;
647         }
648 
649         PyUnicode_InternInPlace(&obj);
650 
651         *pstr->object_p = obj;
652 
653         pstr++;
654     }
655 
656     return NXT_UNIT_OK;
657 }
658 
659 
660 void
nxt_python_done_strings(nxt_python_string_t * pstr)661 nxt_python_done_strings(nxt_python_string_t *pstr)
662 {
663     PyObject  *obj;
664 
665     while (pstr->string.start != NULL) {
666         obj = *pstr->object_p;
667 
668         Py_XDECREF(obj);
669         *pstr->object_p = NULL;
670 
671         pstr++;
672     }
673 }
674 
675 
676 static void
nxt_python_atexit(void)677 nxt_python_atexit(void)
678 {
679     nxt_int_t  i;
680 
681     if (nxt_py_proto.done != NULL) {
682         nxt_py_proto.done();
683     }
684 
685     Py_XDECREF(nxt_py_stderr_flush);
686 
687     if (nxt_py_targets != NULL) {
688         for (i = 0; i < nxt_py_targets->count; i++) {
689             Py_XDECREF(nxt_py_targets->target[i].application);
690         }
691 
692         nxt_unit_free(NULL, nxt_py_targets);
693     }
694 
695     Py_Finalize();
696 
697     if (nxt_py_home != NULL) {
698         nxt_free(nxt_py_home);
699     }
700 }
701 
702 
703 void
nxt_python_print_exception(void)704 nxt_python_print_exception(void)
705 {
706     PyErr_Print();
707 
708 #if PY_MAJOR_VERSION == 3
709     /* The backtrace may be buffered in sys.stderr file object. */
710     {
711         PyObject  *result;
712 
713         result = PyObject_CallFunction(nxt_py_stderr_flush, NULL);
714         if (nxt_slow_path(result == NULL)) {
715             PyErr_Clear();
716             return;
717         }
718 
719         Py_DECREF(result);
720     }
721 #endif
722 }
723