Skip to content

Fix reactive getEx() ignoring PERSIST/KEEPTTL/EXAT/PXAT (fixes #7308) - #7309

Merged
mrniko merged 1 commit into
redisson:masterfrom
stlahxm:fix/reactive-getex-expiration-options
Aug 21, 2026
Merged

mrniko merged 1 commit into
redisson:masterfrom
stlahxm:fix/reactive-getex-expiration-options

Conversation

@stlahxm

@stlahxm stlahxm commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Fixes getEx() in all 10 RedissonReactiveStringCommands.java module variants (Spring Data Redis 2.6 through 4.1) to check Expiration.isPersistent() / isKeepTtl() / isUnixTimestamp() and translate to the matching GETEX option, instead of always sending the raw expiration value as PX.

Why

See #7308 for the full writeup. Short version: Expiration encodes PERSIST/KEEPTTL as negative sentinel values internally. The current code sends whatever getExpirationTimeInMilliseconds() returns as a literal PX argument regardless of what was requested, so:

  • Expiration.persistent() → GETEX key PX -1000 → Redis rejects it: ERR invalid expire time in 'getex' command
  • Expiration.keepTtl() → GETEX key PX -2000 → same error
  • Expiration.unixTimestamp(...) (EXAT/PXAT) → the absolute epoch value is sent as if it were a relative millisecond delay, landing decades in the future instead of at the intended time

This exact bug was already fixed on the sync side (RedissonConnection.getEx()) in commit 6c393a51f. This PR applies the identical fix to the reactive counterpart in the same files, which that commit didn't touch.

Changes

  • getEx() in RedissonReactiveStringCommands.java, all 10 module variants that contain this method (redisson-spring-data-{26,27,30,31,32,33,34,35,40,41}):
if (expiration.isPersistent()) {
    m = write(keyBuf, ByteArrayCodec.INSTANCE, GETEX, keyBuf, "PERSIST");
} else if (expiration.isKeepTtl()) {
    m = write(keyBuf, ByteArrayCodec.INSTANCE, GETEX, keyBuf);
} else if (expiration.isUnixTimestamp()) {
    m = write(keyBuf, ByteArrayCodec.INSTANCE, GETEX, keyBuf,
            "PXAT", expiration.getExpirationTimeInMilliseconds());
} else {
    m = write(keyBuf, ByteArrayCodec.INSTANCE, GETEX, keyBuf,
            "PX", expiration.getExpirationTimeInMilliseconds());
}

Mirrors the sync fix's logic and option choices exactly (including always using PXAT with millisecond precision for the absolute-timestamp case, rather than branching between EXAT/PXAT by original unit).

  • New test RedissonReactiveStringCommandsTest, added to all 10 module variants, covering all four Expiration variants (relative milliseconds, PERSIST, KEEPTTL, EXAT) against a real Redis instance.

Test plan

Before the fix, against both master and the latest release redisson-4.7.0:

testGetExRelativeMillis                      -> PASS (already worked)
testGetExPersistRemovesTtl                   -> ERROR: RedisSystem ERR invalid expire time in 'getex' command. params: [key, PX, -1000]
testGetExKeepTtlPreservesOriginalTtl         -> ERROR: RedisSystem ERR invalid expire time in 'getex' command. params: [key, PX, -2000]
testGetExAbsoluteSecondsUsesExatSemantics    -> FAIL: expected PTTL ~120000ms, got 1787146328998ms (~56.6 years)

After the fix, all four pass in every module (Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 each).

Backward compatibility

No API or behavior change for the one case that already worked (plain relative expiration). PERSIST/KEEPTTL/EXAT/PXAT go from "throws" or "silently wrong" to "correct", which can only fix previously-broken behavior, not change any working behavior.

fixes redisson#7308

RedissonReactiveStringCommands.getEx() always sent the raw Expiration
value as a PX argument regardless of what was requested, since
Expiration encodes PERSIST/KEEPTTL as negative sentinel millisecond
values and EXAT/PXAT as an absolute epoch timestamp rather than a
relative delay. PERSIST/KEEPTTL were rejected by Redis outright, and
EXAT/PXAT produced a wildly wrong TTL.

Mirrors the fix already applied to the sync counterpart,
RedissonConnection.getEx(), in commit 6c393a5, across all 10
Spring Data Redis module variants (2.6 through 4.1) that contain
this method.

Signed-off-by: 심현민 <[email protected]>
@stlahxm
stlahxm force-pushed the fix/reactive-getex-expiration-options branch from 8d97825 to 0249042 Compare August 19, 2026 15:21
@mrniko mrniko added the bug label Aug 20, 2026
@mrniko mrniko added this to the 4.8.0 milestone Aug 20, 2026
@mrniko
mrniko merged commit 6686397 into redisson:master Aug 21, 2026
4 checks passed
@mrniko

mrniko commented Aug 21, 2026

Copy link
Copy Markdown
Member

Thanks for contribution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

2 participants