Skuld.Effects.AtomicState (skuld v0.2.3)
View SourceAtomicState effect - thread-safe state for concurrent contexts.
Unlike the regular State effect which stores state in env.state (copied when
forking to new processes), AtomicState uses external storage (Agent) that can
be safely accessed from multiple processes.
Supports both simple single-state usage and multiple independent states via tags.
Handlers
AtomicState.Agent- Agent-backed handler for production (true atomic ops)AtomicState.Sync- State-backed handler for testing (no Agent processes)
Production Usage (Agent handler)
use Skuld.Syntax
alias Skuld.Effects.AtomicState
comp do
_ <- AtomicState.put(0)
_ <- AtomicState.modify(&(&1 + 1))
value <- AtomicState.get()
value
end
|> AtomicState.Agent.with_handler(0)
|> Comp.run!()
#=> 1Multiple States (explicit tags)
comp do
_ <- AtomicState.put(:counter, 0)
_ <- AtomicState.modify(:counter, &(&1 + 1))
count <- AtomicState.get(:counter)
_ <- AtomicState.put(:cache, %{})
_ <- AtomicState.modify(:cache, &Map.put(&1, :key, "value"))
cache <- AtomicState.get(:cache)
{count, cache}
end
|> AtomicState.Agent.with_handler(0, tag: :counter)
|> AtomicState.Agent.with_handler(%{}, tag: :cache)
|> Comp.run!()
#=> {1, %{key: "value"}}Compare-and-Swap (CAS)
comp do
_ <- AtomicState.put(10)
result1 <- AtomicState.cas(10, 20) # succeeds
result2 <- AtomicState.cas(10, 30) # fails - current is 20, not 10
{result1, result2}
end
|> AtomicState.Agent.with_handler(0)
|> Comp.run!()
#=> {:ok, {:conflict, 20}}Testing (Sync handler)
For testing without spinning up Agents, use the Sync handler:
comp do
_ <- AtomicState.put(0)
_ <- AtomicState.modify(&(&1 + 1))
AtomicState.get()
end
|> AtomicState.Sync.with_handler(0)
|> Comp.run!()
#=> 1
Summary
Functions
Install AtomicState handler via catch clause syntax.
Returns the env.state key used for storing the Agent pid for a given tag.
Atomically modify the state with a function that returns {result, new_state}.
Compare-and-swap: atomically replace state if it equals expected value.
Atomically read the current state.
Atomically modify the state with a function, returning the new value.
Atomically replace the state.
Returns the env.state key used for State-backed storage for a given tag.
Install an Agent-backed AtomicState handler.
Install a State-backed AtomicState handler for testing.
Functions
Install AtomicState handler via catch clause syntax.
Config selects handler type:
catch
AtomicState -> {:agent, 0} # agent handler
AtomicState -> {:agent, {0, tag: :counter}} # agent with opts
AtomicState -> {:sync, 0} # sync handler
AtomicState -> {:sync, {0, tag: :counter}} # sync with opts
Returns the env.state key used for storing the Agent pid for a given tag.
@spec atomic_state((term() -> {term(), term()})) :: Skuld.Comp.Types.computation()
Atomically modify the state with a function that returns {result, new_state}.
Returns the result value.
Examples
AtomicState.atomic_state(fn s -> {:popped, s - 1} end)
AtomicState.atomic_state(:counter, fn s -> {s, s + 1} end)
@spec atomic_state(atom(), (term() -> {term(), term()})) :: Skuld.Comp.Types.computation()
@spec cas(term(), term()) :: Skuld.Comp.Types.computation()
Compare-and-swap: atomically replace state if it equals expected value.
Returns :ok if swap succeeded, {:conflict, current_value} if it failed.
Examples
AtomicState.cas(10, 20) # if state == 10, set to 20
AtomicState.cas(:counter, 10, 20) # with explicit tag
@spec cas(atom(), term(), term()) :: Skuld.Comp.Types.computation()
@spec get(atom()) :: Skuld.Comp.Types.computation()
Atomically read the current state.
Examples
AtomicState.get() # use default tag
AtomicState.get(:counter) # use explicit tag
@spec modify((term() -> term())) :: Skuld.Comp.Types.computation()
Atomically modify the state with a function, returning the new value.
Examples
AtomicState.modify(&(&1 + 1)) # use default tag
AtomicState.modify(:counter, &(&1 + 1)) # use explicit tag
@spec modify(atom(), (term() -> term())) :: Skuld.Comp.Types.computation()
@spec put(term()) :: Skuld.Comp.Types.computation()
Atomically replace the state.
Examples
AtomicState.put(42) # use default tag
AtomicState.put(:counter, 42) # use explicit tag
@spec put(atom(), term()) :: Skuld.Comp.Types.computation()
Returns the env.state key used for State-backed storage for a given tag.
@spec with_agent_handler(Skuld.Comp.Types.computation(), term(), keyword()) :: Skuld.Comp.Types.computation()
Install an Agent-backed AtomicState handler.
Delegates to AtomicState.Agent.with_handler/3.
@spec with_state_handler(Skuld.Comp.Types.computation(), term(), keyword()) :: Skuld.Comp.Types.computation()
Install a State-backed AtomicState handler for testing.
Delegates to AtomicState.Sync.with_handler/3.