[bknr-cvs] hans changed trunk/bknr/datastore/src/data/
BKNR Commits
bknr at bknr.net
Mon Sep 22 22:24:04 UTC 2008
Revision: 3946
Author: hans
URL: http://bknr.net/trac/changeset/3946
Implement rollback for anonymous transactions. All slot changes
made in a transaction will be undone when an error is signalled
from within the transaction. As changes to the ID slots of created
objects are undone as well, objects created in the transaction will
be removed from their indices and eventually garbage collected.
U trunk/bknr/datastore/src/data/object-tests.lisp
U trunk/bknr/datastore/src/data/object.lisp
U trunk/bknr/datastore/src/data/txn.lisp
Modified: trunk/bknr/datastore/src/data/object-tests.lisp
===================================================================
--- trunk/bknr/datastore/src/data/object-tests.lisp 2008-09-22 19:18:24 UTC (rev 3945)
+++ trunk/bknr/datastore/src/data/object-tests.lisp 2008-09-22 22:24:04 UTC (rev 3946)
@@ -277,5 +277,16 @@
(o2 (make-instance 'inherit-multiple :child o1)))
(test-equal o1 (parent-child o2))))
+(defdstest abort-anonymous-transaction ()
+ (let (parent)
+ (with-transaction (:initial)
+ (setf parent (make-instance 'parent :child nil)))
+ (ignore-errors
+ (with-transaction (:abort)
+ (setf (parent-child parent) (make-instance 'child))
+ (error "abort")))
+ (test-equal nil (parent-child parent))
+ (test-equal nil (class-instances 'child))))
+
(defun run-datastore-test (name)
(unit-test:run-test (gethash name *tests*)))
Modified: trunk/bknr/datastore/src/data/object.lisp
===================================================================
--- trunk/bknr/datastore/src/data/object.lisp 2008-09-22 19:18:24 UTC (rev 3945)
+++ trunk/bknr/datastore/src/data/object.lisp 2008-09-22 22:24:04 UTC (rev 3946)
@@ -101,6 +101,11 @@
another persistent object and the pointed-to object is deleted, slot
reads will return nil."))
+(defun undo-set-slot (object slot-name value)
+ (if (eq value 'unbound)
+ (slot-makunbound object slot-name)
+ (setf (slot-value object slot-name) value)))
+
(defmethod (setf slot-value-using-class) :before ((newval t)
(class persistent-class)
object
@@ -110,6 +115,14 @@
(unless (or (in-transaction-p)
(member slot-name '(last-change id)))
(error 'persistent-slot-modified-outside-of-transaction :slot-name slot-name :object object))
+ (when (in-anonymous-transaction-p)
+ (push (list #'undo-set-slot
+ object
+ (slot-definition-name slotd)
+ (if (slot-boundp object (slot-definition-name slotd))
+ (slot-value object (slot-definition-name slotd))
+ 'unbound))
+ (anonymous-transaction-undo-log *current-transaction*)))
(when (and (not (eq :restore (store-state *store*)))
(not (member slot-name '(last-change id))))
(setf (slot-value object 'last-change) (current-transaction-timestamp))))))
Modified: trunk/bknr/datastore/src/data/txn.lisp
===================================================================
--- trunk/bknr/datastore/src/data/txn.lisp 2008-09-22 19:18:24 UTC (rev 3945)
+++ trunk/bknr/datastore/src/data/txn.lisp 2008-09-22 22:24:04 UTC (rev 3946)
@@ -64,10 +64,10 @@
(:documentation
"Signaled when WITH-TRANSACTION forms are nested."))
-(define-condition anonymous-transaction-in-named-transaction (store-error)
+(define-condition anonymous-transaction-in-transaction (store-error)
()
(:documentation
- "Signaled when an anonymous transaction is started from within a named transaction."))
+ "Signaled when an anonymous transaction is started from within another transaction, transactions do not nest."))
(define-condition no-subsystems (store-error)
()
@@ -555,7 +555,9 @@
:initform (error "missing label in anonymous transaction definition"))
(log-buffer :initarg :log-buffer
:accessor anonymous-transaction-log-buffer
- :initform (flex:make-in-memory-output-stream))))
+ :initform (flex:make-in-memory-output-stream))
+ (undo-log :initform nil
+ :accessor anonymous-transaction-undo-log)))
(defmethod print-object ((transaction anonymous-transaction) stream)
(print-unreadable-object (transaction stream :type t)
@@ -592,14 +594,38 @@
:label label
:log-buffer (flex:make-in-memory-input-stream buffer))))
+(define-condition rollback-failed (error)
+ ((transaction :initarg transaction)
+ (original-error :initarg :original-error))
+ (:report (lambda (e stream)
+ (with-slots (transaction original-error) e
+ (format stream "Rollback of transaction ~A failed: ~A" transaction original-error)))))
+
+(defun anonymous-transaction-undo (transaction)
+ (handler-case
+ (dolist (command (anonymous-transaction-undo-log transaction))
+ (apply (car command) (cdr command)))
+ (error (e)
+ (error 'rollback-failed
+ :transaction transaction
+ :original-error e))))
+
+(defun do-with-transaction (label thunk)
+ (when (in-transaction-p)
+ (error 'anonymous-transaction-in-transaction))
+ (let ((txn (make-instance 'anonymous-transaction :label label))
+ (next-object-id (next-object-id (store-object-subsystem))))
+ (with-transaction-log (txn)
+ (handler-case
+ (funcall thunk)
+ (error (e)
+ (setf (next-object-id (store-object-subsystem)) next-object-id)
+ (anonymous-transaction-undo txn)
+ (error e))))))
+
(defmacro with-transaction ((&optional label) &body body)
- (let ((txn (gensym)))
- `(progn
- (when (in-transaction-p)
- (error 'anonymous-transaction-in-named-transaction))
- (let ((,txn (make-instance 'anonymous-transaction :label ,(if (symbolp label) (symbol-name label) label))))
- (with-transaction-log (,txn)
- , at body)))))
+ `(do-with-transaction ,(if (symbolp label) (symbol-name label) label)
+ (lambda () , at body)))
(defmethod execute-unlogged ((transaction anonymous-transaction))
;; EXECUTE-UNLOGGED is called for anonymous transactions only when
More information about the Bknr-cvs
mailing list