Skip to content

HDFS-17972. TestDFSClientRetries: restore the fault injector and release its latch when the deadlock test dies - #8712

Merged
pan3793 merged 2 commits into
apache:trunkfrom
joseluisll:hdfs-testdfsclientretries-injector-leak
Sep 4, 2026
Merged

pan3793 merged 2 commits into
apache:trunkfrom
joseluisll:hdfs-testdfsclientretries-injector-leak

Conversation

@joseluisll

Copy link
Copy Markdown
Contributor

Description of PR

https://issues.apache.org/jira/browse/HDFS-17972

TestDFSClientRetries#testLeaseRenewAndDFSOutputStreamDeadLock installs an anonymous DFSClientFaultInjector into the static DFSClientFaultInjector.instance and never restores it. The injector's delayWhenRenewLeaseTimeout() waits on a CountDownLatch with an unbounded await(), and that latch is counted down in exactly one place - SleepFixedTimeAnswer.answer() - which runs only if the mocked NameNode.complete() is invoked. The test's finally block only calls cluster.shutdown().

LeaseRenewer.run() calls the injector from inside synchronized (this) on the SocketTimeoutException abort path, so a renewer waiting there holds the LeaseRenewer monitor for the whole wait. The test normally passes because complete() releases the latch while out1.close() is still running.

If the write pipeline stalls, closeImpl() never reaches completeFile(), complete() is never invoked, the latch stays at 1, and the renewer holds the monitor indefinitely. out1.close() then deadlocks against it on its own exit path:

DFSOutputStream.close -> closeImpl -> closeThreads -> setClosed
  -> DFSClient.endFileLease -> LeaseRenewer.addClient   (synchronized)

@Timeout(120) does not recover this. In JUnit's default SAME_THREAD mode the deadline interrupts the test thread and then waits for the method to return, and a thread blocked on a monitor is not interruptible. The surefire fork therefore hangs until forkedProcessTimeoutInSeconds kills it, and the results for the remaining tests in the class are lost rather than reported. The static injector is also left pointing at the test's own subclass.

Changes, all test-only:

  1. Bound the wait to 30 seconds. This is what breaks the deadlock - the finally runs only after out1.close() returns, which is exactly where the thread is stuck, so cleanup alone cannot help. With the bound, the renewer releases the monitor and a stalled run reports one ordinary test failure instead of hanging the fork.
  2. Count the latch down in the finally, ahead of restoring the injector and ahead of cluster.shutdown(), so the renewer is released promptly on the normal path.
  3. Save and restore the previous injector in that same finally, matching the convention in TestPread, TestClientProtocolForPipelineRecovery, TestDFSInputStream and TestPipelineCloseRecoveryByteArrayLeak. Restoring alone is insufficient: it does nothing for a renewer already inside the old injector.
  4. Re-assert the interrupt flag instead of e.printStackTrace().

No production code is touched, and the deadlock this test guards (HDFS-9294) is unaffected.

How was this patch tested?

mvn -pl hadoop-hdfs-project/hadoop-hdfs test -Dtest=TestDFSClientRetries - 13 tests, 0 failures, 0 errors (JDK 21, Maven 3.9.16). testLeaseRenewAndDFSOutputStreamDeadLock and testLeaseRenewSocketTimeout also pass together in that order in a single JVM.

The failure was reproduced by driving the unmodified test method while withholding the DataNode's ack (DataNodeFaultInjector.delaySendingAckToUpstream), so DataStreamer.waitForAckedSeqno blocks and complete() is unreachable. Without this change the test method never returns after the JUnit interrupt and DFSClientFaultInjector.get() is still the test's own subclass; with it the method returns and the injector is restored.

For code changes:

  • Does the title of this PR start with the corresponding JIRA issue id (e.g. 'HADOOP-17799. Your PR title ...')?
  • Object storage: Have the integration tests been executed and the endpoint declared according to the connector-specific documentation?
  • If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under ASF 2.0?
  • If applicable, have you updated the LICENSE, LICENSE-binary, NOTICE-binary files?

AI Tooling

Contains content generated by Claude Code.

🤖 Generated with Claude Code

…ase its latch when the deadlock test dies

testLeaseRenewAndDFSOutputStreamDeadLock installs an anonymous
DFSClientFaultInjector into the static DFSClientFaultInjector.instance and
never restores it. That injector's delayWhenRenewLeaseTimeout() waits on a
CountDownLatch that is only counted down from SleepFixedTimeAnswer.answer(),
i.e. only if the mocked NameNode.complete() actually runs. The test's finally
block only shut the cluster down.

