Changeset 3682

Show
Ignore:
Timestamp:
07/29/08 22:25:57 (4 months ago)
Author:
hans
Message:

Fix anonymous transactions: Instead of storing the subtransactions
and then serializing them at the end of the transaction, they are
now serialized immediately to an in-memory buffer and written to
the transaction log at the end of the transaction in one fell swoop.

Add condition classes for most errors that are signaled from txn.lisp

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/bknr/datastore/src/data/object-tests.lisp

    r3038 r3682  
    4646    (close-store))) 
    4747 
     48(defvar *tests* (make-hash-table)) 
     49 
    4850(defmacro define-datastore-test (name &rest body) 
    49   `(make-instance 'datastore-test-class 
    50     :unit :datastore 
    51     :name ,name 
    52     :body (lambda () 
    53             ,@body))) 
     51  `(setf (gethash ,name *tests*) 
     52         (make-instance 'datastore-test-class 
     53                        :unit :datastore 
     54                        :name ,name 
     55                        :body (lambda () 
     56                                ,@body)))) 
    5457 
    55 (define-datastore-test "Datastore setup" 
     58(define-datastore-test :store-setup 
    5659    (test-assert *test-datastore*)) 
    5760 
    58 (define-datastore-test "Create object" 
     61(define-datastore-test :create-object 
    5962    (let ((obj (make-object 'store-object))) 
    6063      (test-assert obj) 
    6164      (test-equal (list obj) (all-store-objects)))) 
    6265 
    63 (define-datastore-test "Create multiple objects" 
     66(define-datastore-test :create-multiple-objects 
    6467    (let ((o1 (make-object 'store-object)) 
    6568          (o2 (make-object 'store-object))) 
     
    6972      (test-assert (subsetp (list o1 o2) (all-store-objects))))) 
    7073 
    71 (define-datastore-test "Delete multiple objects" 
     74(define-datastore-test :delete-multiple-objects 
    7275    (let ((o1 (make-object 'store-object)) 
    7376          (o2 (make-object 'store-object))) 
     
    8184      (test-equal (all-store-objects) nil))) 
    8285 
    83 (define-datastore-test "Restore" 
     86(define-datastore-test :restore 
    8487    (make-object 'store-object) 
    8588  (restore) 
    8689  (test-equal (length (all-store-objects)) 1)) 
    8790 
    88 (define-datastore-test "Snapshot and Restore" 
     91(define-datastore-test :snapshot-and-restore 
    8992    (make-object 'store-object) 
    9093  (snapshot) 
     
    9295  (test-equal (length (all-store-objects)) 1)) 
    9396 
    94 (define-datastore-test "Restore multiple objects" 
     97(define-datastore-test :restore-multiple-objects 
    9598    (dotimes (i 10) (make-object 'store-object)) 
    9699  (restore) 
    97100  (test-equal (length (all-store-objects)) 10)) 
    98101 
    99 (define-datastore-test "Snapshot and Restore multiple objects" 
     102(define-datastore-test :snapshot-restore-multiple-objects 
    100103    (dotimes (i 10) (make-object 'store-object)) 
    101104  (snapshot) 
     
    105108(defconstant +stress-size+ 10000) 
    106109 
    107 (define-datastore-test "Stress test object creation" 
     110(define-datastore-test :stress-test 
    108111    (format t "Creating ~a objects~%" +stress-size+) 
    109112  (time (bknr.datastore::without-sync () 
     
    122125  ()) 
    123126 
    124 (define-datastore-test "Serialize circular dependency in anonymous txn" 
     127(define-datastore-test :serialize-circular-in-anon-txn 
    125128  (let ((parent (make-object 'parent))) 
    126129    (with-transaction (:circular) 
     
    129132  (test-equal (find-class 'child) 
    130133              (class-of (parent-child (first (class-instances 'parent)))))) 
     134 
     135(define-datastore-test :delete-object-in-anon-txn 
     136  (let (object) 
     137    (with-transaction (:make) 
     138      (setf object (make-object 'child))) 
     139    (with-transaction (:delete) 
     140      (delete-object object)) 
     141    (restore) 
     142    (test-assert (object-destroyed-p object)))) 
  • trunk/bknr/datastore/src/data/object.lisp

    r3680 r3682  
    9797(defmethod (setf slot-value-using-class) :after (newval (class persistent-class) object slotd) 
    9898  (when (in-anonymous-transaction-p) 
    99     (push (make-instance 'transaction 
    100                          :timestamp (get-universal-time) 
    101                         :function-symbol 'tx-change-slot-values 
    102                         :args (list object (slot-definition-name slotd) newval)) 
    103          (anonymous-transaction-transactions *current-transaction*)))) 
     99    (encode (make-instance 'transaction 
     100                           :timestamp (get-universal-time) 
     101                          :function-symbol 'tx-change-slot-values 
     102                          :args (list object (slot-definition-name slotd) newval)) 
     103            (anonymous-transaction-log-buffer *current-transaction*)))) 
    104104 
    105105(defmethod direct-slot-definition-class ((class persistent-class) &key &allow-other-keys) 
     
    196196      (prog1 
    197197          (call-next-method) 
    198         (push (make-instance 'transaction 
    199                             :function-symbol 'make-instance 
    200                             :timestamp (get-universal-time) 
    201                             :args (cons (class-name (class-of object)) 
    202                                         (loop for slotd in (class-slots (class-of object)) 
    203                                            for slot-name = (slot-definition-name slotd) 
    204                                            for slot-initarg = (first (slot-definition-initargs slotd)) 
    205                                            when (and slot-initarg 
    206                                                      (slot-boundp object slot-name)) 
    207                                            appending (list slot-initarg (slot-value object slot-name))))) 
    208              (anonymous-transaction-transactions *current-transaction*))) 
     198        (encode (make-instance 'transaction 
     199                               :function-symbol 'make-instance 
     200                               :timestamp (get-universal-time) 
     201                               :args (cons (class-name (class-of object)) 
     202                                          (loop for slotd in (class-slots (class-of object)) 
     203                                              for slot-name = (slot-definition-name slotd) 
     204                                              for slot-initarg = (first (slot-definition-initargs slotd)) 
     205                                              when (and slot-initarg 
     206                                                        (slot-boundp object slot-name)) 
     207                                              appending (list slot-initarg (slot-value object slot-name))))) 
     208                (anonymous-transaction-log-buffer *current-transaction*))) 
    209209      (call-next-method))) 
    210210 
     
    662662 
    663663(defun delete-object (object) 
    664   (if (in-transaction-p) 
     664  (if (and (in-transaction-p) 
     665           (not (in-anonymous-transaction-p))) 
    665666      (destroy-object object) 
    666667      (execute (make-instance 'transaction :function-symbol 'tx-delete-object 
  • trunk/bknr/datastore/src/data/txn.lisp

    r3450 r3682  
    1111  () 
    1212  (:documentation 
    13    "Thrown when an operation on persistent slots is executed outside a transaction context")) 
     13   "Signaled when an operation on persistent slots is executed outside 
     14   a transaction context")) 
    1415 
    1516(define-condition store-not-open (error) 
    1617  () 
    1718  (:documentation 
    18    "Thrown when a transaction is executed on a store that is not opened")) 
     19   "Signaled when a transaction is executed on a store that is not 
     20   opened")) 
     21 
     22(define-condition store-already-open (error) 
     23  () 
     24  (:documentation 
     25   "Signaled when an attempt is made to open a store with another 
     26   store being open")) 
     27 
     28(define-condition invalid-store-random-state (error) 
     29  () 
     30  (:documentation 
     31   "Signaled when the on-disk store random state cannot be read, 
     32   typically because it has been written with another Lisp")) 
     33 
     34(define-condition unsupported-lambda-list-option (error) 
     35  ((option :initarg :option :reader option)) 
     36  (:documentation 
     37   "Signaled when DEFTRANSACTION is used with an unsupported option in 
     38   its lambda list")) 
     39 
     40(define-condition default-arguments-unsupported (error) 
     41  ((tx-name :initarg :tx-name :reader tx-name) 
     42   (argument :initarg :argument :reader argument)) 
     43  (:report (lambda (c stream) 
     44             (format stream "argument ~A defaulted in DEFTRANSACTION ~S" 
     45                     (argument c) (tx-name c)))) 
     46  (:documentation 
     47   "Signaled when an argument in a DEFTRANSACTION definition has a 
     48   default declaration")) 
     49 
     50(define-condition undefined-transaction (error) 
     51  ((tx-name :initarg :tx-name :reader tx-name)) 
     52  (:report (lambda (c stream) 
     53             (format stream "undefined transaction ~A in transaction log, please ensure that all the necessary code is loaded." 
     54                     (tx-name c)))) 
     55  (:documentation 
     56   "Signaled when a named transaction is loaded from the transaction 
     57   log and no matching function definition could be found")) 
     58 
     59(define-condition invalid-transaction-nesting (error) 
     60  () 
     61  (:documentation 
     62   "Signaled when WITH-TRANSACTION forms are nested.")) 
     63 
     64(define-condition anonymous-transaction-in-named-transaction (error) 
     65  () 
     66  (:documentation 
     67   "Signaled when an anonymous transaction is started from within a named transaction.")) 
     68 
     69(define-condition no-subsystems (error) 
     70  () 
     71  (:documentation 
     72   "Signaled when an attempt is made to snapshot a store without subsystems")) 
    1973 
    2074;;; store 
     
    75129        (when (and (boundp '*store*) 
    76130                   *store*) 
    77           (error "A store is already opened.")) 
     131          (error 'store-already-open)) 
    78132      (close-store () 
    79133        :report "Close the opened store." 
     
    154208                    (error (e) 
    155209                      (declare (ignore e)) 
    156                       (error "Invalid store random state")))) 
     210                      (error 'invalid-store-random-state)))) 
    157211          (initialize-store-random-state () 
    158212            :report "Initialize the random state of the store.  Use 
     
    246300  (if (in-transaction-p) 
    247301      *current-transaction* 
    248       (error "store-current-transaction called outside of a transaction"))) 
     302      (error 'not-in-transaction))) 
    249303 
    250304;;; All transactions are executed by an 'executor', which is the store 
     
    263317(defmethod execute-transaction :before (executor transaction) 
    264318  (unless (store-open-p) 
    265     (error (make-condition 'store-not-open)))) 
     319    (error 'store-not-open))) 
    266320 
    267321(defmethod execute-transaction ((executor transaction) transaction) 
     
    318372           (&rest (setf args (cdr args))) ; skip argument, too 
    319373           (&key (setf in-keywords-p t)) 
    320            (otherwise (error "unsupported lambda list option ~A in DEFTRANSACTION" arg)))) 
     374           (otherwise (error 'unsupported-lambda-list-option :option arg)))) 
    321375        (t 
    322376         (when in-keywords-p 
     
    336390    (dolist (arg args) 
    337391      (when (listp arg) 
    338         (error "can't have argument defaults in transaction declaration for transaction ~A, please implement a wrapper" name))) 
     392        (error 'default-arguments-unsupported :tx-name name :argument (car arg)))) 
    339393    (let ((tx-name (intern (format nil "TX-~A" name) 
    340394                           (symbol-package name)))) 
     
    409463    (let ((*current-transaction* transaction)) 
    410464      (apply (or (symbol-function (transaction-function-symbol transaction)) 
    411                  (error "Undefined transaction function ~A, please ensure that all the necessary code is loaded." 
    412                         (transaction-function-symbol transaction))) 
     465                 (error 'undefined-transaction 
     466                        :tx-name (transaction-function-symbol transaction))) 
    413467             (transaction-args transaction))))) 
    414468 
     
    437491  `(with-store-guard () 
    438492     (when (in-transaction-p) 
    439        (error "can't open nested with-transaction-log blocks")) 
     493       (error 'invalid-transaction-nesting)) 
    440494     (with-store-state (:transaction) 
    441495       (prog1 
     
    473527;;; with-transaction macro. 
    474528 
    475 ;;; An anonymous transaction has an optional label which is stored in 
    476 ;;; the transaction log in order to make the source code location where 
     529;;; An anonymous transaction has a label which is stored in the 
     530;;; transaction log in order to make the source code location where 
    477531;;; the actual transaction code lives identifieable. 
    478532 
    479533(defclass anonymous-transaction (transaction) 
    480   ((label :initarg :label :accessor anonymous-transaction-label) 
    481    (transactions :initarg :transactions :accessor anonymous-transaction-transactions)) 
    482   (:default-initargs :transactions nil :label nil)) 
     534  ((label :initarg :label 
     535          :accessor anonymous-transaction-label 
     536          :initform (error "missing label in anonymous transaction definition")) 
     537   (log-buffer :initarg :log-buffer 
     538               :accessor anonymous-transaction-log-buffer 
     539               :initform (flex:make-in-memory-output-stream)))) 
    483540 
    484541(defmethod print-object ((transaction anonymous-transaction) stream) 
    485542  (print-unreadable-object (transaction stream :type t) 
    486     (format stream "~A ~A ~A
     543    (format stream "~A ~A (~A)
    487544            (format-date-time (transaction-timestamp transaction)) 
    488545            (anonymous-transaction-label transaction) 
    489             (anonymous-transaction-transactions transaction)))) 
     546            (class-name (class-of (anonymous-transaction-log-buffer transaction)))))) 
    490547 
    491548(defmethod in-anonymous-transaction-p () 
     
    493550 
    494551(defmethod encode-object ((transaction anonymous-transaction) stream) 
    495   (cond 
    496     ((anonymous-transaction-label transaction) 
    497      (%write-tag #\N stream) 
    498      (%encode-string (anonymous-transaction-label transaction) stream)) 
    499     (t 
    500      (%write-tag #\G stream))) 
    501   (%encode-list (reverse (anonymous-transaction-transactions transaction)) stream)) 
    502  
    503 (defmethod decode-object ((tag (eql #\G)) stream) 
    504   (make-instance 'anonymous-transaction 
    505                  :transactions (%decode-list stream))) 
     552  (%write-tag #\N stream) 
     553  (%encode-string (anonymous-transaction-label transaction) stream) 
     554  (let ((subtxns (flex:get-output-stream-sequence (anonymous-transaction-log-buffer transaction)))) 
     555    (%encode-integer (length subtxns) stream) 
     556    (write-sequence subtxns stream))) 
    506557 
    507558(defvar *txn-log-stream* nil 
     
    511562 
    512563(defmethod decode-object ((tag (eql #\N)) stream) 
    513   ;; When decoding an anonymous transaction from the transaction log, 
    514   ;; we only read its name.  The subtransaction are not read here, but 
    515   ;; rather in EXECUTE-UNLOGGED below.  The reason for this is that we 
    516   ;; need to execute the subtransactions while reading them, as we'd 
    517   ;; otherwise not be able to properly deserialize references to 
    518   ;; objects that have been created within this anonymous transaction. 
    519  
    520   ;; Thus, while restoring, the TRANSACTIONS slot of the anonymous 
    521   ;; transaction object is not used. 
    522   (make-instance 'anonymous-transaction 
    523                  :label (%decode-string stream))) 
     564  (let* ((label (%decode-string stream)) 
     565         (length (%decode-integer stream)) 
     566         (buffer (make-array length :element-type '(unsigned-byte 8)))) 
     567    (read-sequence buffer stream) 
     568    (make-instance 'anonymous-transaction 
     569                   :label label 
     570                   :log-buffer (flex:make-in-memory-input-stream buffer)))) 
    524571 
    525572(defmacro with-transaction ((&optional label) &body body) 
     
    527574    `(progn 
    528575       (when (in-transaction-p)  
    529          (error "tried to start anonymous transaction while in a transaction")) 
     576         (error 'anonymous-transaction-in-named-transaction)) 
    530577       (let ((,txn (make-instance 'anonymous-transaction :label ,(if (symbolp label) (symbol-name label) label)))) 
    531578         (with-transaction-log (,txn) 
     
    538585  (assert (eq :restore (store-state *store*)) () 
    539586          "Unexpected store state ~A for EXECUTE-UNLOGGED on an anonymous transaction" (store-state *store*)) 
    540   (let ((subtxns (%decode-integer *txn-log-stream*))) 
    541     (dotimes (i subtxns) 
    542       (execute-unlogged (decode *txn-log-stream*))) 
    543     (when (plusp subtxns) 
    544       ;; In order to maintain the previous on-disk format, we read the last cdr of the list 
    545       (assert (eq nil (decode *txn-log-stream*)))))) 
    546  
    547 (defmethod execute-transaction :after ((executor anonymous-transaction) transaction) 
    548   (push transaction (anonymous-transaction-transactions executor))) 
     587  (let ((stream (anonymous-transaction-log-buffer transaction))) 
     588    (handler-case 
     589        (loop 
     590           (execute-unlogged (decode stream))) 
     591      (end-of-file ())))) 
     592 
     593(defmethod execute-transaction :before ((executor anonymous-transaction) transaction) 
     594  (encode transaction (anonymous-transaction-log-buffer executor))) 
    549595 
    550596;;; Subsystems 
     
    572618(defmethod snapshot-store ((store store)) 
    573619  (unless (store-open-p) 
    574     (error (make-condition 'store-not-open))) 
     620    (error 'store-not-open)) 
    575621  (when (null (store-subsystems store)) 
    576     (error "Cannot snapshot store without subsystems...")) 
     622    (error 'no-subsystems)) 
    577623  (ensure-store-current-directory store) 
    578624  (with-store-state (:read-only store)