Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/fluent-bit/tls/flb_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
#define FLB_TLS_WANT_READ -0x7e4
#define FLB_TLS_WANT_WRITE -0x7e6

/*
* Maximum consecutive WANT_READ/WANT_WRITE retries before treating the
* connection as stale. Prevents a dead TLS connection from spinning the
* engine thread in a tight yield/resume loop (see #9927).
*/
#define FLB_TLS_WANT_READ_MAX_RETRIES 800

/* Cert Flags */
#define FLB_TLS_CA_ROOT 1
#define FLB_TLS_CERT 2
Expand Down
73 changes: 73 additions & 0 deletions src/tls/flb_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
* limitations under the License.
*/

#include <errno.h>

#include <fluent-bit/flb_info.h>
#include <fluent-bit/flb_time.h>
#include <fluent-bit/flb_socket.h>
Expand Down Expand Up @@ -393,17 +395,50 @@ int flb_tls_net_read_async(struct flb_coro *co,
struct mk_event event_backup;
struct flb_tls *tls;
int ret;
int want_read_retries;

tls = session->tls;

event_restore_needed = FLB_FALSE;

/*
* Track consecutive SSL_ERROR_WANT_READ iterations with no progress.
* When a TLS connection's underlying socket continuously reports
* EPOLLIN but SSL_read() returns WANT_READ (no actual TLS data),
* the coroutine bounces between yield and resume in a tight loop,
* monopolizing the engine thread and starving all other processing
* (output flushes, timers, new connections).
*
* This happens when a dead TCP connection stays ESTABLISHED but
* has no TLS records to deliver — the kernel signals "readable"
* but OpenSSL needs more data.
*
* See: https://github.com/fluent/fluent-bit/issues/9927
*/
want_read_retries = 0;

io_tls_backup_event(session->connection, &event_backup);

retry_read:
ret = tls->api->net_read(session, buf, len);

if (ret == FLB_TLS_WANT_READ) {
want_read_retries++;

if (want_read_retries > FLB_TLS_WANT_READ_MAX_RETRIES) {
flb_warn("[tls] connection #%i exceeded maximum read retries (%i), "
"closing stale connection",
session->connection->fd,
FLB_TLS_WANT_READ_MAX_RETRIES);

session->connection->coroutine = NULL;
session->connection->net_error = ETIMEDOUT;

io_tls_restore_event(session->connection, &event_backup);

return -1;
}

event_restore_needed = FLB_TRUE;

session->connection->coroutine = co;
Expand All @@ -414,6 +449,8 @@ int flb_tls_net_read_async(struct flb_coro *co,
goto retry_read;
}
else if (ret == FLB_TLS_WANT_WRITE) {
/* Progress on a different axis, reset the read retry counter */
want_read_retries = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cap WANT_WRITE retries during TLS reads

When SSL_read() returns FLB_TLS_WANT_WRITE, this branch resets the only retry counter and then retries without any limit. For a stalled TLS read that needs the socket write side, or alternates WANT_READ/WANT_WRITE without returning bytes, the coroutine can still bounce through flb_coro_yield() indefinitely and monopolize the engine thread; count WANT_WRITE as a no-progress retry too, or reset only after a successful read.

Useful? React with 👍 / 👎.

event_restore_needed = FLB_TRUE;

session->connection->coroutine = co;
Expand Down Expand Up @@ -500,11 +537,13 @@ int flb_tls_net_write_async(struct flb_coro *co,
size_t total;
int ret;
struct flb_tls *tls;
int want_retries;

total = 0;
tls = session->tls;

event_restore_needed = FLB_FALSE;
want_retries = 0;

io_tls_backup_event(session->connection, &event_backup);

Expand All @@ -516,6 +555,23 @@ int flb_tls_net_write_async(struct flb_coro *co,
len - total);

if (ret == FLB_TLS_WANT_WRITE) {
want_retries++;

if (want_retries > FLB_TLS_WANT_READ_MAX_RETRIES) {
flb_warn("[tls] connection #%i exceeded maximum write retries (%i), "
"closing stale connection",
session->connection->fd,
FLB_TLS_WANT_READ_MAX_RETRIES);

session->connection->coroutine = NULL;
session->connection->net_error = ETIMEDOUT;
*out_len = total;

io_tls_restore_event(session->connection, &event_backup);

return -1;
}

event_restore_needed = FLB_TRUE;

io_tls_event_switch(session, MK_EVENT_WRITE);
Expand All @@ -525,6 +581,23 @@ int flb_tls_net_write_async(struct flb_coro *co,
goto retry_write;
}
else if (ret == FLB_TLS_WANT_READ) {
want_retries++;

if (want_retries > FLB_TLS_WANT_READ_MAX_RETRIES) {
flb_warn("[tls] connection #%i exceeded maximum write retries (%i), "
"closing stale connection",
session->connection->fd,
FLB_TLS_WANT_READ_MAX_RETRIES);

session->connection->coroutine = NULL;
session->connection->net_error = ETIMEDOUT;
*out_len = total;

io_tls_restore_event(session->connection, &event_backup);

return -1;
}

event_restore_needed = FLB_TRUE;

io_tls_event_switch(session, MK_EVENT_READ);
Expand Down
Loading