Repository navigation
test: Fix flaky touch assertions comparing OBJECT IDLETIME to exactly 0 - #4649
Conversation
OBJECT IDLETIME is derived from the LRU clock, whose resolution is one second. TOUCH stamps the key with the current clock value and the read then subtracts two second-granular values, so a tick landing between the two commands yields 1 rather than 0 regardless of how closely they are issued. Assert <= 1 instead, matching what ObjectCommandsTest already does for the same command. The reported failure is the binary half of the unified test, but the same assertion exists in the String half, in the cluster override and in both halves of the legacy copy, so all five are updated. Fixes redis#4591
kdelay
left a comment
There was a problem hiding this comment.
I looked at this from the completeness and coverage angle, and checked the OBJECT IDLETIME behaviour locally against Redis 8.0.2.
The root cause description matches what the server does: OBJECT IDLETIME is derived from the cached LRU clock (server.lruclock, refreshed in serverCron) at a one second resolution, so a tick landing between TOUCH and the read reports 1. lessThanOrEqualTo(1L) is also the tightest bound that stays deterministic here, and it is consistent with the range-matcher style already used by ObjectCommandsTest#objectIdletime and CommandObjectsServerManagementCommandsTest#testObjectIdletime.
Completeness: I went through every objectIdletime usage in the test tree, and all five brittle exact-zero assertions are covered here.
commands/jedis/AllKindOfValuesCommandsTest#touch(String + binary)commands/unified/AllKindOfValuesCommandsTestBase#touch(String + binary)commands/unified/cluster/ClusterAllKindOfValuesCommandsTest#touch(String only; that override has no binary section)
The remaining callers (ObjectCommandsTest, CommandObjectsServerManagementCommandsTest, and the mocked tests) already assert ranges or mock the reply, so nothing equivalent is left behind.
One observation that is orthogonal to the flake, but relevant to what the test claims to verify: the assertion does not distinguish a touched key from an untouched one. Each key is SET immediately before, so its idle time is already 0 by the time TOUCH runs. Measured on 8.0.2, SET foo1 bar1 followed by OBJECT IDLETIME foo1 as two separate round trips returned 0 in 3000/3000 iterations, so the assertion passes just as well with the touch() call deleted. That was equally true of the old assertEquals(0L, ...), so this PR loses no coverage. It only means touch() exercises the reply value of TOUCH but not its effect on idle time.
Covering the effect would need a real idle window before the TOUCH. The cheapest deterministic shape I could confirm on 8.0.2:
SET foo1 bar1
(wait ~2s)
OBJECT IDLETIME foo1 -> 2 (assert >= 1)
TOUCH foo1
OBJECT IDLETIME foo1 -> 0 (assert <= 1)
The cost is a two second sleep in the suite, which may not be a trade worth making in this test. Would you prefer to keep touch() about the reply values only and cover the reset separately in ObjectCommandsTest, or is a sleep acceptable here?
|
Thanks, that completeness pass matches what I found: the same five exact-zero assertions, and the remaining On the second point, I agree with the observation and would rather not act on it in this PR. You are right that the assertion cannot tell a touched key from an untouched one. It is visible in the test without needing a server: What makes me want to keep it out of here is the cost, which multiplies more than it looks. The assertions sit in parameterized classes, so a 2 s sleep at each of the five sites is executed 16 times in a full build:
That is a little over half a minute of pure waiting added to the suite to assert one property, and it buys a test whose determinism depends on a timing margin, which is the category of thing this PR is trying to remove. If the reset behaviour is worth covering, I would rather it be its own test that pays the sleep once, and I do not think it can extend an existing one: So my preference is to keep |
|
@renechoi
@renechoi @kdelay 2s per test run is an expensive one, and I think this is better validated in server-side tests. From a client perspective, validating the response of |
There was a problem hiding this comment.
π‘ Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5008f32fdd
βΉοΈ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with π.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| assertEquals(1, jedis.touch("foo1")); | ||
| assertEquals(0L, jedis.objectIdletime("foo1").longValue()); | ||
| assertThat(jedis.objectIdletime("foo1"), lessThanOrEqualTo(1L)); |
There was a problem hiding this comment.
Raise the idle-time tolerance above one second
For Redis-backed runs where the JVM or test host is paused for more than a second after TOUCH (for example under GC or loaded CI/sentinel/cluster scheduling), OBJECT IDLETIME can legitimately advance to 2+ before this read, so this still leaves the flake in place; ObjectCommandsTest.objectIdletime already allows up to 10 seconds for the same command. Please use that wider bounded tolerance for these updated TOUCH assertions rather than <= 1.
Useful? React with πΒ / π.
|
Thanks, that settles the sleep question for me. On the automated suggestion above, to widen the bound from 1 s to 10 s: I would rather keep
If a value above 1 ever does turn up in CI, widening is a one-line follow-up with an actual data point behind it. Say the word if you would rather not revisit it and I will change it now. |
Let's keep it to 1 |
|
Thanks for the quick review and the merge, and for the import cleanup on the branch. |
What
Fixes the sporadic
touchfailure reported in #4591, and the same assertion in the sibling copies of that test.Why
The reported failure is:
Line 399 is the
OBJECT IDLETIMEread immediately afterTOUCH:OBJECT IDLETIMEis derived from the LRU clock, whose resolution is one second.TOUCHstamps the key with the current clock value and the read then subtracts two second-granular values, so when the clock ticks between the two commands the result is 1 rather than 0. How closely the commands are issued does not matter β only whether a tick falls between them. The client is behaving correctly here; the assertion is simply stricter than what the command guarantees.Reproduced against the 8.8 test environment by repeating that same back-to-back
TOUCH/OBJECT IDLETIMEpair, with nothing between the two calls, until one pair straddled a tick:which is the value the assertion sees on a CI failure.
Changes
Assert
<= 1instead of== 0. The reported failure is the binary half of the unified test, but the same assertion exists in four other places, so all five are updated:commands/unified/AllKindOfValuesCommandsTestBase#touchβ String and binarycommands/unified/cluster/ClusterAllKindOfValuesCommandsTest#touchβ overrides the base testcommands/jedis/AllKindOfValuesCommandsTest#touchβ String and binary in the legacy copyThe tolerant form matches what
ObjectCommandsTest#objectIdletimealready does for the same command (assertThat(time, lessThanOrEqualTo(10L))).The surrounding assertions are untouched, so
TOUCHreturn values are still checked exactly, and the idle time is still asserted to be at (or next to) zero rather than merely non-negative.Verification
make start version=8.8, then the four classes that run this test, in full rather than just the one method:SentinelAllKindOfValuesCommandsITClusterAllKindOfValuesCommandsTestRedisClientAllKindOfValuesCommandsTestAllKindOfValuesCommandsTest(legacy)432 run, 0 failures, 0 errors, 27 skippedin total across the RESP parameterizations,BUILD SUCCESS.One note
dumpAndRestorehas the same shape a bit further down βRESTORE ... IDLETIME 1000followed byassertEquals(1000, objectIdletime(...))β in both the unified base and the legacy copy. It is exposed to the same tick, though I have not seen it reported. I left it alone to keep this focused on #4591; happy to fold it in here or send it separately, whichever you prefer.Fixes #4591
Note
Low Risk
Test-only assertion and import changes; no library or runtime behavior is modified.
Overview
Fixes intermittent CI failures in
touchintegration tests (#4591) by aligning assertions with Redisβs one-second LRU clock forOBJECT IDLETIME.After
TOUCH, tests no longer require idle time to be exactly 0; they assert<= 1via HamcrestlessThanOrEqualTo, so a clock tick betweenTOUCHandOBJECT IDLETIMEdoes not fail the run. The same change is applied across the unified base, cluster override, and legacyAllKindOfValuesCommandsTest(string and binary keys).The legacy jedis test class also switches several Hamcrest matchers to static imports (e.g.
hasItem,greaterThanOrEqualTo) instead ofMatchers.*.Reviewed by Cursor Bugbot for commit 5008f32. Bugbot is set up for automated code reviews on this repo. Configure here.