Skip to content

Commit

Permalink
Fixes #7515 - Connection limit problem for "onAccepting" connections.
Browse files Browse the repository at this point in the history
* Fixed `ManagedSelector.Accept` to emit the event "accept failed" when closed.
* Fixed `ConnectionLimit` to close connections that exceed the maximum (may happen when the connector is configured with acceptors=0).
* Added test cases.
* Added documentation.

Signed-off-by: Simone Bordet <[email protected]>
  • Loading branch information
sbordet committed Sep 27, 2024
1 parent bcadd0a commit 11476a9
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee10.servlet.ResourceServlet;
import org.eclipse.jetty.ee10.servlet.ResourceServlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
Expand All @@ -61,6 +60,7 @@
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.rewrite.handler.RewriteRegexRule;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.ConnectionLimit;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.FormFields;
Expand Down Expand Up @@ -353,6 +353,28 @@ public void onOpen(NetworkConnector connector)
// end::sameRandomPort[]
}

public void connectionLimit()
{
// tag::connectionLimit[]
Server server = new Server();

// Limit connections to the server, across all connectors.
ConnectionLimit serverConnectionLimit = new ConnectionLimit(1024, server);
server.addBean(serverConnectionLimit);

ServerConnector connector1 = new ServerConnector(server);
connector1.setPort(8080);
server.addConnector(connector1);

ServerConnector connector2 = new ServerConnector(server);
connector2.setPort(9090);
server.addConnector(connector2);
// Limit connections for this connector only.
ConnectionLimit connectorConnectionLimit = new ConnectionLimit(64, connector2);
connector2.addBean(connectorConnectionLimit);
// end::connectionLimit[]
}

public void sslHandshakeListener() throws Exception
{
// tag::sslHandshakeListener[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ This property allows you to cap the max heap memory retained by the pool.
`jetty.byteBufferPool.maxDirectMemory`::
This property allows you to cap the max direct memory retained by the pool.

[[connectionlimit]]
== Module `connectionlimit`

The `connectionlimit` module limits the number of connections accepted by the server, across all connectors.

Once the configured maximum number of connections is reached, Jetty will not accept more connections.
Existing, established connections will work normally.
When existing connections are closed, accepting new connections will be resumed.

NOTE: The number of connections seen at the JVM level may be different from the number of connections seen at the OS level.
For more information, refer to xref:programming-guide:server/http.adoc#connector-limiting[this section].

The module file is `$JETTY_HOME/modules/connectionlimit.mod`:

include::{jetty-home}/modules/connectionlimit.mod[tags=documentation]

[[console-capture]]
== Module `console-capture`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,27 @@ For example:
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=sameRandomPort]
----

[[connector-limiting]]
=== Limiting Connections

It is possible to limit the number of connections accepted by the whole server (and therefore across all connectors), or by a specific connector.

This feature is implemented by class `org.eclipse.jetty.server.ConnectionLimit` and you can use it in this way:

[,java,indent=0,options=nowrap]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java[tags=connectionLimit]
----

[NOTE]
====
When the maximum number of connections is reached, no more connections will be accepted _at the JVM level_ -- but they could be accepted at the OS level.
This means that if you are using OS tools (like Linux's `ss`) to count the number of established connections, you may find a number that may be greater than the maximum number of connections configured in a `ConnectionLimit`.
Note also that different operative systems may behave differently when Jetty is not accepting connections: some OS accepts connections at the TCP level anyway (but does not notify this event to the JVM), some other OS may not accept connections at the TCP level.
====

[[connector-protocol]]
=== Configuring Protocols

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
Expand Down Expand Up @@ -864,14 +865,6 @@ class Accept implements SelectorUpdate, Runnable, Closeable
_selectorManager.onAccepting(channel);
}

@Override
public void close()
{
if (LOG.isDebugEnabled())
LOG.debug("closed accept of {}", channel);
IO.close(channel);
}

@Override
public void update(Selector selector)
{
Expand All @@ -882,10 +875,9 @@ public void update(Selector selector)
}
catch (Throwable x)
{
IO.close(channel);
_selectorManager.onAcceptFailed(channel, x);
if (LOG.isDebugEnabled())
LOG.debug("Could not register channel after accept {}", channel, x);
failed(x);
}
}

Expand All @@ -894,22 +886,28 @@ public void run()
{
try
{
createEndPoint(channel, key);
_selectorManager.onAccepted(channel);
createEndPoint(channel, key);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not process accepted channel {}", channel, x);
failed(x);
}
}

protected void failed(Throwable failure)
@Override
public void close()
{
IO.close(channel);
if (LOG.isDebugEnabled())
LOG.warn("Could not accept {}", channel, failure);
else
LOG.warn("Could not accept {}: {}", channel, String.valueOf(failure));
LOG.debug("Closed accept of {}", channel);
failed(new ClosedChannelException());
}

private void failed(Throwable failure)
{
IO.close(channel);
_selectorManager.onAcceptFailed(channel, failure);
}

Expand Down Expand Up @@ -1028,6 +1026,8 @@ public void update(Selector selector)
IO.close((Closeable)attachment);
}
_selector = null;
if (LOG.isDebugEnabled())
LOG.debug("Closing {} on {}", selector, ManagedSelector.this);
IO.close(selector);
}
finally
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# DO NOT EDIT THIS FILE - See: https://jetty.org/docs/

[description]
Enables a server-wide connection limit.
Enables a server-wide limit on TCP connections.

[tags]
connector
Expand All @@ -13,9 +13,10 @@ server
etc/jetty-connectionlimit.xml

[ini-template]

## The limit of connections to apply
#tag::documentation[]
## The maximum number of TCP connections allowed across all connectors.
#jetty.connectionlimit.maxConnections=1000

## The idle timeout to apply (in milliseconds) when connections are limited
## The idle timeout to apply (in milliseconds) to existing connections when the connection limit is reached.
#jetty.connectionlimit.idleTimeout=1000
#end::documentation[]
Loading

0 comments on commit 11476a9

Please sign in to comment.