LeaseRenewer.run() calls delayWhenRenewLeaseTimeout() from inside
synchronized (this), on the SocketTimeoutException abort path, so a renewer
waiting in that injector holds the LeaseRenewer monitor for as long as it
waits. The test works because complete() releases the latch while
out1.close() is still running, before close() needs that monitor again.

If the write pipeline stalls, out1.close() never reaches completeFile(), so
complete() is never invoked and the latch stays at 1. The renewer then holds
the LeaseRenewer monitor indefinitely, and out1.close() deadlocks against it
on its own way out:

  closeImpl() -> closeThreads() -> setClosed() -> DFSClient.endFileLease()
    -> LeaseRenewer.addClient()   (synchronized)

@timeout does not rescue this. In SAME_THREAD mode JUnit interrupts the test
thread at the deadline and then waits for the method to return, and a thread
blocked on a monitor is not interruptible, so the method never returns. The
surefire fork hangs until forkedProcessTimeoutInSeconds kills it, which loses
the results for the rest of the class.

Fix the test isolation:

- Bound the wait to 30 seconds. This is the part that actually breaks the
  deadlock: the finally block below runs only after out1.close() returns, so
  it cannot help a thread that is stuck inside close(). With the bound, the
  renewer releases the monitor, close() completes, and a stalled run fails as
  one ordinary test failure instead of hanging the fork. Also re-assert the
  interrupt flag rather than printing the trace.
- Count the latch down in the finally, before restoring the injector and
  before cluster.shutdown(), so the renewer is released immediately on the
  normal path instead of waiting out the bound.
- Save the previous injector and restore it in that same finally, so the
  static does not stay pointed at this test's subclass, matching the
  convention used by TestPread, TestClientProtocolForPipelineRecovery,
  TestDFSInputStream and TestPipelineCloseRecoveryByteArrayLeak. Restoring
  alone would not be enough: it does nothing for a renewer already waiting
  inside the old injector.

Verified by simulating the stall so that complete() is never reached: without
the bound the test hangs in out1.close() on the renewer monitor and never
returns; with it the test completes, the injector is restored and the latch
released. testLeaseRenewAndDFSOutputStreamDeadLock and
testLeaseRenewSocketTimeout both still pass in that order, in 10.3s and 4.1s,
so the normal path is unaffected.

This is a test-only change; the deadlock the test covers is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@joseluisll
joseluisll force-pushed the hdfs-testdfsclientretries-injector-leak branch from c155e0a to d84abce Compare September 4, 2026 04:30

@pan3793 pan3793 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the detailed analysis. Verified the mechanism: the injector is called under synchronized (this) in LeaseRenewer.run, and the latch is only counted down in SleepFixedTimeAnswer, so a stalled out1.close() pins the renewer monitor and the JUnit timeout cannot interrupt it.

…p/hdfs/TestDFSClientRetries.java

Co-authored-by: Cheng Pan <pan3793@gmail.com>
@hadoop-yetus

Copy link
Copy Markdown

🎊 +1 overall

Vote Subsystem Runtime Logfile Comment
+0 🆗 reexec 0m 54s Docker mode activated.
_ Prechecks _
+1 💚 dupname 0m 0s No case conflicting files found.
+0 🆗 codespell 0m 0s codespell was not available.
+0 🆗 detsecrets 0m 0s detect-secrets was not available.
+1 💚 @author 0m 0s The patch does not contain any @author tags.
+1 💚 test4tests 0m 0s The patch appears to include 1 new or modified test files.
_ trunk Compile Tests _
+1 💚 mvninstall 52m 29s trunk passed
+1 💚 compile 1m 44s trunk passed with JDK Ubuntu-21.0.12+8-1-24.04-Ubuntu
+1 💚 compile 1m 46s trunk passed with JDK Ubuntu-17.0.20+8-1-24.04-Ubuntu
+1 💚 checkstyle 1m 48s trunk passed
+1 💚 mvnsite 1m 54s trunk passed
+1 💚 javadoc 1m 29s trunk passed with JDK Ubuntu-21.0.12+8-1-24.04-Ubuntu
+1 💚 javadoc 1m 29s trunk passed with JDK Ubuntu-17.0.20+8-1-24.04-Ubuntu
+1 💚 spotbugs 4m 22s trunk passed
+1 💚 shadedclient 38m 22s branch has no errors when building and testing our client artifacts.
_ Patch Compile Tests _
+1 💚 mvninstall 1m 24s the patch passed
+1 💚 compile 1m 17s the patch passed with JDK Ubuntu-21.0.12+8-1-24.04-Ubuntu
+1 💚 javac 1m 17s the patch passed
+1 💚 compile 1m 19s the patch passed with JDK Ubuntu-17.0.20+8-1-24.04-Ubuntu
+1 💚 javac 1m 19s the patch passed
+1 💚 blanks 0m 0s The patch has no blanks issues.
+1 💚 checkstyle 1m 19s the patch passed
+1 💚 mvnsite 1m 28s the patch passed
+1 💚 javadoc 0m 59s the patch passed with JDK Ubuntu-21.0.12+8-1-24.04-Ubuntu
+1 💚 javadoc 1m 3s the patch passed with JDK Ubuntu-17.0.20+8-1-24.04-Ubuntu
+1 💚 spotbugs 4m 4s the patch passed
+1 💚 shadedclient 37m 30s patch has no errors when building and testing our client artifacts.
_ Other Tests _
+1 💚 unit 259m 34s hadoop-hdfs in the patch passed.
+1 💚 asflicense 0m 47s The patch does not generate ASF License warnings.
414m 56s
Subsystem Report/Notes
Docker ClientAPI=1.56 ServerAPI=1.56 base: https://ci-hadoop.apache.org/job/hadoop-multibranch/job/PR-8712/2/artifact/out/Dockerfile
GITHUB PR #8712
Optional Tests dupname asflicense compile javac javadoc mvninstall mvnsite unit shadedclient spotbugs checkstyle codespell detsecrets
uname Linux abbf00a72974 5.15.0-190-generic #200-Ubuntu SMP Fri Aug 7 15:06:04 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
Build tool maven
Personality dev-support/bin/hadoop.sh
git revision trunk / d84abce
Default Java Ubuntu-17.0.20+8-1-24.04-Ubuntu
Multi-JDK versions /usr/lib/jvm/java-21-openjdk-amd64:Ubuntu-21.0.12+8-1-24.04-Ubuntu /usr/lib/jvm/java-17-openjdk-amd64:Ubuntu-17.0.20+8-1-24.04-Ubuntu
Test Results https://ci-hadoop.apache.org/job/hadoop-multibranch/job/PR-8712/2/testReport/
Max. process+thread count 2122 (vs. ulimit of 10000)
modules C: hadoop-hdfs-project/hadoop-hdfs U: hadoop-hdfs-project/hadoop-hdfs
Console output https://ci-hadoop.apache.org/job/hadoop-multibranch/job/PR-8712/2/console
versions git=2.43.0 maven=3.9.15 spotbugs=4.9.7
Powered by Apache Yetus 0.14.1 https://yetus.apache.org

This message was automatically generated.

@hadoop-yetus

Copy link
Copy Markdown

💔 -1 overall

