mod-wsgi-standalone 6.0.0.dev4__tar.gz → 6.0.0.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/PKG-INFO +1 -1
  2. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/PKG-INFO +1 -1
  3. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/diagnostics/environ.py +16 -19
  4. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/apache.py +9 -0
  5. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/options.py +11 -0
  6. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_daemon.c +187 -0
  7. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_interp.c +85 -83
  8. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_interp.h +13 -0
  9. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_logger.c +308 -0
  10. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_logger.h +7 -4
  11. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_metrics.c +123 -3
  12. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_metrics.h +9 -5
  13. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_module.c +12 -3
  14. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_module.h +1 -0
  15. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_server.c +1 -1
  16. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_version.h +1 -1
  17. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/CREDITS.rst +0 -0
  18. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/LICENSE +0 -0
  19. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/MANIFEST.in +0 -0
  20. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/Makefile.in +0 -0
  21. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/README-express.rst +0 -0
  22. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/README-standalone.rst +0 -0
  23. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/README.rst +0 -0
  24. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/configure +0 -0
  25. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/configure.ac +0 -0
  26. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/images/__init__.py +0 -0
  27. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/images/snake-whiskey.jpg +0 -0
  28. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/SOURCES.txt +0 -0
  29. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/dependency_links.txt +0 -0
  30. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/entry_points.txt +0 -0
  31. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/not-zip-safe +0 -0
  32. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/requires.txt +0 -0
  33. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/top_level.txt +0 -0
  34. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/pyproject.toml +0 -0
  35. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/setup.cfg +0 -0
  36. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/setup.py +0 -0
  37. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/__init__.py +0 -0
  38. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/diagnostics/__init__.py +0 -0
  39. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/diagnostics/hello.py +0 -0
  40. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/__init__.py +0 -0
  41. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/cli.py +0 -0
  42. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/management/__init__.py +0 -0
  43. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/management/commands/__init__.py +0 -0
  44. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/management/commands/runmodwsgi.py +0 -0
  45. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/platform.py +0 -0
  46. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/reloader.py +0 -0
  47. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/runtime.py +0 -0
  48. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/scripts.py +0 -0
  49. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/express/server.py +0 -0
  50. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/__init__.py +0 -0
  51. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/management/__init__.py +0 -0
  52. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/management/commands/__init__.py +0 -0
  53. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/management/commands/runmodwsgi.py +0 -0
  54. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/mod_wsgi.c +0 -0
  55. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_adapter.c +0 -0
  56. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_adapter.h +0 -0
  57. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_apache.c +0 -0
  58. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_apache.h +0 -0
  59. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_auth.c +0 -0
  60. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_auth.h +0 -0
  61. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_buckets.c +0 -0
  62. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_buckets.h +0 -0
  63. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_config.c +0 -0
  64. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_config.h +0 -0
  65. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_convert.c +0 -0
  66. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_convert.h +0 -0
  67. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_daemon.h +0 -0
  68. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_dispatch.c +0 -0
  69. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_dispatch.h +0 -0
  70. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_environ.c +0 -0
  71. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_environ.h +0 -0
  72. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_execute.c +0 -0
  73. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_execute.h +0 -0
  74. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_gc.c +0 -0
  75. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_gc.h +0 -0
  76. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_input.c +0 -0
  77. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_input.h +0 -0
  78. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_memory.c +0 -0
  79. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_memory.h +0 -0
  80. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_python.h +0 -0
  81. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_remote.c +0 -0
  82. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_remote.h +0 -0
  83. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_restrict.c +0 -0
  84. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_restrict.h +0 -0
  85. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_server.h +0 -0
  86. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_shutdown.c +0 -0
  87. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_shutdown.h +0 -0
  88. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_signal.c +0 -0
  89. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_signal.h +0 -0
  90. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_stream.c +0 -0
  91. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_stream.h +0 -0
  92. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_telemetry.c +0 -0
  93. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_telemetry.h +0 -0
  94. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_thread.c +0 -0
  95. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_thread.h +0 -0
  96. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_validate.c +0 -0
  97. {mod_wsgi_standalone-6.0.0.dev4 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_validate.h +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mod_wsgi-standalone
3
- Version: 6.0.0.dev4
3
+ Version: 6.0.0.dev5
4
4
  Summary: Installer for Apache/mod_wsgi.
5
5
  Home-page: https://www.modwsgi.org/
6
6
  Author: Graham Dumpleton
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mod_wsgi-standalone
3
- Version: 6.0.0.dev4
3
+ Version: 6.0.0.dev5
4
4
  Summary: Installer for Apache/mod_wsgi.
5
5
  Home-page: https://www.modwsgi.org/
6
6
  Author: Graham Dumpleton
@@ -15,10 +15,16 @@ def application(environ, start_response):
15
15
  input = environ["wsgi.input"]
16
16
  output = StringIO()
17
17
 
18
- print(f"PID: {os.getpid()}", file=output)
19
- print(f"UID: {os.getuid()}", file=output)
20
- print(f"GID: {os.getgid()}", file=output)
21
- print(f"CWD: {os.getcwd()}", file=output)
18
+ if os.name != "nt":
19
+ print(f"PID: {os.getpid()}", file=output)
20
+ print(f"UID: {os.getuid()}", file=output)
21
+ print(f"GID: {os.getgid()}", file=output)
22
+ print(f"CWD: {os.getcwd()}", file=output)
23
+ print(file=output)
24
+
25
+ print(f"STDOUT: {sys.stdout.name}", file=output)
26
+ print(f"STDERR: {sys.stderr.name}", file=output)
27
+ print(f'ERRORS: {environ["wsgi.errors"].name}', file=output)
22
28
  print(file=output)
23
29
 
24
30
  print(f"python.version: {sys.version!r}", file=output)
@@ -36,19 +42,8 @@ def application(environ, start_response):
36
42
 
37
43
  print(f"mod_wsgi.maximum_processes: {mod_wsgi.maximum_processes}", file=output)
38
44
  print(f"mod_wsgi.threads_per_process: {mod_wsgi.threads_per_process}", file=output)
39
- print(f"mod_wsgi.process_metrics: {mod_wsgi.process_metrics()}", file=output)
40
- print(f"mod_wsgi.server_metrics: {mod_wsgi.server_metrics()}", file=output)
41
45
  print(file=output)
42
46
 
43
- metrics = mod_wsgi.server_metrics()
44
-
45
- if metrics:
46
- for process in metrics["processes"]:
47
- for worker in process["workers"]:
48
- print(worker["status"], file=output, end="")
49
- print(file=output)
50
- print(file=output)
51
-
52
47
  print(f"apache.description: {apache.description}", file=output)
53
48
  print(f"apache.build_date: {apache.build_date}", file=output)
54
49
  print(f"apache.mpm_name: {apache.mpm_name}", file=output)
@@ -56,16 +51,18 @@ def application(environ, start_response):
56
51
  print(f"apache.threads_per_process: {apache.threads_per_process}", file=output)
57
52
  print(file=output)
58
53
 
59
- print(f"PATH: {sys.path}", file=output)
54
+ print(f'PATH: {os.environ.get("PATH")}', file=output)
60
55
  print(file=output)
61
56
 
62
- print(f"LANG: {os.environ.get('LANG')}", file=output)
63
- print(f"LC_ALL: {os.environ.get('LC_ALL')}", file=output)
57
+ print(f'LANG: {os.environ.get("LANG")}', file=output)
58
+ print(f'LC_ALL: {os.environ.get("LC_ALL")}', file=output)
64
59
  print(f"sys.getdefaultencoding(): {sys.getdefaultencoding()}", file=output)
65
60
  print(f"sys.getfilesystemencoding(): {sys.getfilesystemencoding()}", file=output)
66
61
  print(f"locale.getlocale(): {locale.getlocale()}", file=output)
67
62
  print(f"locale.getdefaultlocale(): {locale.getdefaultlocale()}", file=output)
68
- print(f"locale.getpreferredencoding(): {locale.getpreferredencoding()}", file=output)
63
+ print(
64
+ f"locale.getpreferredencoding(): {locale.getpreferredencoding()}", file=output
65
+ )
69
66
  print(file=output)
70
67
 
71
68
  for key in sorted(environ):
@@ -816,6 +816,10 @@ APACHE_SETENV_CONFIG = """
816
816
  SetEnv '%(name)s' '%(value)s'
817
817
  """
818
818
 
819
+ APACHE_PYTHON_WARNINGS_CONFIG = """
820
+ WSGIPythonWarnings '%(spec)s'
821
+ """
822
+
819
823
  APACHE_PASSENV_CONFIG = """
820
824
  PassEnv '%(name)s'
821
825
  """
@@ -982,6 +986,11 @@ def generate_apache_config(options):
982
986
  print(APACHE_SETENV_CONFIG % dict(name=name, value=value),
983
987
  file=fp)
984
988
 
989
+ if options['python_warnings']:
990
+ for spec in options['python_warnings']:
991
+ print(APACHE_PYTHON_WARNINGS_CONFIG % dict(spec=spec),
992
+ file=fp)
993
+
985
994
  if options['passenv_variables']:
986
995
  for name in options['passenv_variables']:
987
996
  print(APACHE_PASSENV_CONFIG % dict(name=name), file=fp)
@@ -751,6 +751,17 @@ add_option('all', '--python-eggs', metavar='DIRECTORY-PATH',
751
751
  'unpacking of Python eggs. Defaults to a sub directory of '
752
752
  'the server root directory.')
753
753
 
754
+ add_option('all', '--python-warnings', action='append', default=[],
755
+ metavar='SPEC', help='Add an entry to the Python warnings filter '
756
+ 'using the standard -W syntax (action:message:category:module:lineno). '
757
+ 'May be specified multiple times; each entry is emitted as a '
758
+ 'separate WSGIPythonWarnings directive in the generated '
759
+ 'configuration. The most common form is just an action: '
760
+ 'default, error, always, module, once, or ignore. For example '
761
+ '--python-warnings error converts every warning into an '
762
+ 'exception, which is useful for catching deprecation regressions '
763
+ 'in CI.')
764
+
754
765
  add_option('unix', '--shell-executable', default=SHELL,
755
766
  metavar='FILE-PATH', help='Override the path to the shell '
756
767
  'used in the \'apachectl\' script. The \'bash\' shell will '
@@ -886,6 +886,23 @@ const char *wsgi_set_accept_mutex(cmd_parms *cmd, void *mconfig,
886
886
  static apr_file_t *wsgi_signal_pipe_in = NULL;
887
887
  static apr_file_t *wsgi_signal_pipe_out = NULL;
888
888
 
889
+ /*
890
+ * Dedicated pipe carrying SIGHUP / SIGUSR2 bytes from the signal
891
+ * handler to the signal dispatcher thread. Independent of the
892
+ * shutdown pipe above so that long-running subscriber callbacks
893
+ * cannot delay the daemon main thread's detection of shutdown
894
+ * signals. Write end is set non-blocking after creation so the
895
+ * signal handler can never deadlock on a full pipe buffer; the
896
+ * trade-off is that signals arriving faster than subscribers
897
+ * consume them are silently dropped, which matches the
898
+ * not-refcounted semantics of Unix signals themselves.
899
+ */
900
+
901
+ static apr_file_t *wsgi_signal_event_pipe_in = NULL;
902
+ static apr_file_t *wsgi_signal_event_pipe_out = NULL;
903
+
904
+ static apr_thread_t *wsgi_signal_dispatcher = NULL;
905
+
889
906
  static void wsgi_signal_handler(int signum)
890
907
  {
891
908
  apr_size_t nbytes = 1;
@@ -909,6 +926,22 @@ static void wsgi_signal_handler(int signum)
909
926
  apr_file_write(wsgi_signal_pipe_out, "C", &nbytes);
910
927
  apr_file_flush(wsgi_signal_pipe_out);
911
928
  }
929
+ else if (signum == SIGHUP)
930
+ {
931
+ if (wsgi_signal_event_pipe_out)
932
+ {
933
+ apr_file_write(wsgi_signal_event_pipe_out, "H", &nbytes);
934
+ apr_file_flush(wsgi_signal_event_pipe_out);
935
+ }
936
+ }
937
+ else if (signum == SIGUSR2)
938
+ {
939
+ if (wsgi_signal_event_pipe_out)
940
+ {
941
+ apr_file_write(wsgi_signal_event_pipe_out, "U", &nbytes);
942
+ apr_file_flush(wsgi_signal_event_pipe_out);
943
+ }
944
+ }
912
945
  else
913
946
  {
914
947
  wsgi_daemon_shutdown++;
@@ -918,6 +951,69 @@ static void wsgi_signal_handler(int signum)
918
951
  }
919
952
  }
920
953
 
954
+ static void *wsgi_signal_dispatcher_thread(apr_thread_t *thd, void *data)
955
+ {
956
+ WSGIDaemonProcess *daemon = data;
957
+
958
+ apr_pollfd_t poll_fd;
959
+ apr_int32_t poll_count = 0;
960
+ apr_status_t rv;
961
+
962
+ poll_fd.desc_type = APR_POLL_FILE;
963
+ poll_fd.reqevents = APR_POLLIN;
964
+ poll_fd.desc.f = wsgi_signal_event_pipe_in;
965
+
966
+ while (1)
967
+ {
968
+ char buf[1];
969
+ apr_size_t nbytes = 1;
970
+ int signum;
971
+ const char *signame;
972
+
973
+ rv = apr_poll(&poll_fd, 1, &poll_count, -1);
974
+ if (APR_STATUS_IS_EINTR(rv))
975
+ continue;
976
+ if (rv != APR_SUCCESS)
977
+ break;
978
+
979
+ rv = apr_file_read(wsgi_signal_event_pipe_in, buf, &nbytes);
980
+
981
+ if (APR_STATUS_IS_EOF(rv))
982
+ break;
983
+
984
+ if (rv != APR_SUCCESS || nbytes != 1)
985
+ {
986
+ if (!wsgi_daemon_shutdown)
987
+ {
988
+ wsgi_log_error(APLOG_ALERT, 0, wsgi_server, WSGI_APLOGNO(0208) "Read failed on signal event pipe in "
989
+ "daemon process '%s'; signal dispatcher "
990
+ "exiting.",
991
+ daemon->group->name);
992
+ }
993
+ break;
994
+ }
995
+
996
+ switch (buf[0])
997
+ {
998
+ case 'H':
999
+ signum = SIGHUP;
1000
+ signame = "SIGHUP";
1001
+ break;
1002
+ case 'U':
1003
+ signum = SIGUSR2;
1004
+ signame = "SIGUSR2";
1005
+ break;
1006
+ default:
1007
+ /* Unknown tag byte; defensive skip. */
1008
+ continue;
1009
+ }
1010
+
1011
+ wsgi_publish_process_signal(signum, signame);
1012
+ }
1013
+
1014
+ return NULL;
1015
+ }
1016
+
921
1017
  static void wsgi_exit_daemon_process(int status)
922
1018
  {
923
1019
  if (wsgi_server && wsgi_daemon_group)
@@ -2991,6 +3087,25 @@ static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon)
2991
3087
  }
2992
3088
  }
2993
3089
 
3090
+ /*
3091
+ * Spawn the signal dispatcher thread that drains the signal
3092
+ * event pipe and publishes process_signal events to registered
3093
+ * Python subscribers. Created unconditionally since subscribers
3094
+ * can register at any point during the daemon's lifetime.
3095
+ */
3096
+
3097
+ rv = apr_thread_create(&wsgi_signal_dispatcher, thread_attr,
3098
+ wsgi_signal_dispatcher_thread, daemon, p);
3099
+
3100
+ if (rv != APR_SUCCESS)
3101
+ {
3102
+ wsgi_log_error(APLOG_ERR, rv, wsgi_server, WSGI_APLOGNO(0207) "Unable to create signal dispatcher thread in "
3103
+ "daemon process '%s'; SIGHUP and SIGUSR2 signals "
3104
+ "will not be delivered to mod_wsgi.subscribe_signals "
3105
+ "subscribers.",
3106
+ daemon->group->name);
3107
+ }
3108
+
2994
3109
  /*
2995
3110
  * Start telemetry reporter if configured. Skip for service-script
2996
3111
  * daemons (threads=0): they handle no requests, so there is
@@ -3254,6 +3369,40 @@ static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon)
3254
3369
  }
3255
3370
  }
3256
3371
 
3372
+ /*
3373
+ * Stop the signal dispatcher before publishing process_stopping
3374
+ * so the dispatcher is not still inside a sub-interpreter GIL
3375
+ * acquire when the shutdown publish walks the same interpreter
3376
+ * table. Ignoring SIGHUP/SIGUSR2 first means no new bytes can
3377
+ * appear in the pipe; closing the write end then makes the
3378
+ * dispatcher's pending read return EOF and exit cleanly. The
3379
+ * join is best-effort: if a subscriber callback is wedged the
3380
+ * shutdown-timeout reaper still bounds the total wait via
3381
+ * exit().
3382
+ */
3383
+
3384
+ apr_signal(SIGHUP, SIG_IGN);
3385
+ apr_signal(SIGUSR2, SIG_IGN);
3386
+
3387
+ if (wsgi_signal_event_pipe_out)
3388
+ {
3389
+ apr_file_close(wsgi_signal_event_pipe_out);
3390
+ wsgi_signal_event_pipe_out = NULL;
3391
+ }
3392
+
3393
+ if (wsgi_signal_dispatcher)
3394
+ {
3395
+ rv = apr_thread_join(&thread_rv, wsgi_signal_dispatcher);
3396
+ if (rv != APR_SUCCESS)
3397
+ {
3398
+ wsgi_log_error(APLOG_WARNING, rv, wsgi_server,
3399
+ "Unable to join with signal dispatcher thread "
3400
+ "in daemon process '%s' during shutdown.",
3401
+ daemon->group->name);
3402
+ }
3403
+ wsgi_signal_dispatcher = NULL;
3404
+ }
3405
+
3257
3406
  /*
3258
3407
  * If shutting down process due to reaching request time
3259
3408
  * limit, then try and dump out stack traces of any threads
@@ -3546,6 +3695,31 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
3546
3695
  wsgi_daemon_init_failure_exit();
3547
3696
  }
3548
3697
 
3698
+ /*
3699
+ * Dedicated pipe for the application-facing signal events
3700
+ * (SIGHUP, SIGUSR2). Separate from the shutdown signal pipe
3701
+ * so that the daemon main thread reading shutdown bytes is
3702
+ * never delayed by subscriber callback work. Write end is
3703
+ * set non-blocking so the in-signal-handler write cannot
3704
+ * deadlock if subscribers are slow enough to fill the
3705
+ * kernel pipe buffer.
3706
+ */
3707
+
3708
+ status = apr_file_pipe_create(&wsgi_signal_event_pipe_in,
3709
+ &wsgi_signal_event_pipe_out, p);
3710
+
3711
+ if (status != APR_SUCCESS)
3712
+ {
3713
+ wsgi_log_error(APLOG_ALERT, status, wsgi_server, WSGI_APLOGNO(0206) "Unable to initialise signal event pipe in "
3714
+ "daemon process '%s'. Daemon process will "
3715
+ "exit and be restarted after a delay.",
3716
+ daemon->group->name);
3717
+
3718
+ wsgi_daemon_init_failure_exit();
3719
+ }
3720
+
3721
+ apr_file_pipe_timeout_set(wsgi_signal_event_pipe_out, 0);
3722
+
3549
3723
  wsgi_daemon_shutdown = 0;
