Skip to content

Commit 2aa7ada

Browse files
committed
Fix memory leak orphaned from Wisp
1 parent 3603bf5 commit 2aa7ada

File tree

5 files changed

+104
-89
lines changed

5 files changed

+104
-89
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
java adoptopenjdk-8.0.332+9

modules/vistas-server/dependency-reduced-pom.xml

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -115,29 +115,7 @@
115115
<additionalClasspathElements>
116116
<additionalClasspathElement>${project.basedir}/target/classes</additionalClasspathElement>
117117
</additionalClasspathElements>
118-
</configuration>
119-
</plugin>
120-
<plugin>
121-
<groupId>org.jacoco</groupId>
122-
<artifactId>jacoco-maven-plugin</artifactId>
123-
<version>0.8.8</version>
124-
<executions>
125-
<execution>
126-
<goals>
127-
<goal>prepare-agent</goal>
128-
</goals>
129-
</execution>
130-
<execution>
131-
<id>report</id>
132-
<phase>test</phase>
133-
<goals>
134-
<goal>report</goal>
135-
</goals>
136-
</execution>
137-
</executions>
138-
<configuration>
139-
<destfile>${basedir}/target/coverage-reports/jacoco-unit.exec</destfile>
140-
<datafile>${basedir}/target/coverage-reports/jacoco-unit.exec</datafile>
118+
<argLine>-Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/mats/Desktop</argLine>
141119
</configuration>
142120
</plugin>
143121
<plugin>

modules/vistas-server/pom.xml

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -130,32 +130,9 @@
130130
<additionalClasspathElements>
131131
<additionalClasspathElement>${project.basedir}/target/classes</additionalClasspathElement>
132132
</additionalClasspathElements>
133+
<argLine>-Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/mats/Desktop</argLine>
133134
</configuration>
134135
</plugin>
135-
<plugin>
136-
<groupId>org.jacoco</groupId>
137-
<artifactId>jacoco-maven-plugin</artifactId>
138-
<version>0.8.8</version>
139-
<configuration>
140-
<destfile>${basedir}/target/coverage-reports/jacoco-unit.exec</destfile>
141-
<datafile>${basedir}/target/coverage-reports/jacoco-unit.exec</datafile>
142-
</configuration>
143-
<executions>
144-
<execution>
145-
<goals>
146-
<goal>prepare-agent</goal>
147-
</goals>
148-
</execution>
149-
<!-- attached to Maven test phase -->
150-
<execution>
151-
<id>report</id>
152-
<phase>test</phase>
153-
<goals>
154-
<goal>report</goal>
155-
</goals>
156-
</execution>
157-
</executions>
158-
</plugin>
159136
<plugin>
160137
<groupId>org.apache.maven.plugins</groupId>
161138
<artifactId>maven-shade-plugin</artifactId>
@@ -223,7 +200,7 @@
223200
<dependency>
224201
<groupId>com.coreoz</groupId>
225202
<artifactId>wisp</artifactId>
226-
<version>2.2.1</version>
203+
<version>2.4.0</version>
227204
<scope>compile</scope>
228205
</dependency>
229206

@@ -237,4 +214,4 @@
237214

238215
</dependencies>
239216

240-
</project>
217+
</project>

modules/vistas-server/src/main/java/com/craftmend/vistas/server/base/VistasScheduler.java

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
package com.craftmend.vistas.server.base;
22

3+
import com.coreoz.wisp.JobStatus;
34
import com.coreoz.wisp.Scheduler;
45
import com.coreoz.wisp.schedule.Schedules;
56
import com.craftmend.openaudiomc.generic.platform.interfaces.TaskService;
67
import com.craftmend.openaudiomc.generic.service.Service;
78

89
import java.time.Duration;
9-
import java.util.ArrayList;
10-
import java.util.List;
10+
import java.util.UUID;
1111

