-
Notifications
You must be signed in to change notification settings - Fork 607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Connection manager #278
Connection manager #278
Conversation
}; | ||
} | ||
} | ||
result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error will be passed on to the caller unmodified. I think this is the only feasible generic solution, since depending on the application logic, if the connection fails after redis has processed the request, automatic retrying might have unintended consequences.
This way, the application must deal with the error, but reconnecting will happen automatically.
7c0e0c8
to
3f8d21d
Compare
I have to say I'm a bit stuck here. In the current version of the code, However, once a manager instance is cloned, if instance A replaces the shared future with the reconnect future, instance B will still have a reference to the old shared future, right? That would mean that we would need some kind of wrapper type with interior mutability. For example an Arc: #[derive(Clone)]
pub struct ConnectionManager(Arc<ConnectionManagerInner>);
struct ConnectionManagerInner {
connection_info: ConnectionInfo,
connection: MultiplexedConnection,
} Now references to the connection need to be updated. For read-only references that's easy: fn get_db(&self) -> i64 {
self.0.connection.db
} However, mutable references now complain that the arc contents cannot be borrowed mutably. For example, the reconnect method now cannot replace the connection: error[E0594]: cannot assign to data in an `Arc`
--> src/aio.rs:620:13
|
620 | self.0.connection = new_connection;
| ^^^^^^^^^^^^^^^^^ cannot assign
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `std::sync::Arc<aio::connection_manager::ConnectionManagerInner>` I could now wrap the entire Am I on the wrong path? 🙂 |
No, you will need some sort of interior mutability. https://docs.rs/arc-swap/0.4.4/arc_swap/index.html works well and you can use |
@Marwes thanks for pointing out ArcSwap about their performance characteristics:
Cloning the |
Cloning a |
Perhaps it could cache the connection, but still do a read on the |
I managed to get things working with I'll work on the remaining open issues tomorrow:
Open questions:
|
You could abstract |
A circuitbreaker could be built on top of this but the issue comes with figuring out a good strategy for it or making the strategy pluggable which is complicated in its own right :/
Sounds good to me. |
This comment has been minimized.
This comment has been minimized.
As I see it, as soon as a reconnection attempt is started there is no way to retrieve an old connection since the |
This comment has been minimized.
This comment has been minimized.
TIL that I guess we should use I got a version with async reconnection and non-parallel reconnections to work, will have to clean up the code and push it later. |
b5bf57b
to
3e4fd0e
Compare
Ok, I think now I have a version that does what it should! It's now ready to review. Currently implemented behavior:
This can be tested by decreasing the delay in Initially, the connection still works:
Then I restarted the Redis server. In the debug output, you can see the pointer to the connection future before and after being swapped. If the log output contains "Swap happened", then a stale connection was replaced with a connection future. If the log output contains "Swap did not happen", then another task was faster in creating a connection future. In this case, no connection will be established.
On the next command a few ms later, the server was not yet back up, so the reconnect future fails and is immediately replaced by a new connection future:
Sometimes the reconnection future fails so fast that the next (concurrent) task already sees the new, failed future:
After a few dozen milliseconds, the server is back up and the connection future resolves to a working
Not yet implemented:
TODO:
Note: The rustfmt failures happen because 1.41 introduced a slight change in formatting rules. Maybe the rustfmt check should be pinned to a specific Rust version. The code can then be reformatted when bumping the used Rust version. |
This way, the first task that notices a connection loss will initiate a reconnection, while the other tasks wait for the connection future to complete. In order to avoid having to lock a mutex for replacing the future, the arc-swap crate is used. It performs well in read-often update-seldom scenarios.
3e4fd0e
to
1b6045b
Compare
Rebased against master. I also added a commit that removes the debug prints (should be squashed anyways, but can be temporarily reverted for easier testing). @Marwes is there anything else for me to do here? |
Looks good! Thanks! |
On this occasion, thanks @Marwes for the fantastic mentoring and quick feedback you did here - I probably wouldn't have managed to get to this solution without you 🙂 |
Late to the party, but this looks like a great addition! Thank you! |
python - transaction
Early draft of a ConnectionManager that would address the reconnect behavior discussed in #218.
Would be great to get some feedback on it. @Marwes you've written something like this in the past, right?
To test, run
cargo run --example async-connection-loss --features="tokio-rt-core" reconnect
and then restart and/or stop redis. Output for a 2s server downtime:I'll add some comments inline.
Based on #276.