diff --git a/keystoneclient/session.py b/keystoneclient/session.py index 1f606e43e..ae8736b3c 100644 --- a/keystoneclient/session.py +++ b/keystoneclient/session.py @@ -15,6 +15,7 @@ import functools import hashlib import logging import os +import socket import time from oslo.config import cfg @@ -123,6 +124,9 @@ class Session(object): redirect=_DEFAULT_REDIRECT_LIMIT): if not session: session = requests.Session() + # Use TCPKeepAliveAdapter to fix bug 1323862 + for scheme in session.adapters.keys(): + session.mount(scheme, TCPKeepAliveAdapter()) self.auth = auth self.session = session @@ -778,3 +782,14 @@ class Session(object): kwargs['timeout'] = args.timeout return cls._make(**kwargs) + + +class TCPKeepAliveAdapter(requests.adapters.HTTPAdapter): + """The custom adapter used to set TCP Keep-Alive on all connections.""" + def init_poolmanager(self, *args, **kwargs): + if requests.__version__ >= '2.4.1': + kwargs.setdefault('socket_options', [ + (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ]) + super(TCPKeepAliveAdapter, self).init_poolmanager(*args, **kwargs) diff --git a/keystoneclient/tests/test_session.py b/keystoneclient/tests/test_session.py index 3cd7ae5f6..b6015f069 100644 --- a/keystoneclient/tests/test_session.py +++ b/keystoneclient/tests/test_session.py @@ -205,6 +205,19 @@ class SessionTests(utils.TestCase): self.assertThat(self.requests.request_history, matchers.HasLength(retries + 1)) + def test_uses_tcp_keepalive_by_default(self): + session = client_session.Session() + requests_session = session.session + self.assertIsInstance(requests_session.adapters['http://'], + client_session.TCPKeepAliveAdapter) + self.assertIsInstance(requests_session.adapters['https://'], + client_session.TCPKeepAliveAdapter) + + def test_does_not_set_tcp_keepalive_on_custom_sessions(self): + mock_session = mock.Mock() + client_session.Session(session=mock_session) + self.assertFalse(mock_session.mount.called) + class RedirectTests(utils.TestCase):