- State the invariant the plan cache relies on: from a SYSTEMS block the
plan reads only MODE, which only a reload changes, so block identity is
enough; anything else it reads must join the cache key.
- Build the plan's ingress with only what resolve() routes on, instead of
passing empty peer, radio and stream ids through _voice_forward_plan.
- SubscriptionStore.has_table is abstract; the scan default is gone.
- Both caches are created in RoutingUseCases.__init__, and a full cache
drops its oldest entry instead of all of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A new forward leg gets its header, terminator and embedded LC encoded
(BPTC 196,96 + RS, about 250us). Every leg of a call to the same TG gets
the same LC, so a call bridged to six OpenBridges and a MASTER spent
1.75ms on its first frame doing the same work seven times. The encoded
set is now kept per LC (bounded) and shared by the legs, which only ever
slice it.
- In-band signalling runs on each voice header and terminator and walked
every subscription of the server to find those of the source system. The
store now indexes legs by system, in the order a full scan returns them.
- The remaining full scans used to test or drop a relay table, and the copy
of a whole table just to test it is not empty, use the table index.
Short calls (50 frames), one call bridged to 6 OpenBridges and a MASTER,
200 other bridged TGs in the store, per frame, after the previous commit:
HBP ingress 136.4us -> 97.9us
OBP ingress 98.1us -> 60.7us
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every group voice frame, from HBP or OpenBridge, asked store_has_table()
whether its TG has a relay table. It answered by building a tuple of every
subscription and calling table_key() on each, so the cost of a frame grew
with the size of the network. On a live master (ADN 213, six OpenBridges)
this was 26% of the CPU spent on OpenBridge ingress.
- has_table() on the store answers from the table index it already keeps;
the port gets a default that scans, for stores without one.
- The forward plan of a group call (relay tables, resolved legs, the
MASTER/PEER dedupe and the target entries) depends only on the store and
on the source and target SYSTEMS blocks. It is now kept per
(system, slot, TG) and rebuilt when the store's new revision counter moves
or a reload replaces one of those blocks. ENABLED, quench, keepalive and
contention are still checked per frame in the loop.
- One OBP session lookup per target instead of two.
- store_has_table is imported once, not on every frame.
Per frame, one call bridged to 6 OpenBridges and a MASTER, with N other
bridged TGs in the store:
N=0 N=200
HBP ingress 81.8us -> 52.1us 447.4us -> 55.3us
OBP ingress 95.9us -> 66.8us 469.3us -> 65.7us
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>