Skip to content

retry gnutls_record_send/recv on GNUTLS_E_AGAIN - #47

Open
w4zu wants to merge 1 commit into
LINBIT:masterfrom
w4zu:retry-gnutls-again
Open

w4zu wants to merge 1 commit into
LINBIT:masterfrom
w4zu:retry-gnutls-again

Conversation

@w4zu

@w4zu w4zu commented Sep 18, 2026

Copy link
Copy Markdown

gnutls_record_send() and gnutls_record_recv() may return GNUTLS_E_AGAIN or GNUTLS_E_INTERRUPTED. Both are non-fatal, gnutls_error_is_fatal() returns 0 for them, and the call is meant to be repeated. csync2 passed the negative return straight up instead: conn_read() turned it into a short read and csync_recv_file() called csync_fatal(), while csync_send_file() saw rc != chunk and did the same.

A large transfer therefore died on a condition GnuTLS reports as harmless, in practice after roughly 7.4 GiB over a single connection. Pushing 31.66 GB of mixed files to two peers took seven csync2 runs to converge; with this it takes one.

Both paths need it. Patching only the sending side changed nothing, because the receive path then aborted on the identical condition.

The send loop only advances the offset after a successful send, so a retry repeats the identical (pointer, length) pair, as the GnuTLS manual requires. The socket is blocking, no O_NONBLOCK is ever set on conn_fd_in/conn_fd_out, so the retry cannot spin. The non-TLS branch of WRITE() already loops over partial writes and retries EINTR; this makes the TLS branch behave the same.

gnutls_record_send() and gnutls_record_recv() may return GNUTLS_E_AGAIN or
GNUTLS_E_INTERRUPTED. Both are non-fatal, gnutls_error_is_fatal() returns 0
for them, and the call is meant to be repeated. csync2 passed the negative
return straight up instead: conn_read() turned it into a short read and
csync_recv_file() called csync_fatal(), while csync_send_file() saw
rc != chunk and did the same.

A large transfer therefore died on a condition GnuTLS reports as harmless,
in practice after roughly 7.4 GiB over a single connection. Pushing 31.66 GB
of mixed files to two peers took seven csync2 runs to converge; with this it
takes one.

Both paths need it. Patching only the sending side changed nothing, because
the receive path then aborted on the identical condition.

The send loop only advances the offset after a successful send, so a retry
repeats the identical (pointer, length) pair, as the GnuTLS manual requires.
The socket is blocking, no O_NONBLOCK is ever set on conn_fd_in/conn_fd_out,
so the retry cannot spin. The non-TLS branch of WRITE() already loops over
partial writes and retries EINTR; this makes the TLS branch behave the same.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant