Skip to content

put_newer crashes with "not a valid match specification" when the key contains a map #18

@dfalling

Description

@dfalling

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions