diff --git a/massive/rest/__init__.py b/massive/rest/__init__.py index 5a00da5a..0fc42de3 100644 --- a/massive/rest/__init__.py +++ b/massive/rest/__init__.py @@ -54,6 +54,9 @@ def __init__( connect_timeout: float = 10.0, read_timeout: float = 10.0, num_pools: int = 10, + # Per-host connection limit. 10 rather than urllib3's default of 1 so that + # threaded clients work sensibly out of the box; see BaseClient. + maxsize: int = 10, retries: int = 3, base: str = BASE, pagination: bool = True, @@ -66,6 +69,7 @@ def __init__( connect_timeout=connect_timeout, read_timeout=read_timeout, num_pools=num_pools, + maxsize=maxsize, retries=retries, base=base, pagination=pagination, @@ -78,6 +82,7 @@ def __init__( connect_timeout=connect_timeout, read_timeout=read_timeout, num_pools=num_pools, + maxsize=maxsize, retries=retries, base=base, pagination=pagination, diff --git a/massive/rest/base.py b/massive/rest/base.py index 3349d7ef..05c5694a 100644 --- a/massive/rest/base.py +++ b/massive/rest/base.py @@ -34,6 +34,7 @@ def __init__( verbose: bool, trace: bool, custom_json: Optional[Any] = None, + maxsize: int = 10, ): if api_key is None: raise AuthError( @@ -75,6 +76,11 @@ def __init__( # https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html#urllib3.HTTPConnectionPool self.client = urllib3.PoolManager( num_pools=num_pools, + # Connections kept per HOST. urllib3 defaults this to 1, which serialises + # any threaded client onto a single connection and renegotiates TLS for + # every request that cannot get it. num_pools does not help: it caps the + # number of distinct host pools, and this client talks to one host. + maxsize=maxsize, headers=self.headers, # default headers sent with each request. ca_certs=certifi.where(), cert_reqs="CERT_REQUIRED", diff --git a/test_rest/test_connection_pool.py b/test_rest/test_connection_pool.py new file mode 100644 index 00000000..f8f81196 --- /dev/null +++ b/test_rest/test_connection_pool.py @@ -0,0 +1,22 @@ +import unittest + +from massive import RESTClient + + +class ConnectionPoolTest(unittest.TestCase): + def test_maxsize_defaults_to_ten(self): + client = RESTClient(api_key="test") + + assert client.client.connection_pool_kw["maxsize"] == 10 + + def test_maxsize_is_configurable(self): + client = RESTClient(api_key="test", maxsize=64) + + assert client.client.connection_pool_kw["maxsize"] == 64 + + def test_maxsize_is_independent_of_num_pools(self): + # num_pools caps the number of distinct HOST pools, which does not help a client + # that talks to one host from many threads -- that is what maxsize controls. + client = RESTClient(api_key="test", num_pools=50, maxsize=32) + + assert client.client.connection_pool_kw["maxsize"] == 32