Skip to content

docs: document idle connection behavior for socket callbacks - #20521

Closed
jayvenn21 wants to merge 14 commits into
curl:masterfrom
jayvenn21:docs-idle-connection-socket-callbacks
Closed

jayvenn21 wants to merge 14 commits into
curl:masterfrom
jayvenn21:docs-idle-connection-socket-callbacks

Conversation

@jayvenn21

@jayvenn21 jayvenn21 commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

This PR documents observed behavior around idle connections and socket callbacks that is not currently described in the documentation.

Specifically:

  • When CURLOPT_CLOSESOCKETFUNCTION is copied and when it is invoked
  • How idle connections interact with CURLMOPT_SOCKETFUNCTION
  • Why read/error events may still occur on sockets that have been removed from polling

This behavior is relevant for applications managing large numbers of sockets and custom polling loops (e.g. kqueue/epoll-based integrations).

No functional changes are introduced.

Addresses #20377

socket. This can surface as zero-length reads or spurious data when
the connection is later closed or reused. Applications using
CURLMOPT_SOCKETFUNCTION(3) should handle such events without assuming
the socket is still actively used by libcurl.

@rakshasa rakshasa Feb 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is slightly inverted.

While idle connections are not supposed to receive data (according to http specs), they can receive zero length messages indicating connection closed by server. (non-zero length messages when using https, that decrypt to zero-length message)

However CURL_POLL_REMOVE has told us not to listen for read events.

We can't send CURL_CSELECT_ERR to libcurl in those cases because idle connections don't get handled in curl_multi_socket_action.

So we can't cheat by listening to read events for idle connections for three reasons:

  • There a way to get libcurl to close an fd in idle connection pool.
  • We can't peek at the message to check if it is zero-length. (http)
  • We can't decrypt the message to determine it is zero-length. (https)

Also point out that if a socket is NOT in read nor write, and is closed by the kernel for some reason, that fd can be reused and cause conflicts with libcurl's internally managed list of fds.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the clarification that makes sense.

You're right: the wording is slightly inverted. The intent was not to suggest that applications should listen for read events on idle connections, but rather to document why they may still surface and why libcurl cannot safely act on them once CURL_POLL_REMOVE has been issued.

I’ll update the text to emphasize:

  • That idle connections are not expected to receive application data
  • That zero-length reads (or encrypted data decrypting to zero-length) can still occur on close
  • That libcurl cannot inspect, decrypt, or act on such events for idle connections
  • And that fd reuse after kernel close is the underlying risk applications need to account for

I’ll push a small doc fix shortly.

@jayvenn21
jayvenn21 force-pushed the docs-idle-connection-socket-callbacks branch 2 times, most recently from bd79658 to 6b1fd51 Compare February 5, 2026 13:30
@jayvenn21

Copy link
Copy Markdown
Contributor Author

Looks like the Alpine musl + c-ares DNS flake (test 2103).
Could someone rerun the Alpine job?

@testclutch

Copy link
Copy Markdown

Analysis of PR #20521 at 6b1fd514:

Test 2103 failed, which has NOT been flaky recently, so there could be a real issue in this PR. Note that this test has failed in 12 different CI jobs (the link just goes to one of them).

Generated by Testclutch

@jayvenn21

Copy link
Copy Markdown
Contributor Author

@testclutch Thanks for the heads-up.
This PR is documentation-only and does not touch resolver, socket, or test code.
Given that test 2103 is failing consistently across multiple Alpine/musl + c-ares jobs, this looks like an environment/test expectation issue rather than a change introduced here.
Happy to help investigate if you think it’s related, otherwise I assume this needs a CI rerun or maintainer-side follow-up.

Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
@jayvenn21

Copy link
Copy Markdown
Contributor Author

I’ve updated the documentation to align more closely with the intended contract and wording expectations.

Changes in this revision:
• Removed references to “observed behavior” and reframed the text to describe how libcurl behaves from the API/user perspective.
• Clarified that socketp may be NULL after CURL_POLL_REMOVE, and that pointers previously assigned via curl_multi_assign() are forgotten at that point.
• Tightened language to avoid speculation about kernel behavior or fd reuse, focusing instead on what applications can and cannot rely on when sockets are no longer monitored.
• Simplified wording to be explicit about state transitions (active → idle) without implying unsupported usage.

Please let me know if this better matches the documentation style you’re aiming for, or if further tightening is needed.

Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
@jayvenn21

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review as that helped clarify the scope here.

I’ve updated the PR to:
• Remove CURL_POLL_REMOVE and socket-function-related discussion from the close socket callback man page
• Limit the documentation to the defined behavior and guarantees of CURLOPT_CLOSESOCKETFUNCTION
• Apply the suggested wording changes directly

Any remaining questions about idle connection observability probably belong in the socket function docs or a separate design discussion, so I’ve kept this PR narrowly scoped.

Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
@jayvenn21
jayvenn21 force-pushed the docs-idle-connection-socket-callbacks branch from 8a5a1cd to c2c8195 Compare February 9, 2026 15:35
Comment thread docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.md Outdated
@clayjohn

Copy link
Copy Markdown

Hi @bagder I hate to be the bearer of bad news. But Jayvenn21 is using some sort of continuous AI agent to interact on Github. I highly doubt there is a human reading any of the extensive review comments that you have left :( More likely your comments are being fed directly into an AI chatbot which is generating and pushing further patches.

Since you have left such extensive reviews, maybe the final result is fine, but I would hate to see you waste further time trying to give nice and helpful feedback to an AI chat bot who will not learn from this interaction and will never become a future contributor.

We just banned this user from the Godot repo after they replied to our admonition for submitting non-functional AI-generated code with an AI-generated (non)apology. Example here: godotengine/godot#116056

@jayvenn21

Copy link
Copy Markdown
Contributor Author

Hi @clayjohn fyi and just to clarify, I’m actively reading and responding to review feedback myself. I do use tooling to help reason and personally go through unfamiliar code paths, but all changes are reviewed, understood, and implemented by me. If not, then I wouldn't personally take my time out and read through the comments and iterate over them. Happy to clarify or adjust anything if there are concerns.

Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated

@bagder bagder left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some ideas to improve this

CURL_POLL_REMOVE, the application must stop monitoring that socket on
libcurl's behalf. libcurl does not track idle connections. The pointer
previously assigned to the socket with curl_multi_assign(3) is forgotten by
libcurl.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This note seems to be better placed in the curl_multi_assign(3) man page!


When using the multi interface, the close socket callback is invoked when
libcurl closes a socket it owns. The callback and CURLOPT_CLOSESOCKETDATA(3)
are copied from the *first* easy handle that creates the connection;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
are copied from the *first* easy handle that creates the connection;
are copied from the *first* easy handle that creates the socket used for a connection;

are copied from the *first* easy handle that creates the connection;
changing this option on a subsequent easy handle that reuses the same
connection has no effect for that connection. The callback is stored with
the connection because the connection may outlive the easy handle that

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
the connection because the connection may outlive the easy handle that
the connection because the connection and its associated socket may outlive the easy handle that

changing this option on a subsequent easy handle that reuses the same
connection has no effect for that connection. The callback is stored with
the connection because the connection may outlive the easy handle that
created it, so that libcurl can still invoke it when the connection is

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
created it, so that libcurl can still invoke it when the connection is
created it, so that libcurl can still invoke it when the socket is

inherited by a new connection and that connection may live longer
than the transfer itself in the multi/share handle's connection cache.

# NOTES ON IDLE CONNECTIONS

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This note seems to be about reused connections, not idle ones.

Suggested change
# NOTES ON IDLE CONNECTIONS
# NOTES ON CONNECTION REUSE

jayvenn21 and others added 7 commits June 19, 2026 11:39
Document observed behavior around idle connections and socket callbacks:
- When CURLOPT_CLOSESOCKETFUNCTION is copied and when it is invoked
- How idle connections interact with CURLMOPT_SOCKETFUNCTION
- Why read/error events may still occur on sockets removed from polling

No functional changes.
Co-authored-by: Daniel Stenberg <daniel@haxx.se>
@jayvenn21
jayvenn21 force-pushed the docs-idle-connection-socket-callbacks branch from 9713813 to 9c58ed9 Compare June 19, 2026 15:45
@jayvenn21

Copy link
Copy Markdown
Contributor Author

Thanks for the update and feedback and I updated the docs with the suggested changes.

I renamed the close socket note to connection reuse and adjusted the wording around the first easy handle/socket lifetime. also, I've rebased on current master; the curl_multi_assign(3) note is covered there now, so this PR no longer changes that file.

Comment thread docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.md Outdated
Comment thread docs/libcurl/opts/CURLOPT_CLOSESOCKETFUNCTION.md Outdated
@rakshasa

rakshasa commented Aug 2, 2026

Copy link
Copy Markdown

https://github.com/rakshasa/libtorrent/blob/master/src/net/curl_socket.cc#L377-L450

The above code was necessary to reliably detect libcurl sockets that circumvent the CURLOPT_OPENSOCKETFUNCTION callback. (macos/bsd/linux)

@jayvenn21

jayvenn21 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

thanks i've taken a look and updated based on both of the above comments.

I removed the extra CURL_POLL_REMOVE sentence from the socket callback page since it was just restating the existing contract. Also, I've reworded the close socket callback note, so that the generic behavior is not described as multi-only.

@bagder bagder closed this in 3bc9364 Aug 20, 2026
@bagder

bagder commented Aug 20, 2026

Copy link
Copy Markdown
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

6 participants