3550
3724
 
3551
3725
  wsgi_daemon_pid = getpid();
@@ -3559,6 +3733,19 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
3559
3733
  apr_signal(SIGXCPU, wsgi_signal_handler);
3560
3734
  #endif
3561
3735
 
3736
+ /*
3737
+ * Application-facing signal handlers. SIGHUP and SIGUSR2
3738
+ * are not used by Apache for child process management
3739
+ * (Apache uses SIGHUP at the parent only, and translates
3740
+ * parent shutdown to SIGTERM/SIGUSR1 for the daemon); they
3741
+ * are routed through the signal event pipe to the
3742
+ * dispatcher thread which calls registered Python
3743
+ * subscribers via mod_wsgi.subscribe_signals.
3744
+ */
3745
+
3746
+ apr_signal(SIGHUP, wsgi_signal_handler);
3747
+ apr_signal(SIGUSR2, wsgi_signal_handler);
3748
+
3562
3749
  /* Set limits on amount of CPU time that can be used. */
3563
3750
 
3564
3751
  if (daemon->group->cpu_time_limit > 0)
@@ -1687,8 +1687,6 @@ failure:
1687
1687
  static void wsgi_destroy_interpreter(InterpreterObject *self)
1688
1688
  {
1689
1689
  PyThreadState *tstate = NULL;
1690
- PyObject *module = NULL;
1691
- PyObject *exitfunc = NULL;
1692
1690
 
1693
1691
  PyThreadState *tstate_enter = NULL;
1694
1692
 
@@ -1769,87 +1767,6 @@ static void wsgi_destroy_interpreter(InterpreterObject *self)
1769
1767
  wsgi_server->process->pool));
