Skip to content

Commit 269e2c0

Browse files
stackiaclaude
andcommitted
fix(rtsp): fully drain TCP interleaved socket for edge-triggered pollers
Two edge-triggered (EPOLLET) bugs caused RTSP playback to receive only a small burst of data before stalling: 1. rtsp_handle_tcp_interleaved_data() returned after filling and processing the 4096-byte response buffer once, without looping back to recv() remaining socket data. With level-triggered this was fine (EPOLLIN re-fires immediately), but edge-triggered requires draining until EAGAIN. Fix: add an outer loop that repeats recv+process until EAGAIN or the buffer is stuck full. 2. After the PLAY response transitioned state to PLAYING, the code returned from the awaiting_response branch without falling through to the PLAYING handler. Preserved RTP data in the response buffer and any unread socket data were left unprocessed until the next edge. Fix: fall through to rtsp_handle_tcp_interleaved_data() when state becomes PLAYING. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 423f77f commit 269e2c0

1 file changed

Lines changed: 51 additions & 18 deletions

File tree

src/rtsp.c

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,8 +1053,16 @@ int rtsp_handle_socket_event(rtsp_session_t *session, uint32_t events) {
10531053
/* Real error: set error state */
10541054
rtsp_session_set_state(session, RTSP_STATE_ERROR);
10551055
}
1056+
return result;
1057+
}
1058+
1059+
/* For edge-triggered pollers: after transitioning to PLAYING, fall
1060+
* through to process any preserved RTP data in the response buffer
1061+
* and drain remaining socket data. Without this, the preserved data
1062+
* would sit unprocessed until a new edge arrives. */
1063+
if (session->state != RTSP_STATE_PLAYING) {
1064+
return result;
10561065
}
1057-
return result;
10581066
}
10591067

10601068
if (session->state == RTSP_STATE_PLAYING) {
@@ -1882,28 +1890,53 @@ static int rtsp_process_interleaved_buffer(rtsp_session_t *session,
18821890

18831891
int rtsp_handle_tcp_interleaved_data(rtsp_session_t *session,
18841892
connection_t *conn) {
1885-
/* Drain all available data for edge-triggered pollers (epoll EPOLLET / kqueue EV_CLEAR)
1886-
* where the read event fires only once per data arrival. */
1887-
while (session->response_buffer_pos < RTSP_RESPONSE_BUFFER_SIZE) {
1888-
int bytes_received =
1889-
recv(session->socket,
1890-
session->response_buffer + session->response_buffer_pos,
1891-
RTSP_RESPONSE_BUFFER_SIZE - session->response_buffer_pos, 0);
1892-
if (bytes_received < 0) {
1893-
if (errno == EAGAIN) {
1894-
break; /* No more data available */
1893+
int total_forwarded = 0;
1894+
1895+
/* Outer loop: drain all available data for edge-triggered pollers
1896+
* (epoll EPOLLET / kqueue EV_CLEAR) where the read event fires only once
1897+
* per data arrival. The inner recv loop may fill the small response buffer
1898+
* before hitting EAGAIN; after processing we must loop back to recv more. */
1899+
for (;;) {
1900+
int hit_eagain = 0;
1901+
1902+
/* Fill response buffer from socket */
1903+
while (session->response_buffer_pos < RTSP_RESPONSE_BUFFER_SIZE) {
1904+
int bytes_received =
1905+
recv(session->socket,
1906+
session->response_buffer + session->response_buffer_pos,
1907+
RTSP_RESPONSE_BUFFER_SIZE - session->response_buffer_pos, 0);
1908+
if (bytes_received < 0) {
1909+
if (errno == EAGAIN) {
1910+
hit_eagain = 1;
1911+
break; /* No more data available */
1912+
}
1913+
logger(LOG_ERROR, "RTSP: TCP receive failed: %s", strerror(errno));
1914+
return -1; /* Upstream gone — caller will drain client */
1915+
} else if (bytes_received == 0) {
1916+
logger(LOG_INFO, "RTSP: Server closed connection (EOF received)");
1917+
return -1; /* EOF — caller will drain client */
18951918
}
1896-
logger(LOG_ERROR, "RTSP: TCP receive failed: %s", strerror(errno));
1897-
return -1; /* Upstream gone — caller will drain client */
1898-
} else if (bytes_received == 0) {
1899-
logger(LOG_INFO, "RTSP: Server closed connection (EOF received)");
1900-
return -1; /* EOF — caller will drain client */
1919+
1920+
session->response_buffer_pos += bytes_received;
19011921
}
19021922

1903-
session->response_buffer_pos += bytes_received;
1923+
/* Process complete interleaved frames from buffer */
1924+
int result = rtsp_process_interleaved_buffer(session, conn);
1925+
if (result < 0)
1926+
return result;
1927+
total_forwarded += result;
1928+
1929+
/* If we hit EAGAIN, socket is fully drained */
1930+
if (hit_eagain)
1931+
break;
1932+
1933+
/* If buffer is still full after processing (incomplete packet spanning
1934+
* the entire buffer), we can't make progress — stop to avoid spinning */
1935+
if (session->response_buffer_pos >= RTSP_RESPONSE_BUFFER_SIZE)
1936+
break;
19041937
}
19051938

1906-
return rtsp_process_interleaved_buffer(session, conn);
1939+
return total_forwarded;
19071940
}
19081941

19091942
int rtsp_handle_udp_rtp_data(rtsp_session_t *session, connection_t *conn) {

0 commit comments

Comments
 (0)