I'm out of my depth here, but this patch seems to resolve my problem. It looks in the cache before & after the (through*) swap, picks the "after" value if non-nil and "before" value if the "after" value is nil.

There are a few benefits: less code, only expiration forces you to pay for an additional cache lookup, and it seems like it might be preferable semantically (you don't return a known-to-be-expired value).

A downside is that you may do multiple cache writes if the ttl is low enough (not sure if you can do sub-millisecond ttls?) or the thread scheduling is wonky enough to make that happen. But retries can happen with swap! regardless, so I'm not sure this drawback is significant.

Isn't the problem the TTL cache impl itself? CacheProtocol is purely functional but TTLCache is not (has? or lookup are time sensitive). Cache-invalidation on write (rather than on read) may be a solution that wouldn't require changing the protocol.

Maybe I'm just being dense, but I'd personally be surprised if an implementation of CacheProtocol's lookup fn gave me back a value that should be expired according to the TTL. Imagine if your TTL was 30 seconds, and you did a read 10 minutes after your last write - calling lookup and getting a 10-minute-old value seems problematic.

I'm trying still trying to reproduce it, but I noted this in my logs if my compojure app on a subsequent request to the memoized function.

java.lang.NullPointerException
at clojure.core.memoize$through_STAR_$fn_401$fn_402.invoke(memoize.clj:53)
at clojure.lang.Delay.deref(Delay.java:33)
at clojure.core$deref.invoke(core.clj:2128)
at clojure.core.memoize$build_memoizer$fn__456.doInvoke(memoize.clj:140)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.lang.AFunction$1.doInvoke(AFunction.java:29)
at clojure.lang.RestFn.invoke(RestFn.java:436)

If you call (m) the first time, you get a divide by zero exception as expected. If you call it again you'll get an NPE until the TTL expires (in 5 seconds in this case) at which point you'll get one correct error and then more NPEs.

Note that if you update to Clojure 1.6 the NPEs go away, but the memoizer caches the exception as the result of the function. This is fine for pure functions but not for what we need, so we just wrote a wrapper around memoize/ttl to catch exceptions and evict the cache before rethrowing them. It's ugly but it works good enough.

core.memoize doesn't retry if the orginal function throws an exception.
This is different to clojure.core/memoize, and looks like it's a result of using delays in core.memoize.
I think the core/memoize behaviour makes more sense - if an exception is thrown do not memoize the result, instead retry the function.
Example below.

In order to be analogous to atoms, the current behavior of memo-swap! should be called memo-reset! since it overwrites the entire memo map. Similarly, there should be a function called memo-swap! that does an atomic swap operation on the memo cache so, for example, you can add a single value to the cache.

core.memoize was originally Unk , but its inclusion into contrib means the latter is redundant. The Unk repo should indicate the move and change to a place for docs and examples. Likewise, a formal announcement should be made.

The first call to .cache makes sense, since PluggableMemoization has a 'cache' field, but the second call to .cache assumes that any implementation of CacheProtocol that PluggableMemoization wraps will have a 'cache' field.

I ran into this issue when I desired to have a cache that ignores nils, so I attempted to wrap the TTLCache with an implementation of CacheProtocol that would not cache nil values. Calls to snapshot failed since it was trying to access a 'cache' field on my NoNilCache.

I suspect that the outer call to .cache could be replaced with a call to seq, since defcache adds an implementation of seq to each cache implementation which should pull out the keys/values from the cache.

I'd be glad to work up a patch given that I'm not off base in my assumptions.