Skip to content

fix(db): resolve resource leakage in database close()#21

Open
bladehan1 wants to merge 14 commits intodevelopfrom
481_database_leakage_fix
Open

fix(db): resolve resource leakage in database close()#21
bladehan1 wants to merge 14 commits intodevelopfrom
481_database_leakage_fix

Conversation

@bladehan1
Copy link
Copy Markdown
Owner

@bladehan1 bladehan1 commented Apr 20, 2026

What does this PR do?

Why are these changes required?

This PR has been tested by:

  • Unit Tests
  • Manual Testing

Follow up

Extra details


Summary by cubic

Ensure database resources are always released during shutdown by restructuring close logic in TronDatabase and CheckPointV2Store, preventing RocksDB handle leaks and noisy logs.

  • Bug Fixes
    • Split TronDatabase.close() into separate try-catch blocks and move cleanup into a protected doClose() so dbSource.closeDB() runs even if writeOptions.close() fails; end log is guaranteed in a finally block.
    • Update CheckPointV2Store.close() to close child writeOptions, then delegate to doClose() with debug-level logs to reduce log noise on frequent checkpoints.
    • Add tests covering real resource closure and exception paths to ensure all resources are closed and prevent cross-test contamination.

Written for commit e1bd17c. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced database shutdown reliability with improved error handling and guaranteed resource cleanup to prevent resource leaks during graceful shutdown.
  • Tests

    • Added comprehensive test coverage for database shutdown procedures, including scenarios with resource access failures and error conditions.

warku123 and others added 14 commits February 6, 2026 16:49
Split the try-catch block to ensure parent resources are released
even when writeOptions.close() throws exception.

- Add proper cleanup in finally block to prevent cross-test contamination
- Add Javadoc annotations for test class and methods
- Fix checkstyle violations (import order and line length)

Test: Add unit test to verify super.close() is always called
Split the single try-catch block in TronDatabase.close() into separate
blocks for writeOptions and dbSource to ensure dbSource.closeDB() runs
even when writeOptions.close() throws an exception.

Rewrite tests to verify the exception scenario: mock writeOptions to
throw and assert that dbSource.closeDB() is still called.
Move the "End to close" log back into a finally block so it is
guaranteed to execute even if dbSource.closeDB() throws an Error.
…ug level

TronDatabase.close() now delegates resource cleanup to a protected
doClose() method, keeping info-level logging in the base class.
CheckPointV2Store.close() overrides with debug-level logging and calls
doClose() directly, avoiding log spam on high-frequency checkpoint flushes.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

The pull request refactors database close mechanisms in TronDatabase and CheckPointV2Store by extracting resource cleanup logic into a new protected doClose() method, improving error handling with separate try-catch blocks for writeOptions and dbSource. Comprehensive unit tests are added to verify proper cleanup behavior under failure scenarios.

Changes

Cohort / File(s) Summary
Database Close Refactoring
chainbase/src/main/java/org/tron/core/db/TronDatabase.java, chainbase/src/main/java/org/tron/core/store/CheckPointV2Store.java
Refactored close() methods to extract core teardown into new protected doClose() method in TronDatabase. Errors closing writeOptions and dbSource are now caught and logged separately, with guaranteed "End to close" logging via finally block. CheckPointV2Store.close() now delegates to parent doClose() after handling writeOptions.
Test Coverage for Close Behavior
framework/src/test/java/org/tron/core/db/CheckPointV2StoreTest.java, framework/src/test/java/org/tron/core/db/TronDatabaseTest.java
Added comprehensive test suites using Mockito mocks and reflection to verify resource cleanup in multiple scenarios: normal closure, mocked dbSource failures, and writeOptions exceptions. Tests ensure doClose() completes cleanup even when individual resources throw exceptions.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hops through closer doors,
Where cleanup flows through ordered cores,
Protected paths that never fail,
With mocking spies to test each tale,
Resources freed when all is through!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and clearly describes the main objective: fixing resource leakage in database close() operations, which aligns with the primary intent of refactoring close() methods to ensure parent resources are cleaned up despite exceptions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 481_database_leakage_fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@bladehan1 bladehan1 changed the title 481 database leakage fix fix(db): resolve resource leakage in database close() Apr 20, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
framework/src/test/java/org/tron/core/db/CheckPointV2StoreTest.java (2)

71-87: testCloseWhenDbSourceThrows has no explicit assertion.

The test verifies behavior only implicitly — it passes as long as store.close() does not propagate the exception. Adding verify(mockDbSource).closeDB(); would make the contract explicit (ensures the throwing path was actually exercised, not silently skipped due to a future refactor), and an Assert.fail-style guard could catch accidental propagation:

🧪 Proposed assertion
     try {
       store.close();
+      verify(mockDbSource).closeDB();
     } finally {
       originalDbSource.closeDB();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/test/java/org/tron/core/db/CheckPointV2StoreTest.java` around
lines 71 - 87, The test testCloseWhenDbSourceThrows currently lacks explicit
assertions—update it to verify that the mocked DbSourceInter's closeDB() was
invoked and to explicitly fail if the exception is propagated: wrap
store.close() in a try/catch where a caught exception triggers Assert.fail (or
fail()) to assert no propagation, and after the try/catch add
verify(mockDbSource).closeDB() to ensure the throwing path was exercised;
reference the existing mockDbSource and originalDbSource variables and the
CheckPointV2Store.store.close() invocation when making these additions.

52-128: Duplicated reflection boilerplate + same null-guard caveat as TronDatabaseTest.

Two nits across the four tests:

  1. The dbSourceField / writeOptionsField reflection-and-replace dance is repeated in every test. Extracting a small static helper (e.g., replaceField(Object target, Class<?> owner, String name, Object newValue) returning the original value) would meaningfully shrink each test and make intent clearer.
  2. Each finally calls originalDbSource.closeDB() unconditionally (lines 67, 85, 126). As noted on TronDatabaseTest, if the engine config ever yields a null dbSource, the finally block NPEs and masks the real failure — a null-guard is inexpensive.

Both are non-blocking; feel free to defer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/test/java/org/tron/core/db/CheckPointV2StoreTest.java` around
lines 52 - 128, Tests duplicate reflection boilerplate and call
originalDbSource.closeDB() without a null-guard; create a small static helper
method (e.g., replaceField(Object target, Class<?> owner, String name, Object
newValue) that uses reflection to get the declared Field, setAccessible(true),
swap in newValue and return the original value) and use it to replace usages of
dbSourceField/writeOptionsField in CheckPointV2StoreTest and the related tests,
and update the finally blocks to check that the returned originalDbSource (and
any original writeOptions) is non-null before calling closeDB()/close() to avoid
NPEs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@framework/src/test/java/org/tron/core/db/TronDatabaseTest.java`:
- Around line 140-152: The test can NPE in the finally block if originalDbSource
is null; update the finally to null-check originalDbSource before calling
originalDbSource.closeDB() (i.e., only call closeDB on originalDbSource if
originalDbSource != null) so the test teardown won't mask the real failure when
dbSource wasn't initialized by TronDatabase; locate the variable
originalDbSource and the finally block in the test and add the guard around the
closeDB call.

---

Nitpick comments:
In `@framework/src/test/java/org/tron/core/db/CheckPointV2StoreTest.java`:
- Around line 71-87: The test testCloseWhenDbSourceThrows currently lacks
explicit assertions—update it to verify that the mocked DbSourceInter's
closeDB() was invoked and to explicitly fail if the exception is propagated:
wrap store.close() in a try/catch where a caught exception triggers Assert.fail
(or fail()) to assert no propagation, and after the try/catch add
verify(mockDbSource).closeDB() to ensure the throwing path was exercised;
reference the existing mockDbSource and originalDbSource variables and the
CheckPointV2Store.store.close() invocation when making these additions.
- Around line 52-128: Tests duplicate reflection boilerplate and call
originalDbSource.closeDB() without a null-guard; create a small static helper
method (e.g., replaceField(Object target, Class<?> owner, String name, Object
newValue) that uses reflection to get the declared Field, setAccessible(true),
swap in newValue and return the original value) and use it to replace usages of
dbSourceField/writeOptionsField in CheckPointV2StoreTest and the related tests,
and update the finally blocks to check that the returned originalDbSource (and
any original writeOptions) is non-null before calling closeDB()/close() to avoid
NPEs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c791f9e6-9420-4b32-b600-3c99df3c9ad3

📥 Commits

Reviewing files that changed from the base of the PR and between 2de63bb and e1bd17c.

📒 Files selected for processing (4)
  • chainbase/src/main/java/org/tron/core/db/TronDatabase.java
  • chainbase/src/main/java/org/tron/core/store/CheckPointV2Store.java
  • framework/src/test/java/org/tron/core/db/CheckPointV2StoreTest.java
  • framework/src/test/java/org/tron/core/db/TronDatabaseTest.java

Comment on lines +140 to +152
Field dbSourceField = TronDatabase.class.getDeclaredField("dbSource");
dbSourceField.setAccessible(true);
DbSourceInter<byte[]> originalDbSource = (DbSourceInter<byte[]>) dbSourceField.get(db);
DbSourceInter<byte[]> mockDbSource = mock(DbSourceInter.class);
dbSourceField.set(db, mockDbSource);

try {
db.doClose();
verify(spyWriteOptions).close();
verify(mockDbSource).closeDB();
} finally {
originalDbSource.closeDB();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: originalDbSource could be null if engine config is unset.

TronDatabase's constructor only assigns dbSource when the configured db engine is LEVELDB or ROCKSDB (TronDatabase.java lines 39–47); if the test config ever changes or the engine string doesn't match, line 142 returns null and the finally at line 151 NPEs, masking the real test failure. A null-guard is cheap insurance:

🛡️ Proposed defensive guard
     } finally {
-      originalDbSource.closeDB();
+      if (originalDbSource != null) {
+        originalDbSource.closeDB();
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Field dbSourceField = TronDatabase.class.getDeclaredField("dbSource");
dbSourceField.setAccessible(true);
DbSourceInter<byte[]> originalDbSource = (DbSourceInter<byte[]>) dbSourceField.get(db);
DbSourceInter<byte[]> mockDbSource = mock(DbSourceInter.class);
dbSourceField.set(db, mockDbSource);
try {
db.doClose();
verify(spyWriteOptions).close();
verify(mockDbSource).closeDB();
} finally {
originalDbSource.closeDB();
}
Field dbSourceField = TronDatabase.class.getDeclaredField("dbSource");
dbSourceField.setAccessible(true);
DbSourceInter<byte[]> originalDbSource = (DbSourceInter<byte[]>) dbSourceField.get(db);
DbSourceInter<byte[]> mockDbSource = mock(DbSourceInter.class);
dbSourceField.set(db, mockDbSource);
try {
db.doClose();
verify(spyWriteOptions).close();
verify(mockDbSource).closeDB();
} finally {
if (originalDbSource != null) {
originalDbSource.closeDB();
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/test/java/org/tron/core/db/TronDatabaseTest.java` around lines
140 - 152, The test can NPE in the finally block if originalDbSource is null;
update the finally to null-check originalDbSource before calling
originalDbSource.closeDB() (i.e., only call closeDB on originalDbSource if
originalDbSource != null) so the test teardown won't mask the real failure when
dbSource wasn't initialized by TronDatabase; locate the variable
originalDbSource and the finally block in the test and add the guard around the
closeDB call.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants