Concurrency Control (CAC)
Call Admission Control (CAC) decides whether a new call is allowed to proceed. It enforces concurrency ceilings and rate limits so a single account, user, number, or runaway loop can’t exhaust capacity or rack up spend. Every call passes through admit() in the account’s PresenceDO, where the decision is made atomically (the object is single-threaded, so there are no torn counters and no races).
What CAC enforces
Section titled “What CAC enforces”CAC checks several dimensions in one decision:
| Dimension | Source | Limits |
|---|---|---|
| Account concurrency | account.max_in / max_out / max_dialer | Max concurrent calls per direction for the whole account. |
| Per-user concurrency | sip_user.max_simultaneous | Max simultaneous calls for one user (e.g. an outbound caller). |
| Per-DID channels | did.max_channels | Max concurrent calls on a single inbound number. |
| Sliding-window rate | call_limit rows | ”No more than N calls per M seconds” for a scope. |
Concurrency ceilings
Section titled “Concurrency ceilings”The simplest gates are counters. The account has direction-specific ceilings:
{ "id": "acc_01jf18ah3jeb5w6dfp27sgjsbt", "name": "Acme Corp", "max_in": 100, "max_out": 50, "max_dialer": 20 }…a user has a simultaneous-call ceiling…
{ "id": "us_01jatt0336xwzg2zbf3q9pyfm5", "username": "1001", "max_simultaneous": 2 }…and a DID has a per-number channel cap (max_channels). When a call is admitted, every relevant counter is incremented; when it ends, exactly those counters are decremented.
Sliding-window rate rules
Section titled “Sliding-window rate rules”For “rate,” not just “concurrency,” add call_limit rules. These are time-windowed: no more than max_count calls per period_sec.
{ "id": "lim_01jwt3awpk049tcdhth91evhyz", "account_id": "acc_01jf18ah3jeb5w6dfp27sgjsbt", "scope_kind": "number", "scope_id": null, "direction": "out", "period_sec": 60, "max_count": 10, "hard": 1}| Field | Purpose |
|---|---|
scope_kind | account / user / did / number. |
scope_id | The subject (null for account/number-pattern scopes). |
direction | in / out / dialer / any. |
period_sec / max_count | The sliding window: max_count calls per period_sec. |
hard | 1 = hard block; 0 = warn (advisory). |
Rate windows are time-bucketed and self-healing, so they don’t require a perfectly-clean shutdown to stay accurate. You can stack several rules (e.g. [60s, 10] and [3600s, 100]) to cap both bursts and sustained volume.
How admit() works
Section titled “How admit() works”new call ─▶ PresenceDO.admit(call) │ 1. check account ceiling for the direction │ 2. check per-user simultaneous (if a user is bound) │ 3. check per-DID channels (inbound) │ 4. check every applicable sliding-window rule ▼ all pass? ── no ──▶ { admit: false, reason } ── caller rejected / overflowed │ yes ──▶ increment every touched counter, bump windows, bind callId → {counters}, return { admit: true }If any dimension fails, the call is not admitted and admit() returns a reason:
| Reason | Meaning |
|---|---|
account_concurrency | The account’s directional ceiling is reached. |
user_simultaneous | The user’s simultaneous-call ceiling is reached. |
rate:<key> | A specific sliding-window rule tripped. |
Releasing a call
Section titled “Releasing a call”When a call ends, releaseCall(callId) decrements exactly the counters that call incremented (it’s idempotent, so a duplicate BYE is harmless). Because a BYE carries only the call id, the platform keeps a small cac:<callId> → account index in the key-value store so the right per-account PresenceDO can be reached to decrement. A TTL backstops any missed BYE so a leaked call can’t hold a channel forever.
Failure model
Section titled “Failure model”CAC and the ACD reservation fence have deliberately different failure modes, because they protect different invariants:
- CAC fails open and reconciles. If something goes wrong, it’s better to let a call through than to wrongly block a paying customer; counters are periodically reconciled against the live dialog snapshot from the SIP signaling layer.
- Reservations fail closed. Handing the same agent to two callers is never acceptable, so a reservation only proceeds when it can prove it’s safe.
CAC is enforced per account (no cross-reseller rollup), keeping each tenant’s admission decision local to its own object.
Inspecting & resetting
Section titled “Inspecting & resetting”GET /cac?accountId=returns the live channel-usage view: current concurrency counters and active calls for the account.POST /cac/resetclears stuck/leaked counters for an account: an ops escape hatch if a counter ever drifts.
For a step-by-step, see Set concurrency limits.