aprs.me/test/aprsme/is/is_backpressure_test.exs
Graham McIntire fa89c6d3ca
Add TCP backpressure to packet pipeline
When the PacketProducer buffer crosses 80% capacity, signal Aprsme.Is
to switch its TCP socket to passive mode, leveraging TCP flow control
to slow APRS-IS intake. Resume active mode when buffer drains below 30%.

A 30-second safety valve timer auto-resumes if backpressure gets stuck.
Disconnect/reconnect/terminate paths all clear backpressure state.
2026-02-19 15:31:51 -06:00

96 lines
2.7 KiB
Elixir

defmodule Aprsme.Is.BackpressureTest do
use ExUnit.Case, async: true
alias Aprsme.Is
# These tests call handle_info directly with constructed state,
# so they don't need a running GenServer or real socket.
defp base_state(overrides \\ []) do
defaults = %{
server: "test.aprs2.net",
port: 14_580,
socket: nil,
timer: nil,
keepalive_timer: nil,
connected_at: DateTime.utc_now(),
packet_stats: %{
total_packets: 0,
last_packet_at: nil,
packets_per_second: 0,
last_second_count: 0,
last_second_timestamp: System.system_time(:second)
},
buffer: "",
login_params: %{
user_id: "TEST",
passcode: "-1",
filter: "r/33/-96/100"
},
backpressure_active: false,
safety_valve_timer: nil
}
Enum.reduce(overrides, defaults, fn {k, v}, acc -> Map.put(acc, k, v) end)
end
describe "handle_info {:backpressure, :activate}" do
test "no-op when socket is nil" do
state = base_state()
{:noreply, new_state} = Is.handle_info({:backpressure, :activate}, state)
assert new_state.backpressure_active == false
assert new_state.safety_valve_timer == nil
end
test "no-op when already active" do
timer_ref = make_ref()
state = base_state(backpressure_active: true, safety_valve_timer: timer_ref)
{:noreply, new_state} = Is.handle_info({:backpressure, :activate}, state)
# State unchanged
assert new_state.backpressure_active == true
assert new_state.safety_valve_timer == timer_ref
end
end
describe "handle_info {:backpressure, :deactivate}" do
test "no-op when not active" do
state = base_state()
{:noreply, new_state} = Is.handle_info({:backpressure, :deactivate}, state)
assert new_state.backpressure_active == false
end
test "clears flag when socket is nil and active" do
state = base_state(backpressure_active: true, safety_valve_timer: make_ref())
{:noreply, new_state} = Is.handle_info({:backpressure, :deactivate}, state)
assert new_state.backpressure_active == false
assert new_state.safety_valve_timer == nil
end
end
describe "handle_info :backpressure_safety_valve" do
test "no-op when not active" do
state = base_state()
{:noreply, new_state} = Is.handle_info(:backpressure_safety_valve, state)
assert new_state.backpressure_active == false
end
test "clears state when socket is nil" do
state = base_state(backpressure_active: true, safety_valve_timer: make_ref())
{:noreply, new_state} = Is.handle_info(:backpressure_safety_valve, state)
assert new_state.backpressure_active == false
assert new_state.safety_valve_timer == nil
end
end
end