[RESP] Fix pub/sub: restrict commands in RESP2 subscription mode and fix reentrant lock crash#1669
Draft
[RESP] Fix pub/sub: restrict commands in RESP2 subscription mode and fix reentrant lock crash#1669
Conversation
2808511 to
b929db4
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes two pub/sub issues in the RESP server: (1) enforce Redis-compatible command restrictions while a RESP2 connection is in subscription mode, and (2) prevent a re-entrant network sender lock crash when a subscribed session publishes to its own channel (self-publish), especially relevant for RESP3 where self-notification is expected.
Changes:
- Add RESP2-only subscription-mode command gating (allow only subscribe/unsubscribe variants,
PING,QUIT) with a dedicated error message. - Add re-entrancy detection for pub/sub push callbacks to avoid re-entering the network sender lock during self-publish.
- Add regression tests covering RESP2 command rejection/allow-list and RESP3 self-publish stability.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/Garnet.test/RespPubSubTests.cs | Adds tests for RESP2 subscription-mode restrictions and RESP3 self-publish lock regression. |
| libs/server/Resp/RespServerSession.cs | Enforces RESP2 subscription-mode allow-list and tracks command-processing thread for re-entrancy detection. |
| libs/server/Resp/PubSubCommands.cs | Avoids re-entering the network sender lock for re-entrant publish callbacks; removes stale assertion. |
| libs/server/Resp/Parser/RespCommand.cs | Adds IsAllowedInSubscriptionMode() command allow-list helper. |
| libs/server/Resp/CmdStrings.cs | Adds standardized error string for disallowed commands in RESP2 subscription mode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ntrant lock crash Fixes #1615. Two related bugs: 1. RESP2 subscription mode allowed arbitrary commands (GET, SET, PUBLISH, etc.) instead of restricting to only (P|S)SUBSCRIBE/(P|S)UNSUBSCRIBE/PING/QUIT per the Redis protocol. Added IsAllowedInSubscriptionMode() check in ProcessMessages that rejects disallowed commands with a Redis-compatible error message. 2. PUBLISH from a subscriber session caused SynchronizationLockException because the Publish() callback re-entered the spinlock already held by TryConsumeMessages. Fixed by tracking the command-processing thread ID and detecting reentrant calls in Publish()/PatternPublish() to skip lock acquire/release when on the same thread. RESP3 sessions are not restricted since push message types are distinguishable from regular responses. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
b929db4 to
f043e4d
Compare
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.
Root Cause
Two related pub/sub bugs reported in #1615:
RESP2 command restriction missing: When a client entered subscription mode (via
SUBSCRIBE/PSUBSCRIBE), the server continued to allow arbitrary commands likeGET,SET,PUBLISH, etc. Per the Redis protocol, only(P|S)SUBSCRIBE,(P|S)UNSUBSCRIBE,PING, andQUITare valid in RESP2 subscription mode — other commands cannot be reliably distinguished from out-of-band push messages.Reentrant lock crash: When
PUBLISHwas executed from a subscribed session to a channel it was subscribed to (self-publish),SubscribeBroker.Broadcast()called thePublish()callback on the same session. This callback callednetworkSender.EnterAndGetResponseObject(), re-entering theSpinLockalready held byTryConsumeMessages. When the callback'sfinallyblock released the lock, the outerTryConsumeMessages.finallytried to release it again, causingSynchronizationLockException: The calling thread does not hold the lock.Description of Change
Key changes:
RespCommand.IsAllowedInSubscriptionMode()— NewAggressiveInliningextension method that returnstrueforSUBSCRIBE,UNSUBSCRIBE,PSUBSCRIBE,PUNSUBSCRIBE,SSUBSCRIBE,PING, andQUIT.Reentrant lock detection — Added
commandProcessingThreadIdfield set aroundProcessMessages()inTryConsumeMessages. InPublish()andPatternPublish()callbacks, comparesEnvironment.CurrentManagedThreadIdto detect reentrant calls and skipsEnterAndGetResponseObject()/ExitAndReturnResponseObject()when the lock is already held, writing the push message directly to the existing output buffer.Removed stale assertion —
Debug.Assert(isSubscriptionSession == false)inNetworkPUBLISHwas removed since RESP3 legitimately allowsPUBLISHin subscription mode.Key Technical Details
Affected types/interfaces:
RespServerSession(ProcessMessages,TryConsumeMessages,Publish,PatternPublish) — core command dispatch and pub/sub message deliveryRespCommandExtensions— newIsAllowedInSubscriptionMode()methodCmdStrings— newGenericPubSubCommandNotAllowederror format stringPerformance Impact
None for normal (non-subscription) command processing. The guard check short-circuits immediately on
isSubscriptionSession == false(a boolean field read). ThecommandProcessingThreadIdwrite is a singleintstore perTryConsumeMessagescall — effectively free.What NOT to Do (for future agents)
isProcessingCommandsis not thread-safe: Thread A could set it on Session A, then Thread B reading Session A's flag would incorrectly think it's reentrant. Use thread ID comparison instead.Tests Added
PubSubModeRejectsDisallowedCommandsInResp2— Verifies GET, SET, PUBLISH, MULTI are rejected with correct error in RESP2 subscription modePubSubModeAllowsValidCommandsInResp2— Verifies PING (subscription PONG), SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE, PUNSUBSCRIBE work; also verifies commands work again after full unsubscribePubSubSelfPublishResp3NoLockError— RESP3 self-publish (HELLO 3 → SUBSCRIBE → PUBLISH to same channel) succeeds with correct push message delivery and no crashIssues Fixed
Fixes #1615