[#117392] [Ruby master Feature#20405] Inline comments — "nobu (Nobuyoshi Nakada) via ruby-core" <ruby-core@...>

Issue #20405 has been reported by nobu (Nobuyoshi Nakada).

11 messages 2024/04/01

[#117434] [Ruby master Bug#20409] Missing reporting some invalid breaks — "kddnewton (Kevin Newton) via ruby-core" <ruby-core@...>

Issue #20409 has been reported by kddnewton (Kevin Newton).

8 messages 2024/04/03

[#117458] [Ruby master Bug#20414] `Fiber#raise` should recurse to `resumed_fiber` rather than failing. — "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>

Issue #20414 has been reported by ioquatix (Samuel Williams).

10 messages 2024/04/07

[#117469] [Ruby master Feature#20415] Precompute literal String hash code during compilation — "byroot (Jean Boussier) via ruby-core" <ruby-core@...>

Issue #20415 has been reported by byroot (Jean Boussier).

10 messages 2024/04/09

[#117494] [Ruby master Bug#20421] String#index and String#byteindex don't clear `$~` when offset > size (or bytesize) — "andrykonchin (Andrew Konchin) via ruby-core" <ruby-core@...>

Issue #20421 has been reported by andrykonchin (Andrew Konchin).

7 messages 2024/04/11

[#117498] [Ruby master Feature#20425] Optimize forwarding callers and callees — "tenderlovemaking (Aaron Patterson) via ruby-core" <ruby-core@...>

Issue #20425 has been reported by tenderlovemaking (Aaron Patterson).

14 messages 2024/04/11

[#117531] [Ruby master Bug#20431] Ruby 3.3.0 build fail with make: *** [io_buffer.o] Error 1 — "shubham_yadav (Shubham Yadav) via ruby-core" <ruby-core@...>

SXNzdWUgIzIwNDMxIGhhcyBiZWVuIHJlcG9ydGVkIGJ5IHNodWJoYW1feWFkYXYgKFNodWJoYW0g

11 messages 2024/04/16

[#117564] [Ruby master Bug#20433] Hash.inspect for some hash returns syntax invalid representation — "tompng (tomoya ishida) via ruby-core" <ruby-core@...>

Issue #20433 has been reported by tompng (tomoya ishida).

15 messages 2024/04/17

[#117572] [Ruby master Misc#20435] DevMeeting-2024-06-13 — "mame (Yusuke Endoh) via ruby-core" <ruby-core@...>

Issue #20435 has been reported by mame (Yusuke Endoh).

12 messages 2024/04/17

[#117588] [Ruby master Misc#20436] DevMeeting at RubyKaigi 2024 — "ko1 (Koichi Sasada) via ruby-core" <ruby-core@...>

SXNzdWUgIzIwNDM2IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IGtvMSAoS29pY2hpIFNhc2FkYSkuDQoN

14 messages 2024/04/18

[#117624] [Ruby master Bug#20440] `super` from child class passing keyword arg as Hash if in a method with passthrough args called from base class — "ozydingo (Andrew Schwartz) via ruby-core" <ruby-core@...>

Issue #20440 has been reported by ozydingo (Andrew Schwartz).

7 messages 2024/04/20

[#117644] [Ruby master Feature#20443] Allow Major GC's to be disabled — "eightbitraptor (Matthew Valentine-House) via ruby-core" <ruby-core@...>

Issue #20443 has been reported by eightbitraptor (Matthew Valentine-House).

25 messages 2024/04/22

[#117646] [Ruby master Bug#20444] Kernel#loop: returning the "result" value of StopIteration doesn't work when raised directly — "esad (Esad Hajdarevic) via ruby-core" <ruby-core@...>

Issue #20444 has been reported by esad (Esad Hajdarevic).

9 messages 2024/04/22

[#117653] [Ruby master Bug#20446] OUtdated https://cache.ruby-lang.org/pub/ruby/index.txt — "vo.x (Vit Ondruch) via ruby-core" <ruby-core@...>

Issue #20446 has been reported by vo.x (Vit Ondruch).

7 messages 2024/04/23

[#117657] [Ruby master Bug#20447] Ruby 3.3.1 broken on i686 — "vo.x (Vit Ondruch) via ruby-core" <ruby-core@...>

SXNzdWUgIzIwNDQ3IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IHZvLnggKFZpdCBPbmRydWNoKS4NCg0K

15 messages 2024/04/23

[#117658] [Ruby master Feature#20448] Make coverage event hooking C API public — "ms-tob (Matt S) via ruby-core" <ruby-core@...>

Issue #20448 has been reported by ms-tob (Matt S).

9 messages 2024/04/23

[#117674] [Ruby master Bug#20450] Ruby 3.1.1 broken with bootsnap — "philippe.bs.noel@... (Philippe Noel) via ruby-core" <ruby-core@...>

Issue #20450 has been reported by philippe.bs.noel@tutanota.com (Philippe Noel).

11 messages 2024/04/24

[#117684] [Ruby master Bug#20452] Ruby 3.3 on Alpine Linux results in a relatively shallow SystemStackError exception — "Earlopain (A S) via ruby-core" <ruby-core@...>

Issue #20452 has been reported by Earlopain (A S).

12 messages 2024/04/24

[#117711] [Ruby master Bug#20456] Hash can get stuck marked as iterating through process forking — "blowfishpro (Talia Wong) via ruby-core" <ruby-core@...>

Issue #20456 has been reported by blowfishpro (Talia Wong).

7 messages 2024/04/25

[ruby-core:117544] [Ruby master Feature#20215] Introduce `IO#readable?`

From: "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>
Date: 2024-04-17 04:25:17 UTC
List: ruby-core #117544
Issue #20215 has been updated by ioquatix (Samuel Williams).


After considering the various use cases I have, I think the easiest to describe problem is knowing whether a connection is still "connected" or not, i.e. whether read will definitely fail or might succeed.

I added a full working example of the problem here: <https://github.com/socketry/protocol-http1/blob/540551bdbdbca06d746b4c4545af2d73ebcc7dcc/examples/http1/client.rb#L70>. You can try different implementation of `IO#readable?` to see the behaviour.

The example demonstrates HTTP/1 persistent connection handling, where the remote server may at any time disconnect. In `server.rb`, it has a 50% chance of disconnecting. `client.rb` makes 10 connections, and tries to use persistent connections.

The key problem that I'm trying to address, is that there is no protocol-level mechanism to advertise that the remote server is closing the connection (in contrast, HTTP/2 has such a feature). So, what that means, is in the request loop, when we want to write the request, we want to ensure, with the best effort possible, that the connection is still alive and working. That is the purpose of `IO#readable?` in this context - whether there is a significantly good chance that writing an HTTP request will be successful.

In practice, persistent connections may sit in a connection pool for minutes or hours, and thus when you come to write a request, there is no easy operation to check "Is this connection still working?". That is the purpose of `IO#readable?`. Specifically, before writing a request, we check if the connection is still readable.

The logic for "Is the connection still readable?" depends on the situation and the underlying IO. As you know there are many different semantics for handling Sockets, Pipes, and so on, and we even provide our own blended semantics in `StringIO`. I'd like to introduce `IO#readable?`, `BasicSocket#readable?` based on `recv_nonblock` and `StringIO#readable?` which is similar to non-blocking `eof?`.

In other words, in the case of sockets, `BasicSocket#readable?` is querying the operating system to find out if the TCP connection is still working (i.e. not closed explicitly).

It's true that this can be a race condition, for example the TCP reset/shutdown could be delayed or received while writing the request. However, it's still better to prevent writing the request entirely if possible. That's because not all requests are idempotent e.g. POST requests for handling payments. It's much better to know ahead of time that the request will fail because the persistent connection has been shut down, than to find out half way through writing the non-idempotent request.

Example output from the client:

```
> bundle exec client.rb
Connected to #<Socket:0x0000754fc63d8b60> #<Addrinfo: 127.0.0.1:8080 TCP>
Writing request...
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Client is not readable, closing...
Reconnecting...
Connected to #<Socket:0x0000754fc63d0fa0> #<Addrinfo: 127.0.0.1:8080 TCP>
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Client is not readable, closing...
Reconnecting...
Connected to #<Socket:0x0000754fc644d2d0> #<Addrinfo: 127.0.0.1:8080 TCP>
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Client is not readable, closing...
Reconnecting...
Connected to #<Socket:0x0000754fc6448cf8> #<Addrinfo: 127.0.0.1:8080 TCP>
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Client is not readable, closing...
Reconnecting...
Connected to #<Socket:0x0000754fc6443f00> #<Addrinfo: 127.0.0.1:8080 TCP>
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Client is not readable, closing...
Reconnecting...
Connected to #<Socket:0x0000754fc64bfe20> #<Addrinfo: 127.0.0.1:8080 TCP>
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Writing request...
Client is not readable, closing...
Reconnecting...
Connected to #<Socket:0x0000754fc64bc2e8> #<Addrinfo: 127.0.0.1:8080 TCP>
Reading response...
Got response: ["HTTP/1.1", 200, "OK", #<Protocol::HTTP::Headers [["Content-Type", "text/plain"]]>, #<Protocol::HTTP1::Body::Fixed length=11 remaining=11>]
Hello World
Closing client...
Exiting.
```

Note that `Client is not readable, closing...` indicates that the client was closed before the request was written, which is the ideal case.

----------------------------------------
Feature #20215: Introduce `IO#readable?`
https://bugs.ruby-lang.org/issues/20215#change-107937

* Author: ioquatix (Samuel Williams)
* Status: Open
----------------------------------------
There are some cases where, as an optimisation, it's useful to know whether more data is potentially available.

We already have `IO#eof?` but the problem with using `IO#eof?` is that it can block indefinitely for sockets.

Therefore, code which uses `IO#eof?` to determine if there is potentially more data, may hang.

```ruby
def make_request(path = "/")
  client = connect_remote_host
  # HTTP/1.0 request:
  client.write("GET #{path} HTTP/1.0\r\n\r\n")

  # Read response
  client.gets("\r\n") # => "HTTP/1.0 200 OK\r\n"

  # Assuming connection close, there are two things the server can do:
  # 1. peer.close
  # 2. peer.write(...); peer.close

  if client.eof? # <--- Can hang here!
    puts "Connection closed"
    # Avoid yielding as we know there definitely won't be any data.
  else
    puts "Connection open, data may be available..."
    # There might be data available, so yield.
    yield(client)
  end
ensure
  client&.close
end

make_request do |client|
  puts client.read # <--- Prefer to wait here.
end
```

The proposed `IO#readable?` is similar to `IO#eof?` but rather than blocking, would simply return false. The expectation is the user will subsequently call `read` which may then wait.

The proposed implementation would look something like this:

```ruby
class IO
  def readable?
    !self.closed?
  end
end

class BasicSocket
  # Is it likely that the socket is still connected?
  # May return false positive, but won't return false negative.
  def readable?
    return false unless super
    
    # If we can wait for the socket to become readable, we know that the socket may still be open.
    result = self.recv_nonblock(1, MSG_PEEK, exception: false)
    
    # No data was available - newer Ruby can return nil instead of empty string:
    return false if result.nil?
    
    # Either there was some data available, or we can wait to see if there is data avaialble.
    return !result.empty? || result == :wait_readable
    
  rescue Errno::ECONNRESET
    # This might be thrown by recv_nonblock.
    return false
  end
end
```

For `IO` itself, when there is buffered data, `readable?` would also return true immediately, similar to `eof?`. This is not shown in the above implementation as I'm not sure if there is any Ruby method which exposes "there is buffered data".



-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/

In This Thread