Skip to content

Commit 40db450

Browse files
authored
performance(network): shutdown faster with parallelization (#5029)
2 parents 45bde3d + e528a09 commit 40db450

File tree

3 files changed

+41
-21
lines changed

3 files changed

+41
-21
lines changed

engine/build.gradle

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,12 @@ dependencies {
100100
api "org.lwjgl:lwjgl-opengl"
101101
implementation "org.lwjgl:lwjgl-stb"
102102

103-
implementation 'io.micrometer:micrometer-core:1.8.0'
104-
implementation 'io.micrometer:micrometer-registry-jmx:1.8.0'
105-
api group: 'io.projectreactor', name: 'reactor-core', version: '3.4.14'
106-
api group: 'io.projectreactor.addons', name: 'reactor-extra', version: '3.4.6'
107-
103+
implementation 'io.micrometer:micrometer-core:1.9.0'
104+
implementation 'io.micrometer:micrometer-registry-jmx:1.9.0'
105+
api group: 'io.projectreactor', name: 'reactor-core', version: '3.4.18'
106+
api group: 'io.projectreactor.addons', name: 'reactor-extra', version: '3.4.8'
107+
implementation 'io.projectreactor.netty:reactor-netty-core:1.0.19'
108+
108109
api group: 'org.joml', name: 'joml', version: '1.10.0'
109110
api group: 'org.terasology.joml-ext', name: 'joml-geometry', version: '0.1.0'
110111

engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/PrepareWorld.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 The Terasology Foundation
1+
// Copyright 2022 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33

44
package org.terasology.engine.core.modes.loadProcesses;
@@ -14,6 +14,8 @@
1414
*/
1515
public class PrepareWorld extends VariableStepLoadProcess {
1616

17+
public static int maximumWaitMs = 5000;
18+
1719
private final Context context;
1820
private long startTime;
1921
private WorldRenderer worldRenderer;
@@ -33,9 +35,10 @@ public boolean step() {
3335
if (worldRenderer.pregenerateChunks()) {
3436
return true;
3537
}
38+
Thread.onSpinWait();
3639
EngineTime time = (EngineTime) context.get(Time.class);
3740
timeElapsed = time.getRealTimeInMs() - startTime;
38-
return timeElapsed > 5000;
41+
return timeElapsed > maximumWaitMs;
3942
}
4043

4144
@Override
@@ -47,7 +50,7 @@ public void begin() {
4750

4851
@Override
4952
public float getProgress() {
50-
return (1 / Math.max(1f, 5000f / (float) timeElapsed));
53+
return ((float) timeElapsed) / maximumWaitMs;
5154
}
5255

5356
@Override

engine/src/main/java/org/terasology/engine/network/internal/NetworkSystemImpl.java

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.netty.channel.nio.NioEventLoopGroup;
2626
import io.netty.channel.socket.nio.NioServerSocketChannel;
2727
import io.netty.channel.socket.nio.NioSocketChannel;
28+
import io.netty.util.concurrent.Future;
2829
import io.netty.util.concurrent.GlobalEventExecutor;
2930
import org.slf4j.Logger;
3031
import org.slf4j.LoggerFactory;
@@ -78,15 +79,21 @@
7879
import org.terasology.reflection.metadata.ClassLibrary;
7980
import org.terasology.reflection.metadata.ClassMetadata;
8081
import org.terasology.reflection.metadata.FieldMetadata;
82+
import reactor.core.publisher.Flux;
83+
import reactor.core.publisher.Mono;
84+
import reactor.netty.FutureMono;
8185

8286
import java.net.BindException;
8387
import java.net.InetSocketAddress;
88+
import java.time.Duration;
89+
import java.util.ArrayList;
8490
import java.util.Collection;
8591
import java.util.List;
8692
import java.util.Map;
8793
import java.util.Optional;
8894
import java.util.Set;
8995
import java.util.concurrent.BlockingQueue;
96+
import java.util.concurrent.TimeUnit;
9097

9198
import static org.terasology.engine.registry.InjectionHelper.createWithConstructorInjection;
9299

@@ -95,10 +102,14 @@
95102
* Implementation of the Network System using Netty and TCP/IP
96103
*/
97104
public class NetworkSystemImpl implements EntityChangeSubscriber, NetworkSystem {
105+
public static int shutdownQuietMs = 2_000;
106+
public static int shutdownTimeoutMs = 15_000;
107+
98108
private static final Logger logger = LoggerFactory.getLogger(NetworkSystemImpl.class);
99109
private static final int OWNER_DEPTH_LIMIT = 50;
100110
private static final int NET_TICK_RATE = 50;
101111
private static final int NULL_NET_ID = 0;
112+
102113
private final Set<Client> clientList = Sets.newLinkedHashSet();
103114
private final Set<NetClient> netClientList = Sets.newLinkedHashSet();
104115
// Shared
@@ -231,7 +242,8 @@ public JoinStatus join(String address, int port) throws InterruptedException {
231242
} else {
232243
logger.warn("Failed to connect to server", connectCheck.cause());
233244
connectCheck.channel().closeFuture().awaitUninterruptibly();
234-
clientGroup.shutdownGracefully().syncUninterruptibly();
245+
clientGroup.shutdownGracefully(shutdownQuietMs, shutdownTimeoutMs, TimeUnit.MILLISECONDS)
246+
.syncUninterruptibly();
235247
return new JoinStatusImpl("Failed to connect to server - " + connectCheck.cause().getMessage());
236248
}
237249
}
@@ -251,24 +263,28 @@ public JoinStatus join(String address, int port) throws InterruptedException {
251263
@Override
252264
public void shutdown() {
253265
allChannels.close().awaitUninterruptibly();
266+
List<Future<?>> shutdowns = new ArrayList<>(3);
254267
if (serverChannelFuture != null) {
255-
serverChannelFuture.channel().closeFuture();
256268
// Wait until all threads are terminated.
257-
try {
258-
bossGroup.shutdownGracefully().sync();
259-
workerGroup.shutdownGracefully().sync();
260-
bossGroup.terminationFuture().sync();
261-
workerGroup.terminationFuture().sync();
262-
} catch (InterruptedException e) {
263-
logger.error("Cannot terminateFuture - interrupted");
264-
throw new RuntimeException(e);
265-
}
266-
269+
shutdowns.add(bossGroup.shutdownGracefully(shutdownQuietMs, shutdownTimeoutMs, TimeUnit.MILLISECONDS));
270+
shutdowns.add(workerGroup.shutdownGracefully(shutdownQuietMs, shutdownTimeoutMs, TimeUnit.MILLISECONDS));
267271
}
268272
if (clientGroup != null) {
269-
clientGroup.shutdownGracefully().syncUninterruptibly();
273+
shutdowns.add(clientGroup.shutdownGracefully(shutdownQuietMs, shutdownTimeoutMs, TimeUnit.MILLISECONDS));
270274
}
275+
271276
// Shut down all event loops to terminate all threads.
277+
// I want their timeouts to count in parallel, instead of blocking on one after the other,
278+
// but turning the netty Future into something we can do this with is a bit of a mess until
279+
// we switch to using reactor-netty consistently.
280+
Mono.whenDelayError(
281+
Flux.fromIterable(shutdowns)
282+
.map(x -> {
283+
@SuppressWarnings("unchecked") Future<Void> f = (Future<Void>) x;
284+
return FutureMono.from(f);
285+
})
286+
.collectList()
287+
).block(Duration.ofMillis(shutdownTimeoutMs));
272288

273289
processPendingDisconnects();
274290
clientList.forEach(this::processRemovedClient);

0 commit comments

Comments
 (0)