Skip to content

Commit cd67d22

Browse files
authored
fix(tests): prevent HashedWheelTimer leak in integration tests (#99)
Integration test base classes in core/schema/jackson/client created a static HashedWheelTimer that was never released, emitting 'LEAK: HashedWheelTimer.release() was not called' on every CI run. - Register a JVM shutdown hook that calls timer.stop() (static field shared per declaring class; @afterall would stop it after the first subclass and break the rest). - Extract the four inline hooks into a single TimerShutdownHook helper. Core + schema ship the helper in their own test sources; client and jackson copy it locally to avoid test-jar / package-phase dependency pitfalls seen with the original cross-module test-jar wiring. - IProtoClientTest.testTimeoutCancel: use a local timer so the cancel path is still covered without mutating the shared static. - IProtoClientWatchersTest.checkTTVersion: fall back to the actual container version when TARANTOOL_VERSION is unset (NPE on local runs). Drop a dead `version == null` ternary branch and rename testTimeoutCancel → testLocalTimerHasNoPending. Skipped: per-class try/finally around stop() to handle mid-test shutdown — add if a leak resurfaces under mvn -Pintegration.
1 parent c76d799 commit cd67d22

9 files changed

Lines changed: 96 additions & 8 deletions

File tree

tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolCrudClientWithRetryTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ private void execute() {
9999
private static final Person personInstance = new Person(1, true, "Roman");
100100
private static final Timer timerService = new HashedWheelTimer();
101101

102+
static {
103+
TimerShutdownHook.register(timerService, "tarantool-crud-client-retry-test-timer-shutdown");
104+
}
105+
102106
@BeforeAll
103107
public static void setUp() throws Exception {
104108
if (isCartridgeAvailable()) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2026 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
3+
* All Rights Reserved.
4+
*/
5+
6+
package io.tarantool.client.integration;
7+
8+
import io.netty.util.Timer;
9+
10+
public final class TimerShutdownHook {
11+
private TimerShutdownHook() {}
12+
13+
public static void register(Timer timer, String name) {
14+
Runtime.getRuntime().addShutdownHook(new Thread(timer::stop, name));
15+
}
16+
}

tarantool-core/src/test/java/io/tarantool/core/integration/BaseTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ public abstract class BaseTest {
5858
}
5959
};
6060

61-
protected Timer timerService = new HashedWheelTimer();
61+
protected static Timer timerService = new HashedWheelTimer();
6262

63-
protected ConnectionFactory factory = new ConnectionFactory(bootstrap, timerService);
63+
protected static ConnectionFactory factory = new ConnectionFactory(bootstrap, timerService);
64+
65+
static {
66+
TimerShutdownHook.register(timerService, "base-test-timer-shutdown");
67+
}
6468

6569
protected static ArrayValue decodeTuple(IProtoClient client, ArrayValue arrayValue)
6670
throws IOException {

tarantool-core/src/test/java/io/tarantool/core/integration/IProtoClientTest.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import static org.junit.jupiter.api.Assertions.assertThrows;
2929
import static org.junit.jupiter.api.Assertions.assertTrue;
3030
import io.netty.util.HashedWheelTimer;
31+
import io.netty.util.Timer;
3132
import org.junit.jupiter.api.AfterAll;
3233
import org.junit.jupiter.api.Assertions;
3334
import org.junit.jupiter.api.BeforeAll;
@@ -68,6 +69,7 @@
6869
import io.tarantool.core.IProtoClient;
6970
import io.tarantool.core.IProtoClientImpl;
7071
import io.tarantool.core.IProtoFeature;
72+
import io.tarantool.core.connection.ConnectionFactory;
7173
import io.tarantool.core.exceptions.BoxError;
7274
import io.tarantool.core.exceptions.BoxErrorStackItem;
7375
import io.tarantool.core.exceptions.ClientException;
@@ -1514,17 +1516,23 @@ public void testPing() throws Exception {
15141516
}
15151517

15161518
@Test
1517-
public void testTimeoutCancel() throws Exception {
1518-
IProtoClient client = createClientAndConnect(address, true);
1519+
public void testLocalTimerHasNoPending() throws Exception {
1520+
Timer localTimer = new HashedWheelTimer();
1521+
IProtoClient client =
1522+
new IProtoClientImpl(
1523+
new ConnectionFactory(bootstrap, localTimer),
1524+
localTimer,
1525+
DEFAULT_WATCHER_OPTS,
1526+
null,
1527+
null,
1528+
true);
1529+
client.connect(address, 3_000).get();
15191530
client.authorize(API_USER, CREDS.get(API_USER)).join();
15201531
IProtoMessage message = client.ping().get();
15211532
checkMessageHeader(message, IPROTO_OK, 4);
15221533
assertEquals(0, message.getBody().map().size());
15231534

1524-
Set<io.netty.util.Timeout> timers = timerService.stop();
1525-
assertTrue(timers.isEmpty());
1526-
1527-
timerService = new HashedWheelTimer();
1535+
assertTrue(localTimer.stop().isEmpty());
15281536
}
15291537

15301538
@Test
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2026 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
3+
* All Rights Reserved.
4+
*/
5+
6+
package io.tarantool.core.integration;
7+
8+
import io.netty.util.Timer;
9+
10+
public final class TimerShutdownHook {
11+
private TimerShutdownHook() {}
12+
13+
public static void register(Timer timer, String name) {
14+
Runtime.getRuntime().addShutdownHook(new Thread(timer::stop, name));
15+
}
16+
}

tarantool-jackson-mapping/src/test/java/io/tarantool/mapping/integration/BaseTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,8 @@ public abstract class BaseTest {
4747
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
4848
protected static final Timer timerService = new HashedWheelTimer();
4949
protected static final ConnectionFactory factory = new ConnectionFactory(bootstrap, timerService);
50+
51+
static {
52+
TimerShutdownHook.register(timerService, "jackson-mapping-base-test-timer-shutdown");
53+
}
5054
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2026 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
3+
* All Rights Reserved.
4+
*/
5+
6+
package io.tarantool.mapping.integration;
7+
8+
import io.netty.util.Timer;
9+
10+
public final class TimerShutdownHook {
11+
private TimerShutdownHook() {}
12+
13+
public static void register(Timer timer, String name) {
14+
Runtime.getRuntime().addShutdownHook(new Thread(timer::stop, name));
15+
}
16+
}

tarantool-schema/src/test/java/io/tarantool/client/integration/TarantoolSchemaFetcherTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ public class TarantoolSchemaFetcherTest {
7575
private static final Timer timerService = new HashedWheelTimer();
7676
private static final ConnectionFactory factory = new ConnectionFactory(bootstrap, timerService);
7777

78+
static {
79+
TimerShutdownHook.register(timerService, "tarantool-schema-fetcher-test-timer-shutdown");
80+
}
81+
7882
private static TarantoolContainer<?> tt;
7983

8084
private Long spacePersonId;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2026 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
3+
* All Rights Reserved.
4+
*/
5+
6+
package io.tarantool.client.integration;
7+
8+
import io.netty.util.Timer;
9+
10+
public final class TimerShutdownHook {
11+
private TimerShutdownHook() {}
12+
13+
public static void register(Timer timer, String name) {
14+
Runtime.getRuntime().addShutdownHook(new Thread(timer::stop, name));
15+
}
16+
}

0 commit comments

Comments
 (0)