|
17 | 17 | except ImportError:
|
18 | 18 | import unittest # noqa
|
19 | 19 |
|
| 20 | +from collections import deque |
| 21 | +from threading import RLock |
20 | 22 | from mock import Mock, MagicMock, ANY
|
21 | 23 |
|
22 | 24 | from cassandra import ConsistencyLevel, Unavailable, SchemaTargetType, SchemaChangeType, OperationTimedOut
|
@@ -604,3 +606,28 @@ def test_repeat_orig_query_after_succesful_reprepare(self):
|
604 | 606 | rf._query = Mock(return_value=True)
|
605 | 607 | rf._execute_after_prepare('host', None, None, response)
|
606 | 608 | rf._query.assert_called_once_with('host')
|
| 609 | + |
| 610 | + def test_timeout_does_not_release_stream_id(self): |
| 611 | + """ |
| 612 | + Make sure that stream ID is not reused immediately after client-side |
| 613 | + timeout. Otherwise, a new request could reuse the stream ID and would |
| 614 | + risk getting a response for the old, timed out query. |
| 615 | + """ |
| 616 | + session = self.make_basic_session() |
| 617 | + session.cluster._default_load_balancing_policy.make_query_plan.return_value = [Mock(endpoint='ip1'), Mock(endpoint='ip2')] |
| 618 | + pool = self.make_pool() |
| 619 | + session._pools.get.return_value = pool |
| 620 | + connection = Mock(spec=Connection, lock=RLock(), _requests={}, request_ids=deque()) |
| 621 | + pool.borrow_connection.return_value = (connection, 1) |
| 622 | + |
| 623 | + rf = self.make_response_future(session) |
| 624 | + rf.send_request() |
| 625 | + |
| 626 | + connection._requests[1] = (connection._handle_options_response, ProtocolHandler.decode_message, []) |
| 627 | + |
| 628 | + rf._on_timeout() |
| 629 | + pool.return_connection.assert_called_once_with(connection) |
| 630 | + self.assertRaisesRegexp(OperationTimedOut, "Client request timeout", rf.result) |
| 631 | + |
| 632 | + assert len(connection.request_ids) == 0, \ |
| 633 | + "Request IDs should be empty but it's not: {}".format(connection.request_ids) |
0 commit comments