Actor | Sent | Received | Send Rate | Receive Rate | Activity |
---|---|---|---|---|---|
receiver | 1 | 10 | 55.56 msg/s | 555.56 msg/s | 11 total |
sender | 10 | 1 | 555.56 msg/s | 55.56 msg/s | 11 total |
This is the Elixir code that defines the actor simulation model:
# Quiescence example: sender emits 10 messages, receiver batches and replies once
# Termination: :quiescence (no timers left), with long timeout (60 seconds)
# Minimal delays (1ms each) to advance virtual time and show message flow
defmodule BatchReceiver do
use VirtualTimeGenServer
# args: {sender_name, expected_count}
def init({sender, expected}), do: {:ok, %{sender: sender, expected: expected, received: []}}
def handle_cast({:msg, i}, state) do
new_received = [i | state.received]
if length(new_received) == state.expected do
VirtualTimeGenServer.cast(state.sender, {:batch, Enum.reverse(new_received)})
{:noreply, %{state | received: []}}
else
{:noreply, %{state | received: new_received}}
end
end
end
defmodule BatchSender do
use VirtualTimeGenServer
def init(receiver), do: {:ok, %{receiver: receiver, batch: nil}}
def handle_info(:start, state) do
# Schedule messages with tiny delays (1ms each) to advance virtual time
Enum.each(1..10, fn i ->
VirtualTimeGenServer.send_after(self(), {:send_msg, i}, i)
end)
{:noreply, state}
end
def handle_info({:send_msg, i}, state) do
VirtualTimeGenServer.cast(state.receiver, {:msg, i})
{:noreply, state}
end
def handle_cast({:batch, list}, state), do: {:noreply, %{state | batch: list}}
end
simulation =
ActorSimulation.new(trace: true)
|> ActorSimulation.add_process(:receiver, module: BatchReceiver, args: {:sender, 10})
|> ActorSimulation.add_process(:sender, module: BatchSender, args: :receiver)
# Kick off sending with minimal delay to advance virtual time
VirtualTimeGenServer.send_after(simulation.actors[:sender].pid, :start, 1)
# Run until quiescence (no timers left), up to 60s max virtual time
simulation =
ActorSimulation.run(simulation,
max_duration: 60_000,
terminate_when: :quiescence
)