1 | def autoretry_datastore_timeouts(attempts=5.0, interval=0.1, exponent=2.0): |
---|
2 | """ |
---|
3 | Copyright (C) 2009 twitter.com/rcb |
---|
4 | |
---|
5 | Permission is hereby granted, free of charge, to any person |
---|
6 | obtaining a copy of this software and associated documentation |
---|
7 | files (the "Software"), to deal in the Software without |
---|
8 | restriction, including without limitation the rights to use, |
---|
9 | copy, modify, merge, publish, distribute, sublicense, and/or sell |
---|
10 | copies of the Software, and to permit persons to whom the |
---|
11 | Software is furnished to do so, subject to the following |
---|
12 | conditions: |
---|
13 | |
---|
14 | The above copyright notice and this permission notice shall be |
---|
15 | included in all copies or substantial portions of the Software. |
---|
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
---|
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
---|
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
---|
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
---|
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
---|
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
---|
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
---|
23 | OTHER DEALINGS IN THE SOFTWARE. |
---|
24 | |
---|
25 | ====================================================================== |
---|
26 | |
---|
27 | This function wraps the AppEngine Datastore API to autoretry |
---|
28 | datastore timeouts at the lowest accessible level. |
---|
29 | |
---|
30 | The benefits of this approach are: |
---|
31 | |
---|
32 | 1. Small Footprint: Does not monkey with Model internals |
---|
33 | which may break in future releases. |
---|
34 | 2. Max Performance: Retrying at this lowest level means |
---|
35 | serialization and key formatting is not |
---|
36 | needlessly repeated on each retry. |
---|
37 | At initialization time, execute this: |
---|
38 | |
---|
39 | >>> autoretry_datastore_timeouts() |
---|
40 | |
---|
41 | Should only be called once, subsequent calls have no effect. |
---|
42 | |
---|
43 | >>> autoretry_datastore_timeouts() # no effect |
---|
44 | |
---|
45 | Default (5) attempts: .1, .2, .4, .8, 1.6 seconds |
---|
46 | |
---|
47 | Parameters can each be specified as floats. |
---|
48 | |
---|
49 | :param attempts: maximum number of times to retry. |
---|
50 | :param interval: base seconds to sleep between retries. |
---|
51 | :param exponent: rate of exponential back-off. |
---|
52 | """ |
---|
53 | |
---|
54 | import time |
---|
55 | import logging |
---|
56 | from google.appengine.api import apiproxy_stub_map |
---|
57 | from google.appengine.runtime import apiproxy_errors |
---|
58 | from google.appengine.datastore import datastore_pb |
---|
59 | |
---|
60 | attempts = float(attempts) |
---|
61 | interval = float(interval) |
---|
62 | exponent = float(exponent) |
---|
63 | wrapped = apiproxy_stub_map.MakeSyncCall |
---|
64 | errors = {datastore_pb.Error.TIMEOUT: 'Timeout', |
---|
65 | datastore_pb.Error.CONCURRENT_TRANSACTION: 'TransactionFailedError'} |
---|
66 | |
---|
67 | def wrapper(*args, **kwargs): |
---|
68 | count = 0.0 |
---|
69 | while True: |
---|
70 | try: |
---|
71 | return wrapped(*args, **kwargs) |
---|
72 | except apiproxy_errors.ApplicationError as err: |
---|
73 | errno = err.application_error |
---|
74 | if errno not in errors: |
---|
75 | raise |
---|
76 | sleep = (exponent ** count) * interval |
---|
77 | count += 1.0 |
---|
78 | if count > attempts: |
---|
79 | raise |
---|
80 | msg = "Datastore %s: retry #%d in %s seconds.\n%s" |
---|
81 | vals = '' |
---|
82 | if count == 1.0: |
---|
83 | vals = '\n'.join([str(a) for a in args]) |
---|
84 | logging.warning(msg % (errors[errno], count, sleep, vals)) |
---|
85 | time.sleep(sleep) |
---|
86 | |
---|
87 | setattr(wrapper, '_autoretry_datastore_timeouts', False) |
---|
88 | if getattr(wrapped, '_autoretry_datastore_timeouts', True): |
---|
89 | apiproxy_stub_map.MakeSyncCall = wrapper |
---|