mod-wsgi-standalone 6.0.0.dev3__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.dev3 → mod_wsgi_standalone-6.0.0.dev5}/PKG-INFO +1 -1
  2. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/PKG-INFO +1 -1
  3. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/diagnostics/environ.py +16 -19
  4. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/apache.py +13 -0
  5. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/options.py +35 -2
  6. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/scripts.py +21 -0
  7. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/server.py +52 -1
  8. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_daemon.c +195 -2
  9. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_interp.c +85 -83
  10. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_interp.h +13 -0
  11. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_logger.c +308 -0
  12. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_logger.h +7 -4
  13. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_metrics.c +123 -3
  14. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_metrics.h +9 -5
  15. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_module.c +12 -3
  16. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_module.h +1 -0
  17. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_server.c +1 -1
  18. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_version.h +1 -1
  19. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/CREDITS.rst +0 -0
  20. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/LICENSE +0 -0
  21. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/MANIFEST.in +0 -0
  22. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/Makefile.in +0 -0
  23. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/README-express.rst +0 -0
  24. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/README-standalone.rst +0 -0
  25. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/README.rst +0 -0
  26. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/configure +0 -0
  27. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/configure.ac +0 -0
  28. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/images/__init__.py +0 -0
  29. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/images/snake-whiskey.jpg +0 -0
  30. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/SOURCES.txt +0 -0
  31. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/dependency_links.txt +0 -0
  32. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/entry_points.txt +0 -0
  33. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/not-zip-safe +0 -0
  34. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/requires.txt +0 -0
  35. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/mod_wsgi_standalone.egg-info/top_level.txt +0 -0
  36. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/pyproject.toml +0 -0
  37. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/setup.cfg +0 -0
  38. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/setup.py +0 -0
  39. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/__init__.py +0 -0
  40. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/diagnostics/__init__.py +0 -0
  41. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/diagnostics/hello.py +0 -0
  42. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/__init__.py +0 -0
  43. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/cli.py +0 -0
  44. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/management/__init__.py +0 -0
  45. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/management/commands/__init__.py +0 -0
  46. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/management/commands/runmodwsgi.py +0 -0
  47. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/platform.py +0 -0
  48. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/reloader.py +0 -0
  49. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/express/runtime.py +0 -0
  50. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/__init__.py +0 -0
  51. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/management/__init__.py +0 -0
  52. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/management/commands/__init__.py +0 -0
  53. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/management/commands/runmodwsgi.py +0 -0
  54. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/mod_wsgi.c +0 -0
  55. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_adapter.c +0 -0
  56. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_adapter.h +0 -0
  57. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_apache.c +0 -0
  58. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_apache.h +0 -0
  59. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_auth.c +0 -0
  60. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_auth.h +0 -0
  61. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_buckets.c +0 -0
  62. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_buckets.h +0 -0
  63. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_config.c +0 -0
  64. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_config.h +0 -0
  65. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_convert.c +0 -0
  66. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_convert.h +0 -0
  67. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_daemon.h +0 -0
  68. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_dispatch.c +0 -0
  69. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_dispatch.h +0 -0
  70. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_environ.c +0 -0
  71. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_environ.h +0 -0
  72. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_execute.c +0 -0
  73. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_execute.h +0 -0
  74. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_gc.c +0 -0
  75. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_gc.h +0 -0
  76. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_input.c +0 -0
  77. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_input.h +0 -0
  78. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_memory.c +0 -0
  79. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_memory.h +0 -0
  80. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_python.h +0 -0
  81. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_remote.c +0 -0
  82. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_remote.h +0 -0
  83. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_restrict.c +0 -0
  84. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_restrict.h +0 -0
  85. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_server.h +0 -0
  86. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_shutdown.c +0 -0
  87. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_shutdown.h +0 -0
  88. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_signal.c +0 -0
  89. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_signal.h +0 -0
  90. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_stream.c +0 -0
  91. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_stream.h +0 -0
  92. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_telemetry.c +0 -0
  93. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_telemetry.h +0 -0
  94. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_thread.c +0 -0
  95. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_thread.h +0 -0
  96. {mod_wsgi_standalone-6.0.0.dev3 → mod_wsgi_standalone-6.0.0.dev5}/src/server/wsgi_validate.c +0 -0
  97. {mod_wsgi_standalone-6.0.0.dev3 → 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.dev3
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.dev3
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):
@@ -317,6 +317,10 @@ WSGISlowRequests %(slow_requests)s
317
317
  WSGISwitchInterval %(switch_interval)s
