Commit f7b60322b1 for asterisk.org
commit f7b60322b1b425b932a63148ccfdb3c3adf17c3b
Author: George Joseph <gjoseph@sangoma.com>
Date: Wed Apr 22 09:09:48 2026 -0600
ari_websockets: Fix two issues in the cleanup of outbound websockets.
1. session_cleanup() now saves the websocket type before unlinking the
session from the session registry. This prevents a FRACK when cleaning
up per-call websockets when MALLOC_DEBUG is used.
2. session_shutdown_cb() and outbound_sessions_load() now call
pthread_cancel() to cancel the session handler thread to prevent the
thread from continually trying to connect to a server after the
connection config has been removed by a reload. This required the
thread to use pthread_cleanup_push() to clean up its reference to the
session instead of RAII because RAII destructors don't get run when
pthread_cancel() is used.
Resolves: #1894
diff --git a/res/ari/ari_websockets.c b/res/ari/ari_websockets.c
index cc5bd9e950..b633567579 100644
--- a/res/ari/ari_websockets.c
+++ b/res/ari/ari_websockets.c
@@ -553,19 +553,30 @@ static void session_reset(struct ari_ws_session *session)
* This unlinks the ari_ws_session from the registry and cleans up the
* decrements the reference count.
*/
-static void session_cleanup(struct ari_ws_session *session)
+static void session_cleanup(void *obj)
{
+ struct ari_ws_session *session = obj;
+ enum ast_websocket_type wstype;
+
if (!session) {
return;
}
- ast_debug(3, "%s: Cleaning up ARI websocket session RC: %d\n",
- session->session_id, (int)ao2_ref(session, 0));
+
+ /*
+ * We need to save the websocket type because client-per-call websockets
+ * will get destroyed when they're unlinked from the session_registry
+ * so we won't be able to test it afterwards.
+ */
+ wstype = session->type;
+
+ ast_debug(3, "%s: Cleaning up ARI %s websocket session. Refcount: %d\n",
+ session->session_id, ast_websocket_type_to_str(wstype), (int)ao2_ref(session, 0));
session_reset(session);
if (session_registry) {
- ast_debug(3, "%s: Unlinking websocket session from registry RC: %d\n",
- session->session_id, (int)ao2_ref(session, 0));
+ ast_debug(3, "%s: Unlinking %s websocket session from registry Refcount: %d\n",
+ session->session_id, ast_websocket_type_to_str(wstype), (int)ao2_ref(session, 0));
ao2_unlink(session_registry, session);
}
@@ -574,7 +585,7 @@ static void session_cleanup(struct ari_ws_session *session)
* was held by the registry container so we don't need
* to unref it here.
*/
- if (session->type != AST_WS_TYPE_CLIENT_PER_CALL_CONFIG) {
+ if (wstype != AST_WS_TYPE_CLIENT_PER_CALL_CONFIG) {
session_unref(session);
}
}
@@ -877,7 +888,11 @@ static int session_shutdown_cb(void *obj, void *arg, int flags)
session->closing = 1;
session_cleanup(session);
if (session->ast_ws_session) {
+ ast_debug(3, "%s: Closing websocket\n", session->session_id);
ast_websocket_close(session->ast_ws_session, 1000);
+ } else if (session->thread) {
+ ast_debug(3, "%s: Cancelling handler thread\n", session->session_id);
+ pthread_cancel(session->thread);
}
return 0;
@@ -928,9 +943,15 @@ static struct ari_ws_session *session_find_by_app(const char *app_name,
*/
static void *outbound_session_handler_thread(void *obj)
{
- RAII_VAR(struct ari_ws_session *, session, obj, session_cleanup);
+ struct ari_ws_session *session = obj;
int already_sent_registers = 1;
+ /*
+ * We use pthread_cleanup_push because RAII destructors don't run
+ * if we cancel the thread.
+ */
+ pthread_cleanup_push(session_cleanup, obj);
+
ast_debug(3, "%s: Starting outbound websocket thread RC: %d\n",
session->session_id, (int)ao2_ref(session, 0));
session->thread = pthread_self();
@@ -964,6 +985,13 @@ static void *outbound_session_handler_thread(void *obj)
break;
}
+
+ if (session->closing) {
+ ast_debug(3, "%s: Websocket closing RC: %d\n",
+ session->session_id, (int)ao2_ref(session, 0));
+ break;
+ }
+
usleep(session->owc->websocket_client->reconnect_interval * 1000);
continue;
}
@@ -992,8 +1020,10 @@ static void *outbound_session_handler_thread(void *obj)
if (!upgrade_headers) {
ast_log(LOG_WARNING, "%s: Failed to create upgrade header\n", session->session_id);
session->thread = 0;
+ session->connected = 0;
ast_websocket_close(astws, 1000);
- return NULL;
+ session->ast_ws_session = NULL;
+ break;
}
session->connected = 1;
@@ -1028,6 +1058,8 @@ static void *outbound_session_handler_thread(void *obj)
session->session_id, (int)ao2_ref(session, 0));
session->thread = 0;
+ pthread_cleanup_pop(1);
+
return NULL;
}
@@ -1451,7 +1483,11 @@ static void outbound_sessions_load(const char *name)
session->closing = 1;
session_cleanup(session);
if (session->ast_ws_session) {
+ ast_debug(3, "%s: Closing websocket\n", session->session_id);
ast_websocket_close(session->ast_ws_session, 1000);
+ } else if (session->thread) {
+ ast_debug(3, "%s: Cancelling handler thread\n", session->session_id);
+ pthread_cancel(session->thread);
}
if (session->type == AST_WS_TYPE_CLIENT_PERSISTENT) {