diff options
author | Kevin Benton <blak111@gmail.com> | 2015-06-21 20:50:02 -0700 |
---|---|---|
committer | Kevin Benton <blak111@gmail.com> | 2015-06-25 11:36:35 -0700 |
commit | c7f938ceaa92292aec7df4454566c116c4f6ad8d (patch) | |
tree | 05cc580e425c5bae750222b5fdd42aa0c9a9a28b /oslo_db | |
parent | a9f6a2ed7644ee1130121ea2236c05ab7054a1e2 (diff) | |
download | oslo-db-c7f938ceaa92292aec7df4454566c116c4f6ad8d.tar.gz |
Allow additional exceptions in wrap_db_retry
There are cases where we want to catch and retry some additional
exceptions and treat them as RetryRequests depending on method
that is being excecuted (e.g. StaleDataError or ObjectDeletedError).
This patch changes the wrap_db_retry decorator to take an extra
parameter that is a list of additional exception classes that will
be caught and treated as a RetryRequest.
Change-Id: I6c61b1d2f0331cac31dfde0afa523e44d470b83f
Diffstat (limited to 'oslo_db')
-rw-r--r-- | oslo_db/api.py | 48 | ||||
-rw-r--r-- | oslo_db/tests/test_api.py | 16 |
2 files changed, 46 insertions, 18 deletions
diff --git a/oslo_db/api.py b/oslo_db/api.py index 0ba07e8..ef840a4 100644 --- a/oslo_db/api.py +++ b/oslo_db/api.py @@ -24,10 +24,10 @@ API methods. """ import logging -import sys import threading import time +from oslo_utils import excutils from oslo_utils import importutils import six @@ -100,14 +100,20 @@ class wrap_db_retry(object): :param max_retry_interval: max interval value between retries :type max_retry_interval: int + + :param exception_checker: checks if an exception should trigger a retry + :type exception_checker: callable """ def __init__(self, retry_interval=0, max_retries=0, inc_retry_interval=0, max_retry_interval=0, retry_on_disconnect=False, - retry_on_deadlock=False, retry_on_request=False): + retry_on_deadlock=False, retry_on_request=False, + exception_checker=lambda exc: False): super(wrap_db_retry, self).__init__() self.db_error = () + # default is that we re-raise anything unexpected + self.exception_checker = exception_checker if retry_on_disconnect: self.db_error += (exception.DBConnectionError, ) if retry_on_deadlock: @@ -124,26 +130,20 @@ class wrap_db_retry(object): def wrapper(*args, **kwargs): next_interval = self.retry_interval remaining = self.max_retries - db_error = self.db_error while True: try: return f(*args, **kwargs) - except db_error as e: - if remaining == 0: - LOG.exception(_LE('DB exceeded retry limit.')) - if isinstance(e, exception.RetryRequest): - six.reraise(type(e.inner_exc), - e.inner_exc, - sys.exc_info()[2]) - raise e - if remaining != -1: - remaining -= 1 - # RetryRequest is application-initated exception - # and not an error condition in case retries are - # not exceeded - if not isinstance(e, exception.RetryRequest): - LOG.exception(_LE('DB error.')) + except Exception as e: + with excutils.save_and_reraise_exception() as ectxt: + if remaining > 0: + ectxt.reraise = not self._is_exception_expected(e) + else: + LOG.exception(_LE('DB exceeded retry limit.')) + # if it's a RetryRequest, we need to unpack it + if isinstance(e, exception.RetryRequest): + ectxt.type_ = type(e.inner_exc) + ectxt.value = e.inner_exc # NOTE(vsergeyev): We are using patched time module, so # this effectively yields the execution # context to another green thread. @@ -153,8 +153,20 @@ class wrap_db_retry(object): next_interval * 2, self.max_retry_interval ) + remaining -= 1 + return wrapper + def _is_exception_expected(self, exc): + if isinstance(exc, self.db_error): + # RetryRequest is application-initated exception + # and not an error condition in case retries are + # not exceeded + if not isinstance(exc, exception.RetryRequest): + LOG.exception(_LE('DB error.')) + return True + return self.exception_checker(exc) + class DBAPI(object): """Initialize the chosen DB API backend. diff --git a/oslo_db/tests/test_api.py b/oslo_db/tests/test_api.py index c784afe..56d5529 100644 --- a/oslo_db/tests/test_api.py +++ b/oslo_db/tests/test_api.py @@ -197,6 +197,22 @@ class DBRetryRequestCase(DBAPITestCase): self.assertRaises(ValueError, some_method, res) self.assertEqual(max_retries + 1, res['result']) + def test_retry_wrapper_exception_checker(self): + + def exception_checker(exc): + return isinstance(exc, ValueError) and exc.args[0] < 5 + + @api.wrap_db_retry(max_retries=10, retry_on_request=True, + exception_checker=exception_checker) + def some_method(res): + res['result'] += 1 + raise ValueError(res['result']) + + res = {'result': 0} + self.assertRaises(ValueError, some_method, res) + # our exception checker should have stopped returning True after 5 + self.assertEqual(5, res['result']) + @mock.patch.object(DBAPI, 'api_class_call1') @mock.patch.object(api, 'wrap_db_retry') def test_mocked_methods_are_not_wrapped(self, mocked_wrap, mocked_method): |