aprs.me/lib/aprsme/regex_cache.ex
Graham McIntire 288b9fbbb2
fix: address security vulnerabilities and concurrency issues
- Fix SQL injection in partition_manager, db_optimizer, and release.ex
- Fix XSS vulnerabilities with proper HTML escaping in LiveViews
- Add proper error handling for email delivery functions
- Fix race conditions with advisory locks and atomic operations
- Replace unsupervised spawn/Task.start with supervised alternatives
- Convert ETS operations to GenServer serialization for thread safety
- Change ETS tables from :public to :protected access
- Add client limits to prevent unbounded memory growth
- Add PubSub cleanup in GenServer terminate callbacks
- Fix device upsert to use atomic Repo.insert_all
2026-03-23 16:59:09 -05:00

71 lines
1.7 KiB
Elixir

defmodule Aprsme.RegexCache do
@moduledoc """
A simple in-memory cache for compiled regex patterns to avoid recompilation.
Uses ETS for thread-safe access.
"""
use GenServer
require Logger
@table_name :regex_cache
@max_cache_size 1000
def start_link(_opts) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
@impl true
def init(_) do
# Create ETS table for caching compiled regexes
:ets.new(@table_name, [:set, :public, :named_table, read_concurrency: true])
{:ok, %{}}
end
@doc """
Get or compile a regex pattern. Returns {:ok, regex} or {:error, reason}.
"""
def get_or_compile(pattern_string) do
GenServer.call(__MODULE__, {:get_or_compile, pattern_string})
end
@impl true
def handle_call({:get_or_compile, pattern_string}, _from, state) do
result =
case :ets.lookup(@table_name, pattern_string) do
[{^pattern_string, regex}] ->
{:ok, regex}
[] ->
compile_and_cache(pattern_string)
end
{:reply, result, state}
end
defp compile_and_cache(pattern_string) do
case Regex.compile(pattern_string) do
{:ok, regex} ->
if :ets.info(@table_name, :size) >= @max_cache_size do
clear_oldest_entries()
end
:ets.insert(@table_name, {pattern_string, regex})
{:ok, regex}
error ->
error
end
end
defp clear_oldest_entries do
# Simple strategy: clear half the cache
# In production, you might want LRU eviction
entries = :ets.tab2list(@table_name)
to_remove = Enum.take(entries, div(length(entries), 2))
Enum.each(to_remove, fn {key, _} ->
:ets.delete(@table_name, key)
end)
end
end