31
31
If you want to be certain that the first, and only the first, error is raised,
34
operation = OperationWithCleanups(do_something)
35
operation.add_cleanup(cleanup_something)
36
operation.run_simple()
34
do_with_cleanups(do_something, cleanups)
38
36
This is more inconvenient (because you need to make every try block a
39
37
function), but will ensure that the first error encountered is the one raised,
40
while also ensuring all cleanups are run. See OperationWithCleanups for more
38
while also ensuring all cleanups are run.
45
from collections import deque
47
43
from bzrlib import (
75
def _run_cleanups(funcs):
76
"""Run a series of cleanup functions."""
77
for func, args, kwargs in funcs:
78
_run_cleanup(func, *args, **kwargs)
81
class OperationWithCleanups(object):
82
"""A way to run some code with a dynamic cleanup list.
84
This provides a way to add cleanups while the function-with-cleanups is
89
operation = OperationWithCleanups(some_func)
90
operation.run(args...)
92
where `some_func` is::
94
def some_func(operation, args, ...):
96
operation.add_cleanup(something)
99
Note that the first argument passed to `some_func` will be the
100
OperationWithCleanups object. To invoke `some_func` without that, use
101
`run_simple` instead of `run`.
71
def _run_cleanup_reporting_errors(func, *args, **kwargs):
74
except KeyboardInterrupt:
76
except Exception, exc:
77
trace.mutter('Cleanup failed:')
78
trace.log_exception_quietly()
79
trace.warning('Cleanup failed: %s', exc)
84
def _run_cleanups(funcs, on_error='log'):
85
"""Run a series of cleanup functions.
87
:param errors: One of 'log', 'warn first', 'warn all'
104
def __init__(self, func):
106
self.cleanups = deque()
108
def add_cleanup(self, cleanup_func, *args, **kwargs):
109
"""Add a cleanup to run.
111
Cleanups may be added at any time before or during the execution of
112
self.func. Cleanups will be executed in LIFO order.
114
self.cleanups.appendleft((cleanup_func, args, kwargs))
116
def run(self, *args, **kwargs):
117
return _do_with_cleanups(
118
self.cleanups, self.func, self, *args, **kwargs)
120
def run_simple(self, *args, **kwargs):
121
return _do_with_cleanups(
122
self.cleanups, self.func, *args, **kwargs)
124
def cleanup_now(self):
125
_run_cleanups(self.cleanups)
126
self.cleanups.clear()
129
def _do_with_cleanups(cleanup_funcs, func, *args, **kwargs):
91
if on_error == 'log' or (on_error == 'warn first' and seen_error):
92
seen_error |= _run_cleanup(func)
94
seen_error |= _run_cleanup_reporting_errors(func)
97
def do_with_cleanups(func, cleanup_funcs):
130
98
"""Run `func`, then call all the cleanup_funcs.
132
100
All the cleanup_funcs are guaranteed to be run. The first exception raised
136
104
Conceptually similar to::
139
return func(*args, **kwargs)
141
for cleanup, cargs, ckwargs in cleanup_funcs:
142
cleanup(*cargs, **ckwargs)
109
for cleanup in cleanup_funcs:
144
112
It avoids several problems with using try/finally directly:
145
113
* an exception from func will not be obscured by a subsequent exception
148
116
running (but the first exception encountered is still the one
151
Unike `_run_cleanup`, `_do_with_cleanups` can propagate an exception from a
119
Unike `run_cleanup`, `do_with_cleanups` can propagate an exception from a
152
120
cleanup, but only if there is no exception from func.
154
122
# As correct as Python 2.4 allows.
156
result = func(*args, **kwargs)
158
126
# We have an exception from func already, so suppress cleanup errors.
159
127
_run_cleanups(cleanup_funcs)
163
131
# cleanup_funcs to propagate if one occurs (but only after running all
166
for cleanup, c_args, c_kwargs in cleanup_funcs:
134
for cleanup in cleanup_funcs:
167
135
# XXX: Hmm, if KeyboardInterrupt arrives at exactly this line, we
168
136
# won't run all cleanups... perhaps we should temporarily install a
169
137
# SIGINT handler?
170
138
if exc_info is None:
172
cleanup(*c_args, **c_kwargs)
174
142
# This is the first cleanup to fail, so remember its