/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/cleanup.py

MergeĀ lp:bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
"""Helpers for managing cleanup functions and the errors they might raise.
18
18
 
19
 
The usual way to run cleanup code in Python is::
20
 
 
21
 
    try:
22
 
        do_something()
23
 
    finally:
24
 
        cleanup_something()
25
 
 
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
 
20
look like this::
 
21
 
 
22
    from bzrlib.cleanups import run_cleanup
 
23
    try:
 
24
        do_something()
 
25
    finally:
 
26
        run_cleanup(cleanup_something)
 
27
 
 
28
Any errors from `cleanup_something` will be logged, but not raised.
 
29
Importantly, any errors from do_something will be propagated.
 
30
 
 
31
There is also convenience function for running multiple, independent cleanups
 
32
in sequence: run_cleanups.  e.g.::
 
33
 
 
34
    try:
 
35
        do_something()
 
36
    finally:
 
37
        run_cleanups([cleanup_func_a, cleanup_func_b], ...)
 
38
 
 
39
Developers can use the `-Dcleanup` debug flag to cause cleanup errors to be
 
40
reported in the UI as well as logged.
 
41
 
 
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
 
47
a user.
30
48
 
31
49
If you want to be certain that the first, and only the first, error is raised,
32
50
then use::
33
51
 
34
 
    operation = OperationWithCleanups(do_something)
35
 
    operation.add_cleanup(cleanup_something)
36
 
    operation.run_simple()
 
52
    do_with_cleanups(do_something, cleanups)
37
53
 
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
41
 
details.
 
56
while also ensuring all cleanups are run.
42
57
"""
43
58
 
44
59
 
45
 
from collections import deque
46
60
import sys
47
61
from bzrlib import (
48
62
    debug,
56
70
        trace.warning('bzr: warning: Cleanup failed: %s', exc)
57
71
 
58
72
 
59
 
def _run_cleanup(func, *args, **kwargs):
 
73
def run_cleanup(func, *args, **kwargs):
60
74
    """Run func(*args, **kwargs), logging but not propagating any error it
61
75
    raises.
62
76
 
72
86
    return True
73
87
 
74
88
 
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)
79
 
 
80
 
 
81
 
class OperationWithCleanups(object):
82
 
    """A way to run some code with a dynamic cleanup list.
83
 
 
84
 
    This provides a way to add cleanups while the function-with-cleanups is
85
 
    running.
86
 
 
87
 
    Typical use::
88
 
 
89
 
        operation = OperationWithCleanups(some_func)
90
 
        operation.run(args...)
91
 
 
92
 
    where `some_func` is::
93
 
 
94
 
        def some_func(operation, args, ...):
95
 
            do_something()
96
 
            operation.add_cleanup(something)
97
 
            # etc
98
 
 
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):
 
90
    try:
 
91
        func(*args, **kwargs)
 
92
    except KeyboardInterrupt:
 
93
        raise
 
94
    except Exception, exc:
 
95
        trace.mutter('Cleanup failed:')
 
96
        trace.log_exception_quietly()
 
97
        trace.warning('Cleanup failed: %s', exc)
 
98
        return False
 
99
    return True
 
100
 
 
101
 
 
102
def run_cleanups(funcs, on_error='log'):
 
103
    """Run a series of cleanup functions.
 
104
 
 
105
    :param errors: One of 'log', 'warn first', 'warn all'
102
106
    """
103
 
 
104
 
    def __init__(self, func):
105
 
        self.func = func
106
 
        self.cleanups = deque()
107
 
 
108
 
    def add_cleanup(self, cleanup_func, *args, **kwargs):
109
 
        """Add a cleanup to run.
110
 
 
111
 
        Cleanups may be added at any time before or during the execution of
112
 
        self.func.  Cleanups will be executed in LIFO order.
113
 
        """
114
 
        self.cleanups.appendleft((cleanup_func, args, kwargs))
115
 
 
116
 
    def run(self, *args, **kwargs):
117
 
        return _do_with_cleanups(
118
 
            self.cleanups, self.func, self, *args, **kwargs)
119
 
 
120
 
    def run_simple(self, *args, **kwargs):
121
 
        return _do_with_cleanups(
122
 
            self.cleanups, self.func, *args, **kwargs)
123
 
 
124
 
    def cleanup_now(self):
125
 
        _run_cleanups(self.cleanups)
126
 
        self.cleanups.clear()
127
 
 
128
 
 
129
 
def _do_with_cleanups(cleanup_funcs, func, *args, **kwargs):
 
107
    seen_error = False
 
108
    for func in funcs:
 
109
        if on_error == 'log' or (on_error == 'warn first' and seen_error):
 
110
            seen_error |= run_cleanup(func)
 
111
        else:
 
112
            seen_error |= run_cleanup_reporting_errors(func)
 
113
 
 
114
 
 
115
def do_with_cleanups(func, cleanup_funcs):
130
116
    """Run `func`, then call all the cleanup_funcs.
131
117
 
132
118
    All the cleanup_funcs are guaranteed to be run.  The first exception raised
136
122
    Conceptually similar to::
137
123
 
138
124
        try:
139
 
            return func(*args, **kwargs)
 
125
            return func()
140
126
        finally:
141
 
            for cleanup, cargs, ckwargs in cleanup_funcs:
142
 
                cleanup(*cargs, **ckwargs)
 
127
            for cleanup in cleanup_funcs:
 
128
                cleanup()
143
129
 
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
149
135
       propagated).
150
136
 
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.
153
139
    """
154
140
    # As correct as Python 2.4 allows.
155
141
    try:
156
 
        result = func(*args, **kwargs)
 
142
        result = func()
157
143
    except:
158
144
        # We have an exception from func already, so suppress cleanup errors.
159
 
        _run_cleanups(cleanup_funcs)
 
145
        run_cleanups(cleanup_funcs)
160
146
        raise
161
147
    else:
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
164
150
        # of them).
165
151
        exc_info = None
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:
171
157
                try:
172
 
                    cleanup(*c_args, **c_kwargs)
 
158
                    cleanup()
173
159
                except:
174
160
                    # This is the first cleanup to fail, so remember its
175
161
                    # details.
177
163
            else:
178
164
                # We already have an exception to propagate, so log any errors
179
165
                # but don't propagate them.
180
 
                _run_cleanup(cleanup, *c_args, **kwargs)
 
166
                run_cleanup(cleanup)
181
167
        if exc_info is not None:
182
168
            raise exc_info[0], exc_info[1], exc_info[2]
183
169
        # No error, so we can return the result