Skip to content

Commit a35e336

Browse files
sirknightjYuqi Huang
andauthored
Verification that the threads are properly shutdown in the endOfFragmentTest (#274)
* Verification that the threads are properly shutdown in the endOfFragmentTest * add polling mechanism to ensure all threads are cleaned up * added extra logging for thread clean up process in EndOfFragmentIntegTest --------- Co-authored-by: Yuqi Huang <[email protected]>
1 parent 32f8c0d commit a35e336

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

src/test/java/com/amazonaws/kinesisvideo/common/EndOfFragmentIntegTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.ByteBuffer;
2525
import java.time.Duration;
2626
import java.util.ArrayList;
27+
import java.util.Arrays;
2728
import java.util.List;
2829
import java.util.concurrent.CountDownLatch;
2930
import java.util.concurrent.ExecutorService;
@@ -88,6 +89,20 @@ public class EndOfFragmentIntegTest extends ProducerTestBase {
8889
private static final int NUMBER_OF_FRAMES_TO_STREAM = 10;
8990
private static final int FPS = 5;
9091
private static final int KEYFRAME_INTERVAL = 5;
92+
private static final int SHUTDOWN_TIMEOUT_MS = 5000; //total time allowed for clean up
93+
private static final int INTERVAL_MS = 100; //time interval between each clean up
94+
95+
/**
96+
* Names of the threads that exist before the test.
97+
*/
98+
private List<String> threadsBefore;
99+
100+
/**
101+
* List of thread names to ignore.
102+
*/
103+
private List<String> threadsToIgnore = Arrays.asList(
104+
"java-sdk-http-connection-reaper" // Owned by AWS SDK Java
105+
);
91106

92107
/**
93108
* List of streams created during tests that need to be cleaned up.
@@ -113,6 +128,14 @@ public void setUp() {
113128
assumeTrue("You need to increase the number of streams the client is configured for before starting the test!",
114129
NUMBER_OF_STREAMS_PER_ITERATION <= NUMBER_OF_STREAMS);
115130

131+
// Capture baseline thread state
132+
this.threadsBefore = Thread.getAllStackTraces().keySet()
133+
.stream()
134+
.map(Thread::getName)
135+
.collect(Collectors.toList());
136+
137+
threadsBefore.sort(String.CASE_INSENSITIVE_ORDER);
138+
116139
createProducer();
117140
}
118141

@@ -150,6 +173,44 @@ public void tearDown() {
150173
this.createdStreams.clear();
151174

152175
assertFalse("An exception happened during cleanup!", failure);
176+
177+
freeProducer();
178+
threadsBefore.removeAll(threadsToIgnore);
179+
for (int i = 0; i < SHUTDOWN_TIMEOUT_MS; i += INTERVAL_MS) {
180+
final List<String> threadsNow = Thread.getAllStackTraces().keySet()
181+
.stream()
182+
.map(Thread::getName)
183+
.collect(Collectors.toList());
184+
185+
threadsNow.sort(String.CASE_INSENSITIVE_ORDER);
186+
threadsNow.removeAll(threadsToIgnore);
187+
log.info("Cleanup iteration {}/() ms - Current thread count: {}, Expected: {}",
188+
i, SHUTDOWN_TIMEOUT_MS, threadsNow.size(), this.threadsBefore.size());
189+
190+
if (threadsNow.equals(this.threadsBefore)) {
191+
break; // threads are cleaned up
192+
} else {
193+
//if threads are not clearned up yet
194+
List<String> extraThreads = new ArrayList<>(threadsNow);
195+
extraThreads.removeAll(this.threadsBefore);
196+
if(!extraThreads.isEmpty()) {
197+
log.warn("extra threads are still running: {}", extraThreads);
198+
}
199+
}
200+
201+
if (i + INTERVAL_MS >= SHUTDOWN_TIMEOUT_MS) {
202+
//time has exceeded shutdown timeout
203+
log.error("Expected threads: {}", this.threadsBefore);
204+
log.error("Current threads: {}", threadsNow);
205+
fail("Timeout waiting for threads to be cleaned up properly");
206+
}
207+
208+
try {
209+
Thread.sleep(INTERVAL_MS);
210+
} catch (InterruptedException e) {
211+
Thread.currentThread().interrupt();
212+
}
213+
}
153214
}
154215

155216
// Using this as a way to repeat the test multiple times
@@ -278,6 +339,8 @@ public void test_When_StoppingNearIntermittentProducer_Then_StreamingSuccessfull
278339
.count();
279340

280341
assertTrue("Didn't receive any PERSISTED ACKs. Received: " + this.receivedFragmentAcks_, persistedAcksCount > 0);
342+
343+
executorService.shutdownNow();
281344
}
282345

283346
/**

src/test/java/com/amazonaws/kinesisvideo/common/ProducerTestBase.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import java.util.Optional;
77
import java.util.concurrent.Executors;
88
import java.util.concurrent.ScheduledExecutorService;
9+
import java.util.concurrent.TimeUnit;
910

1011
import static com.amazonaws.kinesisvideo.internal.producer.jni.NativeKinesisVideoProducerJni.PRODUCER_NATIVE_LIBRARY_NAME;
12+
import static org.junit.Assert.assertTrue;
1113
import static org.junit.Assert.fail;
1214

1315
import com.amazonaws.auth.AWSCredentialsProvider;
@@ -179,6 +181,24 @@ protected void createProducer(DeviceInfo deviceInfo) {
179181
}
180182
}
181183

184+
protected void freeProducer() {
185+
try {
186+
this.kinesisVideoProducer.free();
187+
} catch (ProducerException e) {
188+
log.error("Failed to free the producer", e);
189+
fail(e.getMessage());
190+
}
191+
192+
this.executor.shutdownNow();
193+
try {
194+
assertTrue("Didn't shutdown the executor service in time!",
195+
this.executor.awaitTermination(5, TimeUnit.SECONDS));
196+
} catch (final InterruptedException e) {
197+
Thread.currentThread().interrupt();
198+
log.error("Interrupted while waiting for executor to terminate", e);
199+
}
200+
}
201+
182202
/**
183203
* This method is used to create a stream with the specified information using the producer created as a part of
184204
* the createProducer method
@@ -306,6 +326,8 @@ protected void prepareStream(final String streamName) {
306326
}
307327
}
308328
}
329+
330+
kvs.shutdown();
309331
}
310332

311333
/**

0 commit comments

Comments
 (0)