~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# Copyright 2009 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""The lp.services.database package."""

__metaclass__ = type
__all__ = [
    'read_transaction',
    'write_transaction',
    ]

from psycopg2.extensions import TransactionRollbackError
from storm.exceptions import DisconnectionError, IntegrityError
import transaction
from twisted.python.util import mergeFunctionMetadata

from canonical.database.sqlbase import (
    reset_store,
    )


RETRY_ATTEMPTS = 3


def retry_transaction(func):
    """Decorator used to retry database transaction failures.

    The function being decorated should not have side effects outside
    of the transaction.
    """
    def retry_transaction_decorator(*args, **kwargs):
        attempt = 0
        while True:
            attempt += 1
            try:
                return func(*args, **kwargs)
            except (DisconnectionError, IntegrityError,
                    TransactionRollbackError), exc:
                if attempt >= RETRY_ATTEMPTS:
                    raise # tried too many times
    return mergeFunctionMetadata(func, retry_transaction_decorator)


def read_transaction(func):
    """Decorator used to run the function inside a read only transaction.

    The transaction will be aborted on successful completion of the
    function.  The transaction will be retried if appropriate.
    """
    @reset_store
    def read_transaction_decorator(*args, **kwargs):
        transaction.begin()
        try:
            return func(*args, **kwargs)
        finally:
            transaction.abort()
    return retry_transaction(mergeFunctionMetadata(
        func, read_transaction_decorator))


def write_transaction(func):
    """Decorator used to run the function inside a write transaction.

    The transaction will be committed on successful completion of the
    function, and aborted on failure.  The transaction will be retried
    if appropriate.
    """
    @reset_store
    def write_transaction_decorator(*args, **kwargs):
        transaction.begin()
        try:
            ret = func(*args, **kwargs)
        except:
            transaction.abort()
            raise
        transaction.commit()
        return ret
    return retry_transaction(mergeFunctionMetadata(
        func, write_transaction_decorator))