~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/cleanup.py

  • Committer: Andrew Bennetts
  • Date: 2009-10-15 02:53:30 UTC
  • mto: This revision was merged to the branch mainline in revision 4775.
  • Revision ID: andrew.bennetts@canonical.com-20091015025330-9cwu80sdkttu7v58
Add OperationWithCleanups helper, use it to make commit.py simpler and more robust.

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
31
31
If you want to be certain that the first, and only the first, error is raised,
32
32
then use::
33
33
 
34
 
    operation = OperationWithCleanups(do_something)
35
 
    operation.add_cleanup(cleanup_something)
36
 
    operation.run_simple()
 
34
    do_with_cleanups(do_something, cleanups)
37
35
 
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
41
 
details.
 
38
while also ensuring all cleanups are run.
42
39
"""
43
40
 
44
41
 
45
 
from collections import deque
46
42
import sys
47
43
from bzrlib import (
48
44
    debug,
72
68
    return True
73
69
 
74
70
 
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)
 
71
def _run_cleanup_reporting_errors(func, *args, **kwargs):
 
72
    try:
 
73
        func(*args, **kwargs)
 
74
    except KeyboardInterrupt:
 
75
        raise
 
76
    except Exception, exc:
 
77
        trace.mutter('Cleanup failed:')
 
78
        trace.log_exception_quietly()
 
79
        trace.warning('Cleanup failed: %s', exc)
 
80
        return False
 
81
    return True
 
82
 
 
83
 
 
84
def _run_cleanups(funcs, on_error='log'):
 
85
    """Run a series of cleanup functions.
 
86
 
 
87
    :param errors: One of 'log', 'warn first', 'warn all'
 
88
    """
 
89
    seen_error = False
 
90
    for func in funcs:
 
91
        if on_error == 'log' or (on_error == 'warn first' and seen_error):
 
92
            seen_error |= _run_cleanup(func)
 
93
        else:
 
94
            seen_error |= _run_cleanup_reporting_errors(func)
79
95
 
80
96
 
81
97
class OperationWithCleanups(object):
82
 
    """A way to run some code with a dynamic cleanup list.
 
98
    """A helper for using do_with_cleanups with a dynamic cleanup list.
83
99
 
84
100
    This provides a way to add cleanups while the function-with-cleanups is
85
101
    running.
91
107
 
92
108
    where `some_func` is::
93
109
 
94
 
        def some_func(operation, args, ...):
 
110
        def some_func(operation, args, ...)
95
111
            do_something()
96
112
            operation.add_cleanup(something)
97
113
            # 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`.
102
114
    """
103
115
 
104
116
    def __init__(self, func):
105
117
        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))
 
118
        self.cleanups = []
 
119
 
 
120
    def add_cleanup(self, cleanup_func):
 
121
        """Add a cleanup to run.  Cleanups will be executed in LIFO order."""
 
122
        self.cleanups.insert(0, cleanup_func)
115
123
 
116
124
    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):
 
125
        func = lambda: self.func(self, *args, **kwargs)
 
126
        return do_with_cleanups(func, self.cleanups)
 
127
 
 
128
 
 
129
def do_with_cleanups(func, cleanup_funcs):
130
130
    """Run `func`, then call all the cleanup_funcs.
131
131
 
132
132
    All the cleanup_funcs are guaranteed to be run.  The first exception raised
136
136
    Conceptually similar to::
137
137
 
138
138
        try:
139
 
            return func(*args, **kwargs)
 
139
            return func()
140
140
        finally:
141
 
            for cleanup, cargs, ckwargs in cleanup_funcs:
142
 
                cleanup(*cargs, **ckwargs)
 
141
            for cleanup in cleanup_funcs:
 
142
                cleanup()
143
143
 
144
144
    It avoids several problems with using try/finally directly:
145
145
     * an exception from func will not be obscured by a subsequent exception
148
148
       running (but the first exception encountered is still the one
149
149
       propagated).
150
150
 
151
 
    Unike `_run_cleanup`, `_do_with_cleanups` can propagate an exception from a
 
151
    Unike `_run_cleanup`, `do_with_cleanups` can propagate an exception from a
152
152
    cleanup, but only if there is no exception from func.
153
153
    """
154
154
    # As correct as Python 2.4 allows.
155
155
    try:
156
 
        result = func(*args, **kwargs)
 
156
        result = func()
157
157
    except:
158
158
        # We have an exception from func already, so suppress cleanup errors.
159
159
        _run_cleanups(cleanup_funcs)
163
163
        # cleanup_funcs to propagate if one occurs (but only after running all
164
164
        # of them).
165
165
        exc_info = None
166
 
        for cleanup, c_args, c_kwargs in cleanup_funcs:
 
166
        for cleanup in cleanup_funcs:
167
167
            # XXX: Hmm, if KeyboardInterrupt arrives at exactly this line, we
168
168
            # won't run all cleanups... perhaps we should temporarily install a
169
169
            # SIGINT handler?
170
170
            if exc_info is None:
171
171
                try:
172
 
                    cleanup(*c_args, **c_kwargs)
 
172
                    cleanup()
173
173
                except:
174
174
                    # This is the first cleanup to fail, so remember its
175
175
                    # details.
177
177
            else:
178
178
                # We already have an exception to propagate, so log any errors
179
179
                # but don't propagate them.
180
 
                _run_cleanup(cleanup, *c_args, **kwargs)
 
180
                _run_cleanup(cleanup)
181
181
        if exc_info is not None:
182
182
            raise exc_info[0], exc_info[1], exc_info[2]
183
183
        # No error, so we can return the result