with-cache
with-cache
1 Parameters
*use-cache?*
*with-cache-fasl?*
*current-cache-directory*
*current-cache-keys*
*keys-equal?*
2 Utilities
cachefile
parent-directory-exists?
equivalence/  c
get-with-cache-version
with-cache-logger
8.12

with-cache🔗ℹ

 (require with-cache) package: with-cache

Simple, filesystem-based caching. Wrap your large computations in a thunk and let with-cache deal with saving and retrieving the result.

(define fish
  (with-cache (cachefile "stdfish.rktd")
    (λ () (standard-fish 100 50))))

By default, any cache built by an older version of with-cache is invalid. Set *current-cache-keys* to override this default.

Here’s a diagram of what’s happening in with-cache:

image

The with-cache function implements this pipeline and provides hooks for controlling the interesting parts.

procedure

(with-cache cache-path    
  thunk    
  [#:read read-proc    
  #:write write-proc    
  #:use-cache? use-cache?    
  #:fasl? fasl?    
  #:keys keys    
  #:keys-equal? keys-equal?])  any
  cache-path : path-string?
  thunk : (-> any)
  read-proc : (-> any/c any) = deserialize
  write-proc : (-> any/c any) = serialize
  use-cache? : boolean? = (*use-cache?*)
  fasl? : boolean? = (*with-cache-fasl?*)
  keys : (or/c #f (listof (or/c parameter? (-> any/c))))
   = (*current-cache-keys*)
  keys-equal? : equivalence/c = (*keys-equal?*)
If cache-path exists:
  1. reads the contents of cache-path (using s-exp->fasl if *with-cache-fasl?* is #t and read otherwise);

  2. checks whether the result contains keys equal to *current-cache-keys*, where "equal" is determined by keys-equal?;

  3. if so, removes the keys and deserializes a value.

If cache-path does not exist or contains invalid data:
  1. executes thunk, obtains result r;

  2. retrieves the values of *current-cache-keys*;

  3. saves the keys and r to cache-path;

  4. returns r

Uses call-with-file-lock/timeout to protect concurrent reads and writes to the same cache-path. If a thread fails to lock cache-path, with-cache throws an exception (exn:fail:filesystem) giving the location of the problematic lock file. All lock files are generated by make-lock-file-name and stored in (find-system-path 'temp-dir).

Diagnostic information is logged under the with-cache topic. To see logging information, use either:

racket -W with-cache <file.rkt>

or, if you are not invoking racket directly:

PLTSTDERR="error info@with-cache" <CMD>

1 Parameters🔗ℹ

parameter

(*use-cache?*)  boolean?

(*use-cache?* use-cache?)  void?
  use-cache? : boolean?
 = #t
Parameter for disabling with-cache. When #f, with-cache will not read or write any cachefiles.

parameter

(*with-cache-fasl?*)  boolean?

(*with-cache-fasl?* fasl?)  void?
  fasl? : boolean?
 = #t
When #t, write files in fasl format. Otherwise, write files with write.

Note that byte strings written using s-exp->fasl cannot be read by code running a different version of Racket.

parameter

(*current-cache-directory*)

  (and/c path-string? directory-exists?)
(*current-cache-directory* cache-dir)  void?
  cache-dir : (and/c path-string? directory-exists?)
 = (build-path (current-directory) "compiled" "with-cache")
The value of this parameter is the prefix of paths returned by cachefile. Another good default is (find-system-path 'temp-dir). See also the XDG Basedir Library.

parameter

(*current-cache-keys*)

  (or/c #f (listof (or/c parameter? (-> any/c))))
(*current-cache-keys* params)  void?
  params : (or/c #f (listof (or/c parameter? (-> any/c))))
 = (list get-with-cache-version)
List of parameters (or thunks) used to check when a cache is invalid.

Before writing a cache file, with-cache gets the value of *current-cache-keys* (by taking the value of the parameters and forcing the thunks) and writes the result to the file. When reading a cache file, with-cache gets the current value of *current-cache-keys* and compares this value to the value written in the cache file. If the current keys are NOT equal to the old keys (equal in the sense of *keys-equal?*), then the cache is invalid.

For example, (*current-cache-keys* (list current-seconds)) causes with-cache to ignore cachefiles written more than 1 second ago.

(define (fresh-fish)
  (parameterize ([*current-cache-keys* (list current-seconds)])
    (with-cache (cachefile "stdfish.rktd")
      (λ () (standard-fish 100 50)))))
 
(fresh-fish) ; Writes to "compiled/with-cache/stdfish.rktd"
(fresh-fish) ; Reads from "compiled/with-cache/stdfish.rktd"
(sleep 1)
(fresh-fish) ; Writes to "compiled/with-cache/stdfish.rktd"

By default, the only key is a thunk that retrieves the installed version of the with-cache package.

parameter

(*keys-equal?*)  equivalence/c

(*keys-equal?* =?)  void?
  =? : equivalence/c
 = equal?
Used to check whether a cache file is invalid.

A cache is invalid if (=? old-keys current-keys) returns #false, where current-keys is the current value of *current-cache-keys*.

By convention, the function bound to =? should be an equivalence, meaning it obeys the following 3 laws:
  • (=? k k) returns a true value for all k;

  • (=? k1 k2) returns the same value as (=? k2 k1); and

  • (and (=? k1 k2) (=? k2 k3)) implies (=? k1 k3) is true.

The contract equivalence/c does not enforce these laws, but it might in the future.

2 Utilities🔗ℹ

procedure

(cachefile filename)  parent-directory-exists?

  filename : path-string?
Prefix filename with the value of *current-cache-directory*. By contract, this function returns only paths whose parent directory exists.

procedure

(parent-directory-exists? x)  boolean?

  x : any/c
Flat contract that checks whether (path-only x) exists.

procedure

(equivalence/c x)  boolean?

  x : any/c
Flat contract for functions that implement equivalence relations.

Return the current version of with-cache.

A logger that reports events from the with-cache library. Logs 'info events when reading or writing caches and 'error events after detecting corrupted cache files.