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

Add ClientConnectionListener listener #12421

Draft
wants to merge 5 commits into
base: jetty-12.0.x
Choose a base branch
from

Conversation

arsenalzp
Copy link
Contributor

@arsenalzp arsenalzp commented Oct 23, 2024

Here is the draft of changes proposed to solve #9529
Let's discuss it in the issue thread as the implementation was not hardened yet.

@joakime joakime requested a review from sbordet October 23, 2024 13:48
@joakime joakime marked this pull request as draft October 23, 2024 13:48
@gregw
Copy link
Contributor

gregw commented Oct 24, 2024

@arsenalzp Would it be OK if we implemented this in jetty-12.1.0 rather than 12.0.x? We are trying to feature freeze 12.0

@arsenalzp
Copy link
Contributor Author

@arsenalzp Would it be OK if we implemented this in jetty-12.1.0 rather than 12.0.x? We are trying to feature freeze 12.0

This approach is OK for me!

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

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

@arsenalzp good start.

Please review my comments, but I really need you to add test cases.

@arsenalzp
Copy link
Contributor Author

@arsenalzp good start.

Please review my comments, but I really need you to add test cases.

Of course it is good to have test cases, however as I said it is the draft, just to show the framework of solution.

@arsenalzp
Copy link
Contributor Author

I'm thinking about the place of ConnectListenerTest test class.
Naturally it should be put inside org.eclipse.jetty.io package, in that case it is necessary to import Server and HttpClient classes and it leads to modifying of pom.xml, however it causes cyclic reference.

What do you think?

@sbordet
Copy link
Contributor

sbordet commented Oct 25, 2024

@arsenalzp put the test class in the jetty-tests/jetty-test-client-transport module, and test it as parametrized test with transportsTCP (see examples in that module).

@sbordet
Copy link
Contributor

sbordet commented Oct 25, 2024

@arsenalzp please also fix the formatting, see here why the build fails:
https://jenkins.webtide.net/blue/organizations/jenkins/jetty.project/detail/PR-12421/3/pipeline

@arsenalzp
Copy link
Contributor Author

@arsenalzp please also fix the formatting, see here why the build fails: https://jenkins.webtide.net/blue/organizations/jenkins/jetty.project/detail/PR-12421/3/pipeline

Yeah, issue has been fixed already.

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

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

@arsenalzp another thing.
Instead of calling getBeans(ClientConnector.ConnectListener.class) every time, which is an expensive operation, please override add/removeEventListener, and store the ConnectListener in a local CopyOnWriteArrayList field, so the notification is faster.

@arsenalzp
Copy link
Contributor Author

arsenalzp commented Oct 31, 2024

@arsenalzp another thing. Instead of calling getBeans(ClientConnector.ConnectListener.class) every time, which is an expensive operation, please override add/removeEventListener, and store the ConnectListener in a local CopyOnWriteArrayList field, so the notification is faster.

Hello,
What is the idea here?
It is already implemented by superclass ContainerLifeCycle:

private final List<Bean> _beans = new CopyOnWriteArrayList<>();
private final List<Container.Listener> _listeners = new CopyOnWriteArrayList<>();

@sbordet
Copy link
Contributor

sbordet commented Oct 31, 2024

@arsenalzp in the base class you have a field for List<Container.Listener> so that they can be accessed efficiently without having to look up the beans all the times.

I am proposing that you do similarly in ClientConnector, keeping a field of List<ConnectListener> so that those listeners can be accessed very efficiently, instead of calling getBeans(ClientConnector.ConnectListener.class).

@arsenalzp
Copy link
Contributor Author

@arsenalzp in the base class you have a field for List<Container.Listener> so that they can be accessed efficiently without having to look up the beans all the times.

I am proposing that you do similarly in ClientConnector, keeping a field of List<ConnectListener> so that those listeners can be accessed very efficiently, instead of calling getBeans(ClientConnector.ConnectListener.class).

It is clear for me now, thank you!
This improvement has just been implemented.

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

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

@arsenalzp another good step, please review my comments.

boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress;
if (LOG.isDebugEnabled())
LOG.debug("Connecting {} to {}", blocking ? "blocking" : "non-blocking", address);
if (blocking)
{
listeners.forEach(listener -> listener.onConnectBegin(socketChannel, socketAddress));
Copy link
Contributor

Choose a reason for hiding this comment

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

These calls should be wrapped, since we do not want an exception thrown from onConnectBegin() to stop the connection.
Make a method, say connectBegin(), use a for loop instead of forEach(), and wrap listener.onConnectBegin() into try/catch(Throwable).
And same for the other events.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What is the reason of this solution?
ConnectListener methods don't throw an exception.

Copy link
Contributor

Choose a reason for hiding this comment

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

ConnectListener implementations are user code, and it can throw any RuntimeException, but we do not want ClientConnector internal working to be affected by that if it happens.

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

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

@arsenalzp looks good, two things:

  1. wrap the calls to ConnectListener methods into try/catch(Throwable).
  2. Write tests.

Thanks!

{
if (listener instanceof ConnectListener connectListener)
return listeners.add(connectListener);
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

No, you need to call super here.

{
if (listener instanceof ConnectListener connectListener)
return listeners.remove(connectListener);
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

No, you need to call super here.

*/
protected void connectSuccess(SelectableChannel channel)
{

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this newline.

@@ -753,4 +768,29 @@ public ChannelWithAddress newChannelWithAddress(ClientConnector clientConnector,
};
}
}

public interface ConnectListener extends EventListener
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add javadocs to class and methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

4 participants