318
318
  </IfDefine>
319
319
 
320
+ <IfDefine MOD_WSGI_FREE_THREADING>
321
+ WSGIFreeThreading On
322
+ </IfDefine>
323
+
320
324
  <IfDefine MOD_WSGI_TELEMETRY_OPTIONS>
321
325
  %(telemetry_options)s
322
326
  </IfDefine>
@@ -812,6 +816,10 @@ APACHE_SETENV_CONFIG = """
812
816
  SetEnv '%(name)s' '%(value)s'
813
817
  """
814
818
 
819
+ APACHE_PYTHON_WARNINGS_CONFIG = """
820
+ WSGIPythonWarnings '%(spec)s'
821
+ """
822
+
815
823
  APACHE_PASSENV_CONFIG = """
816
824
  PassEnv '%(name)s'
817
825
  """
@@ -978,6 +986,11 @@ def generate_apache_config(options):
978
986
  print(APACHE_SETENV_CONFIG % dict(name=name, value=value),
979
987
  file=fp)
980
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
+
981
994
  if options['passenv_variables']:
982
995
  for name in options['passenv_variables']:
983
996
  print(APACHE_PASSENV_CONFIG % dict(name=name), file=fp)
@@ -540,10 +540,25 @@ add_option('all', '--telemetry-service', metavar='TARGET',
540
540
  'ingester). Remote "udp:host:port" targets are not supported. '
541
541
  'Off by default.')
542
542
 
543
+ add_option('unix', '--enable-telemetry', action='store_true', default=False,
544
+ help='Bundle a telemetry ingester and web UI alongside the WSGI '
545
+ 'application. Generates a service-script daemon that runs the '
546
+ 'mod_wsgi-telemetry ingester on a UNIX socket inside the server '
547
+ 'root and emits WSGITelemetryService pointing at that socket so '
548
+ 'the WSGI processes report to it. Requires the mod_wsgi-telemetry '
549
+ 'package to be installed. Mutually exclusive with '
550
+ '--telemetry-service.')
551
+
552
+ add_option('unix', '--telemetry-ui-port', type='int', default=8888,
553
+ metavar='NUMBER', help='HTTP port the telemetry web UI binds to '
554
+ 'on 127.0.0.1 when --enable-telemetry is set. Defaults to '
555
+ '%default. Two express instances on the same host need distinct '
556
+ 'ports to avoid a bind collision.')
557
+
543
558
  add_option('all', '--telemetry-interval', type='float', default=1.0,
544
559
  metavar='SECONDS', help='Metrics reporter sampling interval '
545
- 'in seconds. Only applies when --telemetry-service is set. '
546
- 'Defaults to %default.')
560
+ 'in seconds. Only applies when --telemetry-service or '
561
+ '--enable-telemetry is set. Defaults to %default.')
547
562
 
