Replace AsyncConnectionConfig with Manager builder pattern#475
Replace AsyncConnectionConfig with Manager builder pattern#475bombsimon wants to merge 5 commits intodeadpool-rs:mainfrom
Conversation
Removes `Manager::new_with_config` which exposed internal redis settings. Adds `Manager::builder()` with `connection_timeout()` and `response_timeout()` setters that only surface user-facing options. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
That's much better. Though I'm seriously wondering if we could add a proper configuration object instead. What I mean by that is the following: Almost all https://github.com/deadpool-rs/deadpool/blob/main/crates/deadpool-redis/src/config.rs For that reason all config structures in deadpool are serde (de)serializable. |
Add connection_timeout and response_timeout to the serde-deserializable Config struct so timeouts can be configured via environment variables or config files, matching the pattern used by other deadpool crates. This reverts the ManagerBuilder added in the previous commits in favor of the Config-based approach suggested. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SGTM! I updated the code to now put the timeouts on the existing With this I now dropped the builder for |
bikeshedder
left a comment
There was a problem hiding this comment.
Just a few nitpicks. Option<Option<Duration>> really isn't great DX and I think we should expose new_with_config as public method.
| client: Client::open(self.params)?, | ||
| connection_timeout: self.connection_timeout, | ||
| response_timeout: self.response_timeout, | ||
| fn new_with_config<T: IntoConnectionInfo>( |
There was a problem hiding this comment.
Now that I think about it I think we should keep that method public. Otherwise users can only create a manager with timeouts using the config way.
I'm unsure if we should force users to always use the config code path. Even though I find that much more ergonomic than fiddling with the redis structures directly.
There was a problem hiding this comment.
Yeah that was my concern as well above and I agree it's not ideal. What do you want this method to accept? Two durations? A struct containing them? A builder pattern?
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
There was a problem hiding this comment.
I see your point. The AsyncConnectionConfig is essentially a "write-only" structure. I hate this pattern and I don't get why so many libraries do that. 😫
I think there are basically two options. Roll our own structure - which is basically what you did at first - or accept that this is essentially something opaque passed by the user and we have no control over it. We can't even validate it.
The only real trouble maker I see in this struct is set_push_sender which deadpool will have to override in the future in order to support RESP3.
There was a problem hiding this comment.
I think a builder pattern (the ManagerBuilder) makes most sense because it's forward compatible and will allow you to add new config options without breaking backwards compatibility.
Then also having the values on the Config, defaulting to the same as redis, and pass them to the manager builder pattern.
There was a problem hiding this comment.
Sorry if this grew, especially now with the AsyncConnectionConfig on main which isn't ideal. Should we put a pin in this until a more generic approach is reached? If so, should I revert the previous change so releasing main isn't blocked?
One thing with #[non_exhaustive] that surfaced with the doc test that I forgot about, it's not possible to build them externally.
Internally I can write a struct literal like this:
ManagerConfig {
connection_timeout: Some(Duration::from_secs(5)),
response_timeout: None,
..Default::default()
}But externally the user would have to write this:
let mut config = ManagerConfig::default();
config.connection_timeout = Some(Duration::from_secs(5));
config.response_timeout = None;I personally don't love that pattern so that could be one argument for a builder pattern I guess.
There was a problem hiding this comment.
Ideally you would never need to hardcode this in your application anyways. Overwritten defaults, yes, maybe. But in an actual application you would probably want to have a VALKEY__CONNECTION_TIMEOUT=2s environment variable (or some sort of configuration file).
I've written a rationale here:
Not being able to spread ..Default::default() is quite a bummer. I was a bit curious why that's the case and got the answer in the document you linked:
We’d expect this code to work without the non_exhaustive attribute:
let c = Config { width: 1920, height: 1080, ..Config::default() }; println!("{:?}", c);Although outside of the defining crate, it will not, because Config could, in the future, contain private fields that the user didn’t account for.
It's too late for me and I seriously don't care THAT much. I just want the deadpool-* crates to be consistent. e.g. in deadpool-postgres I didn't even bother adding #[non_exhaustive] to the config struct. If I ever need to add a new configuration variable it's going to be a breaking change. But one that's easy to fix. Just bump the version number and see if your code still compiles. If you were matching the config it's probably for the better that it's breaking because you might have missed a config variable that you are now interested in.
If you want to call it the day just make it a plain struct without the #[non_exhaustive]. That'd be fine for me as it has the best DX in my opinion.
There was a problem hiding this comment.
fyi. I've spent the past hour debating this with myself ... I see it like that:
- When adding
#[non_exhaustive]to a config struct a builder pattern should be added for convenience. Though I think the fields should stay public for easier access (and being able tomatch). I despise setters and getters for config structs. - Leaving out
#[non_exhaustive]will introduce breaking changes when new fields are added. The structs are fully (de)serializable and adding a fields does change the representation. Maybe it is a good thing that this is a breaking change?
There was a problem hiding this comment.
Let's get this merged. Just drop the #[non_exhaustive] so it's on-par with the other deadpool-* config structures. I'm fine with that solution. It's good enough. 😅
There was a problem hiding this comment.
Sounds good! I think just fixing compiler issues and adding defaults on breakage on new fields is reasonable for now. It's likely not bound to happen that often.
Really appreciate your time on this and the discussion! I've pushed removal of #[non_exhaustive] but am happy to continue discussion here or linked issue.
Add connection_timeout and response_timeout as Option<Duration> fields to Config with serde support and hardcoded defaults matching the redis crate (1s connection, 500ms response). Restore the Manager builder pattern for programmatic use, keeping AsyncConnectionConfig as an internal detail.
|
There is an interesting split now. There is a I guess the most correct approach here would be to add the timeout fields to the Also I just now realized that the new I'm so sorry for mixing that up. 🤦 |
The timeout fields are on the
There's no serialization macro on Maybe I'm misunderstanding both comments? 😅 |
Removes
Manager::new_with_configwhich exposed internal redis settings. AddsManager::builder()withconnection_timeout()andresponse_timeout()setters that only surface user-facing options.Followup from #474. Thanks for the quick turnaround and sorry for confusing you and just exposing the whole config.
Since this now adds two new settings to the
Managerand there's already a builder pattern for theConfigI opted to use the builder pattern here as well. LMK if you rather have a separatenew_with_timeoutsconstructor or something instead.