@spec do_upsert(db :: module(), key :: String.t(), value :: any(), fun :: fun()) :: true
defp do_upsert(db, key, value, fun) do
+ get_lock(db, key)
+
case :ets.lookup(db, key) do
[] ->
v =
[_] ->
:ets.insert(db, {key, [value]})
end
+
+ release_lock(db, key)
end
@spec do_update_if(db :: module(), key :: String.t(), value :: any(), fun :: fun()) ::
:updated | :noop
defp do_update_if(db, key, value, fun) do
+ get_lock(db, key)
+
case :ets.lookup(db, key) do
[] ->
v =
end
:ets.insert(db, {key, v})
+ release_lock(db, key)
:updated
[{_, val} = old] when is_list(val) ->
case fun.(old, value) do
{:update, new} ->
:ets.insert(db, new)
+ release_lock(db, key)
:updated
_ ->
+ release_lock(db, key)
:noop
end
[_] ->
:ets.insert(db, {key, [value]})
+ release_lock(db, key)
:updated
end
end
do_get(db, key)
end)
end
+
+ defp release_lock(db, key) do
+ :ets.delete(db, "#{key}:lock")
+ end
+
+ defp get_lock(db, key) do
+ key = "#{key}:lock"
+ do_get_lock(db, key)
+ end
+
+ defp do_get_lock(db, key) do
+ case :ets.insert_new(db, {key, true}) do
+ false -> do_get_lock(db, key)
+ true -> true
+ end
+ end
end