Vote Subsystem Runtime Logfile Comment
+0 🆗 reexec 0m 23s Docker mode activated.
_ Prechecks _
+1 💚 dupname 0m 0s No case conflicting files found.
+0 🆗 codespell 0m 0s codespell was not available.
+0 🆗 detsecrets 0m 0s detect-secrets was not available.
+1 💚 @author 0m 0s The patch does not contain any @author tags.
+1 💚 test4tests 0m 0s The patch appears to include 1 new or modified test files.
_ trunk Compile Tests _
+1 💚 mvninstall 32m 28s trunk passed
+1 💚 compile 1m 0s trunk passed with JDK Ubuntu-21.0.12+8-1-24.04-Ubuntu
+1 💚 compile 1m 4s trunk passed with JDK Ubuntu-17.0.20+8-1-24.04-Ubuntu
+1 💚 checkstyle 1m 1s trunk passed
+1 💚 mvnsite 1m 4s trunk passed
+1 💚 javadoc 0m 58s trunk passed with JDK Ubuntu-21.0.12+8-1-24.04-Ubuntu
+1 💚 javadoc 0m 52s trunk passed with JDK Ubuntu-17.0.20+8-1-24.04-Ubuntu
+1 💚 spotbugs 2m 15s trunk passed
+1 💚 shadedclient 17m 20s branch has no errors when building and testing our client artifacts.
-0 ⚠️ patch 17m 37s Used diff version of patch file. Binary files and potentially other changes not applied. Please rebase and squash commits if necessary.
_ Patch Compile Tests _
+1 💚 mvninstall 0m 44s the patch passed
+1 💚 compile 0m 41s the patch passed with JDK Ubuntu-21.0.12+8-1-24.04-Ubuntu
+1 💚 javac 0m 41s the patch passed
+1 💚 compile 0m 46s the patch passed with JDK Ubuntu-17.0.20+8-1-24.04-Ubuntu
+1 💚 javac 0m 46s the patch passed
+1 💚 blanks 0m 0s The patch has no blanks issues.
+1 💚 checkstyle 0m 38s the patch passed
+1 💚 mvnsite 0m 46s the patch passed
+1 💚 javadoc 0m 33s the patch passed with JDK Ubuntu-21.0.12+8-1-24.04-Ubuntu
+1 💚 javadoc 0m 35s the patch passed with JDK Ubuntu-17.0.20+8-1-24.04-Ubuntu
+1 💚 spotbugs 2m 6s the patch passed
+1 💚 shadedclient 17m 2s patch has no errors when building and testing our client artifacts.
_ Other Tests _
-1 ❌ unit 198m 36s /patch-unit-hadoop-hdfs-project_hadoop-hdfs.txt hadoop-hdfs in the patch passed.
+1 💚 asflicense 0m 31s The patch does not generate ASF License warnings.
280m 59s
Reason Tests
Failed junit tests hadoop.hdfs.server.balancer.TestBalancerWithHANameNodes
hadoop.hdfs.server.datanode.TestDataNodeLifeline
Subsystem Report/Notes
Docker ClientAPI=1.56 ServerAPI=1.56 base: https://ci-hadoop.apache.org/job/hadoop-multibranch/job/PR-8712/3/artifact/out/Dockerfile
Optional Tests dupname asflicense compile javac javadoc mvninstall mvnsite unit shadedclient spotbugs checkstyle codespell detsecrets
uname Linux 2a961152ce53 5.15.0-190-generic #200-Ubuntu SMP Fri Aug 7 15:06:04 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
Build tool maven
Personality dev-support/bin/hadoop.sh
git revision trunk / 6e82d7b
Default Java Ubuntu-17.0.20+8-1-24.04-Ubuntu
Multi-JDK versions /usr/lib/jvm/java-21-openjdk-amd64:Ubuntu-21.0.12+8-1-24.04-Ubuntu /usr/lib/jvm/java-17-openjdk-amd64:Ubuntu-17.0.20+8-1-24.04-Ubuntu
Test Results https://ci-hadoop.apache.org/job/hadoop-multibranch/job/PR-8712/3/testReport/
Max. process+thread count 4017 (vs. ulimit of 10000)
modules C: hadoop-hdfs-project/hadoop-hdfs U: hadoop-hdfs-project/hadoop-hdfs
Console output https://ci-hadoop.apache.org/job/hadoop-multibranch/job/PR-8712/3/console
versions git=2.43.0 maven=3.9.15 spotbugs=4.9.7
Powered by Apache Yetus 0.14.1 https://yetus.apache.org

This message was automatically generated.

@pan3793
pan3793 merged commit 706d4dc into apache:trunk Sep 4, 2026
3 of 5 checks passed
pan3793 pushed a commit that referenced this pull request Sep 4, 2026
…ase its latch when the deadlock test dies (#8712)

Authored-by: Jose Luis López López <joseluisll@gmail.com>
Signed-off-by: Cheng Pan <chengpan@apache.org>
@pan3793

pan3793 commented Sep 4, 2026

Copy link
Copy Markdown
Member

thanks, merged to trunk/branch-3.5

a side note: @joseluisll, since you have been working on the testing fix and stability improvement recently, you may be interested in re-enabling the tests excluded on GHA by .github/gha-tests/exclude-tests.txt - those tests were disabled either because they were flaky or failed consistently when run on GHA, I believe some tests can be re-enabled with the recent efforts on test improvement

@joseluisll
joseluisll deleted the hdfs-testdfsclientretries-injector-leak branch September 4, 2026 15:47
@joseluisll

Copy link
Copy Markdown
Contributor Author

Yes, I would like to work on improving testing, like: HADOOP-19979 Fix flaky TestLogAggregationService and TestSSLHttpServerMTLS . Also, I will work on the excluded tests from the GHA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants