mod-wsgi 6.0.1__tar.gz → 6.0.3.dev1__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 (95) hide show
  1. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/PKG-INFO +1 -1
  2. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/mod_wsgi.egg-info/PKG-INFO +1 -1
  3. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/setup.py +1 -1
  4. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/apache.py +2 -0
  5. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/cli.py +27 -5
  6. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/options.py +22 -8
  7. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/platform.py +1 -1
  8. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/reloader.py +11 -0
  9. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/server.py +10 -7
  10. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/mod_wsgi.c +14 -23
  11. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_config.c +6 -0
  12. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_daemon.c +20 -0
  13. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_daemon.h +1 -0
  14. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_remote.c +101 -45
  15. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_version.h +2 -2
  16. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/CREDITS.rst +0 -0
  17. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/LICENSE +0 -0
  18. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/MANIFEST.in +0 -0
  19. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/Makefile.in +0 -0
  20. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/README-express.rst +0 -0
  21. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/README-standalone.rst +0 -0
  22. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/README.rst +0 -0
  23. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/configure +0 -0
  24. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/configure.ac +0 -0
  25. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/images/__init__.py +0 -0
  26. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/images/snake-whiskey.jpg +0 -0
  27. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/mod_wsgi.egg-info/SOURCES.txt +0 -0
  28. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/mod_wsgi.egg-info/dependency_links.txt +0 -0
  29. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/mod_wsgi.egg-info/entry_points.txt +0 -0
  30. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/mod_wsgi.egg-info/not-zip-safe +0 -0
  31. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/mod_wsgi.egg-info/top_level.txt +0 -0
  32. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/setup.cfg +0 -0
  33. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/__init__.py +0 -0
  34. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/diagnostics/__init__.py +0 -0
  35. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/diagnostics/environ.py +0 -0
  36. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/diagnostics/hello.py +0 -0
  37. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/__init__.py +0 -0
  38. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/management/__init__.py +0 -0
  39. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/management/commands/__init__.py +0 -0
  40. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/management/commands/runmodwsgi.py +0 -0
  41. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/runtime.py +0 -0
  42. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/express/scripts.py +0 -0
  43. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/__init__.py +0 -0
  44. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/management/__init__.py +0 -0
  45. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/management/commands/__init__.py +0 -0
  46. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/management/commands/runmodwsgi.py +0 -0
  47. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_adapter.c +0 -0
  48. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_adapter.h +0 -0
  49. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_apache.c +0 -0
  50. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_apache.h +0 -0
  51. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_auth.c +0 -0
  52. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_auth.h +0 -0
  53. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_buckets.c +0 -0
  54. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_buckets.h +0 -0
  55. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_config.h +0 -0
  56. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_convert.c +0 -0
  57. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_convert.h +0 -0
  58. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_dispatch.c +0 -0
  59. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_dispatch.h +0 -0
  60. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_environ.c +0 -0
  61. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_environ.h +0 -0
  62. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_execute.c +0 -0
  63. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_execute.h +0 -0
  64. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_gc.c +0 -0
  65. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_gc.h +0 -0
  66. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_input.c +0 -0
  67. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_input.h +0 -0
  68. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_interp.c +0 -0
  69. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_interp.h +0 -0
  70. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_logger.c +0 -0
  71. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_logger.h +0 -0
  72. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_memory.c +0 -0
  73. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_memory.h +0 -0
  74. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_metrics.c +0 -0
  75. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_metrics.h +0 -0
  76. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_module.c +0 -0
  77. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_module.h +0 -0
  78. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_python.h +0 -0
  79. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_remote.h +0 -0
  80. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_restrict.c +0 -0
  81. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_restrict.h +0 -0
  82. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_server.c +0 -0
  83. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_server.h +0 -0
  84. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_shutdown.c +0 -0
  85. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_shutdown.h +0 -0
  86. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_signal.c +0 -0
  87. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_signal.h +0 -0
  88. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_stream.c +0 -0
  89. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_stream.h +0 -0
  90. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_telemetry.c +0 -0
  91. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_telemetry.h +0 -0
  92. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_thread.c +0 -0
  93. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_thread.h +0 -0
  94. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_validate.c +0 -0
  95. {mod_wsgi-6.0.1 → mod_wsgi-6.0.3.dev1}/src/server/wsgi_validate.h +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mod_wsgi
3
- Version: 6.0.1
3
+ Version: 6.0.3.dev1
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
3
- Version: 6.0.1
3
+ Version: 6.0.3.dev1
4
4
  Summary: Installer for Apache/mod_wsgi.
5
5
  Home-page: https://www.modwsgi.org/
6
6
  Author: Graham Dumpleton
@@ -456,6 +456,6 @@ setup(name = package_name,
456
456
  entry_points = { 'console_scripts':
457
457
  ['mod_wsgi-express = mod_wsgi.express.cli:main'],},
458
458
  zip_safe = False,
459
- install_requires = standalone and ['mod_wsgi-httpd==2.4.67.1'] or [],
459
+ install_requires = standalone and ['mod_wsgi-httpd==2.4.68.1'] or [],
460
460
  python_requires='>=3.10',
461
461
  )
@@ -243,6 +243,7 @@ WSGIDaemonProcess %(process_group)s \\
243
243
  header-buffer-size=%(header_buffer_size)s \\
244
244
  response-buffer-size=%(response_buffer_size)s \\
245
245
  response-socket-timeout=%(response_socket_timeout)s \\
246
+ response-flush-delay=%(response_flush_delay)s \\
246
247
  server-metrics=%(server_metrics_flag)s%(daemon_switch_interval_option)s
247
248
  </IfDefine>
248
249
  <IfDefine !MOD_WSGI_MULTIPROCESS>
@@ -273,6 +274,7 @@ WSGIDaemonProcess %(process_group)s \\
273
274
  receive-buffer-size=%(receive_buffer_size)s \\
274
275
  response-buffer-size=%(response_buffer_size)s \\
275
276
  response-socket-timeout=%(response_socket_timeout)s \\
277
+ response-flush-delay=%(response_flush_delay)s \\
276
278
  server-metrics=%(server_metrics_flag)s%(daemon_switch_interval_option)s
277
279
  </IfDefine>
278
280
  </IfDefine>
@@ -67,11 +67,33 @@ def cmd_start_server(params):
67
67
  httpd_arguments.extend(['-f', config['httpd_conf']])
68
68
  httpd_arguments.extend(['-DONE_PROCESS'])
69
69
 
70
- os.environ['MOD_WSGI_MODULES_DIRECTORY'] = config['modules_directory']
71
-
72
- subprocess.call([executable]+httpd_arguments)
73
-
74
- sys.exit(0)
70
+ # On Windows httpd shares our console, so a Ctrl-C delivers a
71
+ # CTRL_C_EVENT to the whole console process group and httpd runs
72
+ # its own graceful shutdown. We must not let the same Ctrl-C kill
73
+ # this launcher first, or it returns to the shell while httpd is
74
+ # still tearing down (and dumps a KeyboardInterrupt traceback). So
75
+ # absorb our own interrupt and keep waiting until httpd exits.
76
+ #
77
+ # A first Ctrl-C is treated as "you should have received the
78
+ # console event, shutting down gracefully, I will wait". If httpd
79
+ # did not actually receive the event (for example a git-bash pty
80
+ # bridge that does not forward it to the child), a second Ctrl-C
81
+ # escalates to terminating it so we cannot hang indefinitely.
82
+
83
+ process = subprocess.Popen([executable]+httpd_arguments, env=environ)
84
+
85
+ interrupts = 0
86
+
87
+ while True:
88
+ try:
89
+ process.wait()
90
+ break
91
+ except KeyboardInterrupt:
92
+ interrupts += 1
93
+ if interrupts >= 2:
94
+ process.terminate()
95
+
96
+ sys.exit(process.returncode)
75
97
 
76
98
  else:
77
99
  executable = posixpath.join(config['server_root'], 'apachectl')
@@ -395,18 +395,32 @@ add_option('unix', '--header-buffer-size', type='int', default=0,
395
395
  'indicating internal default of 32768 bytes is used.')
396
396
 
397
397
  add_option('unix', '--response-buffer-size', type='int', default=0,
398
- metavar='NUMBER', help='Maximum amount of response content '
399
- 'that will be allowed to be buffered in the Apache child '
400
- 'worker process when proxying the response from a daemon '
401
- 'process. Defaults to 0, indicating internal default of '
402
- '65536 bytes is used.')
398
+ metavar='NUMBER', help='Coarse upper bound, in bytes, on how '
399
+ 'much response content is passed down the output filter chain '
400
+ 'without a flush when proxying the response from a daemon '
401
+ 'process. Only bounds a downstream filter that buffers without '
402
+ 'draining; the Apache core output filter handles normal memory '
403
+ 'bounding. Defaults to 0, indicating the internal default of '
404
+ '8388608 bytes (8 MB) is used. Setting it low can interfere with '
405
+ 'a downstream pacing filter such as mod_ratelimit.')
403
406
 
404
407
  add_option('unix', '--response-socket-timeout', type='int', default=0,
405
408
  metavar='SECONDS', help='Maximum number of seconds allowed '
406
409
  'to pass before timing out on a write operation back to the '
407
- 'HTTP client when the response buffer has filled and data is '
408
- 'being forcibly flushed. Defaults to 0 seconds indicating that '
409
- 'it will default to the value of the \'socket-timeout\' option.')
410
+ 'HTTP client when transferring the response body. Defaults to 0 '
411
+ 'seconds indicating that it will default to the value of the '
412
+ '\'socket-timeout\' option.')
413
+
414
+ add_option('unix', '--response-flush-delay', type='int', default=5,
415
+ metavar='MILLISECONDS', help='Number of milliseconds mod_wsgi '
416
+ 'will wait for further response data from a daemon process before '
417
+ 'flushing what it has to the HTTP client. Waiting briefly lets a '
418
+ 'downstream output filter such as mod_ratelimit pace or batch the '
419
+ 'response correctly instead of being forced to emit a short write '
420
+ 'on every momentary stall. Defaults to 5 milliseconds. Setting it '
421
+ 'to 0 flushes on any stall and is not recommended when a pacing '
422
+ 'filter such as mod_ratelimit is in use, as it can throttle '
423
+ 'responses far below the configured rate.')
410
424
 
411
425
  add_option('all', '--enable-sendfile', action='store_true',
412
426
  default=False, help='Flag indicating whether sendfile() support '
@@ -82,7 +82,7 @@ def default_run_group():
82
82
 
83
83
  def find_program(names, default=None, paths=[]):
84
84
  for name in names:
85
- for path in os.environ['PATH'].split(':') + paths:
85
+ for path in os.environ['PATH'].split(os.pathsep) + paths:
86
86
  program = posixpath.join(path, name)
87
87
  if os.path.exists(program):
88
88
  return program
@@ -97,6 +97,17 @@ def track_changes(path):
97
97
  _files.append(path)
98
98
 
99
99
  def start_reloader(interval=1.0):
100
+ # The reloader triggers a restart by sending SIGINT to this process. On
101
+ # Windows os.kill() cannot deliver SIGINT to ourselves: any signal other
102
+ # than CTRL_C_EVENT/CTRL_BREAK_EVENT is implemented as an unconditional
103
+ # TerminateProcess, so the reloader would hard kill the server rather than
104
+ # trigger a graceful restart. Disable it on Windows.
105
+ if os.name == 'nt':
106
+ prefix = 'monitor (pid=%d):' % os.getpid()
107
+ print('%s Source code reloading is not supported on Windows.' % prefix,
108
+ file=sys.stderr)
109
+ return
110
+
100
111
  global _interval
101
112
  if interval < _interval:
102
113
  _interval = interval
@@ -255,14 +255,17 @@ def setup_server(command, args, options):
255
255
  options['access_log_file'] = posixpath.join(
256
256
  options['log_directory'], options['access_log_name'])
257
257
  else:
258
- try:
259
- with open('/dev/stdout', 'w'):
260
- pass
261
- except IOError:
262
- options['access_log_file'] = '|%s' % find_program(
263
- ['tee'], default='tee')
258
+ if os.name == 'nt':
259
+ options['access_log_file'] = 'CON'
264
260
  else:
265
- options['access_log_file'] = '/dev/stdout'
261
+ try:
262
+ with open('/dev/stdout', 'w'):
263
+ pass
264
+ except IOError:
265
+ options['access_log_file'] = '|%s' % find_program(
266
+ ['tee'], default='tee')
267
+ else:
268
+ options['access_log_file'] = '/dev/stdout'
266
269
 
267
270
  if options['access_log_format']:
268
271
  if options['access_log_format'] in ('common', 'combined'):
@@ -793,6 +793,20 @@ static int wsgi_hook_handler(request_rec *r)
793
793
  return HTTP_INTERNAL_SERVER_ERROR;
794
794
  }
795
795
 
796
+ /*
797
+ * Embedded execution needs a Python interpreter in this child. If
798
+ * one was never initialised (or initialisation failed), the
799
+ * per-thread state used by wsgi_execute_script() does not exist and
800
+ * running it would crash the process, so refuse the request.
801
+ */
802
+
803
+ if (!wsgi_python_initialized)
804
+ {
805
+ wsgi_log_rerror(APLOG_ERR, 0, r, WSGI_APLOGNO(0210) "Embedded mode of mod_wsgi cannot be used as Python "
806
+ "was not initialised in this process.");
807
+ return HTTP_INTERNAL_SERVER_ERROR;
808
+ }
809
+
796
810
  return wsgi_execute_script(r);
797
811
  }
798
812
 
@@ -1262,27 +1276,4 @@ module AP_MODULE_DECLARE_DATA wsgi_module = {
1262
1276
 
1263
1277
  /* ------------------------------------------------------------------------- */
1264
1278
 
1265
- #if defined(_WIN32)
1266
- PyMODINIT_FUNC PyInit_mod_wsgi(void)
1267
- {
1268
- /* The 'mod_wsgi' Python module is created at runtime by the Apache
1269
- * module when it sets up each interpreter; it is not a regular
1270
- * Python extension and cannot be imported from a standalone Python
1271
- * process. This stub exists only to satisfy the Windows linker and
1272
- * is not expected to be reached. Set an explicit ImportError so the
1273
- * importer reports the actual situation instead of the generic
1274
- * "initialization of mod_wsgi failed without raising an exception"
1275
- * SystemError. */
1276
-
1277
- PyErr_SetString(PyExc_ImportError,
1278
- "mod_wsgi cannot be imported as a regular Python "
1279
- "module; it is provided by the Apache mod_wsgi "
1280
- "module and is only available inside an Apache "
1281
- "process running mod_wsgi.");
1282
- return NULL;
1283
- }
1284
- #endif
1285
-
1286
- /* ------------------------------------------------------------------------- */
1287
-
1288
1279
  /* vi: set sw=4 expandtab : */
@@ -778,6 +778,12 @@ const char *wsgi_set_restrict_embedded(cmd_parms *cmd, void *mconfig,
778
778
  if (wsgi_python_required == -1)
779
779
  wsgi_python_required = 0;
780
780
  }
781
+ else
782
+ {
783
+ /* Embedded mode enabled, so Python is needed in the child. */
784
+
785
+ wsgi_python_required = 1;
786
+ }
781
787
 
782
788
  return NULL;
783
789
  }
