Refactor server iteration to ServerSource (reduce allocations, improve clarity) - #667
Conversation
|
Thanks @LeaFrock — the refactor itself is sound, and I've verified it merges cleanly alongside #664 with the full suite green (1754 + 26 tests on net10.0). But there's a blocker on when it can ship, plus a few smaller points. The blocker: this is a breaking changeThe PR removes two public types from the public namespace public static class ServerIteratorFactory // removed
public class ServerEnumerable // removedThe checklist has "No breaking changes to public API" ticked, but anyone calling Two ways forward, your call: A — ship in v13.5 (soon), same pattern as #662. Keep both public types as thin B — retarget to v14. Clean removal, no wrappers, no deprecation cycle — but it sits parked until the v14 milestone opens, which isn't scheduled yet. I'd go with A if you want this out with the next release; B only if you'd rather not carry the wrappers. Smaller pointsMissing copyright header. Field naming. No tests, despite the checklist. No test files are touched here, and there's no coverage of server iteration either before or after — so it's not a regression. But the Eager/lazy asymmetry. A note on the perf claim. Caching Off-topic change. Let me know which option you'd prefer for the breaking change and I'll set the milestone accordingly. |
|
Decision from my side: let's go with option A, the I've moved #666 to the v13.5 milestone accordingly. Concretely, what that means for this PR: Keep both public types, delegating to Mark both Watch out for I'll open the v14 follow-up for the actual removal once this lands, mirroring what we did for #662 and #663. No pressure to take this on if you'd rather not carry the wrappers — say the word and I'll push the change onto the branch myself, same as I did for #664. Either way the other points from my previous comment still stand (copyright header, |
Great catch. I did ignore the diff which actually brings a break change. Let's keep both ways lazy in order to bring lowest risks.
The
Issue updated.
In fact it's suggested by the default VS intelligence. Sometimes this kind of code style fix or optimization is too small to create a separated issue/PR... However, if you indeed mind this, I'll do as you wish.
Well, you may notice that I propose we simply remove them in 14.0 and avoid exposing the new On the testing side, given that multiple tests already cover |
|
Thanks for going through these one by one. Off-topic change — you're right, I was being pedantic. If VS suggests it and it's a one-liner, folding it in is fine. Leave it. Eager/lazy — good, keeping both lazy is the right call. It also keeps the exception timing from
|
Got it. I've changed both of them into a slim wrapper of The message of From a more conservative standpoint,
Thank you very much for the positive feedback on my PR. On the unit test side, I’m not yet sure how to add coverage in a way that’s meaningful without introducing unnecessary churn, so I’d prefer to leave that part to you. |
|
This looks good — all four points addressed, and folding the filter into a single Your point about the On One follow-up on
|
|
Interesting...
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
namespace BenchmarkTest;
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net80)]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net10_0)]
[MemoryDiagnoser]
public class YieldTest
{
[Benchmark]
[Arguments(0)]
[Arguments(1)]
public int Run1(int mode)
{
var e = mode == 1
? EnumerateNumbers().Take(1)
: EnumerateNumbers();
var n = 0;
foreach (var i in e)
{
n |= i;
}
return n;
}
[Benchmark]
[Arguments(0)]
[Arguments(1)]
public int Run2(int mode)
{
var n = 0;
foreach (var i in EnumerateNumbers(mode))
{
n |= i;
}
return n;
}
private IEnumerable<int> EnumerateNumbers()
{
for (int i = 0; i < 30; i++)
{
yield return 1 << i;
}
}
private IEnumerable<int> EnumerateNumbers(int mode)
{
for (int i = 0; i < 30; i++)
{
yield return 1 << i;
if (mode == 1)
{
yield break;
}
}
}
} |
|
Thanks for actually measuring it instead of us trading opinions — that settles it, and not in my favour. Reading your numbers:
They win in opposite directions. Mine halves both time and allocation on That's enough for me. Keep yours as it is. Two honest caveats so the numbers don't get over-read in either direction. Your harness enumerates 30 items; Which means my original argument was the weak one anyway. I pitched it on simplicity rather than speed, and one method versus two is a thin case — thin enough that "measured better on the default path" beats it outright. Your version also reads more plainly than a So: nothing more to change here from my side. I'll push the three tests onto this branch — Nice work on this one, and thanks for the pushback on the deprecation shape earlier — the |
Covers the branch that had no test at all: every existing test pins Mode = All in RedisConfigurationForTest, so the Single path was never exercised — and it is the path this refactor reworked most. Six tests, no Redis required (IConnectionMultiplexer is mocked with NSubstitute, ~50ms total): - Single returns only the first matching server - All returns every server - PreferSlave skips primaries - IgnoreIfOtherAvailable skips disconnected servers - GetServers is lazy for both modes: the multiplexer is not touched until the result is enumerated The laziness test is the one that earns its keep — verified it fails against the earlier eager Single implementation and passes against the current one, so it guards the deferred-execution contract rather than just describing it. Note for anyone extending these: IServer mocks need Features stubbed to a Redis version >= 2.8, otherwise Features.Scan is false and the IgnoreIfOtherAvailable filter discards every server. Claude-Session: https://claude.ai/code/session_01WfvJwzENxWcSD79kau7rwn
|
Tests pushed to this branch —
The laziness one is the reason this was worth doing, and I checked it actually bites rather than just describing current behaviour: I temporarily restored the earlier eager One gotcha worth knowing if you extend these: Full suite locally: 1760 + 26 green on net10.0 (1754 before, +6 here), clean build on all TFMs with I touched nothing outside the new test file — Not merging yet; I'll wait in case you want to look over the tests first. Once you're happy this goes in with #664 for v13.5. |
|
I’m leaning toward the First, LINQ APIs are far more familiar to other developers reading the code, which reduces cognitive friction—this is also why I found the previous Second, even though the The unit tests LGTM—much appreciated. Also, I can’t wait to dive into the issue #669 —trying to squeeze it in while I’ve got some free time. |
|
Agreed — and we're already aligned there, Thanks for looking over the tests. Nothing further from me on this PR — it goes in with #664 for v13.5 as soon as I get the go-ahead on merging. On #669: the plan is sound and the sequencing is right — after #664 lands, |
|
Merging. Verified locally on top of #664 first: clean three-way merge (both PRs touched Thanks @LeaFrock — good back-and-forth on this one. The benchmark settled the Ships in v13.5. I'll open the v14 follow-up for removing the deprecated |
Summary
This PR refactors the existing ServerEnumerable / ServerIteratorFactory implementation into a new ServerSource type.
Motivation
Close #666
Changes
Checklist
TreatWarningsAsErrorsis enabled)dotnet test)