[elephant-cvs] CVS elephant/src/contrib/eslick

ieslick ieslick at common-lisp.net
Thu Apr 12 02:45:09 UTC 2007


Update of /project/elephant/cvsroot/elephant/src/contrib/eslick
In directory clnet:/tmp/cvs-serv28024

Added Files:
	snapshot-db.lisp 
Log Message:
Cool snapshot hack


--- /project/elephant/cvsroot/elephant/src/contrib/eslick/snapshot-db.lisp	2007/04/12 02:45:09	NONE
+++ /project/elephant/cvsroot/elephant/src/contrib/eslick/snapshot-db.lisp	2007/04/12 02:45:09	1.1
;;;
;;; Copyright (c) 2007 Ian Eslick <ieslick common-lisp net>
;;;
;;; Simple snapshot sets.  Create a snapshot set for standard objects,
;;; work in-memory and then call snapshot to save the objects to 
;;; the underlying store-controller.
;;;


;;
;; Limitations:
;;
;; - Hashes can be registered as indexes of objects.  Keys
;;   should be simple (numbers, strings, symbols) although 
;;   arrays in an equalp cache are probably OK too.  Values
;;   should also be simple or subclasses of standard-object
;;
;; - When a snapshot is taken of a hash, all values that are
;;   standard objects are registered.  Any refs to registered
;;   objects are properly restored on retrieval
;; 
;; Easy extensions:
;; 
;; - Support arrays of objects as well as hash-tables
;; 
;; - Create a method standard-object-dirty-p that defaults to 't' but
;;   allows users to implement a method that avoids storing unchanged
;;   objects.
;;
;; - Enable versioned or named snapshots 
;;
;; - Better interface API (special object metaclass?) to create a more
;;   natural abstraction.  Could also add object journaling for
;;   prevalence dynamics via rucksack log model.  For example, writes
;;   to slots are saved to a persistent list that gets reused after
;;   snapshots (id slotname value).  Slot reads are as usual.
;;

(in-package :elephant)

(defparameter *use-proxy-objects* t
  "Indicates that the snapshot set should register
   and write any standard-objects found in slots registered
   of standard objects during snapshots")

(defpclass snapshot-set ()
  ((index :accessor snapshot-set-index :initform (make-btree))
   (next-id :accessor snapshot-set-next-id :initform 0)
   (cache :accessor snapshot-set-cache :initform (make-hash-table) :transient t)
   (root :accessor snapshot-set-root :initform nil))
  (:documentation "Keeps track of a set of standard objects
    allowing a single snapshot call to update the store
    controller with the latest state of all objects registered with
    this set"))

(defmethod initialize-instance :after ((set snapshot-set) &rest rest)
  (declare (ignore rest))
  (restore set))

;; =================
;; User methods
;; =================

(defmethod register-object ((object standard-object) (set snapshot-set))
  "Register a standard object.  Not recorded until snapshot is called on db"
  (if (lookup-cached-id object set) nil
      (let ((id (incf (snapshot-set-next-id set))))
	(cache-snapshot-object id object set)
	object)))

(defmethod register-object ((hash hash-table) (set snapshot-set))
  "Adds a hash table to the snapshot set and registers any standard objects
   stored as values that are not already part of the snapshot.  Must call snapshot
   to save."
  (if (lookup-cached-id hash set) nil
      (let ((id (incf (snapshot-set-next-id set))))
	(cache-snapshot-object id hash set)
	hash)))

(defmethod set-root ((set snapshot-set))
  (if (snapshot-set-root set)
      (lookup-cached-object (snapshot-set-root set) set)
      nil))

(defmethod (setf set-root) (value (set snapshot-set))
  (setf (snapshot-set-root set) 
	(ensure-registered value)))

(defmethod unregister-object (object (set snapshot-set))
  "Drops the object from the cache and backing store"
  (let ((id (gethash object (snapshot-set-cache set))))
    (when (null id)
      (error "Object ~A not registered in ~A" object set))
    (drop-cached-object object set)
    (delete-snapshot-object id set)))

(defmethod snapshot ((set snapshot-set))
  (maphash (lambda (obj id)
	     (write-object id obj set))
	   (snapshot-set-cache set)))

(defmethod restore ((set snapshot-set))
  "Restores a snapshot by setting the snapshot-set state to the last snapshot.
   If this is used during runtime, the user needs to drop all references
   to objects and retrieve again from the snapshot set"
  (clear-cache set)
  (let ((proxyrecs nil))
    (map-btree (lambda (k v)
		 (cond ((hash-table-p v)
			(push (list k v) proxyrecs))
		       ((subtypep (type-of v) 'standard-object)
			(cache-snapshot-object k v set))
		       (t (error "Invalid type in snapshot-set type ~A for ~A" (type-of v) v))))
	       (snapshot-set-index set))
    ;; All objects should be loaded so object references in hashes are valid
    (dolist (proxyrec proxyrecs)
      (destructuring-bind (id proxy) proxyrec
	(cache-snapshot-object id (restore-proxy-hash proxy set) set)))))

(defun map-set (fn set)
  "Iterates through all values in the set"
  (maphash (lambda (k v)
	     (declare (ignore v))
	     (funcall fn k))
	   (snapshot-set-cache set)))

;; ===============
;; Shorthand
;; ===============

;; Cache ops

(defun clear-cache (set)
  (clrhash (snapshot-set-cache set)))

(defun lookup-cached-id (obj set)
  (gethash obj (snapshot-set-cache set)))

(defun lookup-cached-object (id set)
  (find-hash-key-by-value id (snapshot-set-cache set)))

(defun find-hash-key-by-value (value hash)
  (maphash (lambda (k v)
	     (when (eq v value) 
	       (return-from find-hash-key-by-value k)))
	   hash))

(defun cache-snapshot-object (id obj set)
  (setf (gethash obj (snapshot-set-cache set)) id))

(defun drop-cached-object (obj set)
  (remhash obj (snapshot-set-cache set)))

;; Save and restore objects

(defun read-snapshot-object (id set)
  (get-value id (snapshot-set-index set)))

(defun write-object (id obj set)
  (setf (get-value id (snapshot-set-index set))
	(cond ((subtypep (type-of obj) 'standard-object) 
	       (make-proxy-object obj set))
	      ((eq (type-of obj) 'hash-table)
	       (make-proxy-hash obj set))
	      (t (error "Cannot only snapshot standard-objects and hash-tables")))))

(defun ensure-registered (obj set)
  "Return object id by cache lookup or register and write object"
  (let ((id (lookup-cached-id obj set)))
    (if id id
	(progn
	  (register-object obj set)
	  (let ((id (lookup-cached-id obj set)))
	    (write-object id obj set)
	    id)))))

(defun delete-snapshot-object (id set)
  (remove-kv id (snapshot-set-index set)))

;; Snapshot ops

(defun reified-class-p (obj)
  (or (subtypep (type-of obj) 'standard-object)
      (eq (type-of obj) 'hash-table)))

(defclass setref ()
  ((id :accessor snapshot-set-reference-id :initarg :id)))

(defun make-proxy-object (obj set)
  (if (not *use-proxy-objects*)
      obj
      (let ((proxy (make-instance (type-of obj))))
	(loop for (slotname value) in (subsets 2 (slots-and-values obj)) do
	     (setf (slot-value proxy slotname)
		   (if (reified-class-p value)
		       (make-instance 'setref :id (ensure-registered value set))
		       value))))))

(defun make-proxy-hash (hash set)
  (let ((proxy (make-hash-table)))
    (maphash (lambda (key value)
	       (setf (gethash key proxy)
		     (if (or (subtypep (type-of value) 'standard-object)
			     (subtypep (type-of value) 'hash-table))
			 (make-instance 'setref :id (ensure-registered value set))
			 value)))
	     hash)
    proxy))

(defun restore-proxy-hash (proxy set)
  "Convert a proxy object to a standard hash, resolving references"
  (let ((hash (make-hash-table)))
    (maphash (lambda (k v)
	       (setf (gethash k hash)
		     (if (eq (type-of v) 'setref)
			 (lookup-cached-object (snapshot-set-reference-id v) set)
			 v)))
	     proxy)
    hash))

	  



More information about the Elephant-cvs mailing list