summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGraham Dumpleton <Graham.Dumpleton@gmail.com>2018-03-04 15:35:19 +1100
committerGraham Dumpleton <Graham.Dumpleton@gmail.com>2018-03-04 15:35:19 +1100
commit630dcf528eb2ae68c1b491d6feb0d0aa54b0d3b5 (patch)
tree824061a502807eace1705c2d54d4671d2bdf323d
parent538cf8f92004e28d73d07c681718fbd5d67783da (diff)
parent68a67c7d11a3608e97f8b89f8fefc27f5643d080 (diff)
downloadmod_wsgi-630dcf528eb2ae68c1b491d6feb0d0aa54b0d3b5.tar.gz
Merge branch 'release/4.6.0'4.6.0
-rw-r--r--README.rst12
-rw-r--r--configure.ac2
-rw-r--r--docs/conf.py2
-rw-r--r--docs/configuration-directives/WSGIApplicationGroup.rst22
-rw-r--r--docs/configuration-directives/WSGIDaemonProcess.rst45
-rw-r--r--docs/configuration-directives/WSGIScriptAlias.rst12
-rw-r--r--docs/configuration-directives/WSGIScriptAliasMatch.rst20
-rw-r--r--docs/release-notes.rst2
-rw-r--r--docs/release-notes/version-4.6.0.rst207
-rw-r--r--docs/user-guides/access-control-mechanisms.rst11
-rw-r--r--setup.py16
-rw-r--r--src/server/__init__.py82
-rw-r--r--src/server/management/commands/runmodwsgi.py66
-rw-r--r--src/server/mod_wsgi.c500
-rw-r--r--src/server/wsgi_apache.c2
-rw-r--r--src/server/wsgi_apache.h2
-rw-r--r--src/server/wsgi_buckets.c2
-rw-r--r--src/server/wsgi_buckets.h2
-rw-r--r--src/server/wsgi_convert.c2
-rw-r--r--src/server/wsgi_convert.h2
-rw-r--r--src/server/wsgi_daemon.c2
-rw-r--r--src/server/wsgi_daemon.h3
-rw-r--r--src/server/wsgi_interp.c142
-rw-r--r--src/server/wsgi_interp.h6
-rw-r--r--src/server/wsgi_logger.c11
-rw-r--r--src/server/wsgi_logger.h2
-rw-r--r--src/server/wsgi_memory.h2
-rw-r--r--src/server/wsgi_metrics.c67
-rw-r--r--src/server/wsgi_metrics.h5
-rw-r--r--src/server/wsgi_python.h2
-rw-r--r--src/server/wsgi_restrict.c2
-rw-r--r--src/server/wsgi_restrict.h2
-rw-r--r--src/server/wsgi_server.c4
-rw-r--r--src/server/wsgi_server.h3
-rw-r--r--src/server/wsgi_stream.c2
-rw-r--r--src/server/wsgi_stream.h2
-rw-r--r--src/server/wsgi_thread.h3
-rw-r--r--src/server/wsgi_validate.c2
-rw-r--r--src/server/wsgi_validate.h2
-rwxr-xr-xsrc/server/wsgi_version.h8
-rw-r--r--tests/auth.wsgi13
-rw-r--r--tests/events.wsgi21
42 files changed, 1083 insertions, 234 deletions
diff --git a/README.rst b/README.rst
index 4fb3182..00454c4 100644
--- a/README.rst
+++ b/README.rst
@@ -12,7 +12,7 @@ The first way of installing mod_wsgi is the traditional way that has been
used by many software packages. This is where it is installed as a module
directly into your Apache installation using the commands ``configure``,
``make`` and ``make install``, a method sometimes referred to by the
-acyronym CMMI. This method works with most UNIX type systems. It cannot
+acronym CMMI. This method works with most UNIX type systems. It cannot
be used on Windows.
The second way of installing mod_wsgi is to install it as a Python package
@@ -49,7 +49,7 @@ they never fixed those problems either. This time there is no easy
workaround as they no longer supply certain tools which are required to
perform the installation.
-The ``pip install`` method along with manual configuration of Apache
+The ``pip install`` method along with the manual configuration of Apache
is also the method you need to use on Windows.
System Requirements
@@ -59,7 +59,7 @@ With either installation method for mod_wsgi, you must have Apache
installed. This must be a complete Apache installation. It is not enough to
have only the runtime packages for Apache installed. You must have the
corresponding development package for Apache installed, which contains the
-Apache header files, as these are required to be able compile and install
+Apache header files, as these are required to be able to compile and install
third party Apache modules.
Similarly with Python, you must have a complete Python installation which
@@ -156,7 +156,7 @@ Note that nothing will be copied into your Apache installation at this
point. As a result, you do not need to run this as the root user unless
installing it into a site wide Python installation rather than a Python
virtual environment. It is recommended you always use Python virtual
-environments and never install any Python package direct into the system
+environments and never install any Python package directly into the system
Python installation.
On a UNIX type system, to verify that the installation was successful, run
@@ -233,7 +233,7 @@ additional steps.
The first thing you must do is supply the ``--user`` and ``--group``
options to say what user and group your Python web application should run
-as. Most Linux distributions will pre define a special user for Apache to
+as. Most Linux distributions will predefine a special user for Apache to
run as, so you can use that. Alternatively you can use any other special
user account you have created for running the Python web application::
@@ -344,7 +344,7 @@ use the ``--setup-only`` option to the ``runmodwsgi`` management command.
--user www-data --group www-data \
--server-root=/etc/mod_wsgi-express-80
-This will setup all the required files and you can use ``apachectl`` to
+This will set up all the required files and you can use ``apachectl`` to
start and stop the Apache instance as explained previously.
Connecting into Apache installation
diff --git a/configure.ac b/configure.ac
index 25afe44..1a58137 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
dnl vim: set sw=4 expandtab :
dnl
-dnl Copyright 2007-2017 GRAHAM DUMPLETON
+dnl Copyright 2007-2018 GRAHAM DUMPLETON
dnl
dnl Licensed under the Apache License, Version 2.0 (the "License");
dnl you may not use this file except in compliance with the License.
diff --git a/docs/conf.py b/docs/conf.py
index dfe26b4..14285a0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -41,7 +41,7 @@ master_doc = 'index'
# General information about the project.
project = u'mod_wsgi'
-copyright = u'2007-2017, Graham Dumpleton'
+copyright = u'2007-2018, Graham Dumpleton'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
diff --git a/docs/configuration-directives/WSGIApplicationGroup.rst b/docs/configuration-directives/WSGIApplicationGroup.rst
index d08228a..101043c 100644
--- a/docs/configuration-directives/WSGIApplicationGroup.rst
+++ b/docs/configuration-directives/WSGIApplicationGroup.rst
@@ -17,6 +17,17 @@ to. All WSGI applications within the same application group will execute
within the context of the same Python sub interpreter of the process
handling the request.
+Setting ``WSGIApplicationGroup`` doesn't control what processes a request
+is handled by, that is what the ``WSGIProcessGroup`` directive does. In
+other words, the ``WSGIProcessGroup`` directive operates distinct from the
+``WSGIApplicationGroup`` directive, with ``WSGIProcessGroup`` dictating
+what named group of processes a request is handled by, and
+``WSGIApplicationGroup`` dictating which named Python sub interpreter
+context (application group) of those processes is used. In each distinct
+process of a named group of processes, there will be a separate sub
+interpreter instance of same name, for handling the requests accepted by
+that process.
+
The argument to the ``WSGIApplicationGroup`` can be either one of four
special expanding variables or an explicit name of your own choosing.
The meaning of the special variables are:
@@ -27,11 +38,12 @@ The meaning of the special variables are:
Any WSGI applications in the global application group will always be
executed within the context of the first interpreter created by Python
- when it is initialised. Forcing a WSGI application to run within the
- first interpreter can be necessary when a third party C extension
- module for Python has used the simplified threading API for
- manipulation of the Python GIL and thus will not run correctly within
- any additional sub interpreters created by Python.
+ when it is initialised, of the process handling the request. Forcing a
+ WSGI application to run within the first interpreter can be necessary
+ when a third party C extension module for Python has used the
+ simplified threading API for manipulation of the Python GIL and thus
+ will not run correctly within any additional sub interpreters created
+ by Python.
**%{SERVER}**
diff --git a/docs/configuration-directives/WSGIDaemonProcess.rst b/docs/configuration-directives/WSGIDaemonProcess.rst
index aaa4ee9..b478fc9 100644
--- a/docs/configuration-directives/WSGIDaemonProcess.rst
+++ b/docs/configuration-directives/WSGIDaemonProcess.rst
@@ -59,6 +59,20 @@ Options which can be supplied to the ``WSGIDaemonProcess`` directive are:
where your code is I/O bound. If you code is CPU bound, you are better
of using at most 3 to 5 threads per process and using more processes.
+ If you set the number of threads to 0 you will enable a special mode
+ intended for using a daemon process to run a managed set of processes.
+ You will need to use ``WSGIImportScript`` to pre-load a Python script
+ into the main application group specified by ``%{GLOBAL}`` where the
+ script runs a never ending task, or does an exec to run an external
+ program. If the script or external program exits, the process is
+ shutdown and replaced with a new one. For the case of using a Python
+ script to run a never ending task, a ``SystemExit`` exception will be
+ injected when a signal is received to shutdown the process. You can
+ use ``signal.signal()`` to register a signal handler for ``SIGTERM``
+ if needing to run special actions before then exiting the process using
+ ``sys.exit()``, or to signal your own threads to exit any processing
+ so you can shutdown in an orderly manner.
+
**display-name=value**
Defines a different name to show for the daemon process when using the
``ps`` command to list processes. If the value is ``%{GROUP}`` then the
@@ -598,6 +612,37 @@ host, the following could be used::
...
</VirtualHost>
+For historical reasons and the inability to change existing behaviour when
+adding or changing features, many of the options to ``WSGIDaemonProcess``,
+especially those related to timeouts are not enabled by default. It is
+strongly recommended you explicitly set these options yourself as this will
+give you a system which is better able to recover from backlogging due to
+overloading when you have too many long running requests or hanging
+requests. As a starting point you can see what ``mod_wsgi-express`` uses as
+defaults, adjusting them as necessary to suit your specific application
+after you research what each option does. For example, consider starting
+out with:
+
+* ``display-name='%{GROUP}'``
+
+* ``lang='en_US.UTF-8'``
+* ``locale='en_US.UTF-8'``
+
+* ``threads=5``
+
+* ``queue-timeout=45``
+* ``socket-timeout=60``
+* ``connect-timeout=15``
+* ``request-timeout=60``
+* ``inactivity-timeout=0``
+* ``startup-timeout=15``
+* ``deadlock-timeout=60``
+* ``graceful-timeout=15``
+* ``eviction-timeout=0``
+* ``restart-interval=0``
+* ``shutdown-timeout=5``
+* ``maximum-requests=0``
+
Note that the ``WSGIDaemonProcess`` directive and corresponding features are
not available on Windows.
diff --git a/docs/configuration-directives/WSGIScriptAlias.rst b/docs/configuration-directives/WSGIScriptAlias.rst
index 708dabe..b8fc49a 100644
--- a/docs/configuration-directives/WSGIScriptAlias.rst
+++ b/docs/configuration-directives/WSGIScriptAlias.rst
@@ -86,12 +86,12 @@ Options which can be supplied to the ``WSGIScriptAlias`` directive are:
If the name is set to be ``%{GLOBAL}`` the application group will be
set to the empty string. Any WSGI applications in the global
application group will always be executed within the context of the
- first interpreter created by Python when it is initialised. Forcing
- a WSGI application to run within the first interpreter can be
- necessary when a third party C extension module for Python has used
- the simplified threading API for manipulation of the Python GIL and
- thus will not run correctly within any additional sub interpreters
- created by Python.
+ first interpreter created by Python when it is initialised, of the
+ process handling the request. Forcing a WSGI application to run within
+ the first interpreter can be necessary when a third party C extension
+ module for Python has used the simplified threading API for
+ manipulation of the Python GIL and thus will not run correctly within
+ any additional sub interpreters created by Python.
If both ``process-group`` and ``application-group`` options are set, the
WSGI script file will be pre-loaded when the process it is to run in is
diff --git a/docs/configuration-directives/WSGIScriptAliasMatch.rst b/docs/configuration-directives/WSGIScriptAliasMatch.rst
index 576797b..0d9c3ce 100644
--- a/docs/configuration-directives/WSGIScriptAliasMatch.rst
+++ b/docs/configuration-directives/WSGIScriptAliasMatch.rst
@@ -59,13 +59,15 @@ Options which can be supplied to the ``WSGIScriptAlias`` directive are:
If the name is set to be ``%{GLOBAL}`` the application group will be
set to the empty string. Any WSGI applications in the global
application group will always be executed within the context of the
- first interpreter created by Python when it is initialised. Forcing
- a WSGI application to run within the first interpreter can be
- necessary when a third party C extension module for Python has used
- the simplified threading API for manipulation of the Python GIL and
- thus will not run correctly within any additional sub interpreters
- created by Python.
+ first interpreter created by Python when it is initialised, of the
+ process handling the request. Forcing a WSGI application to run within
+ the first interpreter can be necessary when a third party C extension
+ module for Python has used the simplified threading API for
+ manipulation of the Python GIL and thus will not run correctly within
+ any additional sub interpreters created by Python.
-If both ``process-group`` and ``application-group`` options are set, the
-WSGI script file will be pre-loaded when the process it is to run in is
-started, rather than being lazily loaded on the first request.
+If both ``process-group`` and ``application-group`` options are set, and
+the WSGI script file doesn't include substiutions values to be supplied
+from the matched URL pattern, the WSGI script file will be pre-loaded when
+the process it is to run in is started, rather than being lazily loaded on
+the first request.
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
index 600e295..3ae5240 100644
--- a/docs/release-notes.rst
+++ b/docs/release-notes.rst
@@ -5,6 +5,8 @@ Release Notes
.. toctree::
:maxdepth: 2
+ release-notes/version-4.6.0
+
release-notes/version-4.5.24
release-notes/version-4.5.23
release-notes/version-4.5.22
diff --git a/docs/release-notes/version-4.6.0.rst b/docs/release-notes/version-4.6.0.rst
new file mode 100644
index 0000000..dc4460d
--- /dev/null
+++ b/docs/release-notes/version-4.6.0.rst
@@ -0,0 +1,207 @@
+=============
+Version 4.6.0
+=============
+
+Version 4.6.0 of mod_wsgi can be obtained from:
+
+ https://codeload.github.com/GrahamDumpleton/mod_wsgi/tar.gz/4.6.0
+
+Bugs Fixed
+----------
+
+* Management of reference counting on Python objects in the access,
+ authentication, authorization and dispatch hooks wasn't correct for
+ certain error cases. The error cases shouldn't have ever occurred, but
+ still fixed.
+
+* Point at which details of Python exceptions occuring during access,
+ authentication, authorization and dispatch hooks was incorrect and not
+ done, with exception cleared, before trying to close per callback error
+ log. That the exception hadn't been cleared would result in the call to
+ close the per callback error log to itself fail as it believed an
+ exception occurred in that call when it hadn't. The result was confusing
+ error messages in the Apache error log.
+
+* The deprecated backwards compatability mode enabled by setting the
+ directive ``WSGILazyInitialization Off``, to have Python initialised
+ in the Apache parent process before forking, was resulting in the Apache
+ parent process crashing on Apache shutdown or restart. This resulted in
+ Apache child processes and daemon process being orphaned. Issue has been
+ fixed, but you should never use this mode and it will be removed in a
+ future update. The reason it shouldn't be used is due to memory leaks
+ in Python interpreter re-initialisation in same process and also the risks
+ due to Python code potentially being run as root.
+
+* When stack traces were being dumped upon request timeout expiring, the
+ line numbers of the definition of each function in the stack trace was
+ being displayed, instead of the actual line number within the body of the
+ function that was executing at the time.
+
+* When stack traces were being dumped upon request timeout expiring, the
+ thread ID was being truncated to 32 bits when displayed, meaning it
+ wouldn't match the actual Python thread ID on 64 bit systems.
+
+Features Changed
+----------------
+
+* Now flagging mod_wsgi package when installing using ``setup.py`` as
+ being not ``zip_safe``. This is to workaround an apparent bug with
+ ``setuptools`` when using Python 3.7 alpha versions. Believe this will
+ disable use of egg file in certain cases.
+
+* When the connection to a client is lost when writing back the response,
+ the HTTP response code logged in the Apache access log will be that for
+ the original response from the WSGI application rather than a 500 error.
+
+ This is done to avoid confusion where a 500 error is recorded in the
+ access log, making you think your WSGI application is at fault when it
+ wasn't, but there is no actual error recorded in the error log as to why
+ the 500 error was recorded in the access log.
+
+ The reason no error is logged in the case of the connection to a client
+ being lost is that doing so would create a lot of noise due to the
+ regularity which it can happen. The only time an error is logged is when
+ a timeout occurs rather than connection being lost. That is done to
+ highlight that connections are hanging due to the effect it can have on
+ available server capacity when connections are kept open for long times.
+
+ Thanks to Jesús Cea Avión for identifying how using the Apache C API it
+ could be identified that the connection had been aborted and in that
+ case the original HTTP response code could safely be used.
+
+* When using the Django integration for ``mod_wsgi-express``, if the
+ ``whitenoise.middleware.WhiteNoiseMiddleware`` middleware is listed in
+ ``MIDDLEWARE`` or ``MIDDLEWARE_CLASSES`` of the Django settings file,
+ Apache will now not be used to host Django's static files. This is being
+ done to allow WhiteNoise middleware to be used in conjunction with front
+ end content delivery networks or other caching systems. If you aren't
+ using such a front end and do want Apache to still host the static files,
+ either don't list the WhiteNoise middleware in the list of middleware
+ classes when using ``mod_wsgi-express``, or pass the ``--url-alias``
+ option explictly, along with the URL mount point for static files and the
+ directory where they have been placed by the ``collectstatic`` management
+ command of Django.
+
+* When running ``mod_wsgi-express`` if the ``TMPDIR`` environment variable
+ is specified, it will be used as the directory under which the default
+ server root directory for generated files will be created. If ``TMPDIR``
+ is not specified, then ``/tmp`` will be used.
+
+ This allows ``TMPDIR`` to be used to control the directory used as a
+ default. On MacOS where ``TMPDIR`` is set to a unique directory for the
+ login session under ``/var/tmp``, this also avoids a problem where a
+ system cron job in MacOS will delete files under ``/tmp`` which are older
+ than a certain date, which can cause a long running instance of
+ ``mod_wsgi-express`` to start failing.
+
+* The "process_stopping" event previously would not be delivered when the
+ process was being shutdown and there were still active requests, such as
+ when a request timeout occurred. Seen as better to always deliver the
+ event if can, even if there were still requests that hadn't been completed.
+ This will allow the event handler to dump out details on what the active
+ requests were, helping to identify long running or stuck requests.
+
+New Features
+------------
+
+* When using ``--compress-responses`` option of ``mod_wsgi-express``,
+ content of type ``application/json`` will now be compressed.
+
+* Added directive ``WSGISocketRotation`` to allow the rotation of the daemon
+ socket file path on restarts of Apache to be disabled. By default it is
+ ``On`` to preserve existing behaviour but can be set to ``Off`` to have
+ the same socket file path always be used for lifetime of that Apache
+ instance.
+
+ Rotation should only be disabled where the Apache configuration for the
+ mod_wsgi application stays constant over time. The rotation was
+ originally done to prevent a request received and handled by an Apache
+ worker process being proxied through to a daemon process created under a
+ newer configuration. This was done to avoid the possibility of an error,
+ or a security issue, due to the old and new configurations being
+ incompatible or out of sync.
+
+ By setting rotation to ``Off``, when a graceful restart is done and the
+ Apache worker process survives for a period of time due to keep alive
+ connections, those subsequent requests on the keep alive connection will
+ now be proxied to the newer daemon processes rather than being failed as
+ occurred before due to no instances of daemon process existing under the
+ older configuration.
+
+ Although socket rotation still defaults to ``On`` for mod_wsgi, this is
+ overridden for ``mod_wsgi-express`` where it is always now set to ``Off``.
+ This is okay as is not possible for configuration to change when using it.
+
+* The ``process-group`` and ``application-group`` options can now be used
+ with the ``WSGIScriptAliasMatch`` directive. If substitutions are not used
+ in the value for the WSGI script file target path, then the WSGI script
+ file will be pre-loaded if both ``process-group`` and ``application-group``
+ options are used at the same time.
+
+ Note that the documentation was wrongly updated recently to suggest that
+ these options were already supported by ``WSGIScriptAliaMatch``. This was
+ done in error. Instead of removing the documentation, the ability to use
+ the options with the directive was instead added with this release.
+
+* Raise an actual exception when installing using ``pip`` or using the
+ ``setup.py`` file on MacOS and it doesn't appear that Xcode application
+ has been installed. Lack of Xcode application will mean that cannot find
+ the SDK which has the Apache include files.
+
+* An explicit error message is now logged when the calculated daemon socket
+ path is too long and would be truncated, causing potential failures. A
+ shorter directory path should be set with the ``WSGISocketPrefix`` option.
+
+* Added the ``--socket-path`` option to ``mod_wsgi-express`` so you can set
+ the daemon socket prefix via the ``WSGISocketPrefix`` directive to an
+ alternate directory if the calculated path would be too long based on
+ where server root is set for ``mod_wsgi-express``.
+
+* Added the ``--isatty`` option to ``mod_wsgi-express`` to indicate that
+ running the command in an interactive terminal session. In this case
+ Apache will be run as a sub process rather than it replacing the current
+ script. Signals such as SIGINT, SIGTERM, SIGHUP and SIGUSR1 will be
+ intercepted and forwarded onto Apache, but the signal SIGWINCH will be
+ ignored. This will avoid the problems of Apache shutting down when the
+ terminal session Apache is run in is resized.
+
+ Technically this could be done automatically by working out if the
+ attached terminal is a tty, but is being done using an option at this
+ point so the reliability of the mechanism used to run Apache as a sub
+ process and the handling of the signals, can be verified. If everything
+ checks out, it is likely that this will become the default behaviour
+ when the attached terminal is a tty.
+
+* When using ``WSGIDaemonProcess``, if you set the number of threads to zero
+ you will enable a special mode intended for using a daemon process to run
+ a managed task or program. You will need to use ``WSGIImportScript`` to
+ pre-load a Python script into the main application group specified by
+ ``%{GLOBAL}`` where the script runs a never ending task, or does an exec
+ to run an external program. If the script or external program exits, the
+ process is shutdown and replaced with a new one. For the case of using a
+ Python script to run a never ending task, a ``SystemExit`` exception will
+ be injected when a signal is received to shutdown the process. You can
+ use ``signal.signal()`` to register a signal handler for ``SIGTERM`` if
+ needing to run special actions before then exiting the process using
+ ``sys.exit()``, or to signal your own threads to exit any processing so
+ you can shutdown in an orderly manner.
+
+ The ability to do something very similar did previously exist in that
+ you could use ``WSGIImportScript`` to run a never ending task even when
+ the number of threads was non zero. This was used by ``--service-script``
+ option of ``mod_wsgi-express``. The difference in setting ``threads=0``
+ is that signals will work correctly and be able to interupt the script.
+ Also once the script exits, the process will shutdown, to be replaced,
+ where as previously the process would stay running until Apache was
+ restart or shutdown. The ``--service-script`` option of ``mod_wsgi-express``
+ has been updated to set the number of threads to zero.
+
+* Added ``mod_wsgi.active_requests`` dictionary. This is populated with the
+ per request data object for active requests, keyed by the Apache request ID.
+
+* Add ``--cpu-time-limit`` option to ``mod_wsgi-express`` so that limit can
+ be imposed on daemon process group as to how much CPU can be used for
+ process is restarted automatically.
+
+* Pass a "shutdown_reason" argument with "process_stopping" event so event
+ handler knows the reason the process is being shutdown.
diff --git a/docs/user-guides/access-control-mechanisms.rst b/docs/user-guides/access-control-mechanisms.rst
index f512224..e8f7763 100644
--- a/docs/user-guides/access-control-mechanisms.rst
+++ b/docs/user-guides/access-control-mechanisms.rst
@@ -95,13 +95,16 @@ is 'get_realm_hash()'. The result of the function must be 'None' if the
user doesn't exist, or a hash string encoding the user name, authentication
realm and password::
- import md5
+ import hashlib
def get_realm_hash(environ, user, realm):
if user == 'spy':
- value = md5.new()
+ value = hashlib.md5()
# user:realm:password
- value.update('%s:%s:%s' % (user, realm, 'secret'))
+ input = '%s:%s:%s' % (user, realm, 'secret')
+ if not isinstance(input, bytes):
+ input = input.encode('UTF-8')
+ value.update(input)
hash = value.hexdigest()
return hash
return None
@@ -217,7 +220,7 @@ configuration would be used::
AuthBasicProvider dbm
AuthDBMUserFile /usr/local/wsgi/accounts.dbm
WSGIAuthGroupScript /usr/local/wsgi/scripts/auth.wsgi
- Require group secret-agents
+ Require wsgi-group secret-agents
Require valid-user
The 'auth.wsgi' script would then need to contain a 'groups_for_user()'
diff --git a/setup.py b/setup.py
index 1f4a8ad..6be132b 100644
--- a/setup.py
+++ b/setup.py
@@ -330,6 +330,20 @@ else:
APR_INCLUDES = get_apr_includes().split()
APU_INCLUDES = get_apu_includes().split()
+if not os.path.exists(APR_CONFIG) and not INCLUDEDIR:
+ if sys.platform == 'darwin':
+ # Likely no Xcode application installed or location of SDK in
+ # Xcode has changed with a new release of Xcode application.
+
+ raise RuntimeError('No Apache installation can be found, do you '
+ 'have the full Apple Xcode installed. It is not enough to '
+ 'have just the xcode command line tools installed.')
+ else:
+ # Set INCLUDEDIR just to avoid having an empty path. Probably
+ # should raise an exception here.
+
+ INCLUDEDIR = '/usr/include'
+
# Write out apxs_config.py which caches various configuration related to
# Apache. For the case of using our own Apache build, this needs to
# calculate values dynamically based on where binaries were installed.
@@ -556,6 +570,7 @@ setup(name = 'mod_wsgi',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
'Topic :: Internet :: WWW/HTTP :: WSGI',
'Topic :: Internet :: WWW/HTTP :: WSGI :: Server'
],
@@ -570,4 +585,5 @@ setup(name = 'mod_wsgi',
ext_modules = [extension],
entry_points = { 'console_scripts':
['mod_wsgi-express = mod_wsgi.server:main'],},
+ zip_safe = False,
)
diff --git a/src/server/__init__.py b/src/server/__init__.py
index 1912091..3c56b0a 100644
--- a/src/server/__init__.py
+++ b/src/server/__init__.py
@@ -321,9 +321,17 @@ WSGIPythonHome '%(python_home)s'
WSGIVerboseDebugging '%(verbose_debugging_flag)s'
+<IfDefine MOD_WSGI_WITH_SOCKET_PREFIX>
+WSGISocketPrefix %(socket_prefix)s/wsgi
+</IfDefine>
+<IfDefine !MOD_WSGI_WITH_SOCKET_PREFIX>
+WSGISocketPrefix %(server_root)s/wsgi
+</IfDefine>
+
+WSGISocketRotation Off
+
<IfDefine !ONE_PROCESS>
WSGIRestrictEmbedded On
-WSGISocketPrefix %(server_root)s/wsgi
<IfDefine MOD_WSGI_MULTIPROCESS>
WSGIDaemonProcess %(host)s:%(port)s \\
display-name='%(daemon_name)s' \\
@@ -346,6 +354,7 @@ WSGIDaemonProcess %(host)s:%(port)s \\
graceful-timeout=%(graceful_timeout)s \\
eviction-timeout=%(eviction_timeout)s \\
restart-interval=%(restart_interval)s \\
+ cpu-time-limit=%(cpu_time_limit)s \\
shutdown-timeout=%(shutdown_timeout)s \\
send-buffer-size=%(send_buffer_size)s \\
receive-buffer-size=%(receive_buffer_size)s \\
@@ -375,6 +384,7 @@ WSGIDaemonProcess %(host)s:%(port)s \\
graceful-timeout=%(graceful_timeout)s \\
eviction-timeout=%(eviction_timeout)s \\
restart-interval=%(restart_interval)s \\
+ cpu-time-limit=%(cpu_time_limit)s \\
shutdown-timeout=%(shutdown_timeout)s \\
send-buffer-size=%(send_buffer_size)s \\
receive-buffer-size=%(receive_buffer_size)s \\
@@ -432,6 +442,7 @@ AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/javascript
+AddOutputFilterByType DEFLATE application/json
</IfDefine>
<IfDefine MOD_WSGI_ROTATE_LOGS>
@@ -946,7 +957,7 @@ WSGIDaemonProcess 'service:%(name)s' \\
user='%(user)s' \\
group='%(group)s' \\
home='%(working_directory)s' \\
- threads=1 \\
+ threads=0 \\
python-path='%(python_path)s' \\
python-eggs='%(python_eggs)s' \\
lang='%(lang)s' \\
@@ -971,7 +982,7 @@ WSGIDaemonProcess 'service:%(name)s' \\
user='%(user)s' \\
group='%(group)s' \\
home='%(working_directory)s' \\
- threads=1 \\
+ threads=0 \\
python-path='%(python_path)s' \\
python-eggs='%(python_eggs)s' \\
lang='%(lang)s' \\
@@ -1913,7 +1924,7 @@ option_list = (
optparse.make_option('--http2', action='store_true', default=False,
help='Flag indicating whether HTTP/2 should be enabled.'
- 'Requires the mod_http2 module to be available.'),
+ 'Requires the mod_http2 module to be available.'),
optparse.make_option('--https-port', type='int', metavar='NUMBER',
help='The specific port to bind to and on which secure '
@@ -1964,7 +1975,7 @@ option_list = (
optparse.make_option('--https-only', action='store_true',
default=False, help='Flag indicating whether any requests '
- 'made using a HTTP request over the non secure connection '
+ 'made using a HTTP request over the non secure connection '
'should be redirected automatically to use a HTTPS request '
'over the secure connection.'),
@@ -2054,6 +2065,13 @@ option_list = (
'the process is forced to exit and restart. Not enabled by '
'default.'),
+ optparse.make_option('--cpu-time-limit', type='int', default='0',
+ metavar='SECONDS', help='Number of seconds of CPU time the '
+ 'process can use before it will be restarted. If graceful '
+ 'timeout is also specified, active requests will be given '
+ 'a chance to complete before the process is forced to exit '
+ 'and restart. Not enabled by default.'),
+
optparse.make_option('--graceful-timeout', type='int', default=15,
metavar='SECONDS', help='Grace period for requests to complete '
'normally, while still accepting new requests, when worker '
@@ -2372,7 +2390,8 @@ option_list = (
optparse.make_option('--server-root', metavar='DIRECTORY-PATH',
help='Specify an alternate directory for where the generated '
'web server configuration, startup files and logs will be '
- 'stored. Defaults to a sub directory of /tmp.'),
+ 'stored. Defaults to the sub directory specified by the '
+ 'TMPDIR environment variable, or /tmp if not specified.'),
optparse.make_option('--server-mpm', action='append',
dest='server_mpm_variables', metavar='NAME', help='Specify '
@@ -2457,6 +2476,11 @@ option_list = (
metavar='FILE-PATH', help='Override the path to the mime types '
'file used by the web server.'),
+ optparse.make_option('--socket-prefix', metavar='DIRECTORY-PATH',
+ help='Specify an alternate directory name prefix to be used '
+ 'for the UNIX domain sockets used by mod_wsgi to communicate '
+ 'between the Apache child processes and the daemon processes.'),
+
optparse.make_option('--add-handler', action='append', nargs=2,
dest='handler_scripts', metavar='EXTENSION SCRIPT-PATH',
help='Specify a WSGI application to be used as a special '
@@ -2579,6 +2603,17 @@ option_list = (
'the generation of the configuration with Apache then later '
'being started separately using the generated \'apachectl\' '
'script.'),
+
+ optparse.make_option('--isatty', action='store_true', default=False,
+ help='Flag indicating whether should assume being run in an '
+ 'interactive terminal session. In this case Apache will not '
+ 'replace this wrapper script, but will be run as a sub process.'
+ 'Signals such as SIGINT, SIGTERM, SIGHUP and SIGUSR1 will be '
+ 'forwarded onto Apache, but SIGWINCH will be blocked so that '
+ 'resizing of a terminal session window will not cause Apache '
+ 'to shutdown. This is a separate option at this time rather '
+ 'than being determined automatically while the reliability of '
+ 'intercepting and forwarding signals is verified.'),
)
def cmd_setup_server(params):
@@ -2625,8 +2660,11 @@ def _cmd_setup_server(command, args, options):
options['port'], os.getuid())
if not options['server_root']:
- options['server_root'] = '/tmp/mod_wsgi-%s:%s:%s' % (options['host'],
- options['port'], os.getuid())
+ tmpdir = os.environ.get('TMPDIR')
+ tmpdir = tmpdir or '/tmp'
+ tmpdir = tmpdir.rstrip('/')
+ options['server_root'] = '%s/mod_wsgi-%s:%s:%s' % (tmpdir,
+ options['host'], options['port'], os.getuid())
try:
os.mkdir(options['server_root'])
@@ -3207,6 +3245,8 @@ def _cmd_setup_server(command, args, options):
options['httpd_arguments_list'].append('-DMOD_WSGI_WITH_TRUSTED_PROXIES')
if options['python_path']:
options['httpd_arguments_list'].append('-DMOD_WSGI_WITH_PYTHON_PATH')
+ if options['socket_prefix']:
+ options['httpd_arguments_list'].append('-DMOD_WSGI_WITH_SOCKET_PREFIX')
if options['with_cgi']:
if os.path.exists(os.path.join(options['modules_directory'],
@@ -3335,7 +3375,31 @@ def cmd_start_server(params):
return
executable = os.path.join(config['server_root'], 'apachectl')
- os.execl(executable, executable, 'start', '-DFOREGROUND')
+
+ if config['isatty'] and sys.stdout.isatty():
+ process = None
+
+ def handler(signum, frame):
+ if process is None:
+ sys.exit(1)
+
+ else:
+ if signum not in [signal.SIGWINCH]:
+ os.kill(process.pid, signum)
+
+ signal.signal(signal.SIGINT, handler)
+ signal.signal(signal.SIGTERM, handler)
+ signal.signal(signal.SIGHUP, handler)
+ signal.signal(signal.SIGUSR1, handler)
+ signal.signal(signal.SIGWINCH, handler)
+
+ process = subprocess.Popen([executable, 'start', '-DFOREGROUND'],
+ preexec_fn=os.setpgrp)
+
+ process.wait()
+
+ else:
+ os.execl(executable, executable, 'start', '-DFOREGROUND')
def cmd_module_config(params):
formatter = optparse.IndentedHelpFormatter()
diff --git a/src/server/management/commands/runmodwsgi.py b/src/server/management/commands/runmodwsgi.py
index 9d0ccd7..3e18703 100644
--- a/src/server/management/commands/runmodwsgi.py
+++ b/src/server/management/commands/runmodwsgi.py
@@ -1,6 +1,8 @@
import os
import sys
import inspect
+import signal
+import subprocess
from django.core.management.base import BaseCommand
@@ -97,22 +99,28 @@ class Command(BaseCommand):
url_aliases = options.setdefault('url_aliases') or []
try:
- if settings.STATIC_URL and settings.STATIC_URL.startswith('/'):
- if settings.STATIC_ROOT:
- # We need a fiddle here as depending on the Python
- # version used, the list of URL aliases we are
- # passed could be either list of tuples or list of
- # lists. We need to ensure we use the same type so
- # that sorting of items in the lists works later.
-
- if not url_aliases:
- url_aliases.insert(0, (
- settings.STATIC_URL.rstrip('/') or '/',
- settings.STATIC_ROOT))
- else:
- url_aliases.insert(0, type(url_aliases[0])((
- settings.STATIC_URL.rstrip('/') or '/',
- settings.STATIC_ROOT)))
+ middleware = getattr(settings, 'MIDDLEWARE', None)
+
+ if middleware is None:
+ middleware = getattr(settings, 'MIDDLEWARE_CLASSES', [])
+
+ if 'whitenoise.middleware.WhiteNoiseMiddleware' not in middleware:
+ if settings.STATIC_URL and settings.STATIC_URL.startswith('/'):
+ if settings.STATIC_ROOT:
+ # We need a fiddle here as depending on the Python
+ # version used, the list of URL aliases we are
+ # passed could be either list of tuples or list of
+ # lists. We need to ensure we use the same type so
+ # that sorting of items in the lists works later.
+
+ if not url_aliases:
+ url_aliases.insert(0, (
+ settings.STATIC_URL.rstrip('/') or '/',
+ settings.STATIC_ROOT))
+ else:
+ url_aliases.insert(0, type(url_aliases[0])((
+ settings.STATIC_URL.rstrip('/') or '/',
+ settings.STATIC_ROOT)))
except AttributeError:
pass
@@ -127,4 +135,28 @@ class Command(BaseCommand):
executable = os.path.join(options['server_root'], 'apachectl')
name = executable.ljust(len(options['process_name']))
- os.execl(executable, name, 'start', '-DFOREGROUND')
+
+ if options['isatty'] and sys.stdout.isatty():
+ process = None
+
+ def handler(signum, frame):
+ if process is None:
+ sys.exit(1)
+
+ else:
+ if signum not in [signal.SIGWINCH]:
+ os.kill(process.pid, signum)
+
+ signal.signal(signal.SIGINT, handler)
+ signal.signal(signal.SIGTERM, handler)
+ signal.signal(signal.SIGHUP, handler)
+ signal.signal(signal.SIGUSR1, handler)
+ signal.signal(signal.SIGWINCH, handler)
+
+ process = subprocess.Popen([executable, 'start', '-DFOREGROUND'],
+ preexec_fn=os.setpgrp)
+
+ process.wait()
+
+ else:
+ os.execl(executable, name, 'start', '-DFOREGROUND')
diff --git a/src/server/mod_wsgi.c b/src/server/mod_wsgi.c
index 99f01b8..6913373 100644
--- a/src/server/mod_wsgi.c
+++ b/src/server/mod_wsgi.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,6 +80,8 @@ static apr_pool_t *wsgi_parent_pool = NULL;
int volatile wsgi_daemon_shutdown = 0;
static int volatile wsgi_daemon_graceful = 0;
+static int wsgi_dump_stack_traces = 0;
+static char *wsgi_shutdown_reason = "";
#if defined(MOD_WSGI_WITH_DAEMONS)
static apr_interval_time_t wsgi_startup_timeout = 0;
@@ -2106,12 +2108,20 @@ static PyObject *Adapter_start_response(AdapterObject *self, PyObject *args)
/* Publish event for the start of the response. */
if (wsgi_event_subscribers()) {
+ WSGIThreadInfo *thread_info;
+
+ thread_info = wsgi_thread_info(0, 0);
+
event = PyDict_New();
PyDict_SetItemString(event, "response_status", status_line);
PyDict_SetItemString(event, "response_headers", headers);
PyDict_SetItemString(event, "exception_info", exc_info);
+#if 0
+ PyDict_SetItemString(event, "request_data", thread_info->request_data);
+#endif
+
wsgi_publish_event("response_started", event);
Py_DECREF(event);
@@ -3002,6 +3012,8 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
WSGIThreadCPUUsage start_usage;
WSGIThreadCPUUsage end_usage;
+ int aborted = 0;
+
#if defined(MOD_WSGI_WITH_DAEMONS)
if (wsgi_idle_timeout && !self->config->ignore_activity) {
apr_thread_mutex_lock(wsgi_monitor_lock);
@@ -3116,6 +3128,10 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
PyDict_SetItemString(event, "application_start", value);
Py_DECREF(value);
+#if 0
+ PyDict_SetItemString(event, "request_data", thread_handle->request_data);
+#endif
+
wsgi_publish_event("request_started", event);
evwrapper = PyDict_GetItemString(event, "application_object");
@@ -3144,8 +3160,6 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
if (self->sequence != NULL) {
if (!Adapter_process_file_wrapper(self)) {
- int aborted = 0;
-
iterator = PyObject_GetIter(self->sequence);
if (iterator != NULL) {
@@ -3181,9 +3195,36 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
}
}
- if (!PyErr_Occurred() && !aborted) {
- if (Adapter_output(self, "", 0, NULL, 0))
+ if (!PyErr_Occurred()) {
+ if (!aborted) {
+ /*
+ * In the case where the response was empty we
+ * need to ensure we explicitly flush out the
+ * headers. This is done by calling the output
+ * routine but with an empty string as content.
+ * This could be gated on whether any content
+ * had already been sent, but easier to just call
+ * it all the time.
+ */
+
+ if (Adapter_output(self, "", 0, NULL, 0))
+ self->result = OK;
+ }
+ else {
+ /*
+ * If the client connection was already marked
+ * as aborted, then it indicates the client has
+ * closed the connection. In this case mark the
+ * final result as okay rather than an error so
+ * that the access log still records the original
+ * HTTP response code for the request rather than
+ * overriding it. If don't do this then access
+ * log will show 500 when the WSGI application
+ * itself had run fine.
+ */
+
self->result = OK;
+ }
}
Py_XDECREF(iterator);
@@ -3191,11 +3232,11 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
/*
* Log warning if more response content generated than was
- * indicated, or less if there was no errors generated by
- * the application.
+ * indicated, or less, if there was no errors generated by
+ * the application and connection wasn't aborted.
*/
- if (self->content_length_set && ((!PyErr_Occurred() &&
+ if (self->content_length_set && ((!PyErr_Occurred() && !aborted &&
self->output_length != self->content_length) ||
(self->output_length > self->content_length))) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r,
@@ -3337,6 +3378,10 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
PyDict_SetItemString(event, "application_time", value);
Py_DECREF(value);
+#if 0
+ PyDict_SetItemString(event, "request_data", thread_handle->request_data);
+#endif
+
wsgi_publish_event("request_finished", event);
Py_DECREF(event);
@@ -3548,7 +3593,8 @@ static PyObject *wsgi_load_source(apr_pool_t *pool, request_rec *r,
const char *name, int exists,
const char* filename,
const char *process_group,
- const char *application_group)
+ const char *application_group,
+ int ignore_system_exit)
{
FILE *fp = NULL;
PyObject *m = NULL;
@@ -3580,13 +3626,13 @@ static PyObject *wsgi_load_source(apr_pool_t *pool, request_rec *r,
if (r) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"mod_wsgi (pid=%d, process='%s', application='%s'): "
- "Loading WSGI script '%s'.", getpid(),
+ "Loading Python script file '%s'.", getpid(),
process_group, application_group, filename);
}
else {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
"mod_wsgi (pid=%d, process='%s', application='%s'): "
- "Loading WSGI script '%s'.", getpid(),
+ "Loading Python script file '%s'.", getpid(),
process_group, application_group, filename);
}
Py_END_ALLOW_THREADS
@@ -3647,13 +3693,13 @@ static PyObject *wsgi_load_source(apr_pool_t *pool, request_rec *r,
if (r) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"mod_wsgi (pid=%d, process='%s', application='%s'): "
- "Failed to parse WSGI script file '%s'.", getpid(),
+ "Failed to parse Python script file '%s'.", getpid(),
process_group, application_group, filename);
}
else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
"mod_wsgi (pid=%d, process='%s', application='%s'): "
- "Failed to parse WSGI script file '%s'.", getpid(),
+ "Failed to parse Python script file '%s'.", getpid(),
process_group, application_group, filename);
}
Py_END_ALLOW_THREADS
@@ -3687,20 +3733,40 @@ static PyObject *wsgi_load_source(apr_pool_t *pool, request_rec *r,
PyModule_AddObject(m, "__mtime__", object);
}
else {
- Py_BEGIN_ALLOW_THREADS
- if (r) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
- "mod_wsgi (pid=%d): Target WSGI script '%s' cannot "
- "be loaded as Python module.", getpid(), filename);
+ if (PyErr_ExceptionMatches(PyExc_SystemExit)) {
+ if (!ignore_system_exit) {
+ Py_BEGIN_ALLOW_THREADS
+ if (r) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "mod_wsgi (pid=%d): SystemExit exception "
+ "raised when doing exec of Python script "
+ "file '%s'.", getpid(), filename);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
+ "mod_wsgi (pid=%d): SystemExit exception "
+ "raised when doing exec of Python script "
+ "file '%s'.", getpid(), filename);
+ }
+ Py_END_ALLOW_THREADS
+ }
}
else {
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
- "mod_wsgi (pid=%d): Target WSGI script '%s' cannot "
- "be loaded as Python module.", getpid(), filename);
- }
- Py_END_ALLOW_THREADS
+ Py_BEGIN_ALLOW_THREADS
+ if (r) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "mod_wsgi (pid=%d): Failed to exec Python script "
+ "file '%s'.", getpid(), filename);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
+ "mod_wsgi (pid=%d): Failed to exec Python script "
+ "file '%s'.", getpid(), filename);
+ }
+ Py_END_ALLOW_THREADS
- wsgi_log_python_error(r, NULL, filename, 0);
+ wsgi_log_python_error(r, NULL, filename, 0);
+ }
}
return m;
@@ -4040,14 +4106,14 @@ static int wsgi_execute_script(request_rec *r)
/* Setup metrics for start of request. */
- thread_info = wsgi_start_request();
+ thread_info = wsgi_start_request(r);
/* Load module if not already loaded. */
if (!module) {
module = wsgi_load_source(r->pool, r, name, exists, script,
config->process_group,
- config->application_group);
+ config->application_group, 0);
}
/* Safe now to release the module lock. */
@@ -4173,6 +4239,15 @@ static apr_status_t wsgi_python_child_cleanup(void *data)
{
PyObject *interp = NULL;
+ /*
+ * If not a daemon process need to publish that process
+ * is shutting down here. For daemon we did it earlier
+ * before trying to wait on request threads.
+ */
+
+ if (!wsgi_daemon_process)
+ wsgi_publish_process_stopping(wsgi_shutdown_reason);
+
/* In a multithreaded MPM must protect table. */
#if APR_HAS_THREADS
@@ -4249,6 +4324,8 @@ static void wsgi_python_child_init(apr_pool_t *p)
PyGILState_STATE state;
PyObject *object = NULL;
+ int ignore_system_exit = 0;
+
/* Working with Python, so must acquire GIL. */
state = PyGILState_Ensure();
@@ -4292,6 +4369,13 @@ static void wsgi_python_child_init(apr_pool_t *p)
#endif
/*
+ * Create an interpreters index using Apache data structure so
+ * can iterate over interpreter names without needing Python GIL.
+ */
+
+ wsgi_interpreters_index = apr_hash_make(p);
+
+ /*
* Initialise the key for data related to a thread and force
* creation of thread info.
*/
@@ -4314,6 +4398,8 @@ static void wsgi_python_child_init(apr_pool_t *p)
PyDict_SetItemString(wsgi_interpreters, "", object);
Py_DECREF(object);
+ apr_hash_set(wsgi_interpreters_index, "", APR_HASH_KEY_STRING, "");
+
/* Restore the prior thread state and release the GIL. */
PyGILState_Release(state);
@@ -4325,6 +4411,9 @@ static void wsgi_python_child_init(apr_pool_t *p)
/* Loop through import scripts for this process and load them. */
+ if (wsgi_daemon_process && wsgi_daemon_process->group->threads == 0)
+ ignore_system_exit = 1;
+
if (wsgi_import_list) {
apr_array_header_t *scripts = NULL;
@@ -4339,6 +4428,14 @@ static void wsgi_python_child_init(apr_pool_t *p)
for (i = 0; i < scripts->nelts; ++i) {
entry = &entries[i];
+ /*
+ * Stop loading scripts if this is a daemon process and
+ * we have already been flagged to be shutdown.
+ */
+
+ if (wsgi_daemon_shutdown)
+ break;
+
if (!strcmp(wsgi_daemon_group, entry->process_group)) {
InterpreterObject *interp = NULL;
PyObject *modules = NULL;
@@ -4409,7 +4506,8 @@ static void wsgi_python_child_init(apr_pool_t *p)
module = wsgi_load_source(p, NULL, name, exists,
entry->handler_script,
entry->process_group,
- entry->application_group);
+ entry->application_group,
+ ignore_system_exit);
if (PyErr_Occurred())
PyErr_Clear();
@@ -4522,7 +4620,7 @@ static const char *wsgi_add_script_alias(cmd_parms *cmd, void *mconfig,
return "Invalid option to WSGI script alias definition.";
}
- if (!cmd->info && !strcmp(option, "application-group")) {
+ if (!strcmp(option, "application-group")) {
if (!*value)
return "Invalid name for WSGI application group.";
@@ -4532,7 +4630,7 @@ static const char *wsgi_add_script_alias(cmd_parms *cmd, void *mconfig,
application_group = value;
}
#if defined(MOD_WSGI_WITH_DAEMONS)
- else if (!cmd->info && !strcmp(option, "process-group")) {
+ else if (!strcmp(option, "process-group")) {
if (!*value)
return "Invalid name for WSGI process group.";
@@ -4579,9 +4677,17 @@ static const char *wsgi_add_script_alias(cmd_parms *cmd, void *mconfig,
entry->callable_object = callable_object;
entry->pass_authorization = pass_authorization;
+ /*
+ * Only add to import list if both process group and application
+ * group are specified, that they don't include substitution values,
+ * and in the case of WSGIScriptAliasMatch, that the WSGI script
+ * target path doesn't include substitutions from URL pattern.
+ */
+
if (process_group && application_group &&
!strstr(process_group, "%{") &&
- !strstr(application_group, "%{")) {
+ !strstr(application_group, "%{") &&
+ (!cmd->info || !strstr(a, "$"))) {
WSGIScriptFile *object = NULL;
@@ -6623,7 +6729,8 @@ static int wsgi_execute_dispatch(request_rec *r)
}
if (!module) {
- module = wsgi_load_source(r->pool, r, name, exists, script, "", group);
+ module = wsgi_load_source(r->pool, r, name, exists, script,
+ "", group, 0);
}
/* Safe now to release the module lock. */
@@ -6632,6 +6739,11 @@ static int wsgi_execute_dispatch(request_rec *r)
apr_thread_mutex_unlock(wsgi_module_lock);
#endif
+ /* Log any details of exceptions if import failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Assume everything will be okay for now. */
status = OK;
@@ -6725,9 +6837,12 @@ static int wsgi_execute_dispatch(request_rec *r)
}
else
status = HTTP_INTERNAL_SERVER_ERROR;
+
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
}
- else
- Py_DECREF(object);
object = NULL;
}
@@ -6805,9 +6920,12 @@ static int wsgi_execute_dispatch(request_rec *r)
}
else
status = HTTP_INTERNAL_SERVER_ERROR;
+
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
}
- else
- Py_DECREF(object);
object = NULL;
}
@@ -6884,9 +7002,12 @@ static int wsgi_execute_dispatch(request_rec *r)
}
else
status = HTTP_INTERNAL_SERVER_ERROR;
+
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
}
- else
- Py_DECREF(object);
object = NULL;
}
@@ -7372,7 +7493,7 @@ static const char *wsgi_add_daemon_process(cmd_parms *cmd, void *mconfig,
return "Invalid thread count for WSGI daemon process.";
threads = atoi(value);
- if (threads < 1 || threads >= WSGI_STACK_LAST-1)
+ if (threads < 0 || threads >= WSGI_STACK_LAST-1)
return "Invalid thread count for WSGI daemon process.";
}
else if (!strcmp(option, "umask")) {
@@ -7838,6 +7959,28 @@ static const char *wsgi_set_socket_prefix(cmd_parms *cmd, void *mconfig,
return NULL;
}
+static const char *wsgi_set_socket_rotation(cmd_parms *cmd, void *mconfig,
+ const char *f)
+{
+ const char *error = NULL;
+ WSGIServerConfig *sconfig = NULL;
+
+ error = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (error != NULL)
+ return error;
+
+ sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module);
+
+ if (strcasecmp(f, "Off") == 0)
+ sconfig->socket_rotation = 0;
+ else if (strcasecmp(f, "On") == 0)
+ sconfig->socket_rotation = 1;
+ else
+ return "WSGISocketRotation must be one of: Off | On";
+
+ return NULL;
+}
+
static const char wsgi_valid_accept_mutex_string[] =
"Valid accept mutex mechanisms for this platform are: default"
#if APR_HAS_FLOCK_SERIALIZE
@@ -8327,6 +8470,14 @@ static int wsgi_setup_socket(WSGIProcessGroup *process)
}
#endif
+ if (strlen(process->socket_path) > sizeof(addr.sun_path)) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server,
+ "mod_wsgi (pid=%d): Length of path for daemon process "
+ "socket exceeds maxmimum allowed value and will be "
+ "truncated, resulting in likely failure to bind the "
+ "socket, or other later related failure.", getpid());
+ }
+
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
apr_cpystrn(addr.sun_path, process->socket_path, sizeof(addr.sun_path));
@@ -9061,6 +9212,8 @@ static void *wsgi_monitor_thread(apr_thread_t *thd, void *data)
"time limit exceeded, stopping process "
"'%s'.", getpid(), group->name);
+ wsgi_shutdown_reason = "request_timeout";
+
wsgi_dump_stack_traces = 1;
restart = 1;
@@ -9075,6 +9228,8 @@ static void *wsgi_monitor_thread(apr_thread_t *thd, void *data)
"timer expired, stopping process '%s'.",
getpid(), group->name);
+ wsgi_shutdown_reason = "startup_timeout";
+
restart = 1;
}
else {
@@ -9109,6 +9264,8 @@ static void *wsgi_monitor_thread(apr_thread_t *thd, void *data)
"stopping process '%s'.", getpid(),
daemon->group->name);
+ wsgi_shutdown_reason = "restart_interval";
+
restart = 1;
}
}
@@ -9150,6 +9307,8 @@ static void *wsgi_monitor_thread(apr_thread_t *thd, void *data)
"stopping process '%s'.", getpid(),
group->name);
+ wsgi_shutdown_reason = "inactivity_timeout";
+
restart = 1;
}
else {
@@ -9262,7 +9421,7 @@ static void wsgi_log_stack_traces(void)
"active Python threads.", getpid());
while (PyDict_Next(threads, &i, &id, &frame)) {
- long thread_id = 0;
+ apr_int64_t thread_id = 0;
PyFrameObject *current = NULL;
@@ -9276,7 +9435,7 @@ static void wsgi_log_stack_traces(void)
char *filename = NULL;
char *name = NULL;
- lineno = current->f_lineno;
+ lineno = PyFrame_GetLineNumber(current);
#if PY_MAJOR_VERSION >= 3
filename = PyUnicode_AsUTF8(current->f_code->co_filename);
@@ -9288,9 +9447,9 @@ static void wsgi_log_stack_traces(void)
if (current == (PyFrameObject *)frame) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server,
- "mod_wsgi (pid=%d): Thread %ld executing "
- "file \"%s\", line %d, in %s", getpid(),
- thread_id, filename, lineno, name);
+ "mod_wsgi (pid=%d): Thread %" APR_INT64_T_FMT
+ " executing file \"%s\", line %d, in %s",
+ getpid(), thread_id, filename, lineno, name);
}
else {
if (current->f_back) {
@@ -9518,6 +9677,8 @@ static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon)
if (buf[0] == 'C') {
if (!wsgi_daemon_graceful) {
+ wsgi_shutdown_reason = "cpu_time_limit";
+
if (wsgi_active_requests) {
wsgi_daemon_graceful++;
@@ -9544,6 +9705,8 @@ static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon)
}
else if (buf[0] == 'G') {
if (!wsgi_daemon_graceful) {
+ wsgi_shutdown_reason = "graceful_signal";
+
if (wsgi_active_requests) {
wsgi_daemon_graceful++;
@@ -9603,6 +9766,8 @@ static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon)
* which are running as a debugging aid.
*/
+ wsgi_publish_process_stopping(wsgi_shutdown_reason);
+
#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 5)
if (wsgi_dump_stack_traces)
wsgi_log_stack_traces();
@@ -10217,9 +10382,14 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
apr_os_sock_put(&daemon->listener, &daemon->group->listener_fd, p);
- /* Run the main routine for the daemon process. */
+ /*
+ * Run the main routine for the daemon process if there
+ * is a non zero number of threads. When number of threads
+ * is zero we actually go on and shutdown immediately.
+ */
- wsgi_daemon_main(p, daemon);
+ if (daemon->group->threads != 0)
+ wsgi_daemon_main(p, daemon);
/*
* Destroy the pool for the daemon process. This will
@@ -10325,9 +10495,18 @@ static int wsgi_start_daemons(apr_pool_t *p)
* create the socket.
*/
- entry->socket_path = apr_psprintf(p, "%s.%d.%d.%d.sock",
- wsgi_server_config->socket_prefix,
- getpid(), mpm_generation, entry->id);
+ entry->socket_rotation = wsgi_server_config->socket_rotation;
+
+ if (entry->socket_rotation) {
+ entry->socket_path = apr_psprintf(p, "%s.%d.%d.%d.sock",
+ wsgi_server_config->socket_prefix,
+ getpid(), mpm_generation, entry->id);
+ }
+ else {
+ entry->socket_path = apr_psprintf(p, "%s.%d.u%d.%d.sock",
+ wsgi_server_config->socket_prefix,
+ getpid(), entry->uid, entry->id);
+ }
apr_hash_set(wsgi_daemon_index, entry->name, APR_HASH_KEY_STRING,
entry);
@@ -10589,8 +10768,9 @@ static int wsgi_connect_daemon(request_rec *r, WSGIDaemonSocket *daemon)
"mod_wsgi (pid=%d): Unable to connect to "
"WSGI daemon process '%s' on '%s' after "
"multiple attempts as listener backlog "
- "limit was exceeded.", getpid(),
- daemon->name, daemon->socket_path);
+ "limit was exceeded or the socket does "
+ "not exist.", getpid(), daemon->name,
+ daemon->socket_path);
apr_socket_close(daemon->socket);
@@ -11227,6 +11407,15 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
if (rv != APR_SUCCESS) {
apr_brigade_destroy(bb);
+ /*
+ * Don't flag error if client connection was aborted
+ * so that access log still records the original HTTP
+ * response code returned by the WSGI application.
+ */
+
+ if (r->connection->aborted)
+ return OK;
+
return HTTP_INTERNAL_SERVER_ERROR;
}
@@ -11270,6 +11459,15 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
if (rv != APR_SUCCESS) {
apr_brigade_destroy(bb);
+ /*
+ * Don't flag error if client connection was aborted
+ * so that access log still records the original HTTP
+ * response code returned by the WSGI application.
+ */
+
+ if (r->connection->aborted)
+ return OK;
+
return HTTP_INTERNAL_SERVER_ERROR;
}
@@ -11290,7 +11488,17 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
} else if (rv != APR_SUCCESS) {
apr_brigade_destroy(bb);
- return HTTP_INTERNAL_SERVER_ERROR;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "mod_wsgi (pid=%d): Failed to proxy response "
+ "from daemon.", getpid());
+
+ /*
+ * Don't flag error if couldn't read from daemon
+ * so that access log still records the original HTTP
+ * response code returned by the WSGI application.
+ */
+
+ return OK;
}
/*
@@ -11362,6 +11570,15 @@ static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb,
if (rv != APR_SUCCESS) {
apr_brigade_destroy(bb);
+ /*
+ * Don't flag error if client connection was aborted
+ * so that access log still records the original HTTP
+ * response code returned by the WSGI application.
+ */
+
+ if (r->connection->aborted)
+ return OK;
+
return HTTP_INTERNAL_SERVER_ERROR;
}
}
@@ -14411,7 +14628,8 @@ static authn_status wsgi_check_password(request_rec *r, const char *user,
}
if (!module) {
- module = wsgi_load_source(r->pool, r, name, exists, script, "", group);
+ module = wsgi_load_source(r->pool, r, name, exists, script,
+ "", group, 0);
}
/* Safe now to release the module lock. */
@@ -14420,6 +14638,11 @@ static authn_status wsgi_check_password(request_rec *r, const char *user,
apr_thread_mutex_unlock(wsgi_module_lock);
#endif
+ /* Log any details of exceptions if import failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Assume an internal server error unless everything okay. */
status = AUTH_GENERAL_ERROR;
@@ -14503,6 +14726,11 @@ static authn_status wsgi_check_password(request_rec *r, const char *user,
adapter->r = NULL;
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Close the log object so data is flushed. */
method = PyObject_GetAttrString(adapter->log, "close");
@@ -14514,19 +14742,22 @@ static authn_status wsgi_check_password(request_rec *r, const char *user,
}
else {
args = PyTuple_New(0);
- object = PyEval_CallObject(method, args);
+ result = PyEval_CallObject(method, args);
+ Py_XDECREF(result);
Py_DECREF(args);
}
- Py_XDECREF(object);
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
Py_XDECREF(method);
/* No longer need adapter object. */
Py_DECREF((PyObject *)adapter);
}
- else
- Py_DECREF(object);
}
else {
Py_BEGIN_ALLOW_THREADS
@@ -14536,11 +14767,6 @@ static authn_status wsgi_check_password(request_rec *r, const char *user,
"'Basic' auth provider.", getpid(), script);
Py_END_ALLOW_THREADS
}
-
- /* Log any details of exceptions if execution failed. */
-
- if (PyErr_Occurred())
- wsgi_log_python_error(r, NULL, script, 0);
}
/* Cleanup and release interpreter, */
@@ -14647,7 +14873,8 @@ static authn_status wsgi_get_realm_hash(request_rec *r, const char *user,
}
if (!module) {
- module = wsgi_load_source(r->pool, r, name, exists, script, "", group);
+ module = wsgi_load_source(r->pool, r, name, exists, script,
+ "", group, 0);
}
/* Safe now to release the module lock. */
@@ -14656,6 +14883,11 @@ static authn_status wsgi_get_realm_hash(request_rec *r, const char *user,
apr_thread_mutex_unlock(wsgi_module_lock);
#endif
+ /* Log any details of exceptions if import failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Assume an internal server error unless everything okay. */
status = AUTH_GENERAL_ERROR;
@@ -14740,6 +14972,11 @@ static authn_status wsgi_get_realm_hash(request_rec *r, const char *user,
adapter->r = NULL;
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Close the log object so data is flushed. */
method = PyObject_GetAttrString(adapter->log, "close");
@@ -14751,19 +14988,22 @@ static authn_status wsgi_get_realm_hash(request_rec *r, const char *user,
}
else {
args = PyTuple_New(0);
- object = PyEval_CallObject(method, args);
+ result = PyEval_CallObject(method, args);
+ Py_XDECREF(result);
Py_DECREF(args);
}
- Py_XDECREF(object);
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
Py_XDECREF(method);
/* No longer need adapter object. */
Py_DECREF((PyObject *)adapter);
}
- else
- Py_DECREF(object);
}
else {
Py_BEGIN_ALLOW_THREADS
@@ -14773,11 +15013,6 @@ static authn_status wsgi_get_realm_hash(request_rec *r, const char *user,
"'Digest' auth provider.", getpid(), script);
Py_END_ALLOW_THREADS
}
-
- /* Log any details of exceptions if execution failed. */
-
- if (PyErr_Occurred())
- wsgi_log_python_error(r, NULL, script, 0);
}
/* Cleanup and release interpreter, */
@@ -14889,7 +15124,8 @@ static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
}
if (!module) {
- module = wsgi_load_source(r->pool, r, name, exists, script, "", group);
+ module = wsgi_load_source(r->pool, r, name, exists, script,
+ "", group, 0);
}
/* Safe now to release the module lock. */
@@ -14898,6 +15134,11 @@ static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
apr_thread_mutex_unlock(wsgi_module_lock);
#endif
+ /* Log any details of exceptions if import failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Assume an internal server error unless everything okay. */
status = HTTP_INTERNAL_SERVER_ERROR;
@@ -14914,7 +15155,7 @@ static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
if (object) {
PyObject *vars = NULL;
PyObject *args = NULL;
- PyObject *sequence = NULL;
+ PyObject *result = NULL;
PyObject *method = NULL;
AuthObject *adapter = NULL;
@@ -14926,15 +15167,15 @@ static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
Py_INCREF(object);
args = Py_BuildValue("(Os)", vars, r->user);
- sequence = PyEval_CallObject(object, args);
+ result = PyEval_CallObject(object, args);
Py_DECREF(args);
Py_DECREF(object);
Py_DECREF(vars);
- if (sequence) {
+ if (result) {
PyObject *iterator;
- iterator = PyObject_GetIter(sequence);
+ iterator = PyObject_GetIter(result);
if (iterator) {
PyObject *item;
@@ -15010,7 +15251,7 @@ static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
Py_END_ALLOW_THREADS
}
- Py_DECREF(sequence);
+ Py_DECREF(result);
}
/*
@@ -15023,6 +15264,11 @@ static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
adapter->r = NULL;
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Close the log object so data is flushed. */
method = PyObject_GetAttrString(adapter->log, "close");
@@ -15034,19 +15280,22 @@ static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
}
else {
args = PyTuple_New(0);
- object = PyEval_CallObject(method, args);
+ result = PyEval_CallObject(method, args);
+ Py_XDECREF(result);
Py_DECREF(args);
}
- Py_XDECREF(object);
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
Py_XDECREF(method);
/* No longer need adapter object. */
Py_DECREF((PyObject *)adapter);
}
- else
- Py_DECREF(object);
}
else {
Py_BEGIN_ALLOW_THREADS
@@ -15056,11 +15305,6 @@ static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config,
"group provider.", getpid(), script);
Py_END_ALLOW_THREADS
}
-
- /* Log any details of exceptions if execution failed. */
-
- if (PyErr_Occurred())
- wsgi_log_python_error(r, NULL, script, 0);
}
/* Cleanup and release interpreter, */
@@ -15087,7 +15331,7 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
const char *script;
const char *group;
- int result = 0;
+ int allow = 0;
if (!config->access_script) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
@@ -15166,7 +15410,8 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
}
if (!module) {
- module = wsgi_load_source(r->pool, r, name, exists, script, "", group);
+ module = wsgi_load_source(r->pool, r, name, exists, script,
+ "", group, 0);
}
/* Safe now to release the module lock. */
@@ -15175,9 +15420,14 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
apr_thread_mutex_unlock(wsgi_module_lock);
#endif
+ /* Log any details of exceptions if import failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Assume not allowed unless everything okay. */
- result = 0;
+ allow = 0;
/* Determine if script exists and execute it. */
@@ -15191,7 +15441,7 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
if (object) {
PyObject *vars = NULL;
PyObject *args = NULL;
- PyObject *flag = NULL;
+ PyObject *result = NULL;
PyObject *method = NULL;
AuthObject *adapter = NULL;
@@ -15203,18 +15453,18 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
Py_INCREF(object);
args = Py_BuildValue("(Oz)", vars, host);
- flag = PyEval_CallObject(object, args);
+ result = PyEval_CallObject(object, args);
Py_DECREF(args);
Py_DECREF(object);
Py_DECREF(vars);
- if (flag) {
- if (flag == Py_None) {
- result = -1;
+ if (result) {
+ if (result == Py_None) {
+ allow = -1;
}
- else if (PyBool_Check(flag)) {
- if (flag == Py_True)
- result = 1;
+ else if (PyBool_Check(result)) {
+ if (result == Py_True)
+ allow = 1;
}
else {
Py_BEGIN_ALLOW_THREADS
@@ -15226,7 +15476,7 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
Py_END_ALLOW_THREADS
}
- Py_DECREF(flag);
+ Py_DECREF(result);
}
/*
@@ -15239,6 +15489,11 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
adapter->r = NULL;
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Close the log object so data is flushed. */
method = PyObject_GetAttrString(adapter->log, "close");
@@ -15250,19 +15505,22 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
}
else {
args = PyTuple_New(0);
- object = PyEval_CallObject(method, args);
+ result = PyEval_CallObject(method, args);
+ Py_XDECREF(result);
Py_DECREF(args);
}
- Py_XDECREF(object);
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
Py_XDECREF(method);
/* No longer need adapter object. */
Py_DECREF((PyObject *)adapter);
}
- else
- Py_DECREF(object);
}
else {
Py_BEGIN_ALLOW_THREADS
@@ -15272,11 +15530,6 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
"host validator.", getpid(), script);
Py_END_ALLOW_THREADS
}
-
- /* Log any details of exceptions if execution failed. */
-
- if (PyErr_Occurred())
- wsgi_log_python_error(r, NULL, script, 0);
}
/* Cleanup and release interpreter, */
@@ -15285,7 +15538,7 @@ static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config,
wsgi_release_interpreter(interp);
- return result;
+ return allow;
}
static int wsgi_hook_access_checker(request_rec *r)
@@ -15422,7 +15675,8 @@ static int wsgi_hook_check_user_id(request_rec *r)
}
if (!module) {
- module = wsgi_load_source(r->pool, r, name, exists, script, "", group);
+ module = wsgi_load_source(r->pool, r, name, exists, script,
+ "", group, 0);
}
/* Safe now to release the module lock. */
@@ -15431,6 +15685,11 @@ static int wsgi_hook_check_user_id(request_rec *r)
apr_thread_mutex_unlock(wsgi_module_lock);
#endif
+ /* Log any details of exceptions if import failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Assume an internal server error unless everything okay. */
status = HTTP_INTERNAL_SERVER_ERROR;
@@ -15512,6 +15771,11 @@ static int wsgi_hook_check_user_id(request_rec *r)
adapter->r = NULL;
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
/* Close the log object so data is flushed. */
method = PyObject_GetAttrString(adapter->log, "close");
@@ -15523,19 +15787,22 @@ static int wsgi_hook_check_user_id(request_rec *r)
}
else {
args = PyTuple_New(0);
- object = PyEval_CallObject(method, args);
+ result = PyEval_CallObject(method, args);
+ Py_XDECREF(result);
Py_DECREF(args);
}
- Py_XDECREF(object);
+ /* Log any details of exceptions if execution failed. */
+
+ if (PyErr_Occurred())
+ wsgi_log_python_error(r, NULL, script, 0);
+
Py_XDECREF(method);
/* No longer need adapter object. */
Py_DECREF((PyObject *)adapter);
}
- else
- Py_DECREF(object);
}
else {
Py_BEGIN_ALLOW_THREADS
@@ -15545,11 +15812,6 @@ static int wsgi_hook_check_user_id(request_rec *r)
"'Basic' auth provider.", getpid(), script);
Py_END_ALLOW_THREADS
}
-
- /* Log any details of exceptions if execution failed. */
-
- if (PyErr_Occurred())
- wsgi_log_python_error(r, NULL, script, 0);
}
/* Cleanup and release interpreter, */
@@ -15798,6 +16060,8 @@ static const command_rec wsgi_commands[] =
NULL, RSRC_CONF, "Specify details of daemon processes to start."),
AP_INIT_TAKE1("WSGISocketPrefix", wsgi_set_socket_prefix,
NULL, RSRC_CONF, "Path prefix for the daemon process sockets."),
+ AP_INIT_TAKE1("WSGISocketRotation", wsgi_set_socket_rotation,
+ NULL, RSRC_CONF, "Enable/Disable rotation of daemon process sockets."),
AP_INIT_TAKE1("WSGIAcceptMutex", wsgi_set_accept_mutex,
NULL, RSRC_CONF, "Set accept mutex type for daemon processes."),
diff --git a/src/server/wsgi_apache.c b/src/server/wsgi_apache.c
index 56f698a..c3f5412 100644
--- a/src/server/wsgi_apache.c
+++ b/src/server/wsgi_apache.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_apache.h b/src/server/wsgi_apache.h
index 7c0fa38..88daaf9 100644
--- a/src/server/wsgi_apache.h
+++ b/src/server/wsgi_apache.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_buckets.c b/src/server/wsgi_buckets.c
index 3d9968f..0ab679a 100644
--- a/src/server/wsgi_buckets.c
+++ b/src/server/wsgi_buckets.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_buckets.h b/src/server/wsgi_buckets.h
index 9a47984..7568476 100644
--- a/src/server/wsgi_buckets.h
+++ b/src/server/wsgi_buckets.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_convert.c b/src/server/wsgi_convert.c
index 1e167d6..7dc5e2e 100644
--- a/src/server/wsgi_convert.c
+++ b/src/server/wsgi_convert.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_convert.h b/src/server/wsgi_convert.h
index 3124d4b..a604b5e 100644
--- a/src/server/wsgi_convert.h
+++ b/src/server/wsgi_convert.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_daemon.c b/src/server/wsgi_daemon.c
index 2430caf..c2003d2 100644
--- a/src/server/wsgi_daemon.c
+++ b/src/server/wsgi_daemon.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_daemon.h b/src/server/wsgi_daemon.h
index 1101330..f8855f8 100644
--- a/src/server/wsgi_daemon.h
+++ b/src/server/wsgi_daemon.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -134,6 +134,7 @@ typedef struct {
rlim_t memory_limit;
rlim_t virtual_memory_limit;
const char *socket_path;
+ int socket_rotation;
int listener_fd;
const char* mutex_path;
apr_proc_mutex_t* mutex;
diff --git a/src/server/wsgi_interp.c b/src/server/wsgi_interp.c
index 41aede7..00c803c 100644
--- a/src/server/wsgi_interp.c
+++ b/src/server/wsgi_interp.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -159,6 +159,24 @@ PyTypeObject SignalIntercept_Type = {
0, /*tp_is_gc*/
};
+/* ------------------------------------------------------------------------- */
+
+static PyObject *wsgi_system_exit(PyObject *self, PyObject *args)
+{
+ PyErr_SetObject(PyExc_SystemExit, 0);
+
+ return NULL;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static PyMethodDef wsgi_system_exit_method[] = {
+ { "system_exit", (PyCFunction)wsgi_system_exit, METH_VARARGS, 0 },
+ { NULL },
+};
+
+/* ------------------------------------------------------------------------- */
+
/* Wrapper around Python interpreter instances. */
const char *wsgi_python_path = NULL;
@@ -562,11 +580,64 @@ InterpreterObject *newInterpreterObject(const char *name)
/*
* Install intercept for signal handler registration
- * if appropriate.
+ * if appropriate. Don't do this though if number of
+ * threads for daemon process was set as 0, indicating
+ * a potential daemon process which is running a
+ * service script.
*/
- if (wsgi_server_config->restrict_signal != 0) {
+ /*
+ * If running in daemon mode and there are no threads
+ * specified, must be running with service script, in
+ * which case we register default signal handler for
+ * SIGINT which throws a SystemExit exception. If
+ * instead restricting signals, replace function for
+ * registering signal handlers so they are ignored.
+ */
+
+ if (wsgi_daemon_process && wsgi_daemon_process->group->threads == 0) {
+ module = PyImport_ImportModule("signal");
+
+ if (module) {
+ PyObject *dict = NULL;
+ PyObject *func = NULL;
+
+ dict = PyModule_GetDict(module);
+ func = PyDict_GetItemString(dict, "signal");
+
+ if (func) {
+ PyObject *res = NULL;
+ PyObject *args = NULL;
+ PyObject *callback = NULL;
+
+ Py_INCREF(func);
+
+ callback = PyCFunction_New(&wsgi_system_exit_method[0], NULL);
+
+ args = Py_BuildValue("(iO)", SIGTERM, callback);
+ res = PyEval_CallObject(func, args);
+
+ if (!res) {
+ Py_BEGIN_ALLOW_THREADS
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
+ "mod_wsgi (pid=%d): Call to "
+ "'signal.signal()' to register exit "
+ "function failed, ignoring.", getpid());
+ Py_END_ALLOW_THREADS
+ }
+
+ Py_XDECREF(res);
+ Py_XDECREF(args);
+ Py_XDECREF(callback);
+
+ Py_DECREF(func);
+ }
+ }
+
+ Py_XDECREF(module);
+ }
+ else if (wsgi_server_config->restrict_signal != 0) {
module = PyImport_ImportModule("signal");
if (module) {
@@ -1168,6 +1239,8 @@ InterpreterObject *newInterpreterObject(const char *name)
PyModule_AddObject(module, "event_callbacks", PyList_New(0));
+ PyModule_AddObject(module, "active_requests", PyDict_New());
+
PyModule_AddObject(module, "request_data", PyCFunction_New(
&wsgi_request_data_method[0], NULL));
@@ -1441,8 +1514,6 @@ static void Interpreter_dealloc(InterpreterObject *self)
PyObject *exitfunc = NULL;
#endif
- PyObject *event = NULL;
-
/*
* We should always enter here with the Python GIL
* held and an active thread state. This should only
@@ -1509,14 +1580,6 @@ static void Interpreter_dealloc(InterpreterObject *self)
Py_END_ALLOW_THREADS
}
- /* Publish event that process is being stopped. */
-
- event = PyDict_New();
-
- wsgi_publish_event("process_stopping", event);
-
- Py_DECREF(event);
-
/*
* Because the thread state we are using was created outside
* of any Python code and is not the same as the Python main
@@ -1994,7 +2057,8 @@ apr_status_t wsgi_python_term(void)
* condition.
*/
- apr_thread_mutex_lock(wsgi_shutdown_lock);
+ if (wsgi_daemon_process)
+ apr_thread_mutex_lock(wsgi_shutdown_lock);
#if defined(MOD_WSGI_WITH_DAEMONS)
wsgi_daemon_shutdown++;
@@ -2002,7 +2066,8 @@ apr_status_t wsgi_python_term(void)
Py_Finalize();
- apr_thread_mutex_unlock(wsgi_shutdown_lock);
+ if (wsgi_daemon_process)
+ apr_thread_mutex_unlock(wsgi_shutdown_lock);
wsgi_python_initialized = 0;
@@ -2355,6 +2420,8 @@ apr_thread_mutex_t* wsgi_shutdown_lock = NULL;
PyObject *wsgi_interpreters = NULL;
+apr_hash_t *wsgi_interpreters_index = NULL;
+
InterpreterObject *wsgi_acquire_interpreter(const char *name)
{
PyThreadState *tstate = NULL;
@@ -2414,6 +2481,16 @@ InterpreterObject *wsgi_acquire_interpreter(const char *name)
}
PyDict_SetItemString(wsgi_interpreters, name, (PyObject *)handle);
+
+ /*
+ * Add interpreter name to index kept in Apache data
+ * strcuture as well. Make a copy of the name just in
+ * case we have been given temporary value.
+ */
+
+ apr_hash_set(wsgi_interpreters_index, apr_pstrdup(
+ apr_hash_pool_get(wsgi_interpreters_index), name),
+ APR_HASH_KEY_STRING, "");
}
else
Py_INCREF(handle);
@@ -2525,4 +2602,39 @@ void wsgi_release_interpreter(InterpreterObject *handle)
/* ------------------------------------------------------------------------- */
+void wsgi_publish_process_stopping(char *reason)
+{
+ InterpreterObject *interp = NULL;
+ apr_hash_index_t *hi;
+
+ hi = apr_hash_first(NULL, wsgi_interpreters_index);
+
+ while (hi) {
+ PyObject *event = NULL;
+ PyObject *object = NULL;
+
+ interp = wsgi_acquire_interpreter((char *)apr_hash_this_key(hi));
+
+ event = PyDict_New();
+
+#if PY_MAJOR_VERSION >= 3
+ object = PyUnicode_DecodeLatin1(reason, strlen(reason), NULL);
+#else
+ object = PyString_FromString(reason);
+#endif
+ PyDict_SetItemString(event, "shutdown_reason", object);
+ Py_DECREF(object);
+
+ wsgi_publish_event("process_stopping", event);
+
+ Py_DECREF(event);
+
+ wsgi_release_interpreter(interp);
+
+ hi = apr_hash_next(hi);
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
/* vi: set sw=4 expandtab : */
diff --git a/src/server/wsgi_interp.h b/src/server/wsgi_interp.h
index 6fad25b..831c078 100644
--- a/src/server/wsgi_interp.h
+++ b/src/server/wsgi_interp.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -68,6 +68,8 @@ extern const char *wsgi_python_eggs;
extern PyObject *wsgi_interpreters;
+extern apr_hash_t *wsgi_interpreters_index;
+
#if APR_HAS_THREADS
extern apr_thread_mutex_t *wsgi_interp_lock;
extern apr_thread_mutex_t* wsgi_shutdown_lock;
@@ -81,6 +83,8 @@ extern apr_status_t wsgi_python_term(void);
extern InterpreterObject *wsgi_acquire_interpreter(const char *name);
extern void wsgi_release_interpreter(InterpreterObject *handle);
+extern void wsgi_publish_process_stopping(char *reason);
+
/* ------------------------------------------------------------------------- */
#endif
diff --git a/src/server/wsgi_logger.c b/src/server/wsgi_logger.c
index 6ee0f78..0cb2021 100644
--- a/src/server/wsgi_logger.c
+++ b/src/server/wsgi_logger.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -694,12 +694,21 @@ void wsgi_log_python_error(request_rec *r, PyObject *log,
PyObject *object = NULL;
if (wsgi_event_subscribers()) {
+ WSGIThreadInfo *thread_info;
+
+ thread_info = wsgi_thread_info(0, 0);
+
event = PyDict_New();
object = Py_BuildValue("(OOO)", type, value, traceback);
PyDict_SetItemString(event, "exception_info", object);
Py_DECREF(object);
+#if 0
+ PyDict_SetItemString(event, "request_data",
+ thread_info->request_data);
+#endif
+
wsgi_publish_event("request_exception", event);
Py_DECREF(event);
diff --git a/src/server/wsgi_logger.h b/src/server/wsgi_logger.h
index c64d1f4..2184ff2 100644
--- a/src/server/wsgi_logger.h
+++ b/src/server/wsgi_logger.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_memory.h b/src/server/wsgi_memory.h
index d32fb9b..dc343f5 100644
--- a/src/server/wsgi_memory.h
+++ b/src/server/wsgi_memory.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_metrics.c b/src/server/wsgi_metrics.c
index e8f0859..7fb61df 100644
--- a/src/server/wsgi_metrics.c
+++ b/src/server/wsgi_metrics.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,6 @@ apr_uint64_t wsgi_total_requests = 0;
int wsgi_active_requests = 0;
static double wsgi_thread_utilization = 0.0;
static apr_time_t wsgi_utilization_last = 0;
-int wsgi_dump_stack_traces = 0;
/* Request tracking and timing. */
@@ -77,13 +76,43 @@ static double wsgi_utilization_time(int adjustment)
return utilization;
}
-WSGIThreadInfo *wsgi_start_request(void)
+WSGIThreadInfo *wsgi_start_request(request_rec *r)
{
WSGIThreadInfo *thread_info;
+ PyObject *module = NULL;
+
thread_info = wsgi_thread_info(1, 1);
+
thread_info->request_data = PyDict_New();
+#if AP_MODULE_MAGIC_AT_LEAST(20100923,2)
+#if PY_MAJOR_VERSION >= 3
+ thread_info->request_id = PyUnicode_DecodeLatin1(r->log_id,
+ strlen(r->log_id), NULL);
+#else
+ thread_info->request_id = PyString_FromString(r->log_id);
+#endif
+
+ module = PyImport_ImportModule("mod_wsgi");
+
+ if (module) {
+ PyObject *dict = NULL;
+ PyObject *requests = NULL;
+
+ dict = PyModule_GetDict(module);
+ requests = PyDict_GetItemString(dict, "active_requests");
+
+ if (requests)
+ PyDict_SetItem(requests, thread_info->request_id,
+ thread_info->request_data);
+
+ Py_DECREF(module);
+ }
+ else
+ PyErr_Clear();
+#endif
+
wsgi_utilization_time(1);
return thread_info;
@@ -93,12 +122,34 @@ void wsgi_end_request(void)
{
WSGIThreadInfo *thread_info;
+ PyObject *module = NULL;
+
thread_info = wsgi_thread_info(0, 1);
if (thread_info) {
+#if AP_MODULE_MAGIC_AT_LEAST(20100923,2)
+ module = PyImport_ImportModule("mod_wsgi");
+
+ if (module) {
+ PyObject *dict = NULL;
+ PyObject *requests = NULL;
+
+ dict = PyModule_GetDict(module);
+ requests = PyDict_GetItemString(dict, "active_requests");
+
+ PyDict_DelItem(requests, thread_info->request_id);
+
+ Py_DECREF(module);
+ }
+ else
+ PyErr_Clear();
+#endif
if (thread_info->log_buffer)
Py_CLEAR(thread_info->log_buffer);
+ if (thread_info->request_id)
+ Py_CLEAR(thread_info->request_id);
+
if (thread_info->request_data)
Py_CLEAR(thread_info->request_data);
}
@@ -611,12 +662,12 @@ static PyObject *wsgi_subscribe_events(PyObject *self, PyObject *args)
PyList_Append(list, callback);
else
return NULL;
+
+ Py_DECREF(module);
}
else
return NULL;
- Py_DECREF(module);
-
Py_INCREF(Py_None);
return Py_None;
}
@@ -639,7 +690,7 @@ long wsgi_event_subscribers(void)
if (list)
result = PyList_Size(list);
- Py_XDECREF(module);
+ Py_DECREF(module);
return result;
}
@@ -663,6 +714,8 @@ void wsgi_publish_event(const char *name, PyObject *event)
list = PyDict_GetItemString(dict, "event_callbacks");
Py_INCREF(list);
+
+ Py_DECREF(module);
}
else {
Py_BEGIN_ALLOW_THREADS
@@ -676,8 +729,6 @@ void wsgi_publish_event(const char *name, PyObject *event)
return;
}
- Py_XDECREF(module);
-
if (!list) {
Py_BEGIN_ALLOW_THREADS
ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
diff --git a/src/server/wsgi_metrics.h b/src/server/wsgi_metrics.h
index 6b9eac0..03145e4 100644
--- a/src/server/wsgi_metrics.h
+++ b/src/server/wsgi_metrics.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,13 +30,12 @@
extern apr_uint64_t wsgi_total_requests;
extern int wsgi_active_requests;
-extern int wsgi_dump_stack_traces;
extern apr_thread_mutex_t* wsgi_monitor_lock;
extern PyMethodDef wsgi_process_metrics_method[];
-extern WSGIThreadInfo *wsgi_start_request(void);
+extern WSGIThreadInfo *wsgi_start_request(request_rec *r);
extern void wsgi_end_request(void);
extern PyMethodDef wsgi_server_metrics_method[];
diff --git a/src/server/wsgi_python.h b/src/server/wsgi_python.h
index dea3c3b..8ae3cfc 100644
--- a/src/server/wsgi_python.h
+++ b/src/server/wsgi_python.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_restrict.c b/src/server/wsgi_restrict.c
index cbd27bf..5ebba4b 100644
--- a/src/server/wsgi_restrict.c
+++ b/src/server/wsgi_restrict.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_restrict.h b/src/server/wsgi_restrict.h
index 86ec64c..0c6c18d 100644
--- a/src/server/wsgi_restrict.h
+++ b/src/server/wsgi_restrict.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_server.c b/src/server/wsgi_server.c
index 22f7f49..229b028 100644
--- a/src/server/wsgi_server.c
+++ b/src/server/wsgi_server.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,6 +82,8 @@ WSGIServerConfig *newWSGIServerConfig(apr_pool_t *p)
object->socket_prefix = ap_server_root_relative(p, object->socket_prefix);
#endif
+ object->socket_rotation = 1;
+
object->verbose_debugging = 0;
object->python_warnings = NULL;
diff --git a/src/server/wsgi_server.h b/src/server/wsgi_server.h
index 20f5a42..02892ae 100644
--- a/src/server/wsgi_server.h
+++ b/src/server/wsgi_server.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -67,6 +67,7 @@ typedef struct {
apr_array_header_t *alias_list;
const char *socket_prefix;
+ int socket_rotation;
apr_lockmech_e lock_mechanism;
int verbose_debugging;
diff --git a/src/server/wsgi_stream.c b/src/server/wsgi_stream.c
index 7e04d65..934479a 100644
--- a/src/server/wsgi_stream.c
+++ b/src/server/wsgi_stream.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_stream.h b/src/server/wsgi_stream.h
index 28ba958..1f3b8a1 100644
--- a/src/server/wsgi_stream.h
+++ b/src/server/wsgi_stream.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_thread.h b/src/server/wsgi_thread.h
index fd6da78..5a29d74 100644
--- a/src/server/wsgi_thread.h
+++ b/src/server/wsgi_thread.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ typedef struct {
int thread_id;
int request_thread;
apr_int64_t request_count;
+ PyObject *request_id;
PyObject *request_data;
PyObject *log_buffer;
} WSGIThreadInfo;
diff --git a/src/server/wsgi_validate.c b/src/server/wsgi_validate.c
index e8741b6..72259ca 100644
--- a/src/server/wsgi_validate.c
+++ b/src/server/wsgi_validate.c
@@ -1,7 +1,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_validate.h b/src/server/wsgi_validate.h
index ecb7b1e..18a56be 100644
--- a/src/server/wsgi_validate.h
+++ b/src/server/wsgi_validate.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/server/wsgi_version.h b/src/server/wsgi_version.h
index 1d7e9ee..d1f1f40 100755
--- a/src/server/wsgi_version.h
+++ b/src/server/wsgi_version.h
@@ -4,7 +4,7 @@
/* ------------------------------------------------------------------------- */
/*
- * Copyright 2007-2017 GRAHAM DUMPLETON
+ * Copyright 2007-2018 GRAHAM DUMPLETON
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,9 +24,9 @@
/* Module version information. */
#define MOD_WSGI_MAJORVERSION_NUMBER 4
-#define MOD_WSGI_MINORVERSION_NUMBER 5
-#define MOD_WSGI_MICROVERSION_NUMBER 24
-#define MOD_WSGI_VERSION_STRING "4.5.24"
+#define MOD_WSGI_MINORVERSION_NUMBER 6
+#define MOD_WSGI_MICROVERSION_NUMBER 0
+#define MOD_WSGI_VERSION_STRING "4.6.0"
/* ------------------------------------------------------------------------- */
diff --git a/tests/auth.wsgi b/tests/auth.wsgi
index 37a90cb..77b6eae 100644
--- a/tests/auth.wsgi
+++ b/tests/auth.wsgi
@@ -1,3 +1,7 @@
+def allow_access(environ, host):
+ print('HOST', host, environ['REQUEST_URI'])
+ return True
+
def check_password(environ, user, password):
print('USER', user, environ['REQUEST_URI'])
if user == 'spy':
@@ -10,14 +14,17 @@ def check_password(environ, user, password):
return False
return None
-import md5
+import hashlib
def get_realm_hash(environ, user, realm):
print('USER', user, environ['REQUEST_URI'])
if user == 'spy':
- value = md5.new()
+ value = hashlib.md5()
# user:realm:password
- value.update('%s:%s:%s' % (user, realm, 'secret'))
+ input = '%s:%s:%s' % (user, realm, 'secret')
+ if not isinstance(input, bytes):
+ input = input.encode('UTF-8')
+ value.update(input)
hash = value.hexdigest()
return hash
return None
diff --git a/tests/events.wsgi b/tests/events.wsgi
index ab3f10f..6d7dfb0 100644
--- a/tests/events.wsgi
+++ b/tests/events.wsgi
@@ -4,6 +4,7 @@ import mod_wsgi
import traceback
import time
import os
+import threading
try:
mod_wsgi.request_data()
@@ -24,15 +25,24 @@ def event_handler(name, **kwargs):
environ = kwargs['request_environ']
start_time = time.time()
request['start_time'] = start_time
+ thread = threading.current_thread()
+ request['thread_name'] = thread.name
+ request['thread_id'] = thread.ident
return dict(application_object=wrapper(kwargs['application_object']))
+ elif name == 'response_started':
+ request = mod_wsgi.request_data()
+ print('CONTENT', request)
+ print('ACTIVE', mod_wsgi.active_requests)
elif name == 'request_finished':
request = mod_wsgi.request_data()
print('REQUEST', request)
print('FINISH', time.time()-request['start_time'])
print('PROCESS', mod_wsgi.process_metrics())
elif name == 'request_exception':
- exc_info = kwargs['exc_info']
- traceback.print_exception(*exc_info)
+ exception_info = kwargs['exception_info']
+ traceback.print_exception(*exception_info)
+ elif name == 'process_stopping':
+ print('SHUTDOWN', mod_wsgi.active_requests)
print('EVENTS', mod_wsgi.event_callbacks)
@@ -42,9 +52,11 @@ print('CALLBACKS', mod_wsgi.event_callbacks)
def application(environ, start_response):
failure_mode = environ.get('HTTP_X_FAILURE_MODE', '')
-
failure_mode = failure_mode.split()
+ sleep_duration = environ.get('HTTP_X_SLEEP_DURATION', 0)
+ sleep_duration = float(sleep_duration or 0)
+
if 'application' in failure_mode:
raise RuntimeError('application')
@@ -57,6 +69,9 @@ def application(environ, start_response):
environ['wsgi.input'].read()
+ if sleep_duration:
+ time.sleep(sleep_duration)
+
try:
yield output