~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:19:43 UTC
  • mfrom: (4634.85.12 cleanup-hof)
  • mto: This revision was merged to the branch mainline in revision 4775.
  • Revision ID: andrew.bennetts@canonical.com-20091015021943-l8tuonz6q30tc4t6
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.

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)
79
 
 
80
 
 
81
 
class ObjectWithCleanups(object):
82
 
    """A mixin for objects that hold a cleanup list.
83
 
 
84
 
    Subclass or client code can call add_cleanup and then later `cleanup_now`.
85
 
    """
86
 
    def __init__(self):
87
 
        self.cleanups = deque()
88
 
 
89
 
    def add_cleanup(self, cleanup_func, *args, **kwargs):
90
 
        """Add a cleanup to run.
91
 
 
92
 
        Cleanups may be added at any time.  
93
 
        Cleanups will be executed in LIFO order.
94
 
        """
95
 
        self.cleanups.appendleft((cleanup_func, args, kwargs))
96
 
 
97
 
    def cleanup_now(self):
98
 
        _run_cleanups(self.cleanups)
99
 
        self.cleanups.clear()
100
 
 
101
 
 
102
 
class OperationWithCleanups(ObjectWithCleanups):
103
 
    """A way to run some code with a dynamic cleanup list.
104
 
 
105
 
    This provides a way to add cleanups while the function-with-cleanups is
106
 
    running.
107
 
 
108
 
    Typical use::
109
 
 
110
 
        operation = OperationWithCleanups(some_func)
111
 
        operation.run(args...)
112
 
 
113
 
    where `some_func` is::
114
 
 
115
 
        def some_func(operation, args, ...):
116
 
            do_something()
117
 
            operation.add_cleanup(something)
118
 
            # etc
119
 
 
120
 
    Note that the first argument passed to `some_func` will be the
121
 
    OperationWithCleanups object.  To invoke `some_func` without that, use
122
 
    `run_simple` instead of `run`.
123
 
    """
124
 
 
125
 
    def __init__(self, func):
126
 
        super(OperationWithCleanups, self).__init__()
127
 
        self.func = func
128
 
 
129
 
    def run(self, *args, **kwargs):
130
 
        return _do_with_cleanups(
131
 
            self.cleanups, self.func, self, *args, **kwargs)
132
 
 
133
 
    def run_simple(self, *args, **kwargs):
134
 
        return _do_with_cleanups(
135
 
            self.cleanups, self.func, *args, **kwargs)
136
 
 
137
 
 
138
 
def _do_with_cleanups(cleanup_funcs, 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)
 
95
 
 
96
 
 
97
def do_with_cleanups(func, cleanup_funcs):
139
98
    """Run `func`, then call all the cleanup_funcs.
140
99
 
141
100
    All the cleanup_funcs are guaranteed to be run.  The first exception raised
145
104
    Conceptually similar to::
146
105
 
147
106
        try:
148
 
            return func(*args, **kwargs)
 
107
            return func()
149
108
        finally:
150
 
            for cleanup, cargs, ckwargs in cleanup_funcs:
151
 
                cleanup(*cargs, **ckwargs)
 
109
            for cleanup in cleanup_funcs:
 
110
                cleanup()
152
111
 
153
112
    It avoids several problems with using try/finally directly:
154
113
     * an exception from func will not be obscured by a subsequent exception
157
116
       running (but the first exception encountered is still the one
158
117
       propagated).
159
118
 
160
 
    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
161
120
    cleanup, but only if there is no exception from func.
162
121
    """
163
122
    # As correct as Python 2.4 allows.
164
123
    try:
165
 
        result = func(*args, **kwargs)
 
124
        result = func()
166
125
    except:
167
126
        # We have an exception from func already, so suppress cleanup errors.
168
127
        _run_cleanups(cleanup_funcs)
172
131
        # cleanup_funcs to propagate if one occurs (but only after running all
173
132
        # of them).
174
133
        exc_info = None
175
 
        for cleanup, c_args, c_kwargs in cleanup_funcs:
 
134
        for cleanup in cleanup_funcs:
176
135
            # XXX: Hmm, if KeyboardInterrupt arrives at exactly this line, we
177
136
            # won't run all cleanups... perhaps we should temporarily install a
178
137
            # SIGINT handler?
179
138
            if exc_info is None:
180
139
                try:
181
 
                    cleanup(*c_args, **c_kwargs)
 
140
                    cleanup()
182
141
                except:
183
142
                    # This is the first cleanup to fail, so remember its
184
143
                    # details.
186
145
            else:
187
146
                # We already have an exception to propagate, so log any errors
188
147
                # but don't propagate them.
189
 
                _run_cleanup(cleanup, *c_args, **kwargs)
 
148
                _run_cleanup(cleanup)
190
149
        if exc_info is not None:
191
 
            try:
192
 
                raise exc_info[0], exc_info[1], exc_info[2]
193
 
            finally:
194
 
                del exc_info
 
150
            raise exc_info[0], exc_info[1], exc_info[2]
195
151
        # No error, so we can return the result
196
152
        return result
197
153