548
563
  add_option('all', '--slow-requests', type='float', default=None,
549
564
  metavar='SECONDS', help='Enable slow-request reporting and set '
@@ -558,6 +573,13 @@ add_option('all', '--switch-interval', type='float', default=None,
558
573
  'switch-interval option on WSGIDaemonProcess. Defaults to '
559
574
  'Python\'s built-in 0.005 (5 ms) when unset.')
560
575
 
576
+ add_option('all', '--free-threading', action='store_true', default=False,
577
+ help='Emit WSGIFreeThreading On in the generated configuration to '
578
+ 'run the Python interpreter without the GIL. Requires a Python '
579
+ 'build with free-threading support (PEP 703, --disable-gil); '
580
+ 'mod_wsgi-express will exit with an error if the running Python '
581
+ 'does not support it.')
582
+
561
583
  add_option('all', '--telemetry-options', action='append', default=[],
562
584
  metavar='ARGS', help='Apache-Options-style metrics-capture '
563
585
  'toggle, passed verbatim to a WSGITelemetryOptions directive in '
@@ -729,6 +751,17 @@ add_option('all', '--python-eggs', metavar='DIRECTORY-PATH',
729
751
  'unpacking of Python eggs. Defaults to a sub directory of '
730
752
  'the server root directory.')
731
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
+
732
765
  add_option('unix', '--shell-executable', default=SHELL,
733
766
  metavar='FILE-PATH', help='Override the path to the shell '
734
767
  'used in the \'apachectl\' script. The \'bash\' shell will '
@@ -166,6 +166,27 @@ def generate_wsgi_handler_script(options):
166
166
  with open(path, 'w') as fp:
167
167
  print(WSGI_DEFAULT_SCRIPT % options, file=fp)
168
168
 
169
+ TELEMETRY_SERVICE_SCRIPT = '''
170
+ # Auto-generated by mod_wsgi-express --enable-telemetry. Runs the
171
+ # mod_wsgi-telemetry ingester and web UI inside a WSGIDaemonProcess
172
+ # threads=0 service daemon. The WSGI processes report to the UNIX
173
+ # socket below via the WSGITelemetryService directive in httpd.conf.
174
+
175
+ from mod_wsgi.telemetry.server import main
176
+
177
+ main([
178
+ "--listen", "unix:%(telemetry_socket)s",
179
+ "--http-host", "127.0.0.1",
180
+ "--http-port", "%(telemetry_ui_port)s",
181
+ "--log-level", "INFO",
182
+ ])
183
+ '''
184
+
185
+ def generate_telemetry_service_script(options):
186
+ path = os.path.join(options['server_root'], 'telemetry-service.py')
187
+ with open(path, 'w') as fp:
188
+ print(TELEMETRY_SERVICE_SCRIPT % options, file=fp)
189
+
169
190
  WSGI_CONTROL_SCRIPT = """
170
191
  #!%(shell_executable)s
171
192
 
@@ -5,12 +5,16 @@ import math
5
5
  import os
6
6
  import posixpath
7
7
  import sys
8
+ import sysconfig
8
9
  import tempfile
9
10
 
10
11
  from . import apxs_config
11
12
  from .platform import find_program, MOD_WSGI_SO, PYTHON_DYLIB
12
13
  from .apache import generate_apache_config
13
- from .scripts import generate_wsgi_handler_script, generate_control_scripts
14
+ from .scripts import (
15
+ generate_wsgi_handler_script, generate_control_scripts,
16
+ generate_telemetry_service_script,
17
+ )
14
18
 
15
19
  def _mpm_module_defines(modules_directory, preferred=None):
16
20
  if os.name == 'nt':
@@ -367,6 +371,41 @@ def setup_server(command, args, options):
367
371
  else:
368
372
  options['server_metrics_flag'] = 'Off'
369
373
 
374
+ if options['enable_telemetry']:
375
+ if options['telemetry_service']:
376
+ raise ConfigurationError(
377
+ "--enable-telemetry and --telemetry-service are "
378
+ "mutually exclusive: --enable-telemetry generates an "
379
+ "ingester service and points the WSGI processes at "
380
+ "it, so an explicit telemetry target would conflict.")
381
+
382
+ try:
383
+ import mod_wsgi.telemetry.server # noqa: F401
384
+ except ImportError:
385
+ raise ConfigurationError(
386
+ "--enable-telemetry requires the mod_wsgi-telemetry "
387
+ "package: install it with "
388
+ "'pip install mod_wsgi-telemetry'.")
389
+
390
+ for name, _ in options['service_scripts'] or []:
391
+ if name == 'telemetry':
392
+ raise ConfigurationError(
393
+ "--enable-telemetry generates a service named "
394
+ "'telemetry'; rename the conflicting "
395
+ "--service-script entry or drop "
396
+ "--enable-telemetry.")
397
+
398
+ socket_path = posixpath.join(options['server_root'],
399
+ 'telemetry.sock')
400
+ options['telemetry_socket'] = socket_path
401
+ options['telemetry_service'] = 'unix:%s' % socket_path
402
+
403
+ script_path = posixpath.join(options['server_root'],
404
+ 'telemetry-service.py')
405
+ options['service_scripts'] = list(
406
+ options['service_scripts'] or [])
407
+ options['service_scripts'].append(('telemetry', script_path))
408
+
370
409
  if options['telemetry_service']:
371
410
  target = options['telemetry_service']
372
411
  if not target.startswith('unix:'):
@@ -396,6 +435,13 @@ def setup_server(command, args, options):
396
435
  options['switch_interval'] = ''
397
436
  options['daemon_switch_interval_option'] = ''
398
437
 
438
+ if options['free_threading']:
439
+ if not sysconfig.get_config_var('Py_GIL_DISABLED'):
440
+ raise ConfigurationError(
441
+ "--free-threading requires a Python build with "
442
+ "free-threading support (PEP 703). Rebuild Python "
443
+ "with --disable-gil to use it.")
444
+
399
445
  if options['handler_scripts']:
400
446
  handler_scripts = []
401
447
  for extension, script in options['handler_scripts']:
@@ -781,6 +827,8 @@ def setup_server(command, args, options):
781
827
  options['httpd_arguments_list'].append('-DMOD_WSGI_SLOW_REQUESTS')
782
828
  if options['switch_interval'] != '':
783
829
  options['httpd_arguments_list'].append('-DMOD_WSGI_SWITCH_INTERVAL')
830
+ if options['free_threading']:
831
+ options['httpd_arguments_list'].append('-DMOD_WSGI_FREE_THREADING')
784
832
  if options['telemetry_options']:
785
833
  options['httpd_arguments_list'].append('-DMOD_WSGI_TELEMETRY_OPTIONS')
786
834
  options['telemetry_options'] = '\n'.join(
@@ -856,6 +904,9 @@ def setup_server(command, args, options):
856
904
 
857
905
  generate_wsgi_handler_script(options)
858
906
 
907
+ if options['enable_telemetry']:
908
+ generate_telemetry_service_script(options)
909
+
859
910
  print('Server URL :', options['url'])
860
911
 
861
912
  if options['https_url']:
@@ -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,9 +3087,34 @@ static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon)
2991
3087
  }
2992
3088
  }
2993
3089
 
2994
- /* Start telemetry reporter if configured. */
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
+
3109
+ /*
3110
+ * Start telemetry reporter if configured. Skip for service-script
3111
+ * daemons (threads=0): they handle no requests, so there is
3112
+ * nothing to accumulate, and an ingester-hosting service script
3113
+ * would otherwise report telemetry datagrams to itself.
3114
+ */
2995
3115
 
2996
- wsgi_telemetry_start_reporter(p);
3116
+ if (daemon->group->threads != 0)
3117
+ wsgi_telemetry_start_reporter(p);
2997
3118
 
2998
3119
  /* Initialise worker stack. */
2999
3120
 
@@ -3248,6 +3369,40 @@ static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon)
3248
3369
  }
3249
3370
  }
3250
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
+
3251
3406
  /*
3252
3407
  * If shutting down process due to reaching request time
3253
3408
  * limit, then try and dump out stack traces of any threads
@@ -3540,6 +3695,31 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
3540
3695
  wsgi_daemon_init_failure_exit();
3541
3696
  }
3542
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
+
3543
3723
  wsgi_daemon_shutdown = 0;
3544
3724
 
3545
3725
  wsgi_daemon_pid = getpid();
@@ -3553,6 +3733,19 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
3553
3733
  apr_signal(SIGXCPU, wsgi_signal_handler);
3554
3734
  #endif
3555
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
+
3556
3749
  /* Set limits on amount of CPU time that can be used. */
3557
3750
 
3558
3751
  if (daemon->group->cpu_time_limit > 0)