From a657582c5cd8a39580c44ad401fd3e69870068b1 Mon Sep 17 00:00:00 2001 From: abhishekkekane Date: Tue, 21 Oct 2014 01:37:42 -0700 Subject: [PATCH] Eventlet green threads not released back to pool Presently, the wsgi server allows persist connections hence even after the response is sent to the client, it doesn't close the client socket connection. Because of this problem, the green thread is not released back to the pool. In order to close the client socket connection explicitly after the response is sent and read successfully by the client, you simply have to set keepalive to False when you create a wsgi server. Add a parameter to take advantage of the new(ish) eventlet socket timeout behaviour. Allows closing idle client connections after a period of time, eg: $ time nc localhost 8776 real 1m0.063s Setting 'client_socket_timeout = 0' means do not timeout. DocImpact: Added wsgi_keep_alive option (default=True). Added client_socket_timeout option (default=0). Conflicts: nova/tests/unit/test_wsgi.py Note: The required unit-tests are manually added to the below path, as new path for unit-tests is not present in stable/icehouse release. nova/tests/compute/test_host_api.py This patch is not 1:1 cherry-pick, I have changed the default value of client_socket_timeout to 0, as per the policy for changes to stable branches. (https://wiki.openstack.org/wiki/StableBranch#Appropriate_Fixes) SecurityImpact Closes-Bug: #1361360 Change-Id: I399b812f6d452226fd306c423de8dcea8520d2aa (cherry picked from commit 04d7a724fdf80db51e73f12c5b8c982db9310742) --- nova/tests/test_wsgi.py | 30 ++++++++++++++++++++++++++++++ nova/wsgi.py | 14 +++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_wsgi.py b/nova/tests/test_wsgi.py index c33112acdf06..c6af487cbbfb 100644 --- a/nova/tests/test_wsgi.py +++ b/nova/tests/test_wsgi.py @@ -157,6 +157,36 @@ class TestWSGIServer(test.NoDBTestCase): server.stop() server.wait() + def test_client_socket_timeout(self): + self.flags(client_socket_timeout=5) + + # mocking eventlet spawn method to check it is called with + # configured 'client_socket_timeout' value. + with mock.patch.object(eventlet, + 'spawn') as mock_spawn: + server = nova.wsgi.Server("test_app", None, + host="127.0.0.1", port=0) + server.start() + _, kwargs = mock_spawn.call_args + self.assertEqual(CONF.client_socket_timeout, + kwargs['socket_timeout']) + server.stop() + + def test_wsgi_keep_alive(self): + self.flags(wsgi_keep_alive=False) + + # mocking eventlet spawn method to check it is called with + # configured 'wsgi_keep_alive' value. + with mock.patch.object(eventlet, + 'spawn') as mock_spawn: + server = nova.wsgi.Server("test_app", None, + host="127.0.0.1", port=0) + server.start() + _, kwargs = mock_spawn.call_args + self.assertEqual(CONF.wsgi_keep_alive, + kwargs['keepalive']) + server.stop() + class TestWSGIServerWithSSL(test.NoDBTestCase): """WSGI server with SSL tests.""" diff --git a/nova/wsgi.py b/nova/wsgi.py index fb52be40c9d4..67855b5ece3b 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -69,6 +69,15 @@ wsgi_opts = [ "max_header_line may need to be increased when using " "large tokens (typically those generated by the " "Keystone v3 API with big service catalogs)."), + cfg.BoolOpt('wsgi_keep_alive', + default=True, + help="If False, closes the client socket connection " + "explicitly."), + cfg.IntOpt('client_socket_timeout', default=0, + help="Timeout for client connections' socket operations. " + "If an incoming connection is idle for this number of " + "seconds it will be closed. A value of '0' means " + "wait forever."), ] CONF = cfg.CONF CONF.register_opts(wsgi_opts) @@ -107,6 +116,7 @@ class Server(object): self._wsgi_logger = logging.WritableLogger(self._logger) self._use_ssl = use_ssl self._max_url_len = max_url_len + self.client_socket_timeout = CONF.client_socket_timeout or None if backlog < 1: raise exception.InvalidInput( @@ -206,7 +216,9 @@ class Server(object): 'custom_pool': self._pool, 'log': self._wsgi_logger, 'log_format': CONF.wsgi_log_format, - 'debug': False + 'debug': False, + 'keepalive': CONF.wsgi_keep_alive, + 'socket_timeout': self.client_socket_timeout } if self._max_url_len: