Am Montag, den 29.11.2010, 10:34 -0300 schrieb Stephen Eilert:
So it is not only "memoization", but a caching system. I was going to
> write something similar, to cache webservice responses.
> 
That it is.

I've got several caches working with the attached code.

The API is kind of patterned after SRFI-69.

It tried however to hide that we're using hash tables, since that I
might want to experiment with.

If that fits approximately your needs I'd love to share the code.  --
and get your comments (API suggestions anyone?).

(It's not yet available as an egg, though.)

Here's an example which hopefully explains how to use it.

(define *resync-cache*
  (make-cache "*resync-cache*"
              eq?
              #f ;; state
              ;; miss:
              (lambda (cs k) (cons k #f))
              ;; hit
              #f ;; (lambda (c es) #t)
              ;; fulfil
              (lambda (c es results)
                (set-cdr! es (if (and (pair? results) (aggregate? (car 
results))) 
                                 #f
                                 (add-duration
                                  *system-time*
                                  (make-time
                                   'time-duration 0
                                   (or (respond-timeout-interval) 20)))))
                (values))
              ;; valid?
              (lambda (es)
                (or (not (cdr es))
                    (srfi19:time<=? *system-time* (cdr es))))
              ;; delete
              #f ;; (lambda (cs es) #f)
              ))

(cache-ref *resync-cache* oid
        (lambda () (%find-frame-sync! oid #f context)))


(define-record-type <cache>
  (%make-cache name mailbox t index state miss hit fulfil valid? delete)
  cache?
  (name cache-name)
  (mailbox cache-mailbox)
  (t cache-thread set-cache-thread!)
  (index cache-index)
  (state cache-state)
  (miss cache-miss)
  (hit cache-hit)
  (fulfil cache-fulfil)
  (valid? cache-valid?)
  (delete cache-delete))

(define (default-hit-handler cache value-state) #f)

(define (default-fulfil-handler cache value-state values-or-false) #f)

(define (default-delete-handler cache-state value-state) #f)

(define (make-cache name eq state miss hit fulfil valid? delete)
  (assert (procedure? miss))
  (assert (procedure? valid?))
  (%make-cache
   name
   (make-mailbox name) #f (make-hash-table eq)
   state miss
   (or hit default-hit-handler)
   (or fulfil default-fulfil-handler)
   valid?
   (or delete default-delete-handler)))

(define-inline (check-cache! cache)
  (or (cache-thread cache)
      (set-cache-thread!
       cache
       (thread-start!
	(make-thread
	 (lambda ()
	   (let loop ()
	     (guard
	      (ex (else (log-condition (cache-name cache) ex)
			(loop)))
	      (let loop ((request (receive-message! (cache-mailbox cache))))
		(send-message! (car request) (call-with-values (cdr request) list))
		(loop (receive-message! (cache-mailbox cache)))))))
	 (dbgname (cache-name cache) "~a-table"))))))

(define (cache-size cache) (hash-table-size (cache-index cache)))

(define-inline (cache-lookup cache key)
  (hash-table-ref/default (cache-index cache) key #f))

(define-inline (cache-lookup/default cache key default)
  (hash-table-ref/default (cache-index cache) key default))

(define-record-type <cache-entry>
  (make-cache-entry mutex avail value)
  cache-entry?
  (mutex cache-entry-mutex)
  (avail cache-entry-avail)
  (value cache-entry-value %set-entry-value!))

(define (set-entry-value! entry old new)
  (with-mutex
   (cache-entry-mutex entry)
   (if (eq? old (cache-entry-value entry))
       (begin
	 (%set-entry-value! entry new)
	 (condition-variable-broadcast! (cache-entry-avail entry))))))

(define-record-type <cache-value>
  (make-cache-value state avail values thunk)
  cache-value?
  (state cache-value-state)
  (avail cache-value-avail)
  (values cache-value-values)
  (thunk cache-value-thunk))

(define (with-cache-index cache thunk)
  (check-cache! cache)
  (let ((m (make-mailbox (cache-name cache))))
    (send-message! (cache-mailbox cache) (cons m thunk))
    (apply values (receive-message! m))))

(define (cache-find! cache key thunk)
  (hash-table-ref
   (cache-index cache) key
   (lambda ()
     (define entry (make-cache-entry
		    (make-mutex (dbgname key "~a-entry"))
		    (make-condition-variable key)
		    (make-cache-value
		     ((cache-miss cache) (cache-state cache) key)
		     (make-mutex (dbgname key "~a-value"))
		     '() thunk)))
     (hash-table-set! (cache-index cache) key entry)
     entry)))

(define-inline (cache-find cache key thunk)
  (hash-table-ref
   (cache-index cache) key
   (lambda ()
     (with-cache-index cache (lambda () (cache-find! cache key thunk))))))

(define-inline (cache-value-fulfiled! cache entry values)
  ((cache-fulfil cache) cache (cache-value-state (cache-entry-value entry)) values))

;; Compute the value, run trigger and signal completion.

(define (cache-entry-force! cache key entry old)
  (guard
   (exception
    (else (let ((new (make-cache-value
		      (cache-value-state old)
		      raise
		      (list (if (condition? exception) exception
				(make-condition
				 &message 'message
				 (format "~a ~a ~a" key (cache-value-thunk old) exception))))
		      (cache-value-thunk old))))
	    (cache-value-fulfiled! cache entry #f)
	    (set-entry-value! entry old new)
	    new)))
   (let ((new (call-with-values (cache-value-thunk old)
		(lambda result
		  (make-cache-value
		   (cache-value-state old)
		   values result
		   (cache-value-thunk old))))))
     (cache-value-fulfiled! cache entry (cache-value-values new))
     (set-entry-value! entry old new)
     new)))

;; Arrange to compute if needed the value and return it.

(define-inline (!cache-entry-force cache key entry)
  (thread-start!
   (make-thread
    (lambda ()
      (let* ((value (cache-entry-value entry))
	     (avail (cache-value-avail value)))
	(if (and (mutex? avail) (not (mutex-owner avail)))
	    (with-mutex
	     avail
	     (if (mutex? (cache-value-avail value))
		 (cache-entry-force! cache key entry value))))))
    (dbgname key "~a-ref"))))

;; Wait for computed value and return it.

(define (cache-entry-wait cache key entry)
  (guard
   (ex (else (if (eq? (mutex-state (cache-entry-mutex entry)) (current-thread))
		 (mutex-unlock! (cache-entry-mutex entry)))
	     (raise ex)))
   (let loop ()
     (mutex-lock! (cache-entry-mutex entry))
     (let* ((value (cache-entry-value entry))
	    (avail (cache-value-avail value)))
       (if (procedure? avail)
	   (begin
	     (mutex-unlock! (cache-entry-mutex entry))
	     (apply avail (cache-value-values value)))
	   (begin
	     (if (not (mutex-owner avail))
		 (!cache-entry-force cache key entry))
	     (mutex-unlock! (cache-entry-mutex entry) (cache-entry-avail entry))
	     (let* ((value (cache-entry-value entry))
		    (avail (cache-value-avail value)))
	       (if (procedure? avail)
		   (apply avail (cache-value-values value))
		   (loop)))))))))

(define-inline (cache-entry-ref cache key entry)
  (let* ((value (cache-entry-value entry))
	 (avail (cache-value-avail value)))
    (if (procedure? avail)
	(if ((cache-valid? cache) (cache-value-state value))
	    (begin
	      ((cache-hit cache) cache (cache-value-state value))
	      (apply avail (cache-value-values value)))
	    (begin
	      (with-mutex
	       (cache-entry-mutex entry)
	       (let* ((value (cache-entry-value entry))
		      (avail (cache-value-avail value)))
		 (if (and (procedure? avail)
			  ((cache-valid? cache) (cache-value-state value)))
		     ((cache-hit cache) cache (cache-value-state value))
		     (begin
		       ((cache-delete cache) (cache-state cache) (cache-value-state value))
		       (%set-entry-value!
			entry
			(make-cache-value
			 ((cache-miss cache) (cache-state cache) key)
			 (make-mutex (dbgname key "~a-value")) #f
			 (cache-value-thunk value)))))))
	      (cache-entry-wait cache key entry)))
	(cache-entry-wait cache key entry))))

(define-inline (cache-entry-ref/default cache key entry default)
  (let* ((value (cache-entry-value entry))
	 (avail (cache-value-avail value)))
    (if (procedure? avail)
	(if ((cache-valid? cache) (cache-value-state value))
	    (begin
	      ((cache-hit cache) cache (cache-value-state value))
	      (apply avail (cache-value-values value)))
	    (with-mutex
	     (cache-entry-mutex entry)
	     (let* ((value (cache-entry-value entry))
		    (avail (cache-value-avail value)))
	       (if (and (procedure? avail)
			((cache-valid? cache) (cache-value-state value)))
		   (begin
		     ((cache-hit cache) cache (cache-value-state value))
		     (apply avail (cache-value-values value)))
		   (begin
		     ((cache-delete cache) (cache-state cache) (cache-value-state value))
		     (%set-entry-value!
		      entry
		      (make-cache-value
		       ((cache-miss cache) (cache-state cache) key)
		       (make-mutex (dbgname key "~a-value")) #f
		       (cache-value-thunk value)))
		     (!cache-entry-force cache key entry)
		     default)))))
	(begin
	  (if (not (mutex-owner avail))
	      (!cache-entry-force cache key entry))
	  default))))

(define (cache-state-update! cache state proc)
  (with-cache-index cache (lambda () (proc (cache-state cache) state))))

(define (raise-deleted-condition message)
  (raise (make-condition &message 'message message)))

(define (cache-entry-delete! cache key entry0)
  (receive
   (entry old)
   (with-cache-index
    cache
    (lambda ()
      (let ((entry (hash-table-ref/default (cache-index cache) key #f)))
	(if entry
	    (let ((value (cache-entry-value entry0)))
	      (if (eq? entry entry0)
		  (hash-table-delete! (cache-index cache) key))
	      ((cache-delete cache) (cache-state cache) (cache-value-state value))
	      (values entry0 value))
	    (values #f #f)))))
   (if entry (set-entry-value!
	      entry old
	      (make-cache-value
	       (cache-value-state old)
	       raise
	       (list (make-condition &message 'message
				     (format "~a[~a] deleted" (cache-name cache) key)))
	       (cache-value-thunk old))))))

(define (%cache-set! cache key job . res)
  (let ((entry (cache-lookup cache key)))
    (if entry
	(let* ((value (cache-entry-value entry))
	       (new (make-cache-value
		     (cache-value-state value)
		     job res
		     (cache-value-thunk value))))
	  (with-mutex
	   (cache-entry-mutex entry)
	   (if (eq? value (cache-entry-value entry))
	       (begin
		 ((cache-delete cache) (cache-state cache) (cache-value-state value))
		 (cache-value-fulfiled! cache entry
					(and (eq? job values) (cache-value-values new)))
		 (%set-entry-value! entry new)
		 (condition-variable-broadcast! (cache-entry-avail entry))))))
	(cache-find! cache key (lambda () (apply job res))))))

;; (cache-set! cache key . job+res)
;;
;; if (null? job+res): remove entry
;; if (null? (cdr job+res)):
;;   (eq? (car job+res) #t): reset to last valid, unfulfiled last thunk
;;   (procedure? (car job+res)): reset to valid, unfilfiled (car job+res)
;; else: set to (apply (car job+res) (cdr job+res))

(define (cache-set! cache key . job+res)  
  (cond
   ((null? job+res)
    (and-let* ((entry (hash-table-ref/default (cache-index cache) key #f)))
	      ;; (!apply cache-entry-delete! (list cache key entry))
	      (cache-entry-delete! cache key entry)))
   ((eq? (car job+res) #t) (cache-invalid! cache key))
   ((null? (cdr job+res)) (cache-invalid! cache key (car job+res)))
   (else (!apply %cache-set! `(,cache ,key . ,job+res)))))

(define (cache-invalid/check! check cache key . thunk)
  (let ((entry (cache-lookup cache key)))
    (if (and entry (check entry))
	(receive
	 (entry old new)
	 (with-cache-index
	  cache
	  (lambda ()
	    (let ((entry (cache-lookup cache key)))
	      (if (and entry (check entry))
		  (let ((old (cache-entry-value entry)))
		    ((cache-delete cache) (cache-state cache) (cache-value-state old))
		    (values
		     entry old
		     (make-cache-value
		      ((cache-miss cache) (cache-state cache) key)
		      (make-mutex (dbgname key "~a-value")) #f
		      (if (pair? thunk) (car thunk) (cache-value-thunk old)))))
		  (values #f #f #f)))))
	 (if entry (set-entry-value! entry old new))
	 entry)
	(begin
	  (if (and (not entry) (pair? thunk))
	      (let ((entry (cache-find cache key (car thunk))))
		(cache-entry-ref/default cache key entry #f)
		entry)
	      entry)))))

;; (cache-invalid! cache key . thunk)
;;
;; Invalidate the cached values.  If there's a running computation,
;; leave it running.  If thunk is given, it's arranged to be called,
;; otherwise default is returned.

(define (cache-invalid! cache key . thunk)
  (define (check entry)
    (and-let* ((val (cache-entry-value entry))
	       (avail (cache-value-avail val))
	       ((not (mutex? avail))))
	      ((cache-valid? cache) (cache-value-state val))))
  (apply cache-invalid/check! check cache key thunk))

;; (cache-invalid/abort! cache key . thunk)
;;
;; Invalidate the cached values and abort any running computation. If
;; thunk is given, it's arranged to be called, otherwise default is
;; returned.

(define (cache-invalid/abort! cache key . thunk)
  (define (check entry)
    (and-let* ((val (cache-entry-value entry))
	       (avail (cache-value-avail val)))
	      (or (mutex? avail)
		  ((cache-valid? cache) (cache-value-state val)))))
  (apply cache-invalid/check! check cache key thunk))

;; (cache-ref/default cache key thunk default)
;;
;; Returns current cached value or default.  Does never wait.  If
;; thunk is given, it's arranged to be called, otherwise default is
;; returned.

(define (cache-ref/default cache key thunk default)
  (if thunk
      (cache-entry-ref/default cache key (cache-find cache key thunk) default)
      (let ((entry (cache-lookup cache key)))
	(if entry (cache-entry-ref/default cache key entry default) default))))

;; (cache-ref cache key thunk . default)
;;
;; If default is given falls back to cache-ref/default.  Otherwise
;; returns the last valid cached values.  Always waits for valid
;; values, possibly arranging thunk to produce them.

(define (cache-ref cache key thunk . default)
  (if (pair? default)
      (cache-ref/default cache key thunk (car default))
      (cache-entry-ref cache key (cache-find cache key thunk))))

(define (cache-reref cache key thunk)
  (cache-entry-ref cache key (cache-invalid! cache key thunk)))

;; (cache-fold cache f nil)
;;
;; fold f(key value nil) over cache content

(define (cache-fold cache f nil)
  (hash-table-fold (cache-index cache)
		   (lambda (k v nil)
		     (if (eq? (cache-value-avail (cache-entry-value v)) values)
			 (f k (car (cache-value-values (cache-entry-value v))) nil)
			 nil))
		   nil))

;; (cache-cleanup! cache [valid?] [used?])
;;
;; valid? : default: (cache-valid cache)
;; used?  : no default; of cache result values arity.
;;
;; Remove all entries, which are not "valid?" and "used?" (if given).
;; Used is applied to the cached values.

(define (cache-cleanup! cache . predicates)
  (let ((valid? (if (and (pair? predicates) (procedure? (car predicates)))
		    (car predicates) (cache-valid? cache)))
	(used? (if (and (pair? predicates) (pair? (cdr predicates)))
		   (cadr predicates) #t))
	(del (cache-delete cache)))
    (for-each
     (lambda (key+entry)
       (define key (car key+entry))
       (define entry (cdr key+entry))
       ;; This appears to be questionable.  We first signal
       ;; the evaluation of an uncached thunk, maybe for no good reason -??-
       ;; then we remove the entry.
       ;;  Questionable: there where pending references when forcing entries.
       ;;    and no good explanation...
       (define value (cache-entry-value entry))
       (if (not (procedure? (cache-value-avail value)))
	   (set-entry-value!
	    entry value
	    (make-cache-value
	     (cache-value-state value)
	     raise
	     (list (make-condition
		    &message 'message
		    (format "~a[~a] deleted in cleanup" (cache-name cache) key)))
	     (cache-value-thunk value)))))
     (with-cache-index
      cache
      (lambda ()
	(let* ((index (cache-index cache))
	       (removable (hash-table-fold
			   index
			   (lambda (key entry init)
			     (if (let ((value (cache-entry-value entry)))
				   (or (mutex? (cache-value-avail value))
				       (and (valid? (cache-value-state value))
					    (or (eq? used? #t)
						(apply used? (cache-value-values value))))))
				 init
				 (begin
				   (guard
				    (ex (else (log-condition (cache-name cache) ex)))
				    (del (cache-state cache) (cache-value-state (cache-entry-value entry))))
				   (cons (cons key entry) init))))
			   '())))
	  (for-each
	   (lambda (key+entry)
	     (hash-table-delete! (cache-index cache) (car key+entry)))
	   removable)
	  removable))))))
_______________________________________________
Chicken-users mailing list
[email protected]
http://lists.nongnu.org/mailman/listinfo/chicken-users

Reply via email to