1212
public class VistasScheduler extends Service implements TaskService {
1313

14-
private Scheduler scheduler;
14+
private final Scheduler scheduler;
1515
private int taskCount = 0;
16-
private List<Integer> runningTasks = new ArrayList<>();
1716

1817
public VistasScheduler() {
1918
scheduler = new Scheduler();
19+
20+
scheduler.schedule(
21+
"Terminated jobs cleaner",
22+
() -> scheduler
23+
.jobStatus()
24+
.stream()
25+
.filter(job -> job.status() == JobStatus.DONE)
26+
// Clean only jobs that have finished executing since at least 10 seconds
27+
.filter(job -> job.lastExecutionEndedTimeInMillis() < (System.currentTimeMillis() - 10000))
28+
.forEach(job -> scheduler.remove(job.name())),
29+
Schedules.fixedDelaySchedule(Duration.ofSeconds(5))
30+
);
2031
}
2132

2233
@Override
@@ -25,29 +36,12 @@ public int scheduleAsyncRepeatingTask(Runnable runnable, int delayUntilFirst, in
2536
int intervalMs = tickInterval * 50;
2637
int currentTask = taskCount++;
2738

28-
runningTasks.add(currentTask);
29-
WrappedRunnable handler = new WrappedRunnable();
30-
31-
handler.setTask(() -> {
32-
if (isCancelled(currentTask)) {
33-
runningTasks.removeIf(task -> task == currentTask);
34-
return;
35-
}
36-
37-
runnable.run();
38-
39-
scheduler.schedule(
40-
() -> {
41-
handler.getTask().run();
42-
},
43-
Schedules.executeOnce(Schedules.fixedDelaySchedule(Duration.ofMillis(intervalMs)))
44-
);
45-
46-
});
47-
4839
scheduler.schedule(
49-
handler.getTask(),
50-
Schedules.executeOnce(Schedules.fixedDelaySchedule(Duration.ofMillis(delayMs)))
40+
currentTask + "",
41+
() -> {
42+
runnable.run();
43+
},
44+
Schedules.fixedDelaySchedule(Duration.ofMillis(intervalMs))
5145
);
5246

5347
return currentTask;
@@ -62,35 +56,27 @@ public int scheduleSyncRepeatingTask(Runnable runnable, int delayUntilFirst, int
6256
public int schduleSyncDelayedTask(Runnable runnable, int delay) {
6357
int delayMs = delay * 50;
6458
int currentTask = taskCount++;
65-
runningTasks.add(currentTask);
6659

6760
scheduler.schedule(
61+
currentTask + "",
6862
() -> {
69-
if (isCancelled(currentTask)) {
70-
runningTasks.removeIf(task -> task == currentTask);
71-
return;
72-
}
7363
runnable.run();
74-
runningTasks.removeIf(task -> task == currentTask);
7564
},
7665
Schedules.executeOnce(Schedules.fixedDelaySchedule(Duration.ofMillis(delayMs)))
7766
);
7867

7968
return currentTask;
8069
}
8170

82-
public boolean isCancelled(int task) {
83-
return !runningTasks.contains(task);
84-
}
85-
8671
@Override
8772
public void cancelRepeatingTask(int i) {
88-
runningTasks.removeIf(task -> task == i);
73+
scheduler.cancel(i + "");
8974
}
9075

9176
@Override
9277
public void runAsync(Runnable runnable) {
9378
scheduler.schedule(
79+
UUID.randomUUID().toString(),
9480
() -> {
9581
runnable.run();
9682
},
@@ -101,6 +87,7 @@ public void runAsync(Runnable runnable) {
10187
@Override
10288
public void runSync(Runnable runnable) {
10389
scheduler.schedule(
90+
UUID.randomUUID().toString(),
10491
runnable,
10592
Schedules.executeOnce(Schedules.fixedDelaySchedule(Duration.ofMillis(1)))
10693
);

modules/vistas-server/src/test/java/vistas/test/TestServer.java

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package vistas.test;
22

33
import com.craftmend.openaudiomc.OpenAudioMc;
4+
import com.craftmend.openaudiomc.generic.client.helpers.ClientRtcLocationUpdate;
5+
import com.craftmend.openaudiomc.generic.client.objects.ClientConnection;
6+
import com.craftmend.openaudiomc.generic.networking.packets.client.voice.PacketClientUpdateVoiceLocations;
7+
import com.craftmend.openaudiomc.generic.networking.payloads.client.voice.ClientVoiceUpdatePeerLocationsPayload;
8+
import com.craftmend.openaudiomc.generic.node.packets.ForwardSocketPacket;
49
import com.craftmend.openaudiomc.generic.oac.OpenaudioAccountService;
510
import com.craftmend.openaudiomc.generic.logging.OpenAudioLogger;
611
import com.craftmend.openaudiomc.generic.proxy.interfaces.UserHooks;
7-
import com.craftmend.openaudiomc.vistas.client.redis.packets.ServerRegisterPacket;
8-
import com.craftmend.openaudiomc.vistas.client.redis.packets.UserJoinPacket;
9-
import com.craftmend.openaudiomc.vistas.client.redis.packets.UserLeavePacket;
12+
import com.craftmend.openaudiomc.vistas.client.Vistas;
13+
import com.craftmend.openaudiomc.vistas.client.client.VistasRedisClient;
14+
import com.craftmend.openaudiomc.vistas.client.redis.packets.*;
1015
import com.craftmend.openaudiomc.vistas.client.server.networking.VistasRedisServer;
1116
import com.craftmend.openaudiomc.vistas.client.users.ServerUserHooks;
1217
import com.craftmend.vistas.server.VistasServer;
@@ -20,6 +25,8 @@
2025
import vistas.test.utils.Waiter;
2126

2227
import java.net.Socket;
28+
import java.util.HashSet;
29+
import java.util.Set;
2330
import java.util.UUID;
2431

2532
public class TestServer extends TestCase {
@@ -68,6 +75,7 @@ public void testFullStack() {
6875
// fake register a minecraft server
6976
UUID fakeServer1 = UUID.randomUUID();
7077
UUID fakeServer2 = UUID.randomUUID();
78+
7179
OpenAudioMc.getService(VistasRedisServer.class).getPacketEvents().handlePacket(null, new ServerRegisterPacket(fakeServer1));
7280
OpenAudioMc.getService(VistasRedisServer.class).getPacketEvents().handlePacket(null, new ServerRegisterPacket(fakeServer2));
7381

@@ -151,6 +159,70 @@ public void testFullStack() {
151159
assertEquals(0, OpenAudioMc.resolveDependency(ServerUserHooks.class).getRemoteUsers().size());
152160
assertEquals(0, OpenAudioMc.resolveDependency(ServerUserHooks.class).getRemoteUsers().size());
153161

162+
startLoggingMemory();
163+
startStressTesting();
164+
}
165+
166+
private void startStressTesting() {
167+
// crate server
168+
UUID fakeServer1 = UUID.randomUUID();
169+
OpenAudioMc.getService(VistasRedisServer.class).getPacketEvents().handlePacket(null, new ServerRegisterPacket(fakeServer1));
170+
171+
Set<ClientRtcLocationUpdate> updates = new HashSet<>();
172+
173+
// loop 50 times
174+
for (int i = 0; i < 50; i++) {
175+
ClientRtcLocationUpdate update = new ClientRtcLocationUpdate("RandomStreamKey" + i, 1, 2, 3, 4);
176+
updates.add(update);
177+
}
178+
179+
TempUser tempUser = new TempUser(UUID.randomUUID().toString(), UUID.randomUUID());
180+
181+
while (true) {
182+
// create user, send 1000 movement packets, then leave
183+
//System.out.println("Stress testing user " + tempUser.getName());
184+
OpenAudioMc.getService(VistasRedisServer.class).getPacketEvents().handlePacket(null, new UserJoinPacket(
185+
tempUser.getName(),
186+
tempUser.getUuid(),
187+
fakeServer1,
188+
"localhost"
189+
));
190+
191+
for (int i = 0; i < 1000; i++) {
192+
// make movement update packet
193+
PacketClientUpdateVoiceLocations packet = new PacketClientUpdateVoiceLocations(new ClientVoiceUpdatePeerLocationsPayload(updates));
194+
OpenAudioMc.getService(VistasRedisServer.class).getPacketEvents().handlePacket(null, new WrappedProxyPacket(
195+
new ForwardSocketPacket(packet),
196+
fakeServer1,
197+
tempUser.getUuid()
198+
));
199+
}
200+
201+
// user logout
202+
OpenAudioMc.getService(VistasRedisServer.class).getPacketEvents().handlePacket(null, new UserLeavePacket(
203+
tempUser.getName(),
204+
tempUser.getUuid(),
205+
fakeServer1
206+
));
207+
}
208+
}
209+
210+
private void startLoggingMemory() {
211+
new Thread(() -> {
212+
int max = 0;
213+
while (true) {
214+
int used = (int) ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024);
215+
if (used > max) max = used;
216+
217+
System.out.println("Memory usage: " + used + "MB (max: " + max + "MB)");
218+
219+
try {
220+
Thread.sleep(500);
221+
} catch (InterruptedException e) {
222+
e.printStackTrace();
223+
}
224+
}
225+
}).start();
154226
}
155227

156228
@Getter

0 commit comments

Comments
 (0)