@@ -149,6 +149,8 @@ const char *wsgi_add_daemon_process(cmd_parms *cmd, void *mconfig,
149
149
 
150
150
  int response_socket_timeout = 0;
151
151
 
152
+ int response_flush_delay = -1;
153
+
152
154
  const char *script_user = NULL;
153
155
  const char *script_group = NULL;
154
156
 
@@ -510,6 +512,17 @@ const char *wsgi_add_daemon_process(cmd_parms *cmd, void *mconfig,
510
512
  if (response_socket_timeout < 0)
511
513
  return "Invalid response socket timeout for WSGI daemon process.";
512
514
  }
515
+ else if (!strcmp(option, "response-flush-delay"))
516
+ {
517
+ if (!*value)
518
+ return "Invalid response flush delay for WSGI daemon process.";
519
+
520
+ /* Value is in milliseconds; 0 disables the delay. */
521
+
522
+ response_flush_delay = atoi(value);
523
+ if (response_flush_delay < 0)
524
+ return "Invalid response flush delay for WSGI daemon process.";
525
+ }
513
526
  else if (!strcmp(option, "socket-user"))
514
527
  {
515
528
  uid_t socket_uid;
@@ -741,6 +754,13 @@ const char *wsgi_add_daemon_process(cmd_parms *cmd, void *mconfig,
741
754
 
742
755
  entry->response_socket_timeout = apr_time_from_sec(response_socket_timeout);
743
756
 
757
+ /* Default the response flush delay to 5 milliseconds. */
758
+
759
+ if (response_flush_delay < 0)
760
+ response_flush_delay = 5;
761
+
762
+ entry->response_flush_delay = apr_time_from_msec(response_flush_delay);
763
+
744
764
  entry->script_user = script_user;
745
765
  entry->script_group = script_group;
746
766
 
@@ -127,6 +127,7 @@ typedef struct
127
127
  int header_buffer_size;
128
128
  int response_buffer_size;
129
129
  apr_time_t response_socket_timeout;
130
+ apr_time_t response_flush_delay;
130
131
  const char *script_user;
131
132
  const char *script_group;
132
133
  int cpu_time_limit;
@@ -803,7 +803,9 @@ static int wsgi_scan_headers_brigade(request_rec *r, apr_bucket_brigade *bb,
803
803
  }
804
804
 
805
805
  static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
806
- apr_size_t buffer_size, apr_time_t timeout)
806
+ apr_size_t buffer_size, apr_time_t timeout,
807
+ apr_socket_t *daemon_socket,
808
+ apr_time_t flush_delay)
807
809
  {
808
810
  apr_bucket *e;
809
811
  apr_read_type_e mode = APR_NONBLOCK_READ;
@@ -815,15 +817,24 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
815
817
 
816
818
  apr_size_t bytes_transfered = 0;
817
819
 
818
- int bucket_count = 0;
819
-
820
820
  apr_status_t rv;
821
821
 
822
822
  apr_socket_t *sock;
823
823
  apr_interval_time_t existing_timeout = 0;
824
824
 
825
+ apr_interval_time_t daemon_timeout = 0;
826
+ int lingering = 0;
827
+
828
+ /*
829
+ * response-buffer-size bounds how many bytes of response content
830
+ * may be passed downstream without a flush, acting as a coarse
831
+ * runaway guard (see where it is applied below). Zero selects the
832
+ * default, which is high enough that the guard does not affect
833
+ * normal operation.
834
+ */
835
+
825
836
  if (buffer_size == 0)
826
- buffer_size = 65536;
837
+ buffer_size = 8 * 1024 * 1024;
827
838
 
828
839
  /*
829
840
  * Override the socket timeout for writing back data to the
@@ -851,15 +862,22 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
851
862
  }
852
863
 
853
864
  /*
854
- * Transfer any response content. We want to avoid the
855
- * problem where the core output filter has no flow control
856
- * to deal with slow HTTP clients and can actually buffer up
857
- * excessive amounts of response content in memory. A fix
858
- * for this was only introduced in Apache 2.3.3, with
859
- * possible further tweaks in Apache 2.4.1. To avoid issue of
860
- * what version it was implemented in, just employ a
861
- * strategy of forcing a flush every time we pass through
862
- * more than a certain amount of data.
865
+ * When a response flush delay is configured, remember the daemon
866
+ * socket's current read timeout. While lingering for more data we
867
+ * lower it to the flush delay and restore it afterwards. Guard
868
+ * against a NULL daemon socket by disabling the linger.
869
+ */
870
+
871
+ if (!daemon_socket)
872
+ flush_delay = 0;
873
+
874
+ if (flush_delay)
875
+ apr_socket_timeout_get(daemon_socket, &daemon_timeout);
876
+
877
+ /*
878
+ * Transfer the response content back to the client, reading it
879
+ * from the daemon a block at a time and passing each block down
880
+ * the output filter chain.
863
881
  */
864
882
 
865
883
  tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
@@ -923,13 +941,46 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
923
941
  rv = apr_bucket_read(e, &data, &length, mode);
924
942
 
925
943
  /*
926
- * If we would have blocked if not in non blocking mode
927
- * we send a flush bucket to ensure that all buffered
928
- * data is sent out before we block waiting for more.
944
+ * A non-blocking read returned no data. Rather than flushing
945
+ * immediately, which would force any downstream pacing filter
946
+ * such as mod_ratelimit to emit a short write and so defeat
947
+ * its rate calculation, first wait a bounded flush delay for
948
+ * more data using a blocking read with a short timeout on the
949
+ * daemon socket. If data arrives within that window the
950
+ * transfer is still active and we keep accumulating without a
951
+ * flush. Only if the producer stays idle for the whole delay
952
+ * do we flush, so streaming responses still reach the client
953
+ * promptly. When the flush delay is zero this step is skipped
954
+ * and the data is flushed on any would-block.
955
+ */
956
+
957
+ if (rv == APR_EAGAIN && mode == APR_NONBLOCK_READ && flush_delay)
958
+ {
959
+ apr_socket_timeout_set(daemon_socket, flush_delay);
960
+
961
+ mode = APR_BLOCK_READ;
962
+ lingering = 1;
963
+
964
+ continue;
965
+ }
966
+
967
+ /*
968
+ * If we would have blocked, or the producer stayed idle for
969
+ * the whole flush delay, we send a flush bucket to ensure that
970
+ * all buffered data is sent out before we block waiting for
971
+ * more.
929
972
  */
930
973
 
931
- if (rv == APR_EAGAIN && mode == APR_NONBLOCK_READ)
974
+ if ((rv == APR_EAGAIN && mode == APR_NONBLOCK_READ) ||
975
+ (rv == APR_TIMEUP && lingering))
932
976
  {
977
+ if (lingering)
978
+ {
979
+ apr_socket_timeout_set(daemon_socket, daemon_timeout);
980
+
981
+ lingering = 0;
982
+ }
983
+
933
984
  APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create(
934
985
  r->connection->bucket_alloc));
935
986
 
@@ -961,8 +1012,6 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
961
1012
 
962
1013
  bytes_transfered = 0;
963
1014
 
964
- bucket_count = 0;
965
-
966
1015
  /*
967
1016
  * Retry read from daemon using a blocking read. We do
968
1017
  * not delete the bucket as we want to operate on the
@@ -991,9 +1040,18 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
991
1040
 
992
1041
  /*
993
1042
  * We had some data to transfer. Next time round we need to
994
- * always be try a non-blocking read first.
1043
+ * always be try a non-blocking read first. If we got here from
1044
+ * a lingering blocking read, restore the daemon socket timeout
1045
+ * that we lowered to the flush delay.
995
1046
  */
996
1047
 
1048
+ if (lingering)
1049
+ {
1050
+ apr_socket_timeout_set(daemon_socket, daemon_timeout);
1051
+
1052
+ lingering = 0;
1053
+ }
1054
+
997
1055
  mode = APR_NONBLOCK_READ;
998
1056
 
999
1057
  /*
@@ -1010,41 +1068,38 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
1010
1068
  APR_BRIGADE_INSERT_TAIL(tmpbb, e);
1011
1069
 
1012
1070
  /*
1013
- * If we have reached the buffer size threshold, we want
1014
- * to flush the data so that we aren't buffering too much
1015
- * in memory and blowing out memory size. We also have a
1016
- * check on the number of buckets we have accumulated as
1017
- * a large number of buckets with very small amounts of
1018
- * data will also accumulate a lot of memory. Apache's
1019
- * own flow control doesn't cope with such a situation.
1020
- * Right now hard wire the max number of buckets at 16
1021
- * which equates to worst case number of separate data
1022
- * blocks can be written by a writev() call on systems
1023
- * such as Solaris.
1071
+ * Each block is passed straight on without a forced flush. The
1072
+ * Apache 2.4 core output filter bounds how much response data
1073
+ * is buffered in the child process: it switches to blocking
1074
+ * writes once its own threshold is reached, which backpressures
1075
+ * through ap_pass_brigade and on to the daemon, and it batches
1076
+ * its own writev() calls. Passing whole blocks on keeps writes
1077
+ * large enough for a downstream pacing filter such as
1078
+ * mod_ratelimit to pace correctly. Pending data is otherwise
1079
+ * flushed when the producer goes idle (above) and at end of
1080
+ * stream.
1081
+ *
1082
+ * As a coarse runaway guard, force a flush if more than
1083
+ * buffer_size bytes have been passed downstream since the last
1084
+ * flush. This bounds a downstream filter that buffers without
1085
+ * draining to roughly buffer_size per request. It counts bytes
1086
+ * transferred, not bytes currently buffered, so on a healthy
1087
+ * connection it just emits an otherwise harmless flush every
1088
+ * buffer_size bytes; buffer_size is high by default so it does
1089
+ * not fire in normal operation.
1024
1090
  */
1025
1091
 
1026
1092
  bytes_transfered += length;
1027
1093
 
1028
- bucket_count += 1;
1029
-
1030
- if (bytes_transfered > buffer_size || bucket_count >= 16)
1094
+ if (bytes_transfered > buffer_size)
1031
1095
  {
1032
1096
  APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create(
1033
1097
  r->connection->bucket_alloc));
1034
1098
 
1035
1099
  bytes_transfered = 0;
1036
-
1037
- bucket_count = 0;
1038
-
1039
- /*
1040
- * Since we flushed the data out to the client, it is
1041
- * okay to go back and do a blocking read the next time.
1042
- */
1043
-
1044
- mode = APR_BLOCK_READ;
1045
1100
  }
1046
1101
 
1047
- /* Pass the heap bucket and any flush bucket on. */
1102
+ /* Pass the heap bucket and any runaway-guard flush on. */
1048
1103
 
1049
1104
  rv = ap_pass_brigade(r->output_filters, tmpbb);
1050
1105
 
@@ -1835,7 +1890,8 @@ int wsgi_execute_remote(request_rec *r)
1835
1890
  /* Transfer any response content. */
1836
1891
 
1837
1892
  return wsgi_transfer_response(r, bbin, group->response_buffer_size,
1838
- group->response_socket_timeout);
1893
+ group->response_socket_timeout,
1894
+ daemon->socket, group->response_flush_delay);
1839
1895
  }
1840
1896
 
1841
1897
  static apr_status_t wsgi_socket_read(apr_socket_t *sock, void *vbuf,
@@ -25,8 +25,8 @@
25
25
 
26
26
  #define MOD_WSGI_MAJORVERSION_NUMBER 6
27
27
  #define MOD_WSGI_MINORVERSION_NUMBER 0
28
- #define MOD_WSGI_MICROVERSION_NUMBER 1
29
- #define MOD_WSGI_VERSION_STRING "6.0.1"
28
+ #define MOD_WSGI_MICROVERSION_NUMBER 3
29
+ #define MOD_WSGI_VERSION_STRING "6.0.3.dev1"
30
30
 
31
31
  /* ------------------------------------------------------------------------- */
32
32
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes