17
17
"""Helpers for managing cleanup functions and the errors they might raise.
19
The usual way to run cleanup code in Python is::
26
However if both `do_something` and `cleanup_something` raise an exception
27
Python will forget the original exception and propagate the one from
28
cleanup_something. Unfortunately, this is almost always much less useful than
29
the original exception.
19
Generally, code that wants to perform some cleanup at the end of an action will
22
from bzrlib.cleanups import run_cleanup
26
run_cleanup(cleanup_something)
28
Any errors from `cleanup_something` will be logged, but not raised.
29
Importantly, any errors from do_something will be propagated.
31
There is also convenience function for running multiple, independent cleanups
32
in sequence: run_cleanups. e.g.::
37
run_cleanups([cleanup_func_a, cleanup_func_b], ...)
39
Developers can use the `-Dcleanup` debug flag to cause cleanup errors to be
40
reported in the UI as well as logged.
42
Note the tradeoff that run_cleanup/run_cleanups makes: errors from
43
`do_something` will not be obscured by errors from `cleanup_something`, but
44
errors from `cleanup_something` will never reach the user, even if there is no
45
error from `do_something`. So run_cleanup is good to use when a failure of
46
internal housekeeping (e.g. failure to finish a progress bar) is unimportant to
31
49
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()
52
do_with_cleanups(do_something, cleanups)
38
54
This is more inconvenient (because you need to make every try block a
39
55
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
56
while also ensuring all cleanups are run.
45
from collections import deque
47
61
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`.
89
def run_cleanup_reporting_errors(func, *args, **kwargs):
92
except KeyboardInterrupt:
94
except Exception, exc:
95
trace.mutter('Cleanup failed:')
96
trace.log_exception_quietly()
97
trace.warning('Cleanup failed: %s', exc)
102
def run_cleanups(funcs, on_error='log'):
103
"""Run a series of cleanup functions.
105
: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):
109
if on_error == 'log' or (on_error == 'warn first' and seen_error):
110
seen_error |= run_cleanup(func)
112
seen_error |= run_cleanup_reporting_errors(func)
115
def do_with_cleanups(func, cleanup_funcs):
130
116
"""Run `func`, then call all the cleanup_funcs.
132
118
All the cleanup_funcs are guaranteed to be run. The first exception raised
136
122
Conceptually similar to::
139
return func(*args, **kwargs)
141
for cleanup, cargs, ckwargs in cleanup_funcs:
142
cleanup(*cargs, **ckwargs)
127
for cleanup in cleanup_funcs:
144
130
It avoids several problems with using try/finally directly:
145
131
* an exception from func will not be obscured by a subsequent exception
148
134
running (but the first exception encountered is still the one
151
Unike `_run_cleanup`, `_do_with_cleanups` can propagate an exception from a
137
Unike `run_cleanup`, `do_with_cleanups` can propagate an exception from a
152
138
cleanup, but only if there is no exception from func.
154
140
# As correct as Python 2.4 allows.
156
result = func(*args, **kwargs)
158
144
# We have an exception from func already, so suppress cleanup errors.
159
_run_cleanups(cleanup_funcs)
145
run_cleanups(cleanup_funcs)
162
148
# No exception from func, so allow the first exception from
163
149
# cleanup_funcs to propagate if one occurs (but only after running all
166
for cleanup, c_args, c_kwargs in cleanup_funcs:
152
for cleanup in cleanup_funcs:
167
153
# XXX: Hmm, if KeyboardInterrupt arrives at exactly this line, we
168
154
# won't run all cleanups... perhaps we should temporarily install a
169
155
# SIGINT handler?
170
156
if exc_info is None:
172
cleanup(*c_args, **c_kwargs)
174
160
# This is the first cleanup to fail, so remember its