From 3fef46e7767b37ee3be9e9027b014025c99fdbac Mon Sep 17 00:00:00 2001
From: Naveen Albert <asterisk@phreaknet.org>
Date: Sat, 9 Sep 2023 11:24:37 -0400
Subject: [PATCH] chan_console: Fix deadlock caused by unclean thread exit.

To terminate a console channel, stop_stream causes pthread_cancel
to make stream_monitor exit. However, commit 5b8fea93d106332bc0faa4b7fa8a6ea71e546cac
added locking to this function which results in deadlock due to
the stream_monitor thread being killed while it's holding the pvt lock.

To resolve this, a flag is now set and read to indicate abort, so
the use of pthread_cancel and pthread_kill can be avoided altogether.

Resolves: #308
(cherry picked from commit 71215561d8d41b9b7993e66e0ab672f38e75eb48)
---
 channels/chan_console.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/channels/chan_console.c b/channels/chan_console.c
index 1d1d94cb92..74ff75f0a7 100644
--- a/channels/chan_console.c
+++ b/channels/chan_console.c
@@ -154,6 +154,8 @@ static struct console_pvt {
 	struct ast_frame fr;
 	/*! Running = 1, Not running = 0 */
 	unsigned int streamstate:1;
+	/*! Abort stream processing? */
+	unsigned int abort:1;
 	/*! On-hook = 0, Off-hook = 1 */
 	unsigned int hookstate:1;
 	/*! Unmuted = 0, Muted = 1 */
@@ -277,18 +279,19 @@ static void *stream_monitor(void *data)
 	};
 
 	for (;;) {
-		pthread_testcancel();
 		console_pvt_lock(pvt);
 		res = Pa_ReadStream(pvt->stream, buf, sizeof(buf) / sizeof(int16_t));
 		console_pvt_unlock(pvt);
-		pthread_testcancel();
 
-		if (!pvt->owner) {
+		if (!pvt->owner || pvt->abort) {
 			return NULL;
 		}
 
-		if (res == paNoError)
+		if (res == paNoError) {
 			ast_queue_frame(pvt->owner, &f);
+		} else {
+			ast_log(LOG_WARNING, "Console ReadStream failed: %s\n", Pa_GetErrorText(res));
+		}
 	}
 
 	return NULL;
@@ -403,8 +406,9 @@ static int stop_stream(struct console_pvt *pvt)
 	if (!pvt->streamstate || pvt->thread == AST_PTHREADT_NULL)
 		return 0;
 
-	pthread_cancel(pvt->thread);
-	pthread_kill(pvt->thread, SIGURG);
+	pvt->abort = 1;
+	/* Wait for pvt->thread to exit cleanly, to avoid killing it while it's holding a lock. */
+	pthread_kill(pvt->thread, SIGURG); /* Wake it up if needed, but don't cancel it */
 	pthread_join(pvt->thread, NULL);
 
 	console_pvt_lock(pvt);