-=[ Mr. Bumblebee ]=-
_Indonesia_
# Copyright (C) 2009, 2010 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Helpers for managing cleanup functions and the errors they might raise.
The usual way to run cleanup code in Python is::
try:
do_something()
finally:
cleanup_something()
However if both `do_something` and `cleanup_something` raise an exception
Python will forget the original exception and propagate the one from
cleanup_something. Unfortunately, this is almost always much less useful than
the original exception.
If you want to be certain that the first, and only the first, error is raised,
then use::
operation = OperationWithCleanups(do_something)
operation.add_cleanup(cleanup_something)
operation.run_simple()
This is more inconvenient (because you need to make every try block a
function), but will ensure that the first error encountered is the one raised,
while also ensuring all cleanups are run. See OperationWithCleanups for more
details.
"""
from __future__ import absolute_import
from collections import deque
import sys
from bzrlib import (
debug,
trace,
)
def _log_cleanup_error(exc):
trace.mutter('Cleanup failed:')
trace.log_exception_quietly()
if 'cleanup' in debug.debug_flags:
trace.warning('bzr: warning: Cleanup failed: %s', exc)
def _run_cleanup(func, *args, **kwargs):
"""Run func(*args, **kwargs), logging but not propagating any error it
raises.
:returns: True if func raised no errors, else False.
"""
try:
func(*args, **kwargs)
except KeyboardInterrupt:
raise
except Exception, exc:
_log_cleanup_error(exc)
return False
return True
def _run_cleanups(funcs):
"""Run a series of cleanup functions."""
for func, args, kwargs in funcs:
_run_cleanup(func, *args, **kwargs)
class ObjectWithCleanups(object):
"""A mixin for objects that hold a cleanup list.
Subclass or client code can call add_cleanup and then later `cleanup_now`.
"""
def __init__(self):
self.cleanups = deque()
def add_cleanup(self, cleanup_func, *args, **kwargs):
"""Add a cleanup to run.
Cleanups may be added at any time.
Cleanups will be executed in LIFO order.
"""
self.cleanups.appendleft((cleanup_func, args, kwargs))
def cleanup_now(self):
_run_cleanups(self.cleanups)
self.cleanups.clear()
class OperationWithCleanups(ObjectWithCleanups):
"""A way to run some code with a dynamic cleanup list.
This provides a way to add cleanups while the function-with-cleanups is
running.
Typical use::
operation = OperationWithCleanups(some_func)
operation.run(args...)
where `some_func` is::
def some_func(operation, args, ...):
do_something()
operation.add_cleanup(something)
# etc
Note that the first argument passed to `some_func` will be the
OperationWithCleanups object. To invoke `some_func` without that, use
`run_simple` instead of `run`.
"""
def __init__(self, func):
super(OperationWithCleanups, self).__init__()
self.func = func
def run(self, *args, **kwargs):
return _do_with_cleanups(
self.cleanups, self.func, self, *args, **kwargs)
def run_simple(self, *args, **kwargs):
return _do_with_cleanups(
self.cleanups, self.func, *args, **kwargs)
def _do_with_cleanups(cleanup_funcs, func, *args, **kwargs):
"""Run `func`, then call all the cleanup_funcs.
All the cleanup_funcs are guaranteed to be run. The first exception raised
by func or any of the cleanup_funcs is the one that will be propagted by
this function (subsequent errors are caught and logged).
Conceptually similar to::
try:
return func(*args, **kwargs)
finally:
for cleanup, cargs, ckwargs in cleanup_funcs:
cleanup(*cargs, **ckwargs)
It avoids several problems with using try/finally directly:
* an exception from func will not be obscured by a subsequent exception
from a cleanup.
* an exception from a cleanup will not prevent other cleanups from
running (but the first exception encountered is still the one
propagated).
Unike `_run_cleanup`, `_do_with_cleanups` can propagate an exception from a
cleanup, but only if there is no exception from func.
"""
# As correct as Python 2.4 allows.
try:
result = func(*args, **kwargs)
except:
# We have an exception from func already, so suppress cleanup errors.
_run_cleanups(cleanup_funcs)
raise
else:
# No exception from func, so allow the first exception from
# cleanup_funcs to propagate if one occurs (but only after running all
# of them).
exc_info = None
for cleanup, c_args, c_kwargs in cleanup_funcs:
# XXX: Hmm, if KeyboardInterrupt arrives at exactly this line, we
# won't run all cleanups... perhaps we should temporarily install a
# SIGINT handler?
if exc_info is None:
try:
cleanup(*c_args, **c_kwargs)
except:
# This is the first cleanup to fail, so remember its
# details.
exc_info = sys.exc_info()
else:
# We already have an exception to propagate, so log any errors
# but don't propagate them.
_run_cleanup(cleanup, *c_args, **kwargs)
if exc_info is not None:
try:
raise exc_info[0], exc_info[1], exc_info[2]
finally:
del exc_info
# No error, so we can return the result
return result
Copyright © 2017 || Recoded By Mr.Bumblebee