Skip to content

Fix PUBLISH failing when called from Lua scripts#1927

Open
lilpard-mk wants to merge 5 commits into
microsoft:mainfrom
lilpard-mk:fix/lua-publish
Open

Fix PUBLISH failing when called from Lua scripts#1927
lilpard-mk wants to merge 5 commits into
microsoft:mainfrom
lilpard-mk:fix/lua-publish

Conversation

@lilpard-mk

Copy link
Copy Markdown

Root Cause

SessionScriptCache.cs creates an internal RespServerSession for Lua script execution with subscribeBroker hardcoded to null. This causes redis.call('PUBLISH', ...) inside Lua scripts to fail with ERR PUBLISH is disabled, even when PubSub is enabled (the default).

The bug affects all Redisson Java users because Redisson's RLock.unlock() relies on redis.call('PUBLISH', ...) inside its Lua unlock script to notify waiters. See Discussion #1628.

Description of Change

  • Pass storeWrapper.subscribeBroker instead of null to the internal Lua processor in SessionScriptCache.cs, aligning its behavior with the network session created in GarnetProvider.cs.

Key Technical Details

  • The 6th parameter enableScripts: false is intentionally kept false to prevent nested EVAL recursion.
  • SubscribeBroker is fully thread-safe (ConcurrentDictionary, Interlocked).
  • SUBSCRIBE / PSUBSCRIBE remain blocked in Lua by the existing NoScript bitmap (Redis protocol compliant).
  • When user passes --no-pubsub, storeWrapper.subscribeBroker is null, so Lua PUBLISH still correctly reports disabled — graceful degradation preserved.

Tests Added

Positive cases (10):

  • LuaPublishSucceeds_WhenCalledViaRedisCall
  • LuaPublishReturnsSubscriberCount
  • LuaPublishToNoSubscribersReturnsZero
  • LuaPublishMultipleSubscribers_AllReceive
  • LuaPublish_PSubscribeReceives
  • LuaPublishMixedWithSetGet
  • LuaPublishSameChannelMultipleTimes
  • LuaPublishUnicodePayload
  • LuaPublishEmptyPayload
  • LuaPublishAfterNonPubSubCommands

NoScript enforcement (regression guard, 3):

  • LuaSubscribe_StillNoScriptBlocked
  • LuaPSubscribe_StillNoScriptBlocked
  • LuaUnsubscribe_StillNoScriptBlocked

Concurrency/state (4):

  • ConcurrentLuaPublish_NoRaceCondition
  • MultipleScriptsSameSession_AllCanPublish
  • LuaPublishPreservesOtherSubscriberState
  • ScriptCachePublish_WorksWithScriptFlush

Reproduction (before fix)

redis-cli PUBLISH test hi
# (integer) 0  

redis-cli EVAL "return redis.call('PUBLISH','test','hi')" 0
# (error) ERR PUBLISH is disabled, enable it with --pubsub option.  

Checklist

  • Code follows .editorconfig (dotnet format --verify-no-changes passes)
  • Builds on ubuntu-latest and windows-latest (Debug + Release), 0 warnings
  • Garnet.test.scripting tests pass locally
  • No Version.props changes (handled by release automation)

Related

Discussion #1628

The internal Lua processor session was created with subscribeBroker = null,
causing redis.call('PUBLISH', ...) in Lua scripts to fail with 'PUBLISH is
disabled' even when PubSub is enabled (the default).

This blocked all Redisson Java users because Redisson's RLock.unlock()
relies on redis.call('PUBLISH', ...) inside its Lua unlock script.

Pass storeWrapper.subscribeBroker to align with network session behavior.
SUBSCRIBE/PSUBSCRIBE remain blocked by the NoScript bitmap (Redis protocol
compliant). When --no-pubsub is set, Lua PUBLISH still correctly reports
disabled (graceful degradation preserved).

Adds 17 tests covering positive cases, NoScript enforcement, and
concurrency.
Copilot AI review requested due to automatic review settings July 12, 2026 06:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a scripting-session configuration bug where Lua redis.call('PUBLISH', ...) failed because the internal RespServerSession used for script execution was created without a SubscribeBroker, diverging from normal network sessions.

Changes:

  • Wire storeWrapper.subscribeBroker into the Lua script execution RespServerSession so publish-side Pub/Sub works when enabled.
  • Add an extensive Lua Pub/Sub regression suite covering publish success, subscriber counts, pattern subscribers, mixed-command scripts, cache flush, and concurrency, plus NoScript enforcement checks.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 14 comments.

File Description
libs/server/Lua/SessionScriptCache.cs Passes the session’s SubscribeBroker into the internal scripting RespServerSession to enable Lua PUBLISH when PubSub is enabled.
test/standalone/Garnet.test.scripting/LuaScriptTests.cs Adds coverage validating Lua-driven PUBLISH behavior (including concurrency and script-cache scenarios) and verifies subscribe commands remain blocked from scripts.

Comment thread libs/server/Lua/SessionScriptCache.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs Outdated
@lilpard-mk

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

- Tighten comment in SessionScriptCache.cs to clarify that only publish-side
  Pub/Sub commands are enabled; SUBSCRIBE/PSUBSCRIBE remain NoScript-blocked.
- Dispose all ManualResetEvent instances via 'using var' or explicit Dispose()
  in finally blocks to avoid leaking OS wait handles across the test suite.
@lilpard-mk

Copy link
Copy Markdown
Author

Thanks @copilot for the review! All 14 comments addressed in the latest push:

  • Refined the SessionScriptCache.cs comment to clarify that only publish-side Pub/Sub is enabled (SUBSCRIBE/PSUBSCRIBE remain NoScript-blocked).
  • Switched all ManualResetEvent instances to 'using var' or explicit Dispose() in finally blocks to prevent OS handle leaks.

Local validation: dotnet format --verify-no-changes passes, Debug+Release builds are 0-warning, all 102 tests pass.

@badrishc
badrishc requested a review from kevin-montrose July 14, 2026 00:08

@kevin-montrose kevin-montrose left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple change, but excessive (and duplicative) tests IMO. Let's trim it down to not bloat CI time and test files as much.

Comment thread test/standalone/Garnet.test.scripting/LuaScriptTests.cs
MeiKe and others added 3 commits July 15, 2026 10:03
Per @kevin-montrose's feedback, reduce the test suite from 17 cases to 3
focused ones to avoid bloating CI time and overlap with RespPubSubTests:

- LuaPublishSucceeds_WhenCalledViaRedisCall: verifies the core fix.
- LuaPublishReturnsSubscriberCount: verifies Redis protocol return value.
- LuaSubscribe_StillNoScriptBlocked: regression guard for NoScript enforcement.

Remove duplicated PubSub-mechanism coverage (multi-subscriber, pattern,
unicode, concurrency, etc.) already provided by RespPubSubTests.
@kevin-montrose kevin-montrose self-assigned this Jul 16, 2026
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.

3 participants