Skip to content

Instantly share code, notes, and snippets.

@tk3369
Created April 28, 2021 06:18
Show Gist options
  • Save tk3369/b1ff550e5ae11786643cc4beae23292b to your computer and use it in GitHub Desktop.
Save tk3369/b1ff550e5ae11786643cc4beae23292b to your computer and use it in GitHub Desktop.

I built a cache that invalidates based on expiration time but then I realized that ExpiringCaches.jl (and few others) exist :-p

Just capturing what I did:

"""
    TimedCache

A time-based cache. To construct a new cache, provie the types for key and value
and a retention period. It is lazy - when a cache entry expires, it is not removed
until the cache is accessed again.
"""
struct TimedCache{K,V}
    data::Dict{K,V}
    registry::Dict{K,DateTime}
    retention::TimePeriod
end

function TimedCache{K,V}(retention::TimePeriod) where {K,V}
    return TimedCache(Dict{K,V}(), Dict{K,DateTime}(), retention)
end

function Base.show(io::IO, c::TimedCache{K,V}) where {K,V}
    print(io, "TimedCache{", K, ",", V, "}(size=", length(c.data), ", retention=", c.retention, ")")
end

for f in (:length, :keys, :values)
    @eval function Base.$f(c::TimedCache)
        clean(c)
        $f(c.data)
    end
end

function Base.getindex(c::TimedCache{K,V}, key::K) where {K,V}
    clean(c)
    return c.data[key]
end

function Base.setindex!(c::TimedCache{K,V}, val::V, key::K) where {K,V}
    clean(c)
    c.data[key] = val
    c.registry[key] = now()
    return val
end


"""
    Base.get!(f::Function, c::TimedCache, key)

Return the value stored in the cache for the given `key`. If `key` is not found in the
cache or if the entry has already expired according to the retention specified in `c`,
then the function `f` is executed and the new value is stored in cache.
"""
function Base.get!(f::Function, c::TimedCache, key)
    clean(c)
    return get!(c.data, key) do
        value = f()
        c.data[key] = value
        c.registry[key] = now()
        value
    end
end

"""
    clean(::TimedCache)

Clean the cache by removing expired entries.
"""
function clean(c::TimedCache)
    current_time = now()
    stale_keys = [k for (k, dt) in c.registry if dt + c.retention < current_time]
    foreach(k -> delete!(c.data, k), stale_keys)
    foreach(k -> delete!(c.registry, k), stale_keys)
end

Test:

using Test
using Dates
using HoJBot: TimedCache

@testset "Cache" begin

    # Test get!
    retention = 1
    tc = TimedCache{Int,String}(Second(retention))
    get!(tc, 1) do; "hey"; end
    @test length(tc) == 1
    sleep(retention)
    @test length(tc) == 0

    # Test keys, values, getindex, setindex!
    tc[1] = "hey"
    @test tc[1] == "hey"
    @test collect(keys(tc)) == [1]
    @test collect(values(tc)) == ["hey"]
    sleep(retention)
    @test_throws KeyError tc[1]
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment