Skip to content

Fix LREM with count = int.MinValue killing the session - #2009

Open
hexonal wants to merge 1 commit into
microsoft:mainfrom
hexonal:fix-lrem-intmin-count
Open

Fix LREM with count = int.MinValue killing the session#2009
hexonal wants to merge 1 commit into
microsoft:mainfrom
hexonal:fix-lrem-intmin-count

Conversation

@hexonal

@hexonal hexonal commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

LREM key -2147483648 <element> terminates the RESP session.

RPUSH L a b c
LREM L -2147483648 a    -> connection closed, no reply
PING                    -> Connection reset by peer

Server log:

crit: Session[0] ProcessMessages threw an exception: System.OverflowException:
Negating the minimum value of a twos complement number is invalid.
   at Garnet.server.ListObject.ListRemove(ObjectInput&, ObjectOutput&)
      in libs/server/Objects/List/ListObjectImpl.cs
   at Garnet.server.ListObject.Operate(...)

The server process stays up and other sessions are unaffected — the blast radius is the issuing connection.

Cause

ListRemove normalizes a negative count with Math.Abs(count), which throws for int.MinValue because |int.MinValue| does not fit in an int.

Fix

Clamp rather than negate. The removal loop is while (removedCount < count && currentNode != null), and removedCount can never exceed the list length, which cannot exceed int.MaxValue — so int.MaxValue removes exactly the same elements that a correctly negated 2147483648 would. Direction is unaffected: fromHeadToTail is decided from the original sign before the clamp.

Scope

I checked the other Math.Abs call sites that take a client-supplied count — HRANDFIELD (HashObjectImpl.cs:138), SRANDMEMBER (SetObjectImpl.cs:219) and ZRANDMEMBER (SortedSetObjectImpl.cs:651,656) — by driving each with -2147483648 against a live server. None of them terminates the session, so LREM is the only instance and the production change stays at one line.

Testing

  • New test LREMWithIntMinValueCountRemovesAllMatches: fails without the change with RedisConnectionException : SocketClosed (ReadEndOfStream), passes with it.
  • RespListTests: 104/104.
  • Semantics spot-checked against a live server: LREM key -2147483648 a on a b a c a returns 3 and leaves b c; LREM key -2 a still returns 2 and leaves a b c (tail-first order preserved); LREM key 2147483647 a unchanged.
  • dotnet format --verify-no-changes clean on both files.
  • macOS arm64, net10.0 Release. net8.0 builds clean but its tests were not run here — no net8.0 runtime in this environment.

ListRemove normalizes a negative count with Math.Abs, which throws
OverflowException for int.MinValue ("Negating the minimum value of a
twos complement number is invalid"). The exception unwinds out of
ProcessMessages and terminates the RESP session: the client gets no
reply and a reset connection. The server process stays up and other
sessions are unaffected.

Reproduced against unmodified main:

    RPUSH L a b c
    LREM L -2147483648 a    -> connection closed, no reply

Clamp instead of negating. The removal loop is bounded by
removedCount < count with removedCount never exceeding the list length,
which cannot exceed int.MaxValue, so int.MaxValue removes exactly the
same elements that a correctly negated 2147483648 would.

Scope: the other Math.Abs call sites on a client-supplied count
(HRANDFIELD, SRANDMEMBER, ZRANDMEMBER) were checked with the same input
and none of them terminates the session, so this is the only instance
and the fix stays at one line.

Verified on macOS arm64, net10.0 Release: the new test fails without the
change with RedisConnectionException SocketClosed, and passes with it.
RespListTests: 104/104. net8.0 builds clean; its tests were not run
here, no net8.0 runtime in this environment.
Copilot AI review requested due to automatic review settings August 3, 2026 05:22

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 crash/connection-drop edge case in the List object implementation when handling LREM with count = int.MinValue, which previously could throw OverflowException and terminate the issuing RESP session.

Changes:

  • Clamp count to int.MaxValue when count == int.MinValue to avoid Math.Abs(int.MinValue) overflow while preserving removal semantics and direction.
  • Add a regression test ensuring LREM with int.MinValue removes all matches and keeps the connection usable.

Reviewed changes

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

File Description
libs/server/Objects/List/ListObjectImpl.cs Prevents OverflowException by clamping int.MinValue before normalization in ListRemove.
test/standalone/Garnet.test.collections/RespListTests.cs Adds a regression test validating the fix and confirming the session remains active after the call.

@hexonal

hexonal commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

The red check on Garnet Standalone (ubuntu-latest, net10.0, Debug, Garnet.test.collections) looks unrelated to this change — happy to be corrected.

19 tests failed: 18 in RespHashTests plus RespListGarnetClientTests.AddElementsToListTail_WithCallback. All of them fail with the same connection error rather than an assertion, e.g.

RedisConnectionException : SocketClosed (ReadEndOfStream, last-recv: 0) on 127.0.0.1:34400/Interactive,
Idle/MarkProcessed, last: HMSET, origin: ReadFromPipe

with last: being HMSET / HPEXPIRE / HRANDFIELD — hash commands. This PR only touches libs/server/Objects/List/ListObjectImpl.cs and RespListTests.cs, and no RespHashTests failed on its own merits.

The branch sits on the current main tip (26b1882) with no other commits, and main's own runs are green. Running the full Garnet.test.collections project locally on this branch gives 747/747 passed, 0 failed, on net10.0 — including all 18 of those hash tests.

I do not have permission to re-run the job. Could someone re-trigger 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.

2 participants