Commit ea04aadb33 for asterisk.org

commit ea04aadb331c046d2e6334f89d2b829cfbeb650d
Author: Tinet-mucw <mucw@ti-net.com.cn>
Date:   Wed Aug 26 18:13:41 2026 -0700

    apps/app_chanspy: don't delay spyee Hangup when spy channel has no reverse media

    channel_spy() waited indefinitely on the spy channel, so after the spyee
    hung up the spy audiohook leaving RUNNING was not noticed until waitfor
    timed out. That kept an autochan reference on the spyee and delayed its
    destructor / Hangup.

    Poll with a short timeout instead. On timeout, only re-check hook status;
    call ast_read() only when the spy channel is readable.

    Resolves: #2123

diff --git a/apps/app_chanspy.c b/apps/app_chanspy.c
index adcd72df20..b9a9f39b3c 100644
--- a/apps/app_chanspy.c
+++ b/apps/app_chanspy.c
@@ -811,14 +811,23 @@ static int channel_spy(struct ast_channel *chan, struct ast_autochan *spyee_auto
 	   channel has gone away.
 	*/

-	/* Note: it is very important that the ast_waitfor() be the first
-	   condition in this expression, so that if we wait for some period
-	   of time before receiving a frame from our spying channel, we check
-	   for hangup on the spied-on channel _after_ knowing that a frame
-	   has arrived, since the spied-on channel could have gone away while
-	   we were waiting
+	/* Use a short waitfor timeout so we notice the spy audiohook leaving
+	   RUNNING promptly. An indefinite wait holds an autochan ref on the
+	   spyee until waitfor times out, delaying destructor / Hangup when
+	   the spy channel is not readable.
 	*/
-	while (ast_waitfor(chan, -1) > -1 && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
+	while (csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
+		int waitres = ast_waitfor(chan, 100);
+
+		if (waitres < 0) {
+			running = -1;
+			break;
+		}
+		if (waitres == 0) {
+			/* Timeout: re-check hook status without reading. */
+			continue;
+		}
+
 		if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
 			running = -1;
 			if (f) {