fix: prevent concurrent regbot rebuilds, raise min expires (#147)

* fix: add mutex to prevent concurrent regbot rebuilds (#2719)

updateCarrierRegbots was called via .then() (not awaited) from checkStatus,
allowing concurrent invocations to race on the shared regbots array. The
last writer wins, and concurrent cleanup can stop() regbots still in use,
permanently breaking their timer chain.

Add a boolean mutex (rebuildInProgress) with try/finally to skip concurrent
invocations. Promote rebuild completion log from debug to info for production
visibility. Add standalone test demonstrating both the bug and the fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* raise default minimum registration expires from 30s to 90s

Prevents overly aggressive re-registration cycles when the registrar
returns a short Expires value, reducing regbot churn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ed Robbins
2026-08-19 08:28:08 -04:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 55c8495970
commit 2f2f522133
4 changed files with 378 additions and 3 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ const {
} = require('./config');
const {isValidDomainOrIP, isValidIPv4} = require('./utils');
const DEFAULT_EXPIRES = (parseInt(JAMBONES_REGBOT_DEFAULT_EXPIRES_INTERVAL) || 3600);
const MIN_EXPIRES = (parseInt(JAMBONES_REGBOT_MIN_EXPIRES_INTERVAL) || 30);
const MIN_EXPIRES = (parseInt(JAMBONES_REGBOT_MIN_EXPIRES_INTERVAL) || 90);
const FAILURE_RETRY_INTERVAL = (parseInt(JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL) || 300);
const REGISTER_FAILURE_THRESHOLD = (parseInt(JAMBONES_REGBOT_REGISTER_FAILURE_THRESHOLD) || 3);
const assert = require('assert');
+9 -2
View File
@@ -14,6 +14,7 @@ const REGBOT_STATUS_CHECK_INTERVAL = 60;
const regbotKey = `${(JAMBONES_CLUSTER_ID || 'default')}:regbot-token`;
const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let initialized = false;
let rebuildInProgress = false;
const regbots = [];
let carriersHash = '';
@@ -207,7 +208,11 @@ const checkStatus = async(logger, srf) => {
};
const updateCarrierRegbots = async(logger, srf) => {
// Check if We are
if (rebuildInProgress) {
logger.info('updateCarrierRegbots: rebuild already in progress, skipping');
return;
}
rebuildInProgress = true;
const { lookupAllVoipCarriers, lookupSipGatewaysByCarrier, lookupAccountBySid } = srf.locals.dbHelpers;
try {
@@ -347,10 +352,12 @@ const updateCarrierRegbots = async(logger, srf) => {
Array.prototype.push.apply(regbots, newRegbots.slice(i, i + 1000));
}
logger.debug(`updateCarrierRegbots: ${regbots.length} regbots active, ` +
logger.info(`updateCarrierRegbots: ${regbots.length} regbots active, ` +
`${keepKeys.size} kept, ${regbots.length - keepKeys.size} new`);
}
} catch (err) {
logger.error({ err }, 'updateCarrierRegbots Error');
} finally {
rebuildInProgress = false;
}
};