Repository navigation
redis.stackexchange: StackExchange.Redis with its connections on the reactor's ring (experimental) - #335
Merged
Conversation
…se transport is a socket on a reactor StackExchange.Redis 3.x has an experimental transport seam (SER009): a Tunnel can hand a multiplexer a whole DuplexTransport through ConnectTransportAsync, and StackExchange.Redis then opens no socket of its own. Outbound the transport is an IBufferWriter staged from any thread with an explicit Flush; inbound is push - OnReceived with transport-owned bytes, OnBatchEnd, OnClosed - and StackExchange.Redis parses and completes commands on the thread that pushed them. SocketSet's SocketSet.StackExchange.Redis is the reference implementation. IoxideTunnel implements it on a reactor: the transport is a RingSocket, its receives complete on the reactor and push into StackExchange.Redis there, one OnBatchEnd is posted per loop turn that delivered anything, and Flush hands the staged bytes to the ring as one send (bytes flushed while a send is in flight queue behind it). With one multiplexer per reactor, made in OnStart, a handler's commands are staged, sent and completed on its own thread. TCP without TLS for now: a configuration asking for TLS is refused with a message saying so. StackExchange.Redis is pinned to 3.4.0, since the seam may change in any release. Tests run against the suite's redis: SET/GET completed on the reactor's thread, 1000 concurrent INCRs, a 1 MiB value, pub/sub, a connection the server kills coming back through the tunnel, and the TLS refusal. The thread test is discriminating: on StackExchange.Redis's own sockets the completion is posted from another thread and it fails. A pending test pins a StackExchange.Redis gap the transport surfaced: RecordConnectionFailed reports a failure only for a connection with a Stream, so a command caught by a transport's close is cancelled instead of failing with the connection's error.
…nections on the reactor's ring
This was referenced Oct 12, 2026
MDA2AV
added a commit
that referenced
this pull request
Oct 12, 2026
ioxide.redis is weight the project does not want to carry: a whole Redis client to maintain - five open fixes against it - when StackExchange.Redis can run on the reactor's ring instead (#335, ioxide.redis.stackexchange). Removed: the package, its Playground sample (Clients/Redis) and the Env helper only it used, its tests (the Redis suite stays, empty, for the transport's tests), its CI pack step, its bench run and row, its pane on the docs page, and docs/learn's two Redis pages with their links. The passing Redis mentions in the Http3/Nghttp3Buffered sample's comments go too. The versions already on nuget.org are untouched.
# Conflicts: # Playground/README.md # README.md # docs/assets/style.css # docs/index.html # ioxide.slnx # scripts/gen-docs-panes.py # tests/Ioxide.Tests.Redis/Ioxide.Tests.Redis.csproj # tests/Ioxide.Tests.Redis/Program.cs # tests/README.md
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
StackExchange.Redis 3.x ships an experimental transport seam (SER009; #3152, #3173, #3188). A
Tunnelcan hand a multiplexer a wholeDuplexTransportthroughConnectTransportAsync, and StackExchange.Redis then opens no socket of its own:IBufferWriter<byte>, staged from any thread, with an explicitFlush().OnReceivedwith transport-owned bytes, thenOnBatchEndandOnClosed. StackExchange.Redis parses the bytes and completes the commands on the thread that pushed them.Marc Gravell's SocketSet has the reference implementation,
SocketSet.StackExchange.Redis. This PR addsioxide.redis.stackexchange: the same seam on a reactor.How it works
IoxideTunnel(reactor): each connection is aRingSocketon that reactor. A name is resolved off the reactor, then each address is tried in turn.OnBatchEndis posted per loop turn that delivered anything, so the responses a burst produces go out in one flush.Flush()hands the staged bytes to the ring as one send. Bytes flushed while a send is in flight queue behind it.OnClosedfires once.IoxideRedis.ConnectAsync(reactor, options)sets the tunnel and connects. With one multiplexer per reactor, made inOnStart, a handler's commands are staged, sent, parsed and completed on its own thread.Bench
The new
Playground/Clients/RedisStackExchangehas a knob,PLAYGROUND_REDIS_TRANSPORT=sockets, that runs the same handler on StackExchange.Redis's own sockets.Setup:
wrk -t2 -c64 -d10sagainst a local redis 7;/procover the run;Clients/Redis, 4 connections per reactor)This is one machine over loopback, with redis on the same box. The ioxide.redis row is its own sample's GET route: the same shape, through a different handler. ioxide.redis has since been dropped (#337); the row stays as the reference it was measured against.
Tests
These are in the Redis suite and run against the suite's redis; with ioxide.redis dropped (#337), they are the suite. 6 pass, 1 pending:
CLIENT KILL) comes back through the tunnel.The thread test was confirmed both ways. On StackExchange.Redis's own sockets the completion is posted from another thread and the test fails; through the tunnel it is posted from the reactor and the test passes.
A StackExchange.Redis gap this surfaced
RecordConnectionFailed(PhysicalConnection.cs:571in 3.4.0) reports a failure only when_ioStream is not null. A transport connection never gets aStream, so when its transport closes:ConnectionFailedevent is raised;TaskCanceledException) instead of failing withRedisConnectionException: SocketClosed, which is what StackExchange.Redis's own sockets give.Reconnection still works. The gap affects every transport, SocketSet's included. A pending test pins it, and it will fail - asking to become a
Test- once StackExchange.Redis reports transport closes too.Not covered