1770
1768
  }
1771
1769
 
1772
- #if 0
1773
- /*
1774
- * Disabled: Py_EndInterpreter on Python 3.7+ runs the
1775
- * threading._shutdown atexit hook itself during sub-interpreter
1776
- * teardown, so the destroy thread no longer needs to be
1777
- * pre-registered in threading._active for that hook to find a
1778
- * Thread handle. Left in #if 0 so the original rationale and
1779
- * call shape are recoverable if a regression turns up.
1780
- */
1781
-
1782
- module = PyImport_ImportModule("threading");
1783
-
1784
- if (!module)
1785
- PyErr_Clear();
1786
-
1787
- if (module)
1788
- {
1789
- PyObject *dict = NULL;
1790
- PyObject *func = NULL;
1791
-
1792
- dict = PyModule_GetDict(module);
1793
- func = PyDict_GetItemString(dict, "current_thread");
1794
- if (func)
1795
- {
1796
- PyObject *res = NULL;
1797
- Py_INCREF(func);
1798
- res = PyObject_CallObject(func, (PyObject *)NULL);
1799
- if (!res)
1800
- {
1801
- PyErr_Clear();
1802
- }
1803
- Py_XDECREF(res);
1804
- Py_DECREF(func);
1805
- }
1806
- }
1807
-
1808
- Py_XDECREF(module);
1809
- #endif
1810
-
1811
- #if 0
1812
- /*
1813
- * Disabled: Py_EndInterpreter on Python 3.7+ calls
1814
- * _PyAtExit_Call on the sub-interpreter itself, so driving
1815
- * atexit._run_exitfuncs here just runs every callback twice
1816
- * (atexit does not deregister as it calls). Left in #if 0 so
1817
- * the original call shape is recoverable.
1818
- */
1819
-
1820
- module = NULL;
1821
-
1822
- if (is_sub)
1823
- {
1824
- module = PyImport_ImportModule("atexit");
1825
-
1826
- if (module)
1827
- {
1828
- PyObject *dict = NULL;
1829
-
1830
- dict = PyModule_GetDict(module);
1831
- exitfunc = PyDict_GetItemString(dict, "_run_exitfuncs");
1832
- }
1833
- else
1834
- PyErr_Clear();
1835
- }
1836
-
1837
- if (exitfunc)
1838
- {
1839
- PyObject *res = NULL;
1840
- Py_INCREF(exitfunc);
1841
- res = PyObject_CallObject(exitfunc, (PyObject *)NULL);
1842
-
1843
- if (res == NULL)
1844
- wsgi_log_python_interp_atexit_error(self->name);
1845
-
1846
- Py_XDECREF(res);
1847
- Py_DECREF(exitfunc);
1848
- }
1849
-
1850
- Py_XDECREF(module);
1851
- #endif
1852
-
1853
1770
  if (is_sub)
1854
1771
  {
1855
1772
  /*
@@ -2883,6 +2800,91 @@ void wsgi_publish_process_stopping(char *reason)
2883
2800
  }
2884
2801
  }
2885
2802
 
2803
+ void wsgi_publish_process_signal(int signum, const char *signame)
2804
+ {
2805
+ apr_pool_t *pool = NULL;
2806
+ apr_array_header_t *names = NULL;
2807
+ apr_hash_index_t *hi;
2808
+ apr_status_t rv;
2809
+ int i;
2810
+
2811
+ /*
2812
+ * Snapshot the interpreter name table under wsgi_interp_lock.
2813
+ * Worker threads may grow the table concurrently via
2814
+ * wsgi_acquire_interpreter, so iterating it directly is unsafe;
2815
+ * a snapshot lets us release the lock before acquiring any
2816
+ * sub-interpreter GIL (acquire then takes wsgi_interp_lock again
2817
+ * internally, which would deadlock if we still held it here).
2818
+ */
2819
+
2820
+ rv = apr_pool_create(&pool, NULL);
2821
+ if (rv != APR_SUCCESS)
2822
+ return;
2823
+
2824
+ apr_thread_mutex_lock(wsgi_interp_lock);
2825
+
2826
+ names = apr_array_make(pool, apr_hash_count(wsgi_interpreters),
2827
+ sizeof(const char *));
2828
+
2829
+ for (hi = apr_hash_first(pool, wsgi_interpreters); hi;
2830
+ hi = apr_hash_next(hi))
2831
+ {
2832
+ const void *key;
2833
+ apr_hash_this(hi, &key, NULL, NULL);
2834
+ APR_ARRAY_PUSH(names, const char *) = apr_pstrdup(pool,
2835
+ (const char *)key);
2836
+ }
2837
+
2838
+ apr_thread_mutex_unlock(wsgi_interp_lock);
2839
+
2840
+ for (i = 0; i < names->nelts; i++)
2841
+ {
2842
+ const char *name = APR_ARRAY_IDX(names, i, const char *);
2843
+ InterpreterObject *interp = NULL;
2844
+ PyObject *event = NULL;
2845
+ PyObject *signame_obj = NULL;
2846
+ PyObject *signum_obj = NULL;
2847
+ int ok = 0;
2848
+
2849
+ interp = wsgi_acquire_interpreter((char *)name);
2850
+
2851
+ if (!interp)
2852
+ continue;
2853
+
2854
+ event = PyDict_New();
2855
+ signame_obj = PyUnicode_FromString(signame);
2856
+ signum_obj = PyLong_FromLong(signum);
2857
+
2858
+ if (event && signame_obj && signum_obj &&
2859
+ PyDict_SetItemString(event, "signame", signame_obj) == 0 &&
2860
+ PyDict_SetItemString(event, "signum", signum_obj) == 0)
2861
+ {
2862
+ wsgi_publish_event("process_signal", event);
2863
+ ok = 1;
2864
+ }
2865
+
2866
+ Py_XDECREF(signum_obj);
2867
+ Py_XDECREF(signame_obj);
2868
+ Py_XDECREF(event);
2869
+
2870
+ if (!ok)
2871
+ {
2872
+ wsgi_log_error_locked(APLOG_ERR, 0, wsgi_server,
2873
+ WSGI_APLOGNO(0209) "Unable to publish "
2874
+ "'process_signal' event "
2875
+ "for %s.",
2876
+ wsgi_format_interp_context(
2877
+ wsgi_server->process->pool, NULL,
2878
+ name));
2879
+ PyErr_Clear();
2880
+ }
2881
+
2882
+ wsgi_release_interpreter(interp);
2883
+ }
2884
+
2885
+ apr_pool_destroy(pool);
2886
+ }
2887
+
2886
2888
  /* ------------------------------------------------------------------------- */
2887
2889
 
2888
2890
  /*
@@ -119,6 +119,19 @@ extern int wsgi_interpreter_exists(const char *name);
119
119
 
120
120
  extern void wsgi_publish_process_stopping(char *reason);
121
121
 
122
+ /*
123
+ * Publish a "process_signal" event into every existing sub-interpreter
124
+ * in this process. Snapshots the interpreter name table under
125
+ * wsgi_interp_lock so the walk is safe against concurrent creation by
126
+ * worker threads, then for each name acquires the interpreter (which
127
+ * takes the GIL for that interpreter), constructs an event dict with
128
+ * signame (canonical str like "SIGHUP") and signum (int) payload keys,
129
+ * and dispatches via wsgi_publish_event. Intended to be called only
130
+ * from the signal dispatcher thread in daemon mode.
131
+ */
132
+
133
+ extern void wsgi_publish_process_signal(int signum, const char *signame);
134
+
122
135
  extern apr_status_t wsgi_python_child_init(apr_pool_t *p);
123
136
 
124
137
  /* ------------------------------------------------------------------------- */