fixed listern verb terminated due to background task listen failure,teardown wrong handler as well (#1554)

This commit is contained in:
Hoan Luu Huu
2026-06-05 18:48:44 +07:00
committed by GitHub
parent d84690dfb1
commit 44fbd91053
5 changed files with 97 additions and 52 deletions
+1 -5
View File
@@ -910,11 +910,7 @@ class TaskGather extends SttTask {
const bugname = fsEvent.getHeader('media-bugname');
const finished = fsEvent.getHeader('transcription-session-finished');
this.logger.debug({evt, bugname, finished, vendor: this.vendor}, 'Gather:_onTranscription raw transcript');
if (bugname && this.bugname !== bugname) {
this.logger.info(
`Gather:_onTranscription - ignoring transcript from ${bugname} because our bug is ${this.bugname}`);
return;
}
if (!this.eventIsForOurBug(fsEvent)) return;
if (finished === 'true') return;
if (this.vendor === 'ibm' && evt?.state === 'listening') return;
+30 -24
View File
@@ -67,6 +67,7 @@ class TaskListen extends Task {
get name() { return TaskName.Listen; }
set bugname(name) { this._bugname = name; }
get bugname() { return this._bugname; }
set ignoreCustomerData(val) { this._ignoreCustomerData = val; }
@@ -192,32 +193,31 @@ class TaskListen extends Task {
}
_initListeners(ep) {
ep.addCustomEventListener(ListenEvents.Connect, this._onConnect.bind(this, ep));
ep.addCustomEventListener(ListenEvents.ConnectFailure, this._onConnectFailure.bind(this, ep));
ep.addCustomEventListener(ListenEvents.Error, this._onError.bind(this, ep));
/* Register via the base-class addCustomEventListener so handlers are tracked
* and removed by reference. Multiple forks (e.g. this listen verb and a
* background recording fork) share one endpoint and subscribe to the same
* event names; removing by reference avoids tearing down the co-tenant
* task's handlers (removing with no handler would call removeAllListeners
* and unsubscribe the event for everyone). */
this.addCustomEventListener(ep, ListenEvents.Connect, this._onConnect.bind(this, ep));
this.addCustomEventListener(ep, ListenEvents.ConnectFailure, this._onConnectFailure.bind(this, ep));
this.addCustomEventListener(ep, ListenEvents.Error, this._onError.bind(this, ep));
this.addCustomEventListener(ep, ListenEvents.KillAudio, this._onKillAudio.bind(this, ep));
this.addCustomEventListener(ep, ListenEvents.Disconnect, this._onDisconnect.bind(this, ep));
/* support bi-directional audio */
if (this.bidirectionalAudio.enabled) {
this.addCustomEventListener(ep, ListenEvents.PlayAudio, this._onPlayAudio.bind(this, ep));
}
if (this.finishOnKey || this.passDtmf) {
ep.on('dtmf', this._dtmfHandler);
}
/* support bi-directional audio */
if (this.bidirectionalAudio.enabled) {
ep.addCustomEventListener(ListenEvents.PlayAudio, this._onPlayAudio.bind(this, ep));
}
ep.addCustomEventListener(ListenEvents.KillAudio, this._onKillAudio.bind(this, ep));
ep.addCustomEventListener(ListenEvents.Disconnect, this._onDisconnect.bind(this, ep));
}
_removeListeners(ep) {
ep.removeCustomEventListener(ListenEvents.Connect);
ep.removeCustomEventListener(ListenEvents.ConnectFailure);
ep.removeCustomEventListener(ListenEvents.Error);
this.removeCustomEventListeners(ep);
if (this.finishOnKey || this.passDtmf) {
ep.removeListener('dtmf', this._dtmfHandler);
}
ep.removeCustomEventListener(ListenEvents.PlayAudio);
ep.removeCustomEventListener(ListenEvents.KillAudio);
ep.removeCustomEventListener(ListenEvents.Disconnect);
}
_onDtmf(ep, evt) {
@@ -253,10 +253,12 @@ class TaskListen extends Task {
this._timer = null;
}
}
_onConnect(ep) {
_onConnect(ep, evt, fsEvent) {
if (!this.eventIsForOurBug(fsEvent)) return;
this.logger.info('TaskListen:_onConnect');
}
_onConnectFailure(ep, evt) {
_onConnectFailure(ep, evt, fsEvent) {
if (!this.eventIsForOurBug(fsEvent)) return;
this.logger.info(evt, 'TaskListen:_onConnectFailure');
this.notifyTaskDone();
}
@@ -279,7 +281,8 @@ class TaskListen extends Task {
}
}
async _onPlayAudio(ep, evt) {
async _onPlayAudio(ep, evt, fsEvent) {
if (!this.eventIsForOurBug(fsEvent)) return;
this.logger.info(`received play_audio event: ${JSON.stringify(evt)}`);
if (!evt.queuePlay) {
this.playAudioQueue = [];
@@ -301,17 +304,20 @@ class TaskListen extends Task {
this.isPlayingAudioFromQueue = false;
}
_onKillAudio(ep) {
_onKillAudio(ep, evt, fsEvent) {
if (!this.eventIsForOurBug(fsEvent)) return;
this.logger.info('received kill_audio event');
ep.api('uuid_break', ep.uuid);
}
_onDisconnect(ep, cs) {
_onDisconnect(ep, evt, fsEvent) {
if (!this.eventIsForOurBug(fsEvent)) return;
this.logger.debug('_onDisconnect: TaskListen terminating task');
this.kill(cs);
this.kill();
}
_onError(ep, evt) {
_onError(ep, evt, fsEvent) {
if (!this.eventIsForOurBug(fsEvent)) return;
this.logger.info(evt, 'TaskListen:_onError');
this.notifyTaskDone();
}
-21
View File
@@ -51,7 +51,6 @@ class SttTask extends Task {
this.compileSonioxTranscripts = compileSonioxTranscripts;
this.consolidateTranscripts = consolidateTranscripts;
this.updateSpeechmaticsPayload = updateSpeechmaticsPayload;
this.eventHandlers = [];
this.isHandledByPrimaryProvider = true;
/**
* Task use taskIncludeRecognizer to identify
@@ -245,26 +244,6 @@ class SttTask extends Task {
return {host, path: `${pathname}${search}`};
}
addCustomEventListener(ep, event, handler) {
this.eventHandlers.push({ep, event, handler});
ep.addCustomEventListener(event, handler);
}
removeCustomEventListeners(ep) {
if (ep) {
// for specific endpoint
this.eventHandlers.filter((h) => h.ep === ep).forEach((h) => {
h.ep.removeCustomEventListener(h.event, h.handler);
});
this.eventHandlers = this.eventHandlers.filter((h) => h.ep !== ep);
return;
} else {
// for all endpoints
this.eventHandlers.forEach((h) => h.ep.removeCustomEventListener(h.event, h.handler));
this.eventHandlers = [];
}
}
async _initSpeechCredentials(cs, vendor, label) {
const {getNuanceAccessToken, getIbmAccessToken, getAwsAuthToken, getVerbioAccessToken} = cs.srf.locals.dbHelpers;
let credentials = cs.getSpeechCredentials(vendor, 'stt', label);
+65
View File
@@ -24,6 +24,10 @@ class Task extends Emitter {
this._killInProgress = false;
this._completionPromise = new Promise((resolve) => this._completionResolver = resolve);
/* tracks {ep, event, handler} for custom event listeners so they can be
* removed by reference (see addCustomEventListener/removeCustomEventListeners) */
this.eventHandlers = [];
/* used when we play a prompt to a member in conference */
this._confPlayCompletionPromise = new Promise((resolve) => this._confPlayCompletionResolver = resolve);
}
@@ -103,6 +107,67 @@ class Task extends Emitter {
}
}
/**
* Decide whether a FreeSWITCH media-bug custom event belongs to this task.
*
* Several FreeSWITCH modules (mod_audio_fork, mod_audio_stream, ...) fire the
* same custom event names for *every* media bug on a shared endpoint. When more
* than one bug runs on a call (e.g. a listen verb plus a background recording
* fork, or transcription plus answering-machine detection) each task must only
* act on events for its own bug, identified by the 'media-bugname' header.
*
* Fails open (returns true) when the header is absent or this task has no
* bugname, preserving single-bug behavior and tolerating older FreeSWITCH
* builds that don't yet stamp the header.
*
* @param {object} fsEvent - raw FreeSWITCH event (has getHeader)
* @returns {boolean} true if the event is for this task's bug
*/
eventIsForOurBug(fsEvent) {
const bugname = fsEvent?.getHeader?.('media-bugname');
if (bugname && this.bugname && bugname !== this.bugname) {
this.logger.debug(
`${this.name}: ignoring media-bug event for ${bugname}, ours is ${this.bugname}`);
return false;
}
return true;
}
/**
* Subscribe to a FreeSWITCH custom event on an endpoint, tracking the handler
* so it can later be removed by reference. Endpoints can be shared by several
* tasks (e.g. a listen verb and a background recording fork), so listeners must
* be removed individually — removing without a handler reference would call
* removeAllListeners and tear down co-tenant tasks' handlers.
* @param {object} ep - the endpoint
* @param {string} event - the custom event name
* @param {function} handler - the (already-bound) listener
*/
addCustomEventListener(ep, event, handler) {
this.eventHandlers.push({ep, event, handler});
ep.addCustomEventListener(event, handler);
}
/**
* Remove the custom event listeners this task registered. With an endpoint,
* removes only listeners for that endpoint; without one, removes all.
* @param {object} [ep] - the endpoint, or undefined for all endpoints
*/
removeCustomEventListeners(ep) {
if (ep) {
// for specific endpoint
this.eventHandlers.filter((h) => h.ep === ep).forEach((h) => {
h.ep.removeCustomEventListener(h.event, h.handler);
});
this.eventHandlers = this.eventHandlers.filter((h) => h.ep !== ep);
return;
} else {
// for all endpoints
this.eventHandlers.forEach((h) => h.ep.removeCustomEventListener(h.event, h.handler));
this.eventHandlers = [];
}
}
/**
* when a subclass Task has completed its work, it should call this method
*/
+1 -2
View File
@@ -488,10 +488,9 @@ class TaskTranscribe extends SttTask {
ep.gracefulShutdownResolver();
}
// make sure this is not a transcript from answering machine detection
const bugname = fsEvent.getHeader('media-bugname');
const finished = fsEvent.getHeader('transcription-session-finished');
const bufferedTranscripts = this._bufferedTranscripts[channel - 1];
if (bugname && this.bugname !== bugname) return;
if (!this.eventIsForOurBug(fsEvent)) return;
if (this.paused) {
this.logger.debug({evt}, 'TaskTranscribe:_onTranscription - paused, ignoring transcript');
}