mirror of
https://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu
synced 2025-08-01 02:34:05 +00:00
Have the transaction code verify that there is at most one active child at the time and no one is using the parent while child is active. This is how the code is intended to be used and also the code almost certainly does not work otherwise. The new code does not cover commiting/rolling back a parent before a child but that is already covered by the existing code (it will trigger when child transaction is rolled back/committed or when leaving the contextmanager from start_transaction). This would have caught 7d758760d1c08bcd3b364f7e6ec76b2c0eafe68a immediately with an assertion error. Signed-off-by: Niels Thykier <niels@thykier.net>
129 lines
4.6 KiB
Python
129 lines
4.6 KiB
Python
class MigrationTransactionState(object):
|
|
|
|
def __init__(self, suite_info, all_binaries, parent=None):
|
|
self._suite_info = suite_info
|
|
self._all_binaries = all_binaries
|
|
self.parent_transaction = parent
|
|
self._is_rolled_back = False
|
|
self._is_committed = False
|
|
self._undo_items = []
|
|
self._pending_child = False
|
|
if self.parent_transaction:
|
|
# Transactions can only support one child transaction at a time
|
|
assert not self.parent_transaction._pending_child
|
|
self.parent_transaction._pending_child = True
|
|
|
|
def add_undo_item(self, undo, updated_binaries):
|
|
# We do not accept any changes to this transaction while it has a child transaction
|
|
# (the undo code does not handle that case correctly)
|
|
assert not self._pending_child
|
|
self._assert_open_transaction()
|
|
self._undo_items.append((undo, updated_binaries))
|
|
|
|
def _assert_open_transaction(self):
|
|
assert not self._is_rolled_back and not self._is_committed
|
|
p = self.parent_transaction
|
|
if p:
|
|
p._assert_open_transaction()
|
|
|
|
@property
|
|
def undo_items(self):
|
|
"""Only needed by a doop_source for the "hint"-hint case"""
|
|
yield from self._undo_items
|
|
|
|
def commit(self):
|
|
"""Commit the transaction
|
|
|
|
After this call, it is not possible to roll these changes
|
|
back (except if there is a parent transaction, which can
|
|
still be rolled back).
|
|
"""
|
|
self._assert_open_transaction()
|
|
self._is_committed = True
|
|
if self.parent_transaction:
|
|
self.parent_transaction._pending_child = False
|
|
for undo_item in self._undo_items:
|
|
self.parent_transaction.add_undo_item(*undo_item)
|
|
|
|
def rollback(self):
|
|
"""Rollback all recorded changes by this transaction
|
|
|
|
The parent transaction (if any) will remain unchanged
|
|
"""
|
|
|
|
self._assert_open_transaction()
|
|
self._is_rolled_back = True
|
|
lundo = self._undo_items
|
|
lundo.reverse()
|
|
|
|
all_binary_packages = self._all_binaries
|
|
target_suite = self._suite_info.target_suite
|
|
sources_t = target_suite.sources
|
|
binaries_t = target_suite.binaries
|
|
provides_t = target_suite.provides_table
|
|
|
|
# Historically, we have done the undo process in "4 steps"
|
|
# with the rule that each step must be fully completed for
|
|
# each undo-item before starting on the next.
|
|
#
|
|
# see commit:ef71f0e33a7c3d8ef223ec9ad5e9843777e68133 and
|
|
# #624716 for the issues we had when we did not do this.
|
|
#
|
|
# Today, only STEP 2 and STEP 3 are known to potentially
|
|
# clash. If there is a point in merging the loops/steps,
|
|
# then it is now feasible.
|
|
|
|
# STEP 1
|
|
# undo all the changes for sources
|
|
for (undo, updated_binaries) in lundo:
|
|
for (k, v) in undo['sources'].items():
|
|
if v is None:
|
|
del sources_t[k]
|
|
else:
|
|
sources_t[k] = v
|
|
|
|
# STEP 2
|
|
# undo all new/updated binaries
|
|
# Note this must be completed fully before starting STEP 3
|
|
# as it potentially breaks STEP 3 if the two are interleaved.
|
|
for (_, updated_binaries) in lundo:
|
|
for pkg_id in updated_binaries:
|
|
pkg_name, _, pkg_arch = pkg_id
|
|
try:
|
|
del binaries_t[pkg_arch][pkg_name]
|
|
except KeyError:
|
|
continue
|
|
|
|
target_suite.remove_binary(pkg_id)
|
|
|
|
# STEP 3
|
|
# undo all other binary package changes (except virtual packages)
|
|
for (undo, updated_binaries) in lundo:
|
|
for p in undo['binaries']:
|
|
binary, arch = p
|
|
binaries_t_a = binaries_t[arch]
|
|
pkgdata = all_binary_packages[undo['binaries'][p]]
|
|
binaries_t_a[binary] = pkgdata
|
|
target_suite.add_binary(pkgdata.pkg_id)
|
|
|
|
# STEP 4
|
|
# undo all changes to virtual packages
|
|
for (undo, _) in lundo:
|
|
for p, value in undo['virtual'].items():
|
|
provided_pkg, arch = p
|
|
if value is None:
|
|
del provides_t[arch][provided_pkg]
|
|
else:
|
|
provides_t[arch][provided_pkg] = undo['virtual'][p]
|
|
|
|
if self.parent_transaction:
|
|
self.parent_transaction._pending_child = False
|
|
|
|
@property
|
|
def is_rolled_back(self):
|
|
return self._is_rolled_back
|
|
|
|
@property
|
|
def is_committed(self):
|
|
return self._is_committed
|