Skip to content

Commit 6ddadec

Browse files
sarankkvietj
authored andcommitted
Allow dynamic update of read and write limits set for Servers
1 parent 3ab73e0 commit 6ddadec

File tree

8 files changed

+273
-17
lines changed

8 files changed

+273
-17
lines changed

src/main/asciidoc/net.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,18 @@ through {@link io.vertx.core.net.NetServerOptions} and for HttpServer it can be
365365
{@link examples.NetExamples#configureTrafficShapingForHttpServer}
366366
----
367367

368+
These traffic shaping options can also be dynamically updated after server start.
369+
370+
[source,$lang]
371+
----
372+
{@link examples.NetExamples#dynamicallyUpdateTrafficShapingForNetServer}
373+
----
374+
375+
[source,$lang]
376+
----
377+
{@link examples.NetExamples#dynamicallyUpdateTrafficShapingForHttpServer}
378+
----
379+
368380
[[ssl]]
369381
=== Configuring servers and clients to work with SSL/TLS
370382

src/main/java/examples/NetExamples.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,23 @@ public void configureTrafficShapingForNetServer(Vertx vertx) {
721721
NetServer server = vertx.createNetServer(options);
722722
}
723723

724+
public void dynamicallyUpdateTrafficShapingForNetServer(Vertx vertx) {
725+
NetServerOptions options = new NetServerOptions()
726+
.setHost("localhost")
727+
.setPort(1234)
728+
.setTrafficShapingOptions(new TrafficShapingOptions()
729+
.setInboundGlobalBandwidth(64 * 1024)
730+
.setOutboundGlobalBandwidth(128 * 1024));
731+
NetServer server = vertx.createNetServer(options);
732+
TrafficShapingOptions update = new TrafficShapingOptions()
733+
.setInboundGlobalBandwidth(2 * 64 * 1024) // twice
734+
.setOutboundGlobalBandwidth(128 * 1024); // unchanged
735+
server
736+
.listen(1234, "localhost")
737+
// wait until traffic shaping handler is created for updates
738+
.onSuccess(v -> server.updateTrafficShapingOptions(update));
739+
}
740+
724741
public void configureTrafficShapingForHttpServer(Vertx vertx) {
725742
HttpServerOptions options = new HttpServerOptions()
726743
.setHost("localhost")
@@ -731,4 +748,22 @@ public void configureTrafficShapingForHttpServer(Vertx vertx) {
731748

732749
HttpServer server = vertx.createHttpServer(options);
733750
}
751+
752+
753+
public void dynamicallyUpdateTrafficShapingForHttpServer(Vertx vertx) {
754+
HttpServerOptions options = new HttpServerOptions()
755+
.setHost("localhost")
756+
.setPort(1234)
757+
.setTrafficShapingOptions(new TrafficShapingOptions()
758+
.setInboundGlobalBandwidth(64 * 1024)
759+
.setOutboundGlobalBandwidth(128 * 1024));
760+
HttpServer server = vertx.createHttpServer(options);
761+
TrafficShapingOptions update = new TrafficShapingOptions()
762+
.setInboundGlobalBandwidth(2 * 64 * 1024) // twice
763+
.setOutboundGlobalBandwidth(128 * 1024); // unchanged
764+
server
765+
.listen(1234, "localhost")
766+
// wait until traffic shaping handler is created for updates
767+
.onSuccess(v -> server.updateTrafficShapingOptions(update));
768+
}
734769
}

src/main/java/io/vertx/core/http/HttpServer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.vertx.core.metrics.Measured;
2222
import io.vertx.core.net.SSLOptions;
2323
import io.vertx.core.net.SocketAddress;
24+
import io.vertx.core.net.TrafficShapingOptions;
2425
import io.vertx.core.net.impl.SocketAddressImpl;
2526
import io.vertx.core.streams.ReadStream;
2627

@@ -182,6 +183,14 @@ default void updateSSLOptions(SSLOptions options, boolean force, Handler<AsyncRe
182183
}
183184
}
184185

186+
/**
187+
* Update traffic shaping options {@code options}, the update happens if valid values are passed for traffic
188+
* shaping options. This update happens synchronously and at best effort for rate update to take effect immediately.
189+
*
190+
* @param options the new traffic shaping options
191+
*/
192+
void updateTrafficShapingOptions(TrafficShapingOptions options);
193+
185194
/**
186195
* Tell the server to start listening. The server will listen on the port and host specified in the
187196
* {@link io.vertx.core.http.HttpServerOptions} that was used when creating the server.

src/main/java/io/vertx/core/net/NetServer.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,12 @@ default void updateSSLOptions(SSLOptions options, boolean force, Handler<AsyncRe
256256
fut.onComplete(handler);
257257
}
258258
}
259+
260+
/**
261+
* Update traffic shaping options {@code options}, the update happens if valid values are passed for traffic
262+
* shaping options. This update happens synchronously and at best effort for rate update to take effect immediately.
263+
*
264+
* @param options the new traffic shaping options
265+
*/
266+
void updateTrafficShapingOptions(TrafficShapingOptions options);
259267
}

src/main/java/io/vertx/core/net/TrafficShapingOptions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,14 @@ public TrafficShapingOptions setMaxDelayToWaitUnit(TimeUnit maxDelayToWaitTimeUn
139139
}
140140

141141
/**
142-
* Set the delay between two computations of performances for channels or 0 if no stats are to be computed
142+
* Set the delay between two computations of performances for channels
143143
*
144144
* @param checkIntervalForStats delay between two computations of performances
145145
* @return a reference to this, so the API can be used fluently
146146
*/
147147
public TrafficShapingOptions setCheckIntervalForStats(long checkIntervalForStats) {
148148
this.checkIntervalForStats = checkIntervalForStats;
149-
ObjectUtil.checkPositive(this.checkIntervalForStats, "checkIntervalForStats");
149+
ObjectUtil.checkPositiveOrZero(this.checkIntervalForStats, "checkIntervalForStats");
150150
return this;
151151
}
152152

src/main/java/io/vertx/core/net/impl/TCPServerBase.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,13 @@ private GlobalTrafficShapingHandler createTrafficShapingHandler(EventLoopGroup e
111111
return null;
112112
}
113113
GlobalTrafficShapingHandler trafficShapingHandler;
114-
if (options.getMaxDelayToWait() != 0 && options.getCheckIntervalForStats() != 0) {
114+
if (options.getMaxDelayToWait() != 0) {
115115
long maxDelayToWaitInMillis = options.getMaxDelayToWaitTimeUnit().toMillis(options.getMaxDelayToWait());
116116
long checkIntervalForStatsInMillis = options.getCheckIntervalForStatsTimeUnit().toMillis(options.getCheckIntervalForStats());
117117
trafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroup, options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInMillis, maxDelayToWaitInMillis);
118-
} else if (options.getCheckIntervalForStats() != 0) {
118+
} else {
119119
long checkIntervalForStatsInMillis = options.getCheckIntervalForStatsTimeUnit().toMillis(options.getCheckIntervalForStats());
120120
trafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroup, options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInMillis);
121-
} else {
122-
trafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroup, options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth());
123121
}
124122
if (options.getPeakOutboundGlobalBandwidth() != 0) {
125123
trafficShapingHandler.setMaxGlobalWriteSize(options.getPeakOutboundGlobalBandwidth());
@@ -147,6 +145,31 @@ public Future<Boolean> updateSSLOptions(SSLOptions options, boolean force) {
147145
}
148146
}
149147

148+
public void updateTrafficShapingOptions(TrafficShapingOptions options) {
149+
if (options == null) {
150+
throw new IllegalArgumentException("Invalid null value passed for traffic shaping options update");
151+
}
152+
if (trafficShapingHandler == null) {
153+
throw new IllegalStateException("Unable to update traffic shaping options because the server was not configured " +
154+
"to use traffic shaping during startup");
155+
}
156+
TCPServerBase server = actualServer;
157+
if (server != null && server != this) {
158+
server.updateTrafficShapingOptions(options);
159+
} else {
160+
long checkIntervalForStatsInMillis = options.getCheckIntervalForStatsTimeUnit().toMillis(options.getCheckIntervalForStats());
161+
trafficShapingHandler.configure(options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInMillis);
162+
163+
if (options.getPeakOutboundGlobalBandwidth() != 0) {
164+
trafficShapingHandler.setMaxGlobalWriteSize(options.getPeakOutboundGlobalBandwidth());
165+
}
166+
if (options.getMaxDelayToWait() != 0) {
167+
long maxDelayToWaitInMillis = options.getMaxDelayToWaitTimeUnit().toMillis(options.getMaxDelayToWait());
168+
trafficShapingHandler.setMaxWriteDelay(maxDelayToWaitInMillis);
169+
}
170+
}
171+
}
172+
150173
public Future<TCPServerBase> bind(SocketAddress address) {
151174
ContextInternal listenContext = vertx.getOrCreateContext();
152175
return listen(address, listenContext).map(this);

src/test/java/io/vertx/core/http/HttpBandwidthLimitingTest.java

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,27 @@ public static Iterable<Object[]> data() {
5555

5656
Function<Vertx, HttpServer> http1ServerFactory = (v) -> Providers.http1Server(v, INBOUND_LIMIT, OUTBOUND_LIMIT);
5757
Function<Vertx, HttpServer> http2ServerFactory = (v) -> Providers.http2Server(v, INBOUND_LIMIT, OUTBOUND_LIMIT);
58+
Function<Vertx, HttpServer> http1NonTrafficShapedServerFactory = (v) -> Providers.http1Server(v, 0, 0);
59+
Function<Vertx, HttpServer> http2NonTrafficShapedServerFactory = (v) -> Providers.http1Server(v, 0, 0);
5860
Function<Vertx, HttpClient> http1ClientFactory = (v) -> v.createHttpClient();
5961
Function<Vertx, HttpClient> http2ClientFactory = (v) -> v.createHttpClient(createHttp2ClientOptions());
6062

6163
return Arrays.asList(new Object[][] {
62-
{ 1.1, http1ServerFactory, http1ClientFactory },
63-
{ 2.0, http2ServerFactory, http2ClientFactory }
64+
{ 1.1, http1ServerFactory, http1ClientFactory, http1NonTrafficShapedServerFactory },
65+
{ 2.0, http2ServerFactory, http2ClientFactory, http2NonTrafficShapedServerFactory }
6466
});
6567
}
6668

6769
private Function<Vertx, HttpServer> serverFactory;
6870
private Function<Vertx, HttpClient> clientFactory;
71+
private Function<Vertx, HttpServer> nonTrafficShapedServerFactory;
6972

7073
public HttpBandwidthLimitingTest(double protoVersion, Function<Vertx, HttpServer> serverFactory,
71-
Function<Vertx, HttpClient> clientFactory) {
74+
Function<Vertx, HttpClient> clientFactory,
75+
Function<Vertx, HttpServer> nonTrafficShapedServerFactory) {
7276
this.serverFactory = serverFactory;
7377
this.clientFactory = clientFactory;
78+
this.nonTrafficShapedServerFactory = nonTrafficShapedServerFactory;
7479
}
7580

7681
@Before
@@ -199,6 +204,63 @@ public void start(Promise<Void> startPromise) {
199204
Assert.assertTrue(elapsedMillis > expectedTimeMillis(totalReceivedLength.get(), OUTBOUND_LIMIT)); // because there are simultaneous 2 requests
200205
}
201206

207+
@Test
208+
public void testDynamicOutboundRateUpdate() throws Exception {
209+
Buffer expectedBuffer = TestUtils.randomBuffer(TEST_CONTENT_SIZE);
210+
211+
HttpServer testServer = serverFactory.apply(vertx);
212+
testServer.requestHandler(HANDLERS.bufferRead(expectedBuffer));
213+
startServer(testServer);
214+
215+
// update outbound rate to twice the limit
216+
TrafficShapingOptions trafficOptions = new TrafficShapingOptions()
217+
.setInboundGlobalBandwidth(INBOUND_LIMIT) // unchanged
218+
.setOutboundGlobalBandwidth(2 * OUTBOUND_LIMIT);
219+
testServer.updateTrafficShapingOptions(trafficOptions);
220+
221+
long startTime = System.nanoTime();
222+
HttpClient testClient = clientFactory.apply(vertx);
223+
read(expectedBuffer, testServer, testClient);
224+
await();
225+
long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
226+
227+
Assert.assertTrue(elapsedMillis < expectedUpperBoundTimeMillis(TEST_CONTENT_SIZE, OUTBOUND_LIMIT));
228+
}
229+
230+
@Test
231+
public void testDynamicInboundRateUpdate() throws Exception {
232+
Buffer expectedBuffer = TestUtils.randomBuffer((TEST_CONTENT_SIZE));
233+
234+
HttpServer testServer = serverFactory.apply(vertx);
235+
testServer.requestHandler(HANDLERS.bufferWrite(expectedBuffer));
236+
startServer(testServer);
237+
238+
// update inbound rate to twice the limit
239+
TrafficShapingOptions trafficOptions = new TrafficShapingOptions()
240+
.setOutboundGlobalBandwidth(OUTBOUND_LIMIT) // unchanged
241+
.setInboundGlobalBandwidth(2 * INBOUND_LIMIT);
242+
testServer.updateTrafficShapingOptions(trafficOptions);
243+
244+
long startTime = System.nanoTime();
245+
HttpClient testClient = clientFactory.apply(vertx);
246+
write(expectedBuffer, testServer, testClient);
247+
await();
248+
long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
249+
250+
Assert.assertTrue(elapsedMillis < expectedUpperBoundTimeMillis(TEST_CONTENT_SIZE, INBOUND_LIMIT));
251+
}
252+
253+
@Test(expected = IllegalStateException.class)
254+
public void testRateUpdateWhenServerStartedWithoutTrafficShaping() {
255+
HttpServer testServer = nonTrafficShapedServerFactory.apply(vertx);
256+
257+
// update inbound rate to twice the limit
258+
TrafficShapingOptions trafficOptions = new TrafficShapingOptions()
259+
.setOutboundGlobalBandwidth(OUTBOUND_LIMIT)
260+
.setInboundGlobalBandwidth(2 * INBOUND_LIMIT);
261+
testServer.updateTrafficShapingOptions(trafficOptions);
262+
}
263+
202264
/**
203265
* The throttling takes a while to kick in so the expected time cannot be strict especially
204266
* for small data sizes in these tests.
@@ -211,6 +273,10 @@ private long expectedTimeMillis(long size, int rate) {
211273
return (long) (TimeUnit.MILLISECONDS.convert(( size / rate), TimeUnit.SECONDS) * 0.5); // multiplied by 0.5 to be more tolerant of time pauses during CI runs
212274
}
213275

276+
private long expectedUpperBoundTimeMillis(long size, int rate) {
277+
return TimeUnit.MILLISECONDS.convert(( size / rate), TimeUnit.SECONDS); // Since existing rate will be upperbound, runs should complete by this time
278+
}
279+
214280
private void read(Buffer expected, HttpServer server, HttpClient client) {
215281
client.request(HttpMethod.GET, server.actualPort(), DEFAULT_HTTP_HOST,"/buffer-read")
216282
.compose(req -> req.send()
@@ -280,19 +346,25 @@ static class Providers {
280346
private static HttpServer http1Server(Vertx vertx, int inboundLimit, int outboundLimit) {
281347
HttpServerOptions options = new HttpServerOptions()
282348
.setHost(DEFAULT_HTTP_HOST)
283-
.setPort(DEFAULT_HTTP_PORT)
284-
.setTrafficShapingOptions(new TrafficShapingOptions()
285-
.setInboundGlobalBandwidth(inboundLimit)
286-
.setOutboundGlobalBandwidth(outboundLimit));
349+
.setPort(DEFAULT_HTTP_PORT);
350+
351+
if (inboundLimit != 0 || outboundLimit != 0) {
352+
options.setTrafficShapingOptions(new TrafficShapingOptions()
353+
.setInboundGlobalBandwidth(inboundLimit)
354+
.setOutboundGlobalBandwidth(outboundLimit));
355+
}
287356

288357
return vertx.createHttpServer(options);
289358
}
290359

291360
private static HttpServer http2Server(Vertx vertx, int inboundLimit, int outboundLimit) {
292-
HttpServerOptions options = createHttp2ServerOptions(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST)
293-
.setTrafficShapingOptions(new TrafficShapingOptions()
294-
.setInboundGlobalBandwidth(inboundLimit)
295-
.setOutboundGlobalBandwidth(outboundLimit));
361+
HttpServerOptions options = createHttp2ServerOptions(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST);
362+
363+
if (inboundLimit != 0 || outboundLimit != 0) {
364+
options.setTrafficShapingOptions(new TrafficShapingOptions()
365+
.setInboundGlobalBandwidth(inboundLimit)
366+
.setOutboundGlobalBandwidth(outboundLimit));
367+
}
296368

297369
return vertx.createHttpServer(options);
298370
}

0 commit comments

Comments
 (0)