You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement RESP3 (Redis 6.0+) protocol negotiation and all RESP3 value
types, completing Phase 6 of the implementation plan.
Protocol layer: extend the RespValue union with 8 new types (RespBoolean,
RespDouble, RespBigNumber, RespBulkError, RespVerbatimString, RespMap,
RespSet, RespPush). Parser handles all RESP3 type bytes in both the
completeness check and destructive parse. New _map_size method handles
the map wire format (count = number of pairs, elements = count * 2).
Session layer: add _SessionNegotiating state for HELLO 3 handshake with
automatic RESP2 fallback when the server doesn't support RESP3. HELLO
failure with a password configured falls through to standard AUTH.
Add on_push to the state interface for routing RESP3 push messages
separately from regular responses. _SessionSubscribed dispatches both
RespArray (RESP2) and RespPush (RESP3) pub/sub messages through a
shared _dispatch_pubsub_values method. ConnectInfo gains protocol
(ProtocolVersion) and username fields for RESP3 and ACL support.
_ResponseHandler routes RespPush before the general RespValue match arm
so push messages reach on_push rather than on_response.
Test coverage includes a Redis 5 container for HELLO fallback integration
testing and unit tests for the HELLO/AUTH command construction primitives.
Design: #2
Copy file name to clipboardExpand all lines: CLAUDE.md
+22-14Lines changed: 22 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,8 +7,8 @@ make # build and run all tests (requires Redis running)
7
7
make unit-tests # run unit tests only (no Redis needed)
8
8
make integration-tests # run integration tests only (requires Redis)
9
9
make build-examples # build example programs
10
-
make start-redis # start plaintext + SSL Redis in Docker
11
-
make stop-redis # stop and remove both Redis containers
10
+
make start-redis # start plaintext, SSL, and RESP2-only Redis in Docker
11
+
make stop-redis # stop and remove all Redis containers
12
12
make clean # clean build artifacts
13
13
```
14
14
@@ -30,22 +30,24 @@ make stop-redis
30
30
31
31
Package: `redis`
32
32
33
-
### RESP2 Protocol Layer
33
+
### Protocol Layer
34
34
35
-
-`RespValue` (union type in `resp_value.pony`): Core type for RESP2 wire format values. Union of `RespSimpleString`, `RespBulkString`, `RespInteger`, `RespArray`, `RespError`, `RespNull`.
35
+
-`RespValue` (union type in `resp_value.pony`): Core type for RESP2/RESP3 wire format values. Union of `RespSimpleString`, `RespBulkString`, `RespInteger`, `RespArray`, `RespError`, `RespNull`, `RespBoolean`, `RespDouble`, `RespBigNumber`, `RespBulkError`, `RespVerbatimString`, `RespMap`, `RespSet`, `RespPush`.
36
36
-`RespMalformed` (in `resp_value.pony`): Parser error type indicating invalid RESP data. Not part of `RespValue` — represents a protocol violation, not a valid value.
37
-
-`_RespParser` (in `_resp_parser.pony`): Two-pass parser — peek-based completeness check, then destructive parse. Returns `(RespValue | None | RespMalformed)` from a `buffered.Reader`.
37
+
-`_RespParser` (in `_resp_parser.pony`): Two-pass parser — peek-based completeness check, then destructive parse. Returns `(RespValue | None | RespMalformed)` from a `buffered.Reader`. Supports all RESP2 and RESP3 type bytes.
38
38
-`_RespSerializer` (in `_resp_serializer.pony`): Serializes commands (`Array[ByteSeq] val`) to RESP2 wire format.
39
+
-`ProtocolVersion` (type alias in `protocol_version.pony`): `(Resp2 | Resp3)`. Controls which protocol the session negotiates on connect.
39
40
40
41
### Session Layer
41
42
42
-
-`Session` (actor in `session.pony`): Main entry point. Manages connection lifecycle and pub/sub via a state machine. Implements `lori.TCPConnectionActor & lori.ClientLifecycleEventReceiver`. All state machine classes (`_SessionUnopened`, `_SessionConnected`, `_SessionReady`, `_SessionSubscribed`, `_SessionClosed`) are in `session.pony`, following the postgres pattern.
-`Session` (actor in `session.pony`): Main entry point. Manages connection lifecycle and pub/sub via a state machine. Implements `lori.TCPConnectionActor & lori.ClientLifecycleEventReceiver`. All state machine classes (`_SessionUnopened`, `_SessionNegotiating`, `_SessionConnected`, `_SessionReady`, `_SessionSubscribed`, `_SessionClosed`) are in `session.pony`, following the postgres pattern.
-`SessionStatusNotify` (in `session_status_notify.pony`): Lifecycle callback interface. All callbacks have default no-op implementations. Callbacks: `redis_session_connected`, `redis_session_connection_failed`, `redis_session_ready`, `redis_session_authentication_failed`, `redis_session_closed`.
45
46
-`ResultReceiver` (in `result_receiver.pony`): Command response callback interface. Callbacks: `redis_response`, `redis_command_failed`.
46
47
-`SubscriptionNotify` (in `subscription_notify.pony`): Pub/sub callback interface. All callbacks have default no-op implementations. Callbacks: `redis_subscribed`, `redis_unsubscribed`, `redis_message`, `redis_psubscribed`, `redis_punsubscribed`, `redis_pmessage`.
47
48
-`ClientError` (in `client_error.pony`): Client-side error trait with `SessionNotReady`, `SessionClosed`, and `SessionInSubscribedMode` primitives.
48
-
-`_ResponseHandler` (in `_response_handler.pony`): Loops `_RespParser` over a `buffered.Reader`, delivering parsed `RespValue`s to the current state. Shuts down on `RespMalformed`.
49
+
-`_ResponseHandler` (in `_response_handler.pony`): Loops `_RespParser` over a `buffered.Reader`, routing `RespPush` to `on_push` and other `RespValue`s to `on_response`. Shuts down on `RespMalformed`.
50
+
-`_BuildHelloCommand` / `_BuildAuthCommand` (primitives in `session.pony`): Build HELLO 3 and AUTH commands for protocol negotiation and authentication.
49
51
-`_IllegalState` / `_Unreachable` (in `_mort.pony`): Primitives for detecting impossible states.
50
52
51
53
### SSL/TLS
@@ -60,13 +62,18 @@ Package: `redis`
60
62
-`_ClosedState`: Mixin for the terminal state — rejects or no-ops all operations.
61
63
-`_ConnectedState`: Mixin for states with a readbuf — handles `on_received` and `_ResponseHandler` dispatch.
62
64
-`_NotReadyForCommands`: Mixin that rejects `execute()` with `SessionNotReady`.
63
-
-`_NotSubscribed`: Mixin that no-ops `subscribe`, `unsubscribe`, `psubscribe`, `punsubscribe` for states where pub/sub is not applicable.
65
+
-`_NotSubscribed`: Mixin that no-ops `subscribe`, `unsubscribe`, `psubscribe`, `punsubscribe` for states where pub/sub is not applicable. Also provides a no-op `on_push` for states that don't handle push messages (only trait that provides `on_push`, to avoid diamond inheritance in `_SessionClosed`).
Commands are pipelined in `_SessionReady`: each `execute()` call sends the command immediately over the wire without waiting for prior responses. Responses are matched to receivers in FIFO order.
82
89
83
-
In `_SessionSubscribed`, any pipelined commands that were in-flight when SUBSCRIBE was sent are drained first (Redis guarantees in-order response delivery), then incoming responses are routed as pub/sub messages.
90
+
In `_SessionSubscribed`, any pipelined commands that were in-flight when SUBSCRIBE was sent are drained first (Redis guarantees in-order response delivery), then incoming responses are routed as pub/sub messages. In RESP3 mode, pub/sub messages arrive as `RespPush` via `on_push`; in RESP2 mode they arrive as `RespArray` via `on_response`.
84
91
85
92
## Test Infrastructure
86
93
87
94
- Unit tests: `--exclude=integration/` — no external dependencies
88
95
- Integration tests: `--only=integration/` — require a running Redis server
89
96
- Test names prefixed with `integration/` for filtering
90
-
-`_RedisTestConfiguration` reads environment variables for both plaintextand SSL Redis:
97
+
-`_RedisTestConfiguration` reads environment variables for plaintext, SSL, and RESP2-only Redis:
91
98
-`REDIS_HOST` / `REDIS_PORT` — plaintext (defaults to `127.0.0.2`/`6379` on Linux)
92
99
-`REDIS_SSL_HOST` / `REDIS_SSL_PORT` — TLS (defaults to same host/`6380`)
100
+
-`REDIS_RESP2_HOST` / `REDIS_RESP2_PORT` — RESP2-only Redis 5 (defaults to same host/`6381`)
93
101
94
102
### SSL-to-Plaintext Deadlock
95
103
96
104
Do not write tests that connect with SSL to a plaintext Redis server. The TLS ClientHello is binary data with no `\r\n`, so Redis's RESP parser buffers it waiting for a line terminator. Meanwhile the SSL client waits for a ServerHello. Neither side sends more data — both block indefinitely. To test the SSL constructor path, connect to a non-listening port instead (TCP connection refused is fast and deterministic).
97
105
98
106
### CI
99
107
100
-
Both `pr.yml` and `breakage-against-ponyc-latest.yml` use the `shared-docker-ci-standard-builder-with-libressl-4.2.0` image (for ssl support) and two Redis service containers: `redis` (plaintext) and `redis-ssl` (TLS via `ghcr.io/ponylang/redis-ci-redis-ssl:latest`). Integration tests receive `REDIS_HOST=redis`, `REDIS_PORT=6379`, `REDIS_SSL_HOST=redis-ssl`, and `REDIS_SSL_PORT=6379`. All make targets pass `ssl=libressl`.
108
+
Both `pr.yml` and `breakage-against-ponyc-latest.yml` use the `shared-docker-ci-standard-builder-with-libressl-4.2.0` image (for ssl support) and three Redis service containers: `redis` (plaintext, Redis 7), `redis-ssl` (TLS via `ghcr.io/ponylang/redis-ci-redis-ssl:latest`), and `redis-resp2` (Redis 5, RESP2-only for HELLO fallback testing). Integration tests receive `REDIS_HOST=redis`, `REDIS_PORT=6379`, `REDIS_SSL_HOST=redis-ssl`, `REDIS_SSL_PORT=6379`, `REDIS_RESP2_HOST=redis-resp2`, and `REDIS_RESP2_PORT=6379`. All make targets pass `ssl=libressl`.
101
109
102
110
The `redis-ssl` CI image is built via `build-ci-image.yml` (manually triggered `workflow_dispatch`). Source: `.ci-dockerfiles/redis-ssl/Dockerfile`. Build locally with `.ci-dockerfiles/redis-ssl/build-and-push.bash`.
0 commit comments