Files
sbc-inbound/lib/call-count-reporter.js
T
Dave HortonandClaude Fable 5 b7b707cc2e fix: complete autoscale scale-in reliably and reject new INVITEs while draining (#248)
Scale-in completion never happened: app.js holds the placeholder
Emitter that autoscale-manager returns synchronously (the real
SnsNotifier replaces it later inside an async IIFE), so the completion
poller never saw operationalState change; it also called the
nonexistent scaleIn() rather than completeScaleIn().  Instances in
Terminating:Wait therefore always burned the full lifecycle hook
heartbeat timeout.

In addition, nothing consumed dryUpCalls: a draining SBC kept accepting
new INVITEs sent directly to its public address right up until
termination.

Changes:
- complete the scale-in from within the ScaleIn handler in
  autoscale-manager, where the real notifier is in scope
- while draining, reject new INVITEs with 503 so senders fail over to
  another SBC (INVITE with Replaces is allowed through since it targets
  a call already in progress here)
- a server may run several sbc-inbound and sbc-outbound processes, and
  completing the hook when only this process is idle would terminate
  the instance while sibling processes still have calls; each process
  now reports its call count to redis (lib/call-count-reporter.js, with
  a companion change in sbc-outbound) and the draining process
  completes only when the server-wide count is zero on two consecutive
  checks, falling back to its own count if no reports are present

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 08:54:19 -04:00

32 lines
1.2 KiB
JavaScript

const os = require('os');
/**
* Periodically report this process's count of calls in progress to redis.
* A server may host several sbc-inbound and sbc-outbound processes; when one
* of them handles an autoscale drain it needs to know when the entire server
* has no calls in progress, not just its own process. Each process writes
* its own count under a per-pid key (with a short expiry, so keys from dead
* processes evaporate) and registers that key in a per-host set that the
* draining process can enumerate.
*/
const REPORT_INTERVAL = 15000;
const KEY_EXPIRY_SECS = 120;
module.exports = ({logger, addKey, addToSet, getCount}) => {
const prefix = process.env.JAMBONES_CLUSTER_ID || 'default';
const setName = `${prefix}:call-count-keys:${os.hostname()}`;
const key = `${prefix}:call-count:${os.hostname()}:${process.pid}`;
const report = () => {
addKey(key, `${getCount()}`, KEY_EXPIRY_SECS)
.catch((err) => logger.error({err}, 'call-count-reporter: error writing call count'));
};
addToSet(setName, key)
.catch((err) => logger.error({err}, `call-count-reporter: error adding ${key} to ${setName}`));
setInterval(report, REPORT_INTERVAL);
report();
return {key, setName};
};