Skip to content

feat: redis-style pub/sub - #55

Merged
kacy merged 8 commits into
mainfrom
feat/pubsub
Feb 8, 2026
Merged

feat: redis-style pub/sub#55
kacy merged 8 commits into
mainfrom
feat/pubsub

Conversation

@kacy

@kacy kacy commented Feb 8, 2026

Copy link
Copy Markdown
Owner

summary

adds redis-compatible pub/sub messaging with full command support:

  • SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH — core pub/sub commands
  • PUBSUB CHANNELS, PUBSUB NUMSUB, PUBSUB NUMPAT — introspection commands
  • PubSubManager — DashMap-backed channel registry with tokio broadcast for lock-free fan-out
  • subscriber mode — connection state machine using tokio::select! to multiplex incoming commands and pushed messages
  • glob pattern matching — custom implementation supporting *, ?, [abc], [^abc], \x (matches redis PSUBSCRIBE behavior)
  • cleanup on disconnect — subscriptions automatically removed when client disconnects
  • concurrent mode — PUBLISH and PUBSUB introspection work; subscribe commands return error (subscriber mode requires full connection handler)

also updates READMEs: 65+ → 76 commands, 609 → 639 tests, ~14k → ~21k LOC.

what was tested

  • 13 unit tests for PubSubManager (glob matching, subscribe/publish, multiple subscribers, pattern matching, unsubscribe, subscription counts)
  • 18 protocol parsing tests (subscribe, unsubscribe, psubscribe, punsubscribe, publish, pubsub channels/numsub/numpat with edge cases)
  • end-to-end with redis-cli: SUBSCRIBE receives PUBLISH messages, PSUBSCRIBE glob patterns match correctly, PUBSUB CHANNELS/NUMSUB/NUMPAT return correct results
  • full workspace test suite passes (639 tests), zero clippy warnings

design considerations

  • tokio broadcast channel per subscription for fan-out. buffer capacity is 256 messages — lagged subscribers miss messages (matching redis behavior)
  • FuturesUnordered for efficient async multiplexing across all broadcast receivers (no busy-wait polling)
  • subscription count only decremented when channel actually exists in registry (avoids counter drift on double-unsubscribe)
  • Bytes::from_static / Bytes::copy_from_slice used in serialization to avoid unnecessary String allocations
  • the PubSubManager is shared across all connections via Arc, same as ServerContext

kacy added 8 commits February 8, 2026 08:23
add SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, and PUBLISH
commands to the protocol layer. parsing only — no execution yet.
introduces PubSubManager backed by DashMap + tokio broadcast for
lock-free fan-out messaging. supports channel subscriptions, glob
pattern matching (PSUBSCRIBE), and concurrent publish. includes
comprehensive unit tests for glob matching and pub/sub semantics.
- server.rs: create and pass PubSubManager to all connections
- connection.rs: subscriber mode state machine with select! loop,
  push message serialization, subscription cleanup on disconnect
- concurrent_handler.rs: PUBLISH support, sub commands return error
- remove unused imports in pubsub.rs
- remove unused parameters from handle_subscriber_mode
- fix formatting in protocol parse functions
covers subscribe, unsubscribe, psubscribe, punsubscribe, publish
with edge cases: no args, multiple channels, wrong arity, case
insensitivity.
completes the pub/sub command set with redis-compatible introspection:
- PUBSUB CHANNELS [pattern] — list active channels with optional glob
- PUBSUB NUMSUB [channel ...] — subscriber counts per channel
- PUBSUB NUMPAT — count of active pattern subscriptions

includes parsing tests and end-to-end verification with redis-cli.
- fix race condition: unsubscribe/punsubscribe now only decrement
  subscription_count when the channel actually exists in the registry
- replace busy-wait polling in recv_any_message with FuturesUnordered
  for proper async multiplexing across all broadcast receivers
- remove unnecessary String allocations in serialize functions (use
  Bytes::copy_from_slice and Bytes::from_static instead)
- simplify is_subscribe_frame using eq_ignore_ascii_case on byte slices
  instead of allocating a String for case comparison
- simplify enter_sub detection with iter().any()
- organize imports (group std::time together)
- README.md: 65+ → 76 commands, 609 → 639 tests, ~14k → ~21k LOC
- README.md: add pub/sub to feature list
- bench/README.md: update command count, remove pub/sub from
  dragonfly advantages (ember now has it)
@kacy
kacy merged commit 54d0520 into main Feb 8, 2026
5 checks passed
@kacy
kacy deleted the feat/pubsub branch February 8, 2026 13:58
kacy added a commit that referenced this pull request Feb 11, 2026
* feat: add pub/sub command parsing

add SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, and PUBLISH
commands to the protocol layer. parsing only — no execution yet.

* feat: add pubsub manager with broadcast channels

introduces PubSubManager backed by DashMap + tokio broadcast for
lock-free fan-out messaging. supports channel subscriptions, glob
pattern matching (PSUBSCRIBE), and concurrent publish. includes
comprehensive unit tests for glob matching and pub/sub semantics.

* feat: wire pub/sub into connection handlers

- server.rs: create and pass PubSubManager to all connections
- connection.rs: subscriber mode state machine with select! loop,
  push message serialization, subscription cleanup on disconnect
- concurrent_handler.rs: PUBLISH support, sub commands return error

* chore: fix clippy warnings and formatting

- remove unused imports in pubsub.rs
- remove unused parameters from handle_subscriber_mode
- fix formatting in protocol parse functions

* test: add pub/sub command parsing tests

covers subscribe, unsubscribe, psubscribe, punsubscribe, publish
with edge cases: no args, multiple channels, wrong arity, case
insensitivity.

* feat: add PUBSUB CHANNELS/NUMSUB/NUMPAT introspection commands

completes the pub/sub command set with redis-compatible introspection:
- PUBSUB CHANNELS [pattern] — list active channels with optional glob
- PUBSUB NUMSUB [channel ...] — subscriber counts per channel
- PUBSUB NUMPAT — count of active pattern subscriptions

includes parsing tests and end-to-end verification with redis-cli.

* refactor: audit fixes for pub/sub implementation

- fix race condition: unsubscribe/punsubscribe now only decrement
  subscription_count when the channel actually exists in the registry
- replace busy-wait polling in recv_any_message with FuturesUnordered
  for proper async multiplexing across all broadcast receivers
- remove unnecessary String allocations in serialize functions (use
  Bytes::copy_from_slice and Bytes::from_static instead)
- simplify is_subscribe_frame using eq_ignore_ascii_case on byte slices
  instead of allocating a String for case comparison
- simplify enter_sub detection with iter().any()
- organize imports (group std::time together)

* docs: update command count to 76, add pub/sub to feature list

- README.md: 65+ → 76 commands, 609 → 639 tests, ~14k → ~21k LOC
- README.md: add pub/sub to feature list
- bench/README.md: update command count, remove pub/sub from
  dragonfly advantages (ember now has it)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant