|
@@ -31,7 +31,11 @@ import org.mockito.invocation.InvocationOnMock;
|
|
|
import org.mockito.stubbing.Answer;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
+import java.lang.management.ManagementFactory;
|
|
|
+import java.lang.management.ThreadInfo;
|
|
|
+import java.lang.management.ThreadMXBean;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
|
|
import static org.junit.Assert.assertSame;
|
|
|
|
|
@@ -168,6 +172,11 @@ public class TestLeaseRenewer {
|
|
|
|
|
|
renewer.closeClient(mockClient1);
|
|
|
renewer.closeClient(mockClient2);
|
|
|
+ renewer.closeClient(MOCK_DFSCLIENT);
|
|
|
+
|
|
|
+ // Make sure renewer is not running due to expiration.
|
|
|
+ Thread.sleep(FAST_GRACE_PERIOD * 2);
|
|
|
+ Assert.assertTrue(!renewer.isRunning());
|
|
|
}
|
|
|
|
|
|
@Test
|
|
@@ -197,4 +206,82 @@ public class TestLeaseRenewer {
|
|
|
Assert.assertFalse(renewer.isRunning());
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Test for HDFS-14575. In this fix, the LeaseRenewer clears all clients
|
|
|
+ * and expires immediately via setting empty time to 0 before it's removed
|
|
|
+ * from factory. Previously, LeaseRenewer#daemon thread might leak.
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testDaemonThreadLeak() throws Exception {
|
|
|
+ Assert.assertFalse("Renewer not initially running", renewer.isRunning());
|
|
|
+
|
|
|
+ // Pretend to create a file#1, daemon#1 starts
|
|
|
+ renewer.put(MOCK_DFSCLIENT);
|
|
|
+ Assert.assertTrue("Renewer should have started running",
|
|
|
+ renewer.isRunning());
|
|
|
+ Pattern daemonThreadNamePattern = Pattern.compile("LeaseRenewer:\\S+");
|
|
|
+ Assert.assertEquals(1, countThreadMatching(daemonThreadNamePattern));
|
|
|
+
|
|
|
+ // Pretend to create file#2, daemon#2 starts due to expiration
|
|
|
+ LeaseRenewer lastRenewer = renewer;
|
|
|
+ renewer =
|
|
|
+ LeaseRenewer.getInstance(FAKE_AUTHORITY, FAKE_UGI_A, MOCK_DFSCLIENT);
|
|
|
+ Assert.assertEquals(lastRenewer, renewer);
|
|
|
+
|
|
|
+ // Pretend to close file#1
|
|
|
+ renewer.closeClient(MOCK_DFSCLIENT);
|
|
|
+ Assert.assertEquals(1, countThreadMatching(daemonThreadNamePattern));
|
|
|
+
|
|
|
+ // Pretend to be expired
|
|
|
+ renewer.setEmptyTime(0);
|
|
|
+
|
|
|
+ renewer =
|
|
|
+ LeaseRenewer.getInstance(FAKE_AUTHORITY, FAKE_UGI_A, MOCK_DFSCLIENT);
|
|
|
+ renewer.setGraceSleepPeriod(FAST_GRACE_PERIOD);
|
|
|
+ boolean success = renewer.put(MOCK_DFSCLIENT);
|
|
|
+ if (!success) {
|
|
|
+ LeaseRenewer.remove(renewer);
|
|
|
+ renewer =
|
|
|
+ LeaseRenewer.getInstance(FAKE_AUTHORITY, FAKE_UGI_A, MOCK_DFSCLIENT);
|
|
|
+ renewer.setGraceSleepPeriod(FAST_GRACE_PERIOD);
|
|
|
+ renewer.put(MOCK_DFSCLIENT);
|
|
|
+ }
|
|
|
+
|
|
|
+ int threadCount = countThreadMatching(daemonThreadNamePattern);
|
|
|
+ //Sometimes old LR#Daemon gets closed and lead to count 1 (rare scenario)
|
|
|
+ Assert.assertTrue(1 == threadCount || 2 == threadCount);
|
|
|
+
|
|
|
+ // After grace period, both daemon#1 and renewer#1 will be removed due to
|
|
|
+ // expiration, then daemon#2 will leak before HDFS-14575.
|
|
|
+ Thread.sleep(FAST_GRACE_PERIOD * 2);
|
|
|
+
|
|
|
+ // Pretend to close file#2, renewer#2 will be created
|
|
|
+ lastRenewer = renewer;
|
|
|
+ renewer =
|
|
|
+ LeaseRenewer.getInstance(FAKE_AUTHORITY, FAKE_UGI_A, MOCK_DFSCLIENT);
|
|
|
+ Assert.assertEquals(lastRenewer, renewer);
|
|
|
+ renewer.setGraceSleepPeriod(FAST_GRACE_PERIOD);
|
|
|
+ renewer.closeClient(MOCK_DFSCLIENT);
|
|
|
+ renewer.setEmptyTime(0);
|
|
|
+ // Make sure LeaseRenewer#daemon threads will terminate after grace period
|
|
|
+ Thread.sleep(FAST_GRACE_PERIOD * 2);
|
|
|
+ Assert.assertEquals("LeaseRenewer#daemon thread leaks", 0,
|
|
|
+ countThreadMatching(daemonThreadNamePattern));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int countThreadMatching(Pattern pattern) {
|
|
|
+ ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
|
|
|
+ ThreadInfo[] infos =
|
|
|
+ threadBean.getThreadInfo(threadBean.getAllThreadIds(), 1);
|
|
|
+ int count = 0;
|
|
|
+ for (ThreadInfo info : infos) {
|
|
|
+ if (info == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (pattern.matcher(info.getThreadName()).matches()) {
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+ }
|
|
|
}
|