Skip to content

0.19 deadlocks on JRuby: the websocket handshake write never returns #676

Closed

Description

@mshibuya

0.19.0/0.19.1 hang on JRuby before the browser is usable — Ferrum::Browser.new alone is enough. The main thread is stuck in the very first write to the CDP socket, the websocket upgrade request (207 bytes), and the call to write_nonblock never returns:

#<Thread:0x...@main run>
  ferrum-0.19.1/lib/ferrum/client/web_socket.rb:153:in `write'
  websocket-driver-0.8.0-java/lib/websocket/driver/client.rb:55:in `start'
  ferrum-0.19.1/lib/ferrum/client/web_socket.rb:54:in `block in initialize'

#<Thread:0x... sleep>   # the reader started one line earlier
  ferrum-0.19.1/lib/ferrum/client/web_socket.rb:208:in `readpartial'

The change it comes from is 080aadc (#644), which replaced the plain @sock.write in Client::WebSocket#write with a write_nonblock(data, exception: false) + IO.select loop so a stalled browser can't hold the send lock forever. Prepending the 0.18 implementation back onto Ferrum::Client::WebSocket is enough to make 0.19.1 work — same script, same gems, page open in 0.9s. 0.17.1 and 0.18 are fine as released.

The platform difference behind it: on JRuby, a socket that another thread is blocked reading with readpartial takes no writes from other threads, while CRuby writes straight through. ferrum starts its reader thread immediately before the handshake is written, so the new loop meets exactly that state. A reduced TCPSocket-pair script blocks on the plain write too, so I can't claim that's the whole mechanism — but the replacement above is reproducibly the dividing line.

Environment: jruby-9.4.14.0 (3.1.7), OpenJDK 23.0.2, macOS arm64, Chrome 156, websocket-driver 0.8.0-java.

Also on GitHub Actions ubuntu-latest with jruby-9.4: after cuprite 0.19 / ferrum 0.19.1 were released, rails_admin's JRuby jobs stopped finishing — this one ran out the 6-hour default timeout with nothing printed, 4 attempts in a row, while every CRuby job stayed green on the same lockfile. With cuprite pinned to < 0.19 the same job finishes in 8 minutes.

JRuby isn't in your test matrix, so this is a heads-up rather than a complaint. Happy to send a PR if you'd like one — e.g. taking the nonblocking path only when a timeout was actually passed, or falling back to a plain write on JRuby.

Activity

  1. route commented on Oct 26, 2026

    @route
    Member

    @mshibuya thanks for the detailed report, it was spot on. Reproduced it on JRuby 10.1.2: IO#readpartial waits in a NIO select holding the channel's blocking lock, and write_nonblock needs that same lock, so the handshake write never returns.

    The fix is on the 0-19-stable branch, which will become 0.19.2. #644 is reverted and frames are now written by a dedicated writer thread with plain blocking writes, so the socket is only read by one thread and written by another, and a stalled browser still can't freeze the client.

    Could you give it a try on JRuby as soon as you can?

    gem "ferrum", github: "rubycdp/ferrum", branch: "0-19-stable"

    I'll release 0.19.2 once you confirm it works for you.

  2. route commented on Oct 26, 2026

    @route
    Member

    I'll go ahead and merge this #680
    It's next planned 0.20 release that I was going to ship, but this one 0-19-stable is going to be 0.19.2 if all works.

  3. reopened this on Oct 26, 2026
  4. mshibuya commented on Oct 26, 2026

    @mshibuya
    Author

    Thank you for the quick fix!
    Verified that your change works, as you saw in the draft rails_admin PR you opened 👍

  5. route commented on Oct 26, 2026

    @route
    Member

    Yep, thanks! I released 0.19.2 so you can start using it, fix is also going to be included in 0.20. Meanwhile I noticed you pass flatten: false... I'll open a draft for it, with explanation

  6. added a commit that references this issue on Oct 27, 2026
    3fb1a00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions