def test_errback(self): session = self.make_session() pool = session._pools.get.return_value connection = Mock(spec=Connection) pool.borrow_connection.return_value = (connection, 1) query = SimpleStatement("INSERT INFO foo (a, b) VALUES (1, 2)") message = QueryMessage(query=query, consistency_level=ConsistencyLevel.ONE) rf = ResponseFuture(session, message, query, 1) rf._query_retries = 1 rf.send_request() rf.add_errback(self.assertIsInstance, Exception) result = Mock(spec=UnavailableErrorMessage, info={ "required_replicas": 2, "alive_replicas": 1, "consistency": 1 }) result.to_exception.return_value = Exception() rf._set_result(None, None, None, result) self.assertRaises(Exception, rf.result) # this should get called immediately now that the error is set rf.add_errback(self.assertIsInstance, Exception)
def test_multiple_errbacks(self): session = self.make_session() pool = session._pools.get.return_value connection = Mock(spec=Connection) pool.borrow_connection.return_value = (connection, 1) query = SimpleStatement("INSERT INFO foo (a, b) VALUES (1, 2)") query.retry_policy = Mock() query.retry_policy.on_unavailable.return_value = (RetryPolicy.RETHROW, None) message = QueryMessage(query=query, consistency_level=ConsistencyLevel.ONE) rf = ResponseFuture(session, message, query, 1) rf.send_request() callback = Mock() arg = "positional" kwargs = {'one': 1, 'two': 2} rf.add_errback(callback, arg, **kwargs) callback2 = Mock() arg2 = "another" kwargs2 = {'three': 3, 'four': 4} rf.add_errback(callback2, arg2, **kwargs2) expected_exception = Unavailable("message", 1, 2, 3) result = Mock(spec=UnavailableErrorMessage, info={'something': 'here'}) result.to_exception.return_value = expected_exception rf._set_result(result) self.assertRaises(Exception, rf.result) callback.assert_called_once_with(expected_exception, arg, **kwargs) callback2.assert_called_once_with(expected_exception, arg2, **kwargs2)
def test_multiple_errbacks(self): session = self.make_session() pool = session._pools.get.return_value connection = Mock(spec=Connection) pool.borrow_connection.return_value = (connection, 1) query = SimpleStatement("INSERT INFO foo (a, b) VALUES (1, 2)") query.retry_policy = Mock() query.retry_policy.on_unavailable.return_value = (RetryPolicy.RETHROW, None) message = QueryMessage(query=query, consistency_level=ConsistencyLevel.ONE) rf = ResponseFuture(session, message, query) rf.send_request() callback = Mock() arg = "positional" kwargs = {'one': 1, 'two': 2} rf.add_errback(callback, arg, **kwargs) callback2 = Mock() arg2 = "another" kwargs2 = {'three': 3, 'four': 4} rf.add_errback(callback2, arg2, **kwargs2) expected_exception = Unavailable("message", 1, 2, 3) result = Mock(spec=UnavailableErrorMessage, info={'something': 'here'}) result.to_exception.return_value = expected_exception rf._set_result(result) self.assertRaises(Exception, rf.result) callback.assert_called_once_with(expected_exception, arg, **kwargs) callback2.assert_called_once_with(expected_exception, arg2, **kwargs2)
def wrap_future( response_future: ResponseFuture, callback_fn: Callable[..., None], callback_args: Any, errback_fn: Callable[..., None], errback_args: Any, ) -> ResponseFuture: """Patch ResponseFuture.result() to wait for callback or errback to complete. The callback_fn and errback_fn are given special treatment: they must be complete before a result will be returned from ResponseFuture.result(). They are not given precedence over other callbacks or errbacks, so if another callback triggers the response from the service (and the server span is closed) the special callback might not complete. The special callback is added first and callbacks are executed in order, so generally the special callback should finish before any other callbacks. This fixes a race condition where the server span can complete before the callback has closed out the child span. """ response_future._callback_event = Event() response_future.add_callback(callback_fn, callback_args, response_future._callback_event) response_future.add_errback(errback_fn, errback_args, response_future._callback_event) def wait_for_callbacks_result(self: ResponseFuture) -> Any: exc = None try: result = ResponseFuture.result(self) except Exception as e: exc = e # wait for either _on_execute_complete or _on_execute_failed to run wait_result = self._callback_event.wait(timeout=0.01) if not wait_result: logger.warning( "Cassandra metrics callback took too long. Some metrics may be lost." ) if exc: raise exc # pylint: disable=E0702 return result # call __get__ to turn wait_for_callbacks_result into a bound method bound_method = wait_for_callbacks_result.__get__( # type: ignore response_future, ResponseFuture) # patch the ResponseFuture instance response_future.result = bound_method return response_future
def test_errback(self): session = self.make_session() query = SimpleStatement("INSERT INFO foo (a, b) VALUES (1, 2)") query.retry_policy = Mock() query.retry_policy.on_unavailable.return_value = (RetryPolicy.RETHROW, None) message = QueryMessage(query=query, consistency_level=ConsistencyLevel.ONE) rf = ResponseFuture(session, message, query) rf.send_request() rf.add_errback(self.assertIsInstance, Exception) result = Mock(spec=UnavailableErrorMessage, info={}) rf._set_result(result) self.assertRaises(Exception, rf.result) # this should get called immediately now that the error is set rf.add_errback(self.assertIsInstance, Exception)
def test_errback(self): session = self.make_session() query = SimpleStatement("INSERT INFO foo (a, b) VALUES (1, 2)") query.retry_policy = Mock() query.retry_policy.on_unavailable.return_value = (RetryPolicy.RETHROW, None) message = QueryMessage(query=query, consistency_level=ConsistencyLevel.ONE) rf = ResponseFuture(session, message, query) rf.send_request() rf.add_errback(self.assertIsInstance, Exception) result = Mock(spec=UnavailableErrorMessage, info={}) rf._set_result(result) self.assertRaises(Exception, rf.result) # this should get called immediately now that the error is set rf.add_errback(self.assertIsInstance, Exception)
def _wrap_future(f: ResponseFuture) -> asyncio.Future: """Wrap a cassandra Future into an asyncio.Future object. Args: f: future to wrap Returns: And asyncio.Future object which can be awaited. """ loop = asyncio.get_event_loop() aio_future = loop.create_future() def on_result(result): loop.call_soon_threadsafe(aio_future.set_result, result) def on_error(exception, *_): loop.call_soon_threadsafe(aio_future.set_exception, exception) f.add_callback(on_result) f.add_errback(on_error) return aio_future
def test_errback(self): session = self.make_session() pool = session._pools.get.return_value connection = Mock(spec=Connection) pool.borrow_connection.return_value = (connection, 1) query = SimpleStatement("INSERT INFO foo (a, b) VALUES (1, 2)") message = QueryMessage(query=query, consistency_level=ConsistencyLevel.ONE) rf = ResponseFuture(session, message, query, 1) rf._query_retries = 1 rf.send_request() rf.add_errback(self.assertIsInstance, Exception) result = Mock(spec=UnavailableErrorMessage, info={"required_replicas":2, "alive_replicas": 1, "consistency": 1}) result.to_exception.return_value = Exception() rf._set_result(None, None, None, result) self.assertRaises(Exception, rf.result) # this should get called immediately now that the error is set rf.add_errback(self.assertIsInstance, Exception)