bug 9855; fix db undo/redo operation to delay signal emission

Certain multiple commit transactions can leave the db in an inconsistent
state in between commits.  If signals are emitted at each commit, GUI
elements can see the inconsistent state and report errors.  This fix
delays the signal emission until all the commits are complete, presuming
that the db is consistent before and after the complete transaction.
This commit is contained in:
prculley 2017-01-07 12:06:52 -06:00 committed by Nick Hall
parent 7e90144db3
commit cea4d15d7d
2 changed files with 70 additions and 18 deletions

View File

@ -144,7 +144,15 @@ class DbGenericUndo(DbUndo):
if key == REFERENCE_KEY:
self.undo_reference(new_data, handle)
else:
self.undo_data(new_data, handle, key, db.emit, SIGBASE[key])
self.undo_data(new_data, handle, key)
# now emit the signals
for record_id in subitems:
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key != REFERENCE_KEY:
self.undo_signals(new_data, handle, key,
db.emit, SIGBASE[key])
self.db._txn_commit()
except:
self.db._txn_abort()
@ -187,8 +195,15 @@ class DbGenericUndo(DbUndo):
if key == REFERENCE_KEY:
self.undo_reference(old_data, handle)
else:
self.undo_data(old_data, handle, key, db.emit, SIGBASE[key])
self.undo_data(old_data, handle, key)
# now emit the signals
for record_id in subitems:
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key != REFERENCE_KEY:
self.undo_signals(old_data, handle, key,
db.emit, SIGBASE[key])
self.db._txn_commit()
except:
self.db._txn_abort()
@ -224,7 +239,26 @@ class DbGenericUndo(DbUndo):
"VALUES(?, ?, ?, ?)")
self.db.dbapi.execute(sql, data)
def undo_data(self, data, handle, obj_key, emit, signal_root):
def undo_data(self, data, handle, obj_key):
"""
Helper method to undo/redo the changes made
"""
cls = KEY_TO_CLASS_MAP[obj_key]
table = cls.lower()
if data is None:
sql = "DELETE FROM %s WHERE handle = ?" % table
self.db.dbapi.execute(sql, [handle])
else:
if self.db.has_handle(obj_key, handle):
sql = "UPDATE %s SET blob_data = ? WHERE handle = ?" % table
self.db.dbapi.execute(sql, [pickle.dumps(data), handle])
else:
sql = "INSERT INTO %s (handle, blob_data) VALUES (?, ?)" % table
self.db.dbapi.execute(sql, [handle, pickle.dumps(data)])
obj = self.db.get_table_func(cls)["class_func"].create(data)
self.db._update_secondary_values(obj)
def undo_signals(self, data, handle, obj_key, emit, signal_root):
"""
Helper method to undo/redo the changes made
"""
@ -232,19 +266,11 @@ class DbGenericUndo(DbUndo):
table = cls.lower()
if data is None:
emit(signal_root + '-delete', ([handle],))
sql = "DELETE FROM %s WHERE handle = ?" % table
self.db.dbapi.execute(sql, [handle])
else:
if self.db.has_handle(obj_key, handle):
signal = signal_root + '-update'
sql = "UPDATE %s SET blob_data = ? WHERE handle = ?" % table
self.db.dbapi.execute(sql, [pickle.dumps(data), handle])
else:
signal = signal_root + '-add'
sql = "INSERT INTO %s (handle, blob_data) VALUES (?, ?)" % table
self.db.dbapi.execute(sql, [handle, pickle.dumps(data)])
obj = self.db.get_table_func(cls)["class_func"].create(data)
self.db._update_secondary_values(obj)
emit(signal, ([handle],))
class Cursor:

View File

@ -237,8 +237,15 @@ class DbUndo:
if key == REFERENCE_KEY:
self.undo_reference(old_data, handle, self.mapbase[key])
else:
self.undo_data(old_data, handle, self.mapbase[key],
db.emit, _SIGBASE[key])
self.undo_data(old_data, handle, self.mapbase[key])
# now emit the signals
for record_id in subitems:
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key != REFERENCE_KEY:
self.undo_signals(old_data, handle, self.mapbase[key],
db.emit, _SIGBASE[key])
# Notify listeners
if db.undo_callback:
if self.undo_count > 0:
@ -275,8 +282,15 @@ class DbUndo:
if key == REFERENCE_KEY:
self.undo_reference(new_data, handle, self.mapbase[key])
else:
self.undo_data(new_data, handle, self.mapbase[key],
db.emit, _SIGBASE[key])
self.undo_data(new_data, handle, self.mapbase[key])
# Process all signals in the transaction
for record_id in subitems:
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key != REFERENCE_KEY:
self.undo_signals(new_data, handle, self.mapbase[key],
db.emit, _SIGBASE[key])
# Notify listeners
if db.undo_callback:
db.undo_callback(_("_Undo %s")
@ -308,21 +322,33 @@ class DbUndo:
self.db._log_error()
raise DbError(msg)
def undo_data(self, data, handle, db_map, emit, signal_root):
def undo_data(self, data, handle, db_map):
"""
Helper method to undo/redo the changes made
"""
try:
if data is None:
db_map.delete(handle, txn=self.txn)
else:
db_map.put(handle, data, txn=self.txn)
except DBERRS as msg:
self.db._log_error()
raise DbError(msg)
def undo_signals(self, data, handle, db_map, emit, signal_root):
"""
Helper method to undo/redo the changes made
"""
try:
if data is None:
emit(signal_root + '-delete', ([handle.decode('utf-8')],))
db_map.delete(handle, txn=self.txn)
else:
ex_data = db_map.get(handle, txn=self.txn)
if ex_data:
signal = signal_root + '-update'
else:
signal = signal_root + '-add'
db_map.put(handle, data, txn=self.txn)
emit(signal, ([handle.decode('utf-8')],))
except DBERRS as msg: