Skip to content

Propagate read errors in IO backend#7967

Merged
headius merged 3 commits intojruby:masterfrom
headius:socket_rst_fixes
Oct 18, 2023
Merged

Propagate read errors in IO backend#7967
headius merged 3 commits intojruby:masterfrom
headius:socket_rst_fixes

Conversation

@headius
Copy link
Member

@headius headius commented Oct 18, 2023

The JRuby IO subsystem simulates a POSIX environment by capturing Java exceptions and translating them to their equivalent errno values. When a read produces an error, we return -1 and set the local shim's errno. This can then be used by POSIX-like consumers of the API to process the error without exception-handling.

However in the case of IO read operations, we were not appropriately checking the errno result for a failed (i.e. -1) read. Instead we assumed it was always an EAGAIN-like result and proceeded to select for read on the socket. Under normal circumstances, like the socket being fully closed on both ends, that select would raise an error anyway so no errors were lost.

In the example from #7961 (and likely the case in #6346), only one side of the socket was closed using SO_LINGER=0, causing an RST packet to be returned to future reads, manifested by the read operation as a SocketException("Connection reset"). The logic described above then proceeded directly to a select operation, which did not in this case raise any error. Instead, select reported that the socket was still readable, so we loop back to the read ad infinatum.

The changes here make three changes:

  • Properly handle IOExceptions with a message of "Connection reset" by producing an ECONNRESET errno. The translation logic from Java exceptions to errnos was also cleaned up a bit.
  • When read returns -1, only proceed to selection if the errno result was EAGAIN/EWOULDBLOCK, otherwise raising the appropriate error.
  • An unrelated change to avoid using an iterator to check select results.

Fixes #7961 and #6346.

This can be improved in 11 by using
select(Consumer<SelectionKey>) and related forms, avoiding the
Set as well.
The case of SocketException("Connection reset") was not handled,
and IOException only checked for one message. This combines the
logic for errnoFromException with the logic for errnorFromMessage.

The exception handling stack was also cleaned up here.
@headius
Copy link
Member Author

headius commented Oct 18, 2023

Risks from this PR:

  • We will only proceed to waitReadable if errno is EAGAIN/EWOULDBLOCK, which may not cover other cases that could be selected and read again. I do not know of such a case offhand.
  • I could have screwed up the errno translation, but most of the refactor was done by IntelliJ.

Other notes:

  • I included a small change here to use Set.contains rather than create an Iterator to get the sole contained value. We can further improve this by moving to Java 11 and using Selector.select(Consumer) which will eliminate the Set as well.

This code previously assumed that all -1 return values from read
meant EAGAIN and that we should proceed to selection. If the read
result was actually equivalent to EAGAIN, this was correct. If the
read result was some other error, we still would proceed to the
selection, and in most cases the same or similar error would be
raised at that point. However at least one particular case: a
"Connection reset" event caused by the other end sending RST (due
in this case to SO_LINGER=0), the selection operation did *not*
raise any error, and appeared to be readable. As a result we went
back to the read, another "Connection reset" was triggered, we
assumed it was EAGAIN, and so on.

This commit checks the errno for exactly matching EAGAIN or
EWOULDBLOCK before performing the selection, otherwise raising an
error for the failed read.

Fixes jruby#7961
@headius headius merged commit f72776a into jruby:master Oct 18, 2023
@headius headius deleted the socket_rst_fixes branch October 18, 2023 21:52
@headius headius restored the socket_rst_fixes branch October 31, 2023 18:27
@headius headius deleted the socket_rst_fixes branch October 31, 2023 18:28
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.

Socket.each loops after receiving RST pegging CPU at 100%

2 participants