Skip to content
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

Merged

Conversation

DaveCTurner
Copy link
Contributor

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

@DaveCTurner DaveCTurner added >bug :Distributed Coordination/Cluster Coordination Cluster formation and cluster state publication, including cluster membership and fault detection. v8.0.0 v7.16.0 labels Sep 6, 2021
@elasticmachine elasticmachine added the Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. label Sep 6, 2021
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-distributed (Team:Distributed)

Copy link
Contributor Author

@DaveCTurner DaveCTurner left a 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.

Comment on lines -252 to -280
/**
* {@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.
*/
Copy link
Contributor Author

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
Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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());
Copy link
Contributor Author

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.

Copy link
Contributor

@henningandersen henningandersen left a 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.

// 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
Copy link
Contributor

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.

@henningandersen henningandersen self-requested a review September 13, 2021 16:17
Copy link
Contributor

@henningandersen henningandersen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@DaveCTurner DaveCTurner merged commit a67e07e into elastic:master Sep 14, 2021
@DaveCTurner DaveCTurner deleted the 2021-09-06-releasable-connections branch September 14, 2021 05:35
DaveCTurner added a commit to DaveCTurner/elasticsearch that referenced this pull request Sep 14, 2021
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
elasticsearchmachine pushed a commit that referenced this pull request Sep 14, 2021
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
DaveCTurner added a commit to DaveCTurner/elasticsearch that referenced this pull request Oct 20, 2021
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
elasticsearchmachine pushed a commit that referenced this pull request Oct 20, 2021
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
DaveCTurner added a commit that referenced this pull request Oct 20, 2021
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>bug :Distributed Coordination/Cluster Coordination Cluster formation and cluster state publication, including cluster membership and fault detection. Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. v7.16.0 v8.0.0-beta1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

node-left ... reason: disconnected triggered by earlier node-left event rather than network issue
5 participants