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