From 071790e4f1e99087960aa7b9828eb86f59029094 Mon Sep 17 00:00:00 2001 From: Rouven Bauer Date: Mon, 13 Jun 2022 14:53:44 +0200 Subject: [PATCH 1/5] Update and extend tests for `connection_acquisition_timeout` setting. Tests did not encompass routing drivers --- .../driver_parameters/scripts/router.script | 12 ++ .../scripts/router_hello_delay.script | 16 +++ .../scripts/router_route_delay.script | 15 ++ .../scripts/session_run.script | 12 ++ .../scripts/session_run_auth_delay.script | 6 +- .../tx_without_commit_or_rollback.script | 2 +- .../test_connection_acquisition_timeout_ms.py | 128 +++++++++++++++--- .../test_max_connection_pool_size.py | 4 +- 8 files changed, 168 insertions(+), 27 deletions(-) create mode 100644 tests/stub/driver_parameters/scripts/router.script create mode 100644 tests/stub/driver_parameters/scripts/router_hello_delay.script create mode 100644 tests/stub/driver_parameters/scripts/router_route_delay.script create mode 100644 tests/stub/driver_parameters/scripts/session_run.script diff --git a/tests/stub/driver_parameters/scripts/router.script b/tests/stub/driver_parameters/scripts/router.script new file mode 100644 index 000000000..a0b6d6ec1 --- /dev/null +++ b/tests/stub/driver_parameters/scripts/router.script @@ -0,0 +1,12 @@ +!: BOLT 5.0 +!: AUTO RESET +!: ALLOW RESTART + +A: HELLO {"{}": "*"} +*: RESET +{+ + C: ROUTE "*" "*" "*" + S: SUCCESS { "rt": { "ttl": 1000, "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010"], "role":"READ"}, {"addresses": ["#HOST#:9010"], "role":"WRITE"}]}} + *: RESET ++} +?: GOODBYE diff --git a/tests/stub/driver_parameters/scripts/router_hello_delay.script b/tests/stub/driver_parameters/scripts/router_hello_delay.script new file mode 100644 index 000000000..8aba0fe84 --- /dev/null +++ b/tests/stub/driver_parameters/scripts/router_hello_delay.script @@ -0,0 +1,16 @@ +!: BOLT 5.0 +!: AUTO RESET +!: ALLOW RESTART + +C: HELLO {"{}": "*"} +S: 2 + + 2 + SUCCESS {"server": "Neo4j/5.0.0", "connection_id": "bolt-123456789"} +*: RESET +{+ + C: ROUTE "*" "*" "*" + S: SUCCESS { "rt": { "ttl": 1000, "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010"], "role":"READ"}, {"addresses": ["#HOST#:9010"], "role":"WRITE"}]}} + *: RESET ++} +?: GOODBYE diff --git a/tests/stub/driver_parameters/scripts/router_route_delay.script b/tests/stub/driver_parameters/scripts/router_route_delay.script new file mode 100644 index 000000000..36f2c8d4d --- /dev/null +++ b/tests/stub/driver_parameters/scripts/router_route_delay.script @@ -0,0 +1,15 @@ +!: BOLT 5.0 +!: AUTO RESET +!: ALLOW RESTART + +A: HELLO {"{}": "*"} +*: RESET +{+ + C: ROUTE "*" "*" "*" + S: 2 + + 2 + SUCCESS { "rt": { "ttl": 1000, "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010"], "role":"READ"}, {"addresses": ["#HOST#:9010"], "role":"WRITE"}]}} + *: RESET ++} +?: GOODBYE diff --git a/tests/stub/driver_parameters/scripts/session_run.script b/tests/stub/driver_parameters/scripts/session_run.script new file mode 100644 index 000000000..a3d909b19 --- /dev/null +++ b/tests/stub/driver_parameters/scripts/session_run.script @@ -0,0 +1,12 @@ +!: BOLT 5.0 +!: ALLOW CONCURRENT + +C: HELLO {"{}": "*"} +*: RESET +C: RUN "*" "*" "*" +S: SUCCESS {"fields": ["n"]} +C: PULL {"n": "*"} +S: RECORD [1] + SUCCESS {"type": "r"} + +*: RESET diff --git a/tests/stub/driver_parameters/scripts/session_run_auth_delay.script b/tests/stub/driver_parameters/scripts/session_run_auth_delay.script index fc67660b2..9cf5bb894 100644 --- a/tests/stub/driver_parameters/scripts/session_run_auth_delay.script +++ b/tests/stub/driver_parameters/scripts/session_run_auth_delay.script @@ -1,13 +1,11 @@ -!: BOLT 4.4 +!: BOLT 5.0 !: ALLOW CONCURRENT C: HELLO {"{}": "*"} S: 2 2 - - 2 - SUCCESS {"server": "Neo4j/9.9.9", "connection_id": "bolt-123456789"} + SUCCESS {"server": "Neo4j/5.0.0", "connection_id": "bolt-123456789"} *: RESET C: RUN "*" "*" "*" S: SUCCESS {"fields": ["n"]} diff --git a/tests/stub/driver_parameters/scripts/tx_without_commit_or_rollback.script b/tests/stub/driver_parameters/scripts/tx_without_commit_or_rollback.script index 21f83003a..ee6fdfc76 100644 --- a/tests/stub/driver_parameters/scripts/tx_without_commit_or_rollback.script +++ b/tests/stub/driver_parameters/scripts/tx_without_commit_or_rollback.script @@ -1,4 +1,4 @@ -!: BOLT 4.4 +!: BOLT 5.0 !: ALLOW CONCURRENT A: HELLO {"{}": "*"} diff --git a/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py b/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py index cf6bce8e9..40d2a1129 100644 --- a/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py +++ b/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py @@ -9,7 +9,7 @@ class TestConnectionAcquisitionTimeoutMs(TestkitTestCase): """ - Connection Acquition Timeout Tests. + Connection Acquisition Timeout Tests. The connection acquisition timeout must account for the whole acquisition execution time, whether a new connection is created, @@ -32,13 +32,14 @@ class TestConnectionAcquisitionTimeoutMs(TestkitTestCase): """ required_features = ( - types.Feature.BOLT_4_4, + types.Feature.BOLT_5_0, types.Feature.API_CONNECTION_ACQUISITION_TIMEOUT ) def setUp(self): super().setUp() - self._server = StubServer(9001) + self._server = StubServer(9010) + self._router = StubServer(9000) self._driver = None self._session = None self._sessions = [] @@ -46,6 +47,7 @@ def setUp(self): def tearDown(self) -> None: self._server.reset() + self._router.reset() for tx in self._txs: with self.assertRaises(types.DriverError): # The server does not accept ending the transaction. @@ -63,6 +65,10 @@ def tearDown(self) -> None: return super().tearDown() + def _start_server(self, server, script): + server.start(self.script_path(script), + vars_={"#HOST#": self._router.host}) + def test_should_work_when_every_step_is_done_in_time(self): """ Everything in time scenario. @@ -75,9 +81,7 @@ def test_should_work_when_every_step_is_done_in_time(self): Then the query is executed successfully """ - self._server.start( - self.script_path("session_run_auth_delay.script") - ) + self._start_server(self._server, "session_run_auth_delay.script") auth = types.AuthorizationToken("basic", principal="neo4j", credentials="pass") @@ -90,6 +94,95 @@ def test_should_work_when_every_step_is_done_in_time(self): list(self._session.run("RETURN 1 as n")) + def test_encompasses_router_connection_time(self): + """Router connection times out.""" + uri = "neo4j://10.255.255.255" + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + def test_encompasses_router_handshake(self): + """Router available but with delayed HELLO response.""" + self._start_server(self._router, "router_hello_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._server.reset() + reader_connections = self._server.count_responses("") + self.assertEqual(0, reader_connections) + + def test_encompasses_router_route_response(self): + """Router available but with delayed ROUTE response.""" + self._start_server(self._router, "router_route_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._server.reset() + reader_connections = self._server.count_responses("") + self.assertEqual(0, reader_connections) + + def test_combined_router_and_reader_delay(self): + """Slow but in time router + slow but in time router == too slow.""" + self._start_server(self._router, "router_hello_delay.script") + self._start_server(self._server, "session_run_auth_delay.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=6000, + connection_timeout_ms=720000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._server.reset() + reader_connections = self._server.count_responses("") + self.assertEqual(1, reader_connections) + def test_should_encompass_the_handshake_time(self): """ Handshake takes longer scenario. @@ -104,9 +197,7 @@ def test_should_encompass_the_handshake_time(self): Then the query is not executed since the connection acquisition timed out. """ - self._server.start( - self.script_path("session_run_auth_delay.script") - ) + self._start_server(self._server, "session_run_auth_delay.script") auth = types.AuthorizationToken("basic", principal="neo4j", credentials="pass") @@ -122,7 +213,7 @@ def test_should_encompass_the_handshake_time(self): def test_should_fail_when_acquisition_timeout_is_reached_first(self): """ - Connection creation bigger then acquisition timeout scenario. + Connection creation bigger than acquisition timeout scenario. This test scenario tests the case where: @@ -150,7 +241,7 @@ def test_should_fail_when_acquisition_timeout_is_reached_first(self): def test_should_fail_when_connection_timeout_is_reached_first(self): """ - Acquisition timeout bigger then connection creation timeout scenario. + Acquisition timeout bigger than connection creation timeout scenario. This test scenario tests the case where: @@ -182,19 +273,16 @@ def test_should_regulate_the_time_for_acquiring_connections(self): No connection available scenario. This test scenario tests the case where: - - 1. the connection acquisition timeout is higher than - the connection creation timeout - 2. the connection is successfully created and in due time - 3. the connection pool doesn't have connections available in - suitable time + 1. The connection pool is configured for max 1 connection + 2. A connection is acquired and locked by another transaction + 3. When the new session try to acquire a connection, the connection + pool doesn't have connections available in suitable time Then the begin transaction is not executed since the connection acquisition times out. """ - self._server.start( - self.script_path("tx_without_commit_or_rollback.script") - ) + self._start_server(self._server, + "tx_without_commit_or_rollback.script") auth = types.AuthorizationToken("basic", principal="neo4j", credentials="pass") diff --git a/tests/stub/driver_parameters/test_max_connection_pool_size.py b/tests/stub/driver_parameters/test_max_connection_pool_size.py index d2d828771..90e8f1c84 100644 --- a/tests/stub/driver_parameters/test_max_connection_pool_size.py +++ b/tests/stub/driver_parameters/test_max_connection_pool_size.py @@ -9,11 +9,11 @@ class TestMaxConnectionPoolSize(TestkitTestCase): - required_features = types.Feature.BOLT_4_4, + required_features = types.Feature.BOLT_5_0, def setUp(self): super().setUp() - self._server = StubServer(9999) + self._server = StubServer(9000) self._server.start( self.script_path("tx_without_commit_or_rollback.script") ) From f4e9992ac34f30e3135da348efb7289b1ca8ebe6 Mon Sep 17 00:00:00 2001 From: Rouven Bauer Date: Tue, 14 Jun 2022 09:02:18 +0200 Subject: [PATCH 2/5] Re-do flakiness fix for JavaScript Also added a comment to avoid future changes to touch the port. --- .../driver_parameters/test_max_connection_pool_size.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/stub/driver_parameters/test_max_connection_pool_size.py b/tests/stub/driver_parameters/test_max_connection_pool_size.py index 90e8f1c84..60e464e14 100644 --- a/tests/stub/driver_parameters/test_max_connection_pool_size.py +++ b/tests/stub/driver_parameters/test_max_connection_pool_size.py @@ -13,7 +13,11 @@ class TestMaxConnectionPoolSize(TestkitTestCase): def setUp(self): super().setUp() - self._server = StubServer(9000) + # This needs to be a port that's not used by other tests. + # Else, when testing the javascript driver in a browser (specifically + # Firefox), the browser might block this port for the driver after this + # test for security reasons. + self._server = StubServer(9999) self._server.start( self.script_path("tx_without_commit_or_rollback.script") ) @@ -24,7 +28,7 @@ def setUp(self): def tearDown(self): # If test raised an exception this will make sure that the stub server - # is killed and it's output is dumped for analysis. + # is killed, and it's output is dumped for analysis. self._server.reset() for tx in self._transactions: with self.assertRaises(types.DriverError): From a17f9eea4a408cd570653ae82f9191d7f7c6ee97 Mon Sep 17 00:00:00 2001 From: Rouven Bauer Date: Mon, 20 Jun 2022 13:39:55 +0200 Subject: [PATCH 3/5] Add new timeout driver config options * `sessionConnectionTimeoutMs` * `updateRoutingTableTimeoutMs` --- nutkit/frontend/driver.py | 5 +- nutkit/protocol/feature.py | 12 +- nutkit/protocol/requests.py | 3 + .../scripts/session_run.script | 4 +- .../scripts/session_run_auth_delay.script | 2 +- .../test_connection_acquisition_timeout_ms.py | 139 +++----- .../test_session_connection_timeout.py | 175 ++++++++++ .../test_update_routing_table_timeout_ms.py | 314 ++++++++++++++++++ 8 files changed, 555 insertions(+), 99 deletions(-) create mode 100644 tests/stub/driver_parameters/test_session_connection_timeout.py create mode 100644 tests/stub/driver_parameters/test_update_routing_table_timeout_ms.py diff --git a/nutkit/frontend/driver.py b/nutkit/frontend/driver.py index a5f134af4..87b0e119b 100644 --- a/nutkit/frontend/driver.py +++ b/nutkit/frontend/driver.py @@ -6,7 +6,8 @@ class Driver: def __init__(self, backend, uri, auth_token, user_agent=None, resolver_fn=None, domain_name_resolver_fn=None, connection_timeout_ms=None, fetch_size=None, - max_tx_retry_time_ms=None, encrypted=None, + max_tx_retry_time_ms=None, session_connection_timeout_ms=None, + update_routing_table_timeout_ms=None, encrypted=None, trusted_certificates=None, liveness_check_timeout_ms=None, max_connection_pool_size=None, connection_acquisition_timeout_ms=None): @@ -18,6 +19,8 @@ def __init__(self, backend, uri, auth_token, user_agent=None, resolverRegistered=resolver_fn is not None, domainNameResolverRegistered=domain_name_resolver_fn is not None, connectionTimeoutMs=connection_timeout_ms, + sessionConnectionTimeoutMs=session_connection_timeout_ms, + updateRoutingTableTimeoutMs=update_routing_table_timeout_ms, fetchSize=fetch_size, maxTxRetryTimeMs=max_tx_retry_time_ms, encrypted=encrypted, trustedCertificates=trusted_certificates, liveness_check_timeout_ms=liveness_check_timeout_ms, diff --git a/nutkit/protocol/feature.py b/nutkit/protocol/feature.py index f25cfc8c6..15e576478 100644 --- a/nutkit/protocol/feature.py +++ b/nutkit/protocol/feature.py @@ -4,7 +4,7 @@ class Feature(Enum): # === FUNCTIONAL FEATURES === - # The driver offers a configuration option to limit time it spend at most, + # The driver offers a configuration option to limit time it spends at most, # trying to acquire a connection from the pool. # The connection acquisition timeout must account for the whole acquisition # execution time, whether a new connection is created, an idle connection @@ -40,6 +40,13 @@ class Feature(Enum): # If there are more than records, the driver emits a warning. # This method is supposed to always exhaust the result stream. API_RESULT_SINGLE_OPTIONAL = "Feature:API:Result.SingleOptional" + # The driver offers a configuration option to limit time it spends at most, + # trying to acquire a usable read/write connection for any session. + # The connection acquisition timeout must account for the whole acquisition + # execution time, whether a new connection is created, an idle connection + # is picked up instead, we need to wait until the full pool depletes, or + # a routing table must be fetched. + API_SESSION_CONNECTION_TIMEOUT = "Feature:API:SessionConnectionTimeout" # The driver implements explicit configuration options for SSL. # - enable / disable SSL # - verify signature against system store / custom cert / not at all @@ -53,6 +60,9 @@ class Feature(Enum): API_TYPE_SPATIAL = "Feature:API:Type.Spatial" # The driver supports sending and receiving temporal data types. API_TYPE_TEMPORAL = "Feature:API:Type.Temporal" + # The driver offers a configuration option to limit time it spends at most, + # trying to update the routing table whenever needed. + API_UPDATE_ROUTING_TABLE_TIMEOUT = "Feature:API:UpdateRoutingTableTimeout" # The driver supports single-sign-on (SSO) by providing a bearer auth token # API. AUTH_BEARER = "Feature:Auth:Bearer" diff --git a/nutkit/protocol/requests.py b/nutkit/protocol/requests.py index 4b4132dfd..9fad519f3 100644 --- a/nutkit/protocol/requests.py +++ b/nutkit/protocol/requests.py @@ -68,6 +68,7 @@ class NewDriver: def __init__( self, uri, authToken, userAgent=None, resolverRegistered=False, domainNameResolverRegistered=False, connectionTimeoutMs=None, + sessionConnectionTimeoutMs=None, updateRoutingTableTimeoutMs=None, fetchSize=None, maxTxRetryTimeMs=None, encrypted=None, trustedCertificates=None, liveness_check_timeout_ms=None, max_connection_pool_size=None, @@ -82,6 +83,8 @@ def __init__( self.resolverRegistered = resolverRegistered self.domainNameResolverRegistered = domainNameResolverRegistered self.connectionTimeoutMs = connectionTimeoutMs + self.sessionConnectionTimeoutMs = sessionConnectionTimeoutMs + self.updateRoutingTableTimeoutMs = updateRoutingTableTimeoutMs self.fetchSize = fetchSize self.maxTxRetryTimeMs = maxTxRetryTimeMs self.livenessCheckTimeoutMs = liveness_check_timeout_ms diff --git a/tests/stub/driver_parameters/scripts/session_run.script b/tests/stub/driver_parameters/scripts/session_run.script index a3d909b19..fca3281b0 100644 --- a/tests/stub/driver_parameters/scripts/session_run.script +++ b/tests/stub/driver_parameters/scripts/session_run.script @@ -1,12 +1,12 @@ !: BOLT 5.0 !: ALLOW CONCURRENT -C: HELLO {"{}": "*"} +A: HELLO {"{}": "*"} *: RESET C: RUN "*" "*" "*" S: SUCCESS {"fields": ["n"]} C: PULL {"n": "*"} S: RECORD [1] SUCCESS {"type": "r"} - *: RESET +?: GOODBYE diff --git a/tests/stub/driver_parameters/scripts/session_run_auth_delay.script b/tests/stub/driver_parameters/scripts/session_run_auth_delay.script index 9cf5bb894..32946241a 100644 --- a/tests/stub/driver_parameters/scripts/session_run_auth_delay.script +++ b/tests/stub/driver_parameters/scripts/session_run_auth_delay.script @@ -12,5 +12,5 @@ S: SUCCESS {"fields": ["n"]} C: PULL {"n": "*"} S: RECORD [1] SUCCESS {"type": "r"} - *: RESET +?: GOODBYE diff --git a/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py b/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py index 40d2a1129..3f34dd685 100644 --- a/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py +++ b/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py @@ -33,7 +33,7 @@ class TestConnectionAcquisitionTimeoutMs(TestkitTestCase): required_features = ( types.Feature.BOLT_5_0, - types.Feature.API_CONNECTION_ACQUISITION_TIMEOUT + types.Feature.API_CONNECTION_ACQUISITION_TIMEOUT, ) def setUp(self): @@ -92,96 +92,7 @@ def test_should_work_when_every_step_is_done_in_time(self): self._session = self._driver.session("r") - list(self._session.run("RETURN 1 as n")) - - def test_encompasses_router_connection_time(self): - """Router connection times out.""" - uri = "neo4j://10.255.255.255" - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=2000, - connection_timeout_ms=720000) - - self._session = self._driver.session("r") - - with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 as n")) - - def test_encompasses_router_handshake(self): - """Router available but with delayed HELLO response.""" - self._start_server(self._router, "router_hello_delay.script") - self._start_server(self._server, "session_run.script") - - uri = "neo4j://%s" % self._router.address - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=2000, - connection_timeout_ms=720000) - - self._session = self._driver.session("r") - - with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 as n")) - - self._session.close() - self._session = None - self._driver.close() - self._driver = None - self._server.reset() - reader_connections = self._server.count_responses("") - self.assertEqual(0, reader_connections) - - def test_encompasses_router_route_response(self): - """Router available but with delayed ROUTE response.""" - self._start_server(self._router, "router_route_delay.script") - self._start_server(self._server, "session_run.script") - - uri = "neo4j://%s" % self._router.address - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=2000, - connection_timeout_ms=720000) - - self._session = self._driver.session("r") - - with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 as n")) - - self._session.close() - self._session = None - self._driver.close() - self._driver = None - self._server.reset() - reader_connections = self._server.count_responses("") - self.assertEqual(0, reader_connections) - - def test_combined_router_and_reader_delay(self): - """Slow but in time router + slow but in time router == too slow.""" - self._start_server(self._router, "router_hello_delay.script") - self._start_server(self._server, "session_run_auth_delay.script") - - uri = "neo4j://%s" % self._router.address - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=6000, - connection_timeout_ms=720000) - - self._session = self._driver.session("r") - - with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 as n")) - - self._session.close() - self._session = None - self._driver.close() - self._driver = None - self._server.reset() - reader_connections = self._server.count_responses("") - self.assertEqual(1, reader_connections) + list(self._session.run("RETURN 1 AS n")) def test_should_encompass_the_handshake_time(self): """ @@ -209,7 +120,7 @@ def test_should_encompass_the_handshake_time(self): self._session = self._driver.session("r") with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 as n")) + list(self._session.run("RETURN 1 AS n")) def test_should_fail_when_acquisition_timeout_is_reached_first(self): """ @@ -237,7 +148,7 @@ def test_should_fail_when_acquisition_timeout_is_reached_first(self): self._session = self._driver.session("r") with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 as n")) + list(self._session.run("RETURN 1 AS n")) def test_should_fail_when_connection_timeout_is_reached_first(self): """ @@ -265,7 +176,47 @@ def test_should_fail_when_connection_timeout_is_reached_first(self): self._session = self._driver.session("r") with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 as n")) + list(self._session.run("RETURN 1 AS n")) + + def test_does_not_encompass_router_handshake(self): + self._start_server(self._router, "router_hello_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + self._session = self._driver.session("r") + list(self._session.run("RETURN 1 AS n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._router.done() + self._server.done() + + def test_does_not_encompass_router_route_response(self): + self._start_server(self._router, "router_route_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + self._session = self._driver.session("r") + list(self._session.run("RETURN 1 AS n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._router.done() + self._server.done() @driver_feature(types.Feature.OPT_EAGER_TX_BEGIN) def test_should_regulate_the_time_for_acquiring_connections(self): diff --git a/tests/stub/driver_parameters/test_session_connection_timeout.py b/tests/stub/driver_parameters/test_session_connection_timeout.py new file mode 100644 index 000000000..51b168a4e --- /dev/null +++ b/tests/stub/driver_parameters/test_session_connection_timeout.py @@ -0,0 +1,175 @@ +from nutkit.frontend import Driver +import nutkit.protocol as types +from tests.shared import TestkitTestCase +from tests.stub.shared import StubServer + + +class TestConnectionAcquisitionTimeoutMs(TestkitTestCase): + """ + Connection Acquisition Timeout Tests. + + The connection acquisition timeout must account for the + whole acquisition execution time, whether a new connection is created, + an idle connection is picked up instead or we need to wait + until the full pool depletes. + + In particular, the connection acquisition timeout (CAT) has precedence + over the socket connection timeout (SCT). + + If the SCT is set to 2 hours and CAT to 50ms, + the connection acquisition should time out after 50ms, + even if the connection is successfully created within the SCT period. + + The CAT must NOT be replaced by the lowest of the two values (CAT and SCT). + Indeed, even if SCT is lower than CAT, there could be situations + where the pool takes longer to borrow an _idle_ connection than the SCT. + Such a scenario should work as long as the overall acquisition happens + within the CAT. + This is unfortunately hard to translate into a test. + """ + + required_features = ( + types.Feature.BOLT_5_0, + types.Feature.API_SESSION_CONNECTION_TIMEOUT + ) + + def setUp(self): + super().setUp() + self._server = StubServer(9010) + self._router = StubServer(9000) + self._driver = None + self._session = None + self._sessions = [] + self._txs = [] + + def tearDown(self) -> None: + self._server.reset() + self._router.reset() + for tx in self._txs: + with self.assertRaises(types.DriverError): + # The server does not accept ending the transaction. + # We still call it to potentially free resources. + tx.commit() + + for s in self._sessions: + s.close() + + if self._session: + self._session.close() + + if self._driver: + self._driver.close() + + return super().tearDown() + + def _start_server(self, server, script): + server.start(self.script_path(script), + vars_={"#HOST#": self._router.host}) + + def test_should_work_when_every_step_is_done_in_time(self): + self._start_server(self._server, "session_run_auth_delay.script") + + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + uri = "bolt://%s" % self._server.address + self._driver = Driver(self._backend, uri, auth, + session_connection_timeout_ms=10000) + + self._session = self._driver.session("r") + + def test_should_work_when_every_step_is_done_in_time_with_routing(self): + self._start_server(self._server, "session_run_auth_delay.script") + self._start_server(self._router, "router_route_delay.script") + + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + uri = "bolt://%s" % self._server.address + self._driver = Driver(self._backend, uri, auth, + session_connection_timeout_ms=10000) + + self._session = self._driver.session("r") + + def test_encompasses_router_connection_time(self): + """Router connection times out.""" + uri = "neo4j://10.255.255.255" + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + session_connection_timeout_ms=2000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + def test_encompasses_router_handshake(self): + """Router available but with delayed HELLO response.""" + self._start_server(self._router, "router_hello_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + session_connection_timeout_ms=2000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._server.reset() + reader_connections = self._server.count_responses("") + self.assertEqual(0, reader_connections) + + def test_encompasses_router_route_response(self): + """Router available but with delayed ROUTE response.""" + self._start_server(self._router, "router_route_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + session_connection_timeout_ms=2000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._server.reset() + reader_connections = self._server.count_responses("") + self.assertEqual(0, reader_connections) + + def test_combined_router_and_reader_delay(self): + """Slow but in time router + slow but in time router == too slow.""" + self._start_server(self._router, "router_hello_delay.script") + self._start_server(self._server, "session_run_auth_delay.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + session_connection_timeout_ms=6000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._server.reset() + reader_connections = self._server.count_responses("") + self.assertEqual(1, reader_connections) diff --git a/tests/stub/driver_parameters/test_update_routing_table_timeout_ms.py b/tests/stub/driver_parameters/test_update_routing_table_timeout_ms.py new file mode 100644 index 000000000..3bb2f0885 --- /dev/null +++ b/tests/stub/driver_parameters/test_update_routing_table_timeout_ms.py @@ -0,0 +1,314 @@ +from nutkit.frontend import Driver +import nutkit.protocol as types +from tests.shared import ( + driver_feature, + TestkitTestCase, +) +from tests.stub.shared import StubServer + + +class TestUpdateRoutingTableTimeoutMs(TestkitTestCase): + + required_features = ( + types.Feature.BOLT_5_0, + types.Feature.API_UPDATE_ROUTING_TABLE_TIMEOUT, + ) + + def setUp(self): + super().setUp() + self._server = StubServer(9010) + self._router = StubServer(9000) + self._driver = None + self._session = None + self._sessions = [] + self._txs = [] + + def tearDown(self) -> None: + self._server.reset() + self._router.reset() + for tx in self._txs: + with self.assertRaises(types.DriverError): + # The server does not accept ending the transaction. + # We still call it to potentially free resources. + tx.commit() + + for s in self._sessions: + s.close() + + if self._session: + self._session.close() + + if self._driver: + self._driver.close() + + return super().tearDown() + + def _start_server(self, server, script): + server.start(self.script_path(script), + vars_={"#HOST#": self._router.host}) + + def test_encompasses_router_connection_time(self): + """Router connection times out.""" + uri = "neo4j://10.255.255.255" + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + update_routing_table_timeout_ms=2000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + def test_encompasses_router_handshake(self): + """Router available but with delayed HELLO response.""" + self._start_server(self._router, "router_hello_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + update_routing_table_timeout_ms=2000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._server.reset() + reader_connections = self._server.count_responses("") + self.assertEqual(0, reader_connections) + + def test_encompasses_router_route_response(self): + """Router available but with delayed ROUTE response.""" + self._start_server(self._router, "router_route_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + update_routing_table_timeout_ms=2000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 as n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._server.reset() + reader_connections = self._server.count_responses("") + self.assertEqual(0, reader_connections) + + def test_does_not_encompass_reader_connection_time(self): + self._start_server(self._router, "router.script") + self._start_server(self._server, "session_run_auth_delay.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + update_routing_table_timeout_ms=2000) + + self._session = self._driver.session("r") + + list(self._session.run("RETURN 1 AS n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._router.done() + self._server.done() + + def test_should_work_when_every_step_is_done_in_time(self): + """ + Everything in time scenario. + + This test scenario tests the case where: + + 1. the connection acquisition timeout is higher than + the connection creation timeout + 2. the connection is successfully created and in due time + + Then the query is executed successfully + """ + self._start_server(self._server, "session_run_auth_delay.script") + + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + uri = "bolt://%s" % self._server.address + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=10000, + connection_timeout_ms=5000) + + self._session = self._driver.session("r") + + list(self._session.run("RETURN 1 AS n")) + + def test_should_encompass_the_handshake_time(self): + """ + Handshake takes longer scenario. + + This test scenario tests the case where: + + 1. the connection acquisition timeout is smaller than + the connection creation timeout + 2. the connection is successfully created and in due time + 3. the handshake takes longer than the connection acquisition timeout + + Then the query is not executed since the connection acquisition + timed out. + """ + self._start_server(self._server, "session_run_auth_delay.script") + + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + uri = "bolt://%s" % self._server.address + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 AS n")) + + def test_should_fail_when_acquisition_timeout_is_reached_first(self): + """ + Connection creation bigger than acquisition timeout scenario. + + This test scenario tests the case where: + + 1. the connection acquisition timeout is smaller than + the connection creation timeout + 2. the connection takes longer than the + acquisition timeout to be created + + Then the query is not executed since the connection acquisition + times out. + """ + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + + # Non routable address + uri = "bolt://10.255.255.255" + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 AS n")) + + def test_should_fail_when_connection_timeout_is_reached_first(self): + """ + Acquisition timeout bigger than connection creation timeout scenario. + + This test scenario tests the case where: + + 1. the connection acquisition timeout is bigger than + the connection creation timeout + 2. the connection is successfully takes longer than the + connection timeout to be created + + Then the query is not executed since the connection creation + times out. + """ + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + + # Non routable address + uri = "bolt://10.255.255.255" + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=72000, + connection_timeout_ms=2000) + + self._session = self._driver.session("r") + + with self.assertRaises(types.DriverError): + list(self._session.run("RETURN 1 AS n")) + + def test_does_not_encompass_router_handshake(self): + self._start_server(self._router, "router_hello_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + self._session = self._driver.session("r") + list(self._session.run("RETURN 1 AS n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._router.done() + self._server.done() + + def test_does_not_encompass_router_route_response(self): + self._start_server(self._router, "router_route_delay.script") + self._start_server(self._server, "session_run.script") + + uri = "neo4j://%s" % self._router.address + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000) + self._session = self._driver.session("r") + list(self._session.run("RETURN 1 AS n")) + + self._session.close() + self._session = None + self._driver.close() + self._driver = None + self._router.done() + self._server.done() + + @driver_feature(types.Feature.OPT_EAGER_TX_BEGIN) + def test_should_regulate_the_time_for_acquiring_connections(self): + """ + No connection available scenario. + + This test scenario tests the case where: + 1. The connection pool is configured for max 1 connection + 2. A connection is acquired and locked by another transaction + 3. When the new session try to acquire a connection, the connection + pool doesn't have connections available in suitable time + + Then the begin transaction is not executed + since the connection acquisition times out. + """ + self._start_server(self._server, + "tx_without_commit_or_rollback.script") + + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + uri = "bolt://%s" % self._server.address + self._driver = Driver(self._backend, uri, auth, + connection_acquisition_timeout_ms=2000, + connection_timeout_ms=720000, + max_connection_pool_size=1) + + self._sessions = [ + self._driver.session("r"), + self._driver.session("r"), + ] + + self._txs = [self._sessions[0].begin_transaction()] + + with self.assertRaises(types.DriverError): + self._txs.append(self._sessions[1].begin_transaction()) From db41deccb8ad1ca3341907596716f8f850f2a3cf Mon Sep 17 00:00:00 2001 From: Rouven Bauer Date: Mon, 20 Jun 2022 16:49:44 +0200 Subject: [PATCH 4/5] Clean up copy-paste mistake --- .../test_connection_acquisition_timeout_ms.py | 2 +- .../test_session_connection_timeout.py | 2 +- .../test_update_routing_table_timeout_ms.py | 207 ++---------------- 3 files changed, 20 insertions(+), 191 deletions(-) diff --git a/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py b/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py index 3f34dd685..af4b778e5 100644 --- a/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py +++ b/tests/stub/driver_parameters/test_connection_acquisition_timeout_ms.py @@ -45,7 +45,7 @@ def setUp(self): self._sessions = [] self._txs = [] - def tearDown(self) -> None: + def tearDown(self): self._server.reset() self._router.reset() for tx in self._txs: diff --git a/tests/stub/driver_parameters/test_session_connection_timeout.py b/tests/stub/driver_parameters/test_session_connection_timeout.py index 51b168a4e..1dd78ff6b 100644 --- a/tests/stub/driver_parameters/test_session_connection_timeout.py +++ b/tests/stub/driver_parameters/test_session_connection_timeout.py @@ -42,7 +42,7 @@ def setUp(self): self._sessions = [] self._txs = [] - def tearDown(self) -> None: + def tearDown(self): self._server.reset() self._router.reset() for tx in self._txs: diff --git a/tests/stub/driver_parameters/test_update_routing_table_timeout_ms.py b/tests/stub/driver_parameters/test_update_routing_table_timeout_ms.py index 3bb2f0885..71f9e883a 100644 --- a/tests/stub/driver_parameters/test_update_routing_table_timeout_ms.py +++ b/tests/stub/driver_parameters/test_update_routing_table_timeout_ms.py @@ -1,9 +1,6 @@ from nutkit.frontend import Driver import nutkit.protocol as types -from tests.shared import ( - driver_feature, - TestkitTestCase, -) +from tests.shared import TestkitTestCase from tests.stub.shared import StubServer @@ -23,7 +20,7 @@ def setUp(self): self._sessions = [] self._txs = [] - def tearDown(self) -> None: + def tearDown(self): self._server.reset() self._router.reset() for tx in self._txs: @@ -47,6 +44,22 @@ def _start_server(self, server, script): server.start(self.script_path(script), vars_={"#HOST#": self._router.host}) + def test_should_work_when_every_step_is_done_in_time(self): + """Everything in time.""" + self._start_server(self._router, "router.script") + self._start_server(self._server, "session_run.script") + uri = "neo4j://10.255.255.255" + auth = types.AuthorizationToken("basic", principal="neo4j", + credentials="pass") + + uri = "neo4j://%s" % self._router.address + self._driver = Driver(self._backend, uri, auth, + update_routing_table_timeout_ms=2000) + + self._session = self._driver.session("r") + + list(self._session.run("RETURN 1 AS n")) + def test_encompasses_router_connection_time(self): """Router connection times out.""" uri = "neo4j://10.255.255.255" @@ -128,187 +141,3 @@ def test_does_not_encompass_reader_connection_time(self): self._driver = None self._router.done() self._server.done() - - def test_should_work_when_every_step_is_done_in_time(self): - """ - Everything in time scenario. - - This test scenario tests the case where: - - 1. the connection acquisition timeout is higher than - the connection creation timeout - 2. the connection is successfully created and in due time - - Then the query is executed successfully - """ - self._start_server(self._server, "session_run_auth_delay.script") - - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - uri = "bolt://%s" % self._server.address - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=10000, - connection_timeout_ms=5000) - - self._session = self._driver.session("r") - - list(self._session.run("RETURN 1 AS n")) - - def test_should_encompass_the_handshake_time(self): - """ - Handshake takes longer scenario. - - This test scenario tests the case where: - - 1. the connection acquisition timeout is smaller than - the connection creation timeout - 2. the connection is successfully created and in due time - 3. the handshake takes longer than the connection acquisition timeout - - Then the query is not executed since the connection acquisition - timed out. - """ - self._start_server(self._server, "session_run_auth_delay.script") - - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - uri = "bolt://%s" % self._server.address - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=2000, - connection_timeout_ms=720000) - - self._session = self._driver.session("r") - - with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 AS n")) - - def test_should_fail_when_acquisition_timeout_is_reached_first(self): - """ - Connection creation bigger than acquisition timeout scenario. - - This test scenario tests the case where: - - 1. the connection acquisition timeout is smaller than - the connection creation timeout - 2. the connection takes longer than the - acquisition timeout to be created - - Then the query is not executed since the connection acquisition - times out. - """ - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - - # Non routable address - uri = "bolt://10.255.255.255" - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=2000, - connection_timeout_ms=720000) - - self._session = self._driver.session("r") - - with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 AS n")) - - def test_should_fail_when_connection_timeout_is_reached_first(self): - """ - Acquisition timeout bigger than connection creation timeout scenario. - - This test scenario tests the case where: - - 1. the connection acquisition timeout is bigger than - the connection creation timeout - 2. the connection is successfully takes longer than the - connection timeout to be created - - Then the query is not executed since the connection creation - times out. - """ - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - - # Non routable address - uri = "bolt://10.255.255.255" - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=72000, - connection_timeout_ms=2000) - - self._session = self._driver.session("r") - - with self.assertRaises(types.DriverError): - list(self._session.run("RETURN 1 AS n")) - - def test_does_not_encompass_router_handshake(self): - self._start_server(self._router, "router_hello_delay.script") - self._start_server(self._server, "session_run.script") - - uri = "neo4j://%s" % self._router.address - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=2000, - connection_timeout_ms=720000) - self._session = self._driver.session("r") - list(self._session.run("RETURN 1 AS n")) - - self._session.close() - self._session = None - self._driver.close() - self._driver = None - self._router.done() - self._server.done() - - def test_does_not_encompass_router_route_response(self): - self._start_server(self._router, "router_route_delay.script") - self._start_server(self._server, "session_run.script") - - uri = "neo4j://%s" % self._router.address - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=2000, - connection_timeout_ms=720000) - self._session = self._driver.session("r") - list(self._session.run("RETURN 1 AS n")) - - self._session.close() - self._session = None - self._driver.close() - self._driver = None - self._router.done() - self._server.done() - - @driver_feature(types.Feature.OPT_EAGER_TX_BEGIN) - def test_should_regulate_the_time_for_acquiring_connections(self): - """ - No connection available scenario. - - This test scenario tests the case where: - 1. The connection pool is configured for max 1 connection - 2. A connection is acquired and locked by another transaction - 3. When the new session try to acquire a connection, the connection - pool doesn't have connections available in suitable time - - Then the begin transaction is not executed - since the connection acquisition times out. - """ - self._start_server(self._server, - "tx_without_commit_or_rollback.script") - - auth = types.AuthorizationToken("basic", principal="neo4j", - credentials="pass") - uri = "bolt://%s" % self._server.address - self._driver = Driver(self._backend, uri, auth, - connection_acquisition_timeout_ms=2000, - connection_timeout_ms=720000, - max_connection_pool_size=1) - - self._sessions = [ - self._driver.session("r"), - self._driver.session("r"), - ] - - self._txs = [self._sessions[0].begin_transaction()] - - with self.assertRaises(types.DriverError): - self._txs.append(self._sessions[1].begin_transaction()) From 86f24fe632998f13b2fadfa11a2dc5317709409e Mon Sep 17 00:00:00 2001 From: Rouven Bauer Date: Wed, 22 Jun 2022 13:14:52 +0200 Subject: [PATCH 5/5] Optimize tests when driver support sessionAcquisitionTimeout --- .../test_max_connection_pool_size.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/stub/driver_parameters/test_max_connection_pool_size.py b/tests/stub/driver_parameters/test_max_connection_pool_size.py index 60e464e14..63cccc591 100644 --- a/tests/stub/driver_parameters/test_max_connection_pool_size.py +++ b/tests/stub/driver_parameters/test_max_connection_pool_size.py @@ -48,6 +48,10 @@ def _open_driver(self, max_pool_size=None): types.Feature.API_CONNECTION_ACQUISITION_TIMEOUT ): kwargs["connection_acquisition_timeout_ms"] = 500 + if self.driver_supports_features( + types.Feature.API_SESSION_CONNECTION_TIMEOUT + ): + kwargs["session_connection_timeout_ms"] = 1000 if max_pool_size is not None: kwargs["max_connection_pool_size"] = max_pool_size auth = types.AuthorizationToken("basic", principal="neo4j", @@ -57,8 +61,12 @@ def _open_driver(self, max_pool_size=None): @contextmanager def _backend_timeout_adjustment(self): - if self.driver_supports_features( - types.Feature.API_CONNECTION_ACQUISITION_TIMEOUT + if any( + self.driver_supports_features(feature) + for feature in ( + types.Feature.API_CONNECTION_ACQUISITION_TIMEOUT, + types.Feature.API_SESSION_CONNECTION_TIMEOUT, + ) ): yield else: