Commit 7380030f3a for asterisk.org
commit 7380030f3a93899c0f8baf57cc4017444980e098
Author: Gian Diego Javes <gjaves@galcyon.com>
Date: Tue Aug 25 19:44:25 2026 -0500
res_websocket_client: Release lock_obj when retries are exhausted
ast_websocket_client_connect() takes lock_obj at the top of every pass
through the retry loop. When the attempts run out it leaves through the
break and returns NULL without releasing it, so the calling thread ends
up holding it. The other two exits, the ast_calloc failure and the
success path, do release it.
The retry path also takes the lock again instead of releasing it before
sleeping. The header describes lock_obj as "an ao2 object to lock while
the connection is being attempted", and the usleep is the wait between
attempts rather than an attempt, so that is where it should be released.
chan_websocket.c passes the per-call instance as lock_obj, allocated
with ao2_alloc and no flags, so it gets the default
AO2_ALLOC_OPT_LOCK_MUTEX. When the object is freed, __ao2_ref() reaches
ast_mutex_destroy() with the mutex still held and frees the memory
anyway. There is no deadlock, Asterisk mutexes are recursive, but every
call that fails to connect logs two ERROR lines.
With each pass balanced, the lock is held exactly once when the loop
breaks, so a single unlock before the return is enough.
Fixes: #2121
diff --git a/res/res_websocket_client.c b/res/res_websocket_client.c
index fbc00fc2f9..eff603ee58 100644
--- a/res/res_websocket_client.c
+++ b/res/res_websocket_client.c
@@ -477,11 +477,15 @@ struct ast_websocket *ast_websocket_client_connect(struct ast_websocket_client *
}
if (lock_obj) {
- ao2_lock(lock_obj);
+ ao2_unlock(lock_obj);
}
usleep(wc->reconnect_interval * 1000);
}
+ if (lock_obj) {
+ ao2_unlock(lock_obj);
+ }
+
return NULL;
}