-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Improve control of outgoing connection lifecycles #77295
Improve control of outgoing connection lifecycles #77295
Conversation
Pinging @elastic/es-distributed (Team:Distributed) |
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.
Note to reviewers: it will be easier to read this as a sequence of commits rather than looking just at the finished result.
/** | ||
* {@link ConnectionTarget} ensures that we are never concurrently connecting to and disconnecting from a node, and that we eventually | ||
* either connect to or disconnect from it according to whether {@link ConnectionTarget#connect(ActionListener)} or | ||
* {@link ConnectionTarget#disconnect()} was called last. | ||
* <p> | ||
* Each {@link ConnectionTarget} is in one of these states: | ||
* <p> | ||
* - idle ({@link ConnectionTarget#future} has no listeners) | ||
* - awaiting connection ({@link ConnectionTarget#future} may contain listeners awaiting a connection) | ||
* - awaiting disconnection ({@link ConnectionTarget#future} may contain listeners awaiting a disconnection) | ||
* <p> | ||
* It will be awaiting connection (respectively disconnection) after calling {@code connect()} (respectively {@code disconnect()}). It | ||
* will eventually become idle if these methods are not called infinitely often. | ||
* <p> | ||
* These methods return a {@link Runnable} which starts the connection/disconnection process iff it was idle before the method was | ||
* called, and which notifies any failed listeners if the {@code ConnectionTarget} went from {@code CONNECTING} to {@code DISCONNECTING} | ||
* or vice versa. The connection/disconnection process continues until all listeners have been removed, at which point it becomes idle | ||
* again. | ||
* <p> | ||
* Additionally if the last step of the process was a disconnection then this target is removed from the current set of targets. Thus | ||
* if this {@link ConnectionTarget} is idle and in the current set of targets then it should be connected. | ||
* <p> | ||
* All of the {@code listeners} are awaiting the completion of the same activity, which is either a connection or a disconnection. If | ||
* we are currently connecting and then {@link ConnectionTarget#disconnect()} is called then all connection listeners are | ||
* removed from the list so they can be notified of failure; once the connecting process has finished a disconnection will be started. | ||
* Similarly if we are currently disconnecting and then {@link ConnectionTarget#connect(ActionListener)} is called then all | ||
* disconnection listeners are immediately removed for failure notification and a connection is started once the disconnection is | ||
* complete. | ||
*/ |
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.
Treat the changes to NodeConnectionsService
as a complete rewrite (with slightly different semantics, see test changes)
// it's using and if we're becoming the master then join validation will hold open the connections to the joining peers; this set of | ||
// peers is a quorum so that's good enough. | ||
// | ||
// Note however that this might still close connections to other master-eligible nodes that we discovered but which aren't currently |
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.
This is a change in behaviour vs today. As per the comment I think it's ok, but we can discuss alternatives too.
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.
If we disconnect from the other node, do we risk that node dropping its connection to the master too? If this is in the middle of a join I wonder if it could lead to similar issues except AFAICS only once and thus not an issue.
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.
We discussed in another channel and decided that this won't happen: we're only closing connections that we initiated, the changes here won't have any effect on incoming connections.
if (connectingRefCounter.hasReferences() == false) { | ||
logger.trace("connection manager shut down, closing transport connection to [{}]", node); | ||
} else if (conn.hasReferences()) { | ||
logger.info("transport connection to [{}] closed by remote", node.descriptionWithoutAttributes()); |
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.
This also emits logs for connections to nodes in remote clusters. I think that's useful to see, we investigate cases that turn out to be flaky cross-cluster connections at a nonzero rate.
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.
I have been through most of this, but not all the tests yet. This looks great and I like how it simplifies NodeConnectionsService
, seems more natural now.
libs/core/src/main/java/org/elasticsearch/core/AbstractRefCounted.java
Outdated
Show resolved
Hide resolved
// it's using and if we're becoming the master then join validation will hold open the connections to the joining peers; this set of | ||
// peers is a quorum so that's good enough. | ||
// | ||
// Note however that this might still close connections to other master-eligible nodes that we discovered but which aren't currently |
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.
If we disconnect from the other node, do we risk that node dropping its connection to the master too? If this is in the middle of a join I wonder if it could lead to similar issues except AFAICS only once and thus not an issue.
server/src/test/java/org/elasticsearch/transport/ClusterConnectionManagerTests.java
Show resolved
Hide resolved
server/src/main/java/org/elasticsearch/cluster/NodeConnectionsService.java
Outdated
Show resolved
Hide resolved
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.
LGTM.
server/src/internalClusterTest/java/org/elasticsearch/discovery/ClusterDisruptionIT.java
Show resolved
Hide resolved
Today we open connections to other nodes in various places and largely assume that they remain open as needed, only closing them when applying a cluster state that removes the remote node from the cluster. This isn't ideal: we might preserve unnecessary connections to remote nodes that aren't in the cluster if they never manage to join the cluster, and we might also disconnect from a node that left the cluster while it's in the process of re-joining too (see elastic#67873). With this commit we move to a model in which each user of a connection to a remote node acquires a reference to the connection that must be released once it's no longer needed. Connections remain open while there are any live references, but are now actively closed when all references are released. Fixes elastic#67873 Backport of elastic#77295
Today we open connections to other nodes in various places and largely assume that they remain open as needed, only closing them when applying a cluster state that removes the remote node from the cluster. This isn't ideal: we might preserve unnecessary connections to remote nodes that aren't in the cluster if they never manage to join the cluster, and we might also disconnect from a node that left the cluster while it's in the process of re-joining too (see #67873). With this commit we move to a model in which each user of a connection to a remote node acquires a reference to the connection that must be released once it's no longer needed. Connections remain open while there are any live references, but are now actively closed when all references are released. Fixes #67873 Backport of #77295
Today if the `PeerFinder` experiences a connection failure then it removes the `Peer` with the corresponding transport address. However the removed `Peer` might be a different instance which is actually fine and holds a connection reference which therefore leaks. With this commit we only remove ourselves from the tracked set of peers. Relates elastic#77295 Closes elastic#79550
Today if the `PeerFinder` experiences a connection failure then it removes the `Peer` with the corresponding transport address. However the removed `Peer` might be a different instance which is actually fine and holds a connection reference which therefore leaks. With this commit we only remove ourselves from the tracked set of peers. Relates #77295 Closes #79550
Today if the `PeerFinder` experiences a connection failure then it removes the `Peer` with the corresponding transport address. However the removed `Peer` might be a different instance which is actually fine and holds a connection reference which therefore leaks. With this commit we only remove ourselves from the tracked set of peers. Relates #77295 Closes #79550
Today we open connections to other nodes in various places and largely
assume that they remain open as needed, only closing them when applying
a cluster state that removes the remote node from the cluster. This
isn't ideal: we might preserve unnecessary connections to remote nodes
that aren't in the cluster if they never manage to join the cluster, and
we might also disconnect from a node that left the cluster while it's in
the process of re-joining too (see #67873).
With this commit we move to a model in which each user of a connection
to a remote node acquires a reference to the connection that must be
released once it's no longer needed. Connections remain open while there
are any live references, but are now actively closed when all references
are released.
Fixes #67873