async def test_connection_context_create_connection_if_context_returns_with_exception( self, event_loop, mocker, not_closed_connection): # Checks that if a connection returns with an exception, if there are waiters a new connection is created. create_protocol = mocker.patch( "emcache.connection_pool.create_protocol", CoroutineMock( side_effect=[not_closed_connection, not_closed_connection]), ) connection_pool = ConnectionPool("localhost", 11211, 1, 1, None, None, lambda _: _, False, False, None) async def coro(connection_context, raise_exception=False): async with connection_context as _: if raise_exception: raise Exception() # Try to get a connection many times. The first one will # get and use the connection_closed, when this connection is # returned back to the pool, a new one will be created connection_context1 = connection_pool.create_connection_context() connection_context2 = connection_pool.create_connection_context() connection_context3 = connection_pool.create_connection_context() task1 = event_loop.create_task(coro(connection_context1)) task2 = event_loop.create_task(coro(connection_context2)) # Remember that due to the LIFO nature, we have to raise the exception using the last # pushed connection context task3 = event_loop.create_task( coro(connection_context3, raise_exception=True)) await asyncio.gather(task1, task2, task3, return_exceptions=True) # check that we have called the create_protocol twice create_protocol.assert_has_calls([ call("localhost", 11211, ssl=False, ssl_verify=False, ssl_extra_ca=None, timeout=None), call("localhost", 11211, ssl=False, ssl_verify=False, ssl_extra_ca=None, timeout=None), ]) assert connection_pool.total_connections == 1 # test specific atributes of the metrics assert connection_pool.metrics().connections_closed == 1 assert connection_pool.metrics().operations_executed_with_error == 1 assert connection_pool.metrics().operations_executed == 2
async def test_metrics_create_connection_latencies(self): # Check that the latencies measured are well calculated when metrics # method is called. connection_pool = ConnectionPool("localhost", 11211, 1, 1, None, None, lambda _: _, False, False, None) connection_pool._create_connection_latencies = [ float(i) for i in range(0, 101) ] assert connection_pool.metrics().create_connection_avg == 50.0 assert connection_pool.metrics().create_connection_p50 == 50.0 assert connection_pool.metrics().create_connection_p99 == 99.0 assert connection_pool.metrics().create_connection_upper == 100.0 await connection_pool.close()
async def test_create_connection_tries_again_if_error( self, event_loop, mocker, not_closed_connection, exc): # If there is an error trying to acquire a connection and there are waiters waiting we will keep # trying to create a connection # Avoid the annoying sleep added by the backoff mocker.patch("emcache.connection_pool.asyncio.sleep", CoroutineMock()) # First we return a Timeout and second try returns an usable connection create_protocol = mocker.patch( "emcache.connection_pool.create_protocol", CoroutineMock(side_effect=[exc, not_closed_connection]), ) connection_pool = ConnectionPool("localhost", 11211, 1, 1, None, 1.0, lambda _: _, False, False, None) async def coro(connection_context): async with connection_context as _: pass connection_context = connection_pool.create_connection_context() # wait for a new connection available in another task task = event_loop.create_task(coro(connection_context)) await task # check that we have called twice the create_protocol create_protocol.assert_has_calls([ call("localhost", 11211, ssl=False, ssl_verify=False, ssl_extra_ca=None, timeout=1.0), call("localhost", 11211, ssl=False, ssl_verify=False, ssl_extra_ca=None, timeout=1.0), ]) # test specific atributes of the metrics assert connection_pool.metrics().cur_connections == 1 assert connection_pool.metrics().connections_created == 1 assert connection_pool.metrics().connections_created_with_error == 1
async def test_metrics(self): # Just test that the type returned is the right one and there is a method # published. Specific attribute value are tested in the other tests which are # not specific to metrics but they are simulating most of the situations that # are needed for incrementing the counters. connection_pool = ConnectionPool("localhost", 11211, 1, 1, None, None, lambda _: _, False, False, None) assert isinstance(connection_pool.metrics(), ConnectionPoolMetrics) # checks that without latencies create connection times are None values assert connection_pool.metrics().create_connection_avg is None assert connection_pool.metrics().create_connection_p50 is None assert connection_pool.metrics().create_connection_p99 is None assert connection_pool.metrics().create_connection_upper is None await connection_pool.close()
async def test_waiters_LIFO(self, event_loop, mocker, not_closed_connection): # Check that waiters queue are seen as LIFO queue, where we try to rescue the latency # of the last ones. mocker.patch("emcache.connection_pool.create_protocol", CoroutineMock(return_value=not_closed_connection)) connection_pool = ConnectionPool("localhost", 11211, 1, 1, None, None, lambda _: _, False, False, None) waiters_woken_up = [] async def coro(connection_context): async with connection_context as _: waiters_woken_up.append(connection_context) # Try to get a connection many times. connection_context1 = connection_pool.create_connection_context() connection_context2 = connection_pool.create_connection_context() connection_context3 = connection_pool.create_connection_context() task1 = event_loop.create_task(coro(connection_context1)) task2 = event_loop.create_task(coro(connection_context2)) task3 = event_loop.create_task(coro(connection_context3)) await asyncio.gather(task1, task2, task3) # check that where called in the right order assert waiters_woken_up == [ connection_context3, connection_context2, connection_context1 ] # test specific atributes of the metrics assert connection_pool.metrics().operations_waited == 3
async def test_connection_context_not_use_a_closed_connections( self, event_loop, mocker, not_closed_connection): # Checks that if a connection is in closed state its not used and purged. connection_closed = Mock() connection_closed.closed.return_value = True connection_pool = ConnectionPool("localhost", 11211, 2, 1, None, None, lambda _: _, False, False, None) # we add by hand a closed connection and a none closed one to the pool connection_pool._unused_connections.append(not_closed_connection) connection_pool._unused_connections.append(connection_closed) connection_pool._total_connections = 2 connection_context = connection_pool.create_connection_context() async with connection_context as connection: assert connection is not_closed_connection # double check that the closed one has been removed and the none closed # one is still there. assert not_closed_connection in connection_pool._unused_connections assert connection_closed not in connection_pool._unused_connections assert connection_pool.total_connections == 1 # test specific atributes of the metrics assert connection_pool.metrics().connections_closed == 1
async def test_initalizes_creating_min_connection(self, mocker, not_closed_connection, min_connections): ev = asyncio.Event() connections_created = 0 def f(*args, **kwargs): nonlocal connections_created connections_created += 1 if connections_created == min_connections: ev.set() return not_closed_connection create_protocol = mocker.patch( "emcache.connection_pool.create_protocol", CoroutineMock(side_effect=f)) max_connections = min_connections connection_pool = ConnectionPool("localhost", 11211, max_connections, min_connections, None, None, lambda _: _, False, False, None) # wait till the min_connections have been created. await ev.wait() create_protocol.assert_called_with("localhost", 11211, ssl=False, ssl_verify=False, ssl_extra_ca=None, timeout=None) assert connection_pool.total_connections == min_connections # test specific atributes of the metrics assert connection_pool.metrics().cur_connections == min_connections assert connection_pool.metrics().connections_created == min_connections assert connection_pool.metrics().create_connection_avg is not None assert connection_pool.metrics().create_connection_p50 is not None assert connection_pool.metrics().create_connection_p99 is not None assert connection_pool.metrics().create_connection_upper is not None
async def test_connection_context_create_connection_if_closed( self, event_loop, mocker, not_closed_connection): # Checks that if a connection is returned in closed state, if there are waiters a new connection is created. connection_closed = Mock() connection_closed.closed.return_value = True create_protocol = mocker.patch( "emcache.connection_pool.create_protocol", CoroutineMock( side_effect=[connection_closed, not_closed_connection]), ) connection_pool = ConnectionPool("localhost", 11211, 1, 1, None, None, lambda _: _, False, False, None) async def coro(connection_context): async with connection_context as _: pass # Try to get a connection many times. The first one will # get and use the connection_closed, when this connection is # returned back to the pool, a new one will be created connection_context1 = connection_pool.create_connection_context() connection_context2 = connection_pool.create_connection_context() connection_context3 = connection_pool.create_connection_context() task1 = event_loop.create_task(coro(connection_context1)) task2 = event_loop.create_task(coro(connection_context2)) task3 = event_loop.create_task(coro(connection_context3)) await asyncio.gather(task1, task2, task3) # check that we have called the create_protocol twice create_protocol.assert_has_calls([ call("localhost", 11211, ssl=False, ssl_verify=False, ssl_extra_ca=None, timeout=None), call("localhost", 11211, ssl=False, ssl_verify=False, ssl_extra_ca=None, timeout=None), ]) assert connection_pool.total_connections == 1 # test specific atributes of the metrics assert connection_pool.metrics().connections_closed == 1
async def test_purge_not_the_beyond_min_connections( self, event_loop, mocker, min_connections): # Mock everything, we will be calling it by hand get_running_loop = Mock() call_later = Mock() asyncio_patched = mocker.patch("emcache.connection_pool.asyncio") asyncio_patched.get_running_loop = get_running_loop get_running_loop.return_value.call_later = call_later max_connections = min_connections connection_pool = ConnectionPool("localhost", 11211, max_connections, min_connections, 60, None, lambda _: _, False, False, None) # we add N min expired connections that must not be removed since they are the minimum ones expired_connections = [Mock() for _ in range(min_connections)] for expired_connection in expired_connections: connection_pool._unused_connections.append(expired_connection) connection_pool._connections_last_time_used[ expired_connection] = time.monotonic() - 61 connection_pool._total_connections = min_connections # Run the purge connection_pool._purge_unused_connections() # Check that the expired has not been removed the minumum connections for expired_connection in expired_connections: assert expired_connection in connection_pool._unused_connections assert expired_connection in connection_pool._connections_last_time_used assert connection_pool.total_connections == min_connections # test specific atributes of the metrics assert connection_pool.metrics().cur_connections == min_connections assert connection_pool.metrics().connections_purged == 0
async def test_purge_connections(self, event_loop, mocker): # Mock everything, we will be calling it by hand get_running_loop = Mock() call_later = Mock() asyncio_patched = mocker.patch("emcache.connection_pool.asyncio") asyncio_patched.get_running_loop = get_running_loop get_running_loop.return_value.call_later = call_later connection_pool = ConnectionPool("localhost", 11211, 3, 1, 60, None, lambda _: _, False, False, None) # Check that the call late was done with the right parameters call_later.assert_called_with( 60, connection_pool._purge_unused_connections) # we add three connections by hand, with different timestamps. none_expired_connection = Mock() expired_connection_1 = Mock() expired_connection_2 = Mock() connection_pool._unused_connections.append(none_expired_connection) connection_pool._unused_connections.append(expired_connection_1) connection_pool._unused_connections.append(expired_connection_2) connection_pool._connections_last_time_used[ none_expired_connection] = time.monotonic() connection_pool._connections_last_time_used[ expired_connection_1] = time.monotonic() - 61 connection_pool._connections_last_time_used[ expired_connection_2] = time.monotonic() - 61 connection_pool._total_connections = 3 # Run the purge connection_pool._purge_unused_connections() # Check that only one expired connection has been removed and the none expired is still there assert expired_connection_1 not in connection_pool._unused_connections assert expired_connection_2 in connection_pool._unused_connections assert none_expired_connection in connection_pool._unused_connections assert expired_connection_1 not in connection_pool._connections_last_time_used assert expired_connection_2 in connection_pool._connections_last_time_used assert none_expired_connection in connection_pool._connections_last_time_used assert connection_pool.total_connections == 2 assert connection_pool.metrics().cur_connections == 2 assert connection_pool.metrics().connections_purged == 1 # Run the purge again, which should purge the other unused connection connection_pool._purge_unused_connections() assert expired_connection_2 not in connection_pool._unused_connections assert none_expired_connection in connection_pool._unused_connections assert expired_connection_2 not in connection_pool._connections_last_time_used assert none_expired_connection in connection_pool._connections_last_time_used assert connection_pool.total_connections == 1 assert connection_pool.metrics().cur_connections == 1 assert connection_pool.metrics().connections_purged == 2 # Run the purge again, nothing should happen connection_pool._purge_unused_connections() assert none_expired_connection in connection_pool._unused_connections assert none_expired_connection in connection_pool._connections_last_time_used assert connection_pool.total_connections == 1