When PartitionedBuffer.Map.put_newer/4 is called twice for the same key within one buffer flush interval and that key contains a map, the second call raises:
** (ArgumentError) errors were found at the given arguments:
* 2nd argument: not a valid match specification
(stdlib 7.2) :ets.select_replace/2
(partitioned_buffer 0.4.2) lib/partitioned_buffer/partition.ex:138
Root cause
In replace_match_spec/3 (lib/partitioned_buffer/partition.ex), the match head embeds key raw, while the body wraps it via ms_literal/1:
[
{
entry(key: key, value: :_, version: :"$1", updates: :"$2"), # head — raw key
[{:>, version, :"$1"}],
[
{entry(
key: ms_literal(key), # body — wrapped key (maps → {:const, map})
...
)}
]
}
]
If key is {:foo, [%{type: :bar}]}, the head contains a raw %{type: :bar}, which ETS does not accept in match-spec heads — raw maps are not matchable. The spec is rejected before any matching happens.
Example
{:ok, _} = PartitionedBuffer.Map.start_link(name: :buf, processor: fn _ -> :ok end)
key = {:user, %{id: 1}}
:ok = PartitionedBuffer.Map.put_newer(:buf, key, "v1", 1)
:ok = PartitionedBuffer.Map.put_newer(:buf, key, "v2", 2) # crashes
The first call goes through :ets.insert_new/2 (succeeds). The second falls into the select_replace branch with the bad spec.
Workaround
We're working around it by hashing the map portion of our cache keys before storing.
When
PartitionedBuffer.Map.put_newer/4is called twice for the same key within one buffer flush interval and that key contains a map, the second call raises:Root cause
In
replace_match_spec/3(lib/partitioned_buffer/partition.ex), the match head embedskeyraw, while the body wraps it viams_literal/1:If
keyis{:foo, [%{type: :bar}]}, the head contains a raw%{type: :bar}, which ETS does not accept in match-spec heads — raw maps are not matchable. The spec is rejected before any matching happens.Example
The first call goes through
:ets.insert_new/2(succeeds). The second falls into theselect_replacebranch with the bad spec.Workaround
We're working around it by hashing the map portion of our cache keys before storing.