Compare commits

..
Author SHA1 Message Date
Dave Horton 11764e40b0 0.9.10 2026-09-15 12:39:12 -04:00
Hoan Luu HuuandClaude Opus 5 b9e9fe305a Fix/siprec survives fs transfer (#232)
* fix: keep siprec recording alive across a feature server transfer

A cross-feature-server move (enqueue/dequeue or conference) re-negotiates the
feature-server leg in _onFeatureServerTransfer, but nothing rebuilt the rtpengine
subscription the SIPREC recording forks from, so the recorder went silent from the
moment the call moved. The fresh destroy handler installed on the new leg also
dropped the _stopRecording() call the original handlers have, so the SIPREC dialog
was never BYEd and the recorder had to wait out its media timeout.

Rebuild the subscription after the transfer re-negotiates media, and stop the
recording when the transferred leg ends. The resubscribe call is guarded so this
is safe to deploy before @jambonz/siprec-client-utils is bumped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: drop the siprec re-subscribe, keep the missing teardown

The cross-feature-server move was tested on a real two-feature-server cluster
(inbound path): the rtpengine subscription survives the REFER on its own, so
resubscribe() was fixing a fault that does not exist. rtpengine keeps
non-offer-answer subscriptions across an answer.

What does fail, and what this branch still fixes, is the teardown: the destroy
handler installed on the transferred leg never stopped the recording, so the
recorder was left without a BYE. Verified on the inbound path; the outbound
transfer path has the identical handler and the same omission.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 18:42:47 +01:00
5 changed files with 5 additions and 47 deletions
+1 -13
View File
@@ -70,8 +70,7 @@ const {
isMemberOfSet,
addKey,
deleteKey,
retrieveKey,
addToSet
retrieveKey
} = require('@jambonz/realtimedb-helpers')({}, logger);
const activeCallIds = new Map();
@@ -125,17 +124,6 @@ const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], lo
});
srf.locals.getRtpEngine = getRtpEngine;
/* report our call count to redis so a draining sbc-inbound process can count
calls across all sbc-inbound and sbc-outbound processes on this server */
if (!process.env.K8S && 'test' !== process.env.NODE_ENV) {
srf.locals.callCountReporter = require('./lib/call-count-reporter')({
logger,
addKey,
addToSet,
getCount: () => activeCallIds.size
});
}
if (process.env.DRACHTIO_HOST && !process.env.K8S) {
const cidrs = process.env.JAMBONES_NETWORK_CIDR
.split(',')
-31
View File
@@ -1,31 +0,0 @@
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};
};
+1
View File
@@ -1266,6 +1266,7 @@ Duration=${payload.duration} `
this.activeCallIds.delete(this.req.get('Call-ID'));
if (this.activeCallIds.size === 0) this.idleEmitter.emit('idle');
uac.other.destroy();
this._stopRecording();
if (this.req.locals.cdr) {
const now = Date.now();
const day = new Date();
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sbc-outbound",
"version": "0.9.9",
"version": "0.9.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sbc-outbound",
"version": "0.9.9",
"version": "0.9.10",
"license": "MIT",
"dependencies": {
"@babel/helpers": "^7.26.10",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sbc-outbound",
"version": "0.9.9",
"version": "0.9.10",
"main": "app.js",
"engines": {
"node": ">= 20.0.0"