Commit 9a7fd6d816 for asterisk.org

commit 9a7fd6d8160ab2fb21853569d016a43c3cf76859
Author: phoneben <3232963@gmail.com>
Date:   Thu Oct 15 19:18:21 2026 +0300

    main/lock: log failed mutex unlock in non-DEBUG_THREADS builds

    main/lock: log failed mutex unlock in non-DEBUG_THREADS builds

    In non-DEBUG_THREADS builds, a failed pthread_mutex_unlock() is silent,
    so an unlock by a non-owning thread leaves the mutex locked and the only
    symptom is a later hang. Log an error naming the mutex, file, line and
    function when the unlock fails. The success path costs one branch.

    Tested on 20.21.0 with a throwaway module covering both a never-locked
    unlock and a cross-thread unlock.

    AI assistance: Claude (Anthropic) helped draft this. I reviewed and
    tested the change.

    Resolves: #2233

diff --git a/main/lock.c b/main/lock.c
index f67fa738eb..b310692e54 100644
--- a/main/lock.c
+++ b/main/lock.c
@@ -509,6 +509,19 @@ int __ast_pthread_mutex_unlock(const char *filename, int lineno, const char *fun

 	res = pthread_mutex_unlock(&t->mutex);

+#ifndef DEBUG_THREADS
+	/*
+	 * An unlock failure (e.g. EPERM) means the mutex may stay locked.
+	 * The strcmp only runs when the unlock failed, so there is no
+	 * performance impact on normal unlocks. It avoids recursion if the
+	 * logger's own mutex fails to unlock.
+	 */
+	if (res && strcmp(filename, "logger.c")) {
+		ast_log(LOG_ERROR, "Failed to unlock mutex '%s' at %s:%d (%s): %s\n",
+			mutex_name, filename, lineno, func, strerror(res));
+	}
+#endif
+
 #ifdef DEBUG_THREADS
 	if (res) {
 		log_mutex_error(canlog, "%s line %d (%s): Error releasing mutex: %s\n",