feat(pubsub): inline Pub/Sub APIs for exact channels - #308
Conversation
Signed-off-by: Alex Le <alex.le@improving.com>
jamesx-improving
left a comment
There was a problem hiding this comment.
CHANGELOG.md still needs a 1.1.0 entry for this — it's the largest user-facing change in the release and the checklist box is unchecked.
On your open question: milliseconds is the right wire unit — glide-core parses the last argument as a u64 ms count and Go's peer API names the parameter timeoutMs — but timeout: reads as seconds next to every other timeout in this client, so consider renaming the kwarg timeout_ms: while the API is still unreleased.
| include FunctionCommands | ||
| # TODO: https://github.com/valkey-io/valkey-glide-ruby/issues/135 | ||
| # include PubSubCommands | ||
| include PubSubCommands |
There was a problem hiding this comment.
This also mixes PubSubCommands into Valkey::Pipeline (pipeline.rb:7 includes Commands), which never assigns @pubsub — so r.pipelined { |p| p.publish("m", "c") } raises NoMethodError: undefined method 'publish' for nil and aborts the whole batch, where PUBLISH would otherwise pipeline fine.
Suggested fix: have PubSubCommands#publish call send_command(RequestType::PUBLISH, [channel.to_s, message.to_s]) on self (keeping the sharded: guard in the mixin) so it works for both the client and a pipeline; the subscribe/consume methods aren't pipelineable and are worth an explicit Pipeline override that raises something readable.
There was a problem hiding this comment.
This is a good point and raised an oversight in my design of the PubSub class. In order to support PubSub in Batch, we will need to refactor PubSub into a mixin. I will handle this in a follow up PR once this is merged.
There was a problem hiding this comment.
I guess it is less of an in compatibility and more that it is an anti-pattern with how the current client work. See my comment.
My suggestions is to perform this refactor after this is merged. Then proceed with support for other APIs
|
|
||
| # glide-core takes the timeout as the last command argument, in whole | ||
| # milliseconds, and reads a zero as "no deadline". | ||
| def timeout_argument(timeout) |
There was a problem hiding this comment.
timeout: nil → "0", and glide-core reads 0 as no deadline: wait_for_sync sets deadline = None and falls through to a bare notified.await, and the pubsub interception bypasses request_timeout. Since command is blocking: true, a subscribe that never reconciles (server restarted right after connect) parks the thread permanently — Timeout.timeout and Thread#raise can't interrupt a thread inside a blocking FFI call — and this PR removed the "nil blocks indefinitely" line from every public doc comment.
Suggested fix: restore that wording on the blocking methods, or default timeout: to the client's request_timeout so the failure is a Valkey::TimeoutError instead of a hang.
|
|
||
| - **Ruby 3.0+ Required:** Minimum per `valkey.gemspec` | ||
| - **FFI dependency:** `ffi ~> 1.17.0`; do not break ABI without rebuilding native lib | ||
| - **FFI bindings MUST pass `blocking: true`:** Every `attach_function` in `lib/valkey/bindings.rb` releases the GVL. Ruby-FFI defaults to `blocking: false`, which holds the GVL for the entire native call — that stalls every other Ruby thread, and deadlocks outright whenever the native call waits on a thread that needs the GVL to make progress. |
There was a problem hiding this comment.
"Every attach_function" isn't accurate — 16 of the 21 in bindings.rb still default to blocking: false, including create_client and create_client_from_uri (bindings.rb:238, :245), the two that install this Pub/Sub callback and that hold the GVL for the entire connect-and-retry window.
Suggested fix: add blocking: true to both create_client* functions, and scope the rule to calls that can actually block (connect, command, batch, script, close) — the free_* and OTel span helpers don't need it.
Signed-off-by: Alex Le <alex.le@improving.com>
Signed-off-by: Alex Le <alex.le@improving.com>
Summary
This adds the part 2 of PubSub APIs implementation plan.
Issue link
Related #135
Changes
PubSubimplemented to support the APIs, including the ffi callback.Limitations
publishtakes the message first —publish(message, channel)— matching the other GLIDE clients rather than redis-rb'spublish(channel, message).timeout:is documented and treated as milliseconds, not seconds. Worth confirming that's the intended public unit, since the rest of the client's timeouts are in seconds.Testing
bundle exec rubocop— clean.bundle exec rake test:unit— 227 tests, 480 assertions, 0 failures, 0 errors, 0 skips.test/unit/glide/pubsub_test.rbgrows by ~234 lines covering the RESP3 guard,timeout_argumentconversion and rejection, and the push handler's kind filtering / NUL-safe reads / exception swallowing.test/integration/valkey/pubsub_test.rb— new, ~277 lines, wired into the standalone suite. Covers subscribe/publish/receive round trips, blocking vs non-blocking consume, and the negative paths.Checklist
Before submitting the PR make sure the following are checked:
git commit -s) per the DCO.bundle exec rubocop) and pass.