• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

whitfin / cachex / 64ffdb79131a3eb9e15e483ea81eedd63160a005-PR-426

30 Oct 2025 04:39AM UTC coverage: 99.042% (-1.0%) from 100.0%
64ffdb79131a3eb9e15e483ea81eedd63160a005-PR-426

Pull #426

github

whitfin
Remove tagging from inspection and commands
Pull Request #426: Simplify and naturalize API signatures and return types

68 of 74 new or added lines in 25 files covered. (91.89%)

2 existing lines in 2 files now uncovered.

827 of 835 relevant lines covered (99.04%)

1054.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.0
/lib/cachex/actions/prune.ex
1
defmodule Cachex.Actions.Prune do
2
  @moduledoc false
3
  # Command module to allow pruning a cache to a maximum size.
4
  #
5
  # This command will trigger an LRW-style pruning of a cache based on
6
  # the provided maximum value. Various controls are provided on how to
7
  # exactly prune the table.
8
  #
9
  # This command is used by the various limit hooks provided by Cachex.
10
  alias Cachex.Query
11
  alias Cachex.Services.Informant
12

13
  # add required imports
14
  import Cachex.Spec
15

16
  # compile our match to avoid recalculating
17
  @query Query.build(output: {:key, :modified})
18

19
  ##############
20
  # Public API #
21
  ##############
22

23
  @doc """
24
  Prunes cache keyspsace to the provided amount.
25

26
  This function will enforce cache bounds using a least recently written (LRW)
27
  eviction policy. It will trigger a Janitor purge to clear expired records
28
  before attempting to trim older cache entries.
29
  """
30
  def execute(cache() = cache, size, options) do
31
    buffer =
106✔
32
      case Keyword.get(options, :buffer, 100) do
33
        val when val < 0 -> 100
1✔
34
        val -> val
105✔
35
      end
36

37
    reclaim = Keyword.get(options, :reclaim, 0.1)
106✔
38
    reclaim_bound = round(size * reclaim)
106✔
39

40
    case Cachex.size(cache, const(:local) ++ const(:notify_false)) do
106✔
41
      cache_size when cache_size <= size ->
42
        notify_worker(0, cache)
101✔
43

44
      cache_size ->
45
        cache_size
46
        |> calculate_reclaim(size, reclaim_bound)
47
        |> calculate_poffset(cache)
48
        |> erase_lower_bound(cache, buffer)
49
        |> notify_worker(cache)
5✔
50
    end
51

52
    true
53
  end
54

55
  ###############
56
  # Private API #
57
  ###############
58

59
  # Calculates the space to reclaim inside a cache.
60
  #
61
  # This is a function of the maximum cache size, the reclaim bound and the
62
  # current size of the cache. A positive result from this function means that
63
  # we need to carry out evictions, whereas a negative results means that the
64
  # cache is currently underpopulated.
65
  defp calculate_reclaim(current_size, size, reclaim_bound),
66
    do: (size - reclaim_bound - current_size) * -1
5✔
67

68
  # Calculates the purge offset of a cache.
69
  #
70
  # Basically this means that if the cache is overpopulated, we would trigger
71
  # a Janitor purge to see if expirations bring the cache back under the max
72
  # size limits. The resulting amount of removed records is then offset against
73
  # the reclaim space, meaning that a positive result require us to carry out
74
  # further evictions manually down the chain.
75
  defp calculate_poffset(reclaim_space, cache) when reclaim_space > 0,
76
    do: reclaim_space - Cachex.purge(cache, const(:local))
5✔
77

78
  # Erases the least recently written records up to the offset limit.
79
  #
80
  # If the provided offset is not positive we don't do anything as it signals that
81
  # the cache is already within the correctly sized limits so we just pass through
82
  # as a no-op.
83
  #
84
  # In the case the offset is positive, it represents the number of entries we need
85
  # to remove from the cache table. We do this by traversing the underlying ETS table,
86
  # which only selects the key and touch time as a minor optimization. The key is
87
  # naturally required when it comes to removing the document, and the touch time is
88
  # used to determine the sort order required for LRW.
89
  defp erase_lower_bound(offset, cache, buffer) when offset > 0 do
90
    options =
4✔
91
      :local
92
      |> const()
93
      |> Enum.concat(const(:notify_false))
94
      |> Enum.concat(buffer: buffer)
95

96
    case Cachex.stream(cache, @query, options) do
4✔
97
      {:error, _reason} = error ->
NEW
98
        error
×
99

100
      stream ->
101
        cache(name: name) = cache
4✔
102

103
        stream
104
        |> Enum.sort(fn {_k1, t1}, {_k2, t2} -> t1 < t2 end)
1,743✔
105
        |> Enum.take(offset)
106
        |> Enum.each(fn {k, _t} -> :ets.delete(name, k) end)
4✔
107

108
        offset
4✔
109
    end
110
  end
111

112
  defp erase_lower_bound(offset, _state, _buffer),
113
    do: offset
1✔
114

115
  # Broadcasts the number of removed entries to the cache hooks.
116
  #
117
  # If the offset is not positive we didn't have to remove anything and so we
118
  # don't broadcast any results. An 0 Tuple is returned just to keep compatibility
119
  # with the response type from `Informant.broadcast/3`.
120
  #
121
  # It should be noted that we use a `:clear` action here as these evictions are
122
  # based on size and not on expiration. The evictions done during the purge earlier
123
  # in the pipeline are reported separately and we're only reporting the delta at this
124
  # point in time. Therefore remember that it's important that we're ignoring the
125
  # results of `clear()` and `purge()` calls in this hook, otherwise we would end
126
  # up in a recursive loop due to the hook system.
127
  defp notify_worker(offset, state) when offset > 0,
128
    do: Informant.broadcast(state, {:clear, [[]]}, offset)
4✔
129

130
  defp notify_worker(_offset, _state),
102✔
131
    do: :ok
132
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc