Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions chainbase/src/main/java/org/tron/core/db/TronDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,23 @@ public void reset() {
@Override
public void close() {
logger.info("******** Begin to close {}. ********", getName());
try {
doClose();
} finally {
logger.info("******** End to close {}. ********", getName());
}
}

protected void doClose() {
try {
writeOptions.close();
} catch (Exception e) {
logger.warn("Failed to close writeOptions in {}.", getName(), e);
}
try {
dbSource.closeDB();
} catch (Exception e) {
logger.warn("Failed to close {}.", getName(), e);
} finally {
logger.info("******** End to close {}. ********", getName());
logger.warn("Failed to close dbSource in {}.", getName(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,16 @@ public void updateByBatch(Map<byte[], byte[]> rows) {
this.dbSource.updateByBatch(rows, writeOptions);
}

/**
* close the database.
*/
@Override
public void close() {
logger.debug("******** Begin to close {}. ********", getName());
try {
writeOptions.close();
dbSource.closeDB();
} catch (Exception e) {
logger.warn("Failed to close {}.", getName(), e);
logger.warn("Failed to close writeOptions in {}.", getName(), e);
}
try {
doClose();
} finally {
logger.debug("******** End to close {}. ********", getName());
}
Expand Down
129 changes: 129 additions & 0 deletions framework/src/test/java/org/tron/core/db/CheckPointV2StoreTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.tron.core.db;

import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import java.io.IOException;
import java.lang.reflect.Field;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.rocksdb.RocksDB;
import org.tron.common.TestConstants;
import org.tron.common.storage.WriteOptionsWrapper;
import org.tron.core.config.args.Args;
import org.tron.core.db.common.DbSourceInter;
import org.tron.core.store.CheckPointV2Store;

public class CheckPointV2StoreTest {

@ClassRule
public static final TemporaryFolder temporaryFolder = new TemporaryFolder();

static {
RocksDB.loadLibrary();
}

@BeforeClass
public static void initArgs() throws IOException {
Args.setParam(
new String[]{"-d", temporaryFolder.newFolder().toString()},
TestConstants.TEST_CONF
);
}

@AfterClass
public static void destroy() {
Args.clearParam();
}

@Test
public void testCloseWithRealResources() {
CheckPointV2Store store = new CheckPointV2Store("test-close-real");
// Exercises the real writeOptions.close() and dbSource.closeDB() code paths
store.close();
}

@Test
public void testCloseReleasesAllResources() throws Exception {
CheckPointV2Store store = new CheckPointV2Store("test-close");

// Replace dbSource with a mock so we can verify closeDB()
Field dbSourceField = TronDatabase.class.getDeclaredField("dbSource");
dbSourceField.setAccessible(true);
DbSourceInter<byte[]> originalDbSource = (DbSourceInter<byte[]>) dbSourceField.get(store);
DbSourceInter<byte[]> mockDbSource = mock(DbSourceInter.class);
dbSourceField.set(store, mockDbSource);

try {
store.close();

verify(mockDbSource).closeDB();
} finally {
originalDbSource.closeDB();
}
}

@Test
public void testCloseWhenDbSourceThrows() throws Exception {
CheckPointV2Store store = new CheckPointV2Store("test-close-dbsource-throws");

Field dbSourceField = TronDatabase.class.getDeclaredField("dbSource");
dbSourceField.setAccessible(true);
DbSourceInter<byte[]> originalDbSource = (DbSourceInter<byte[]>) dbSourceField.get(store);
DbSourceInter<byte[]> mockDbSource = mock(DbSourceInter.class);
doThrow(new RuntimeException("simulated dbSource failure")).when(mockDbSource).closeDB();
dbSourceField.set(store, mockDbSource);

try {
store.close();
} finally {
originalDbSource.closeDB();
}
}

@Test
public void testCloseDbSourceWhenWriteOptionsThrows() throws Exception {
CheckPointV2Store store = new CheckPointV2Store("test-close-exception");

// Replace child writeOptions with a spy that throws on close
Field childWriteOptionsField = CheckPointV2Store.class.getDeclaredField("writeOptions");
childWriteOptionsField.setAccessible(true);
WriteOptionsWrapper childWriteOptions =
(WriteOptionsWrapper) childWriteOptionsField.get(store);
WriteOptionsWrapper spyChildWriteOptions = spy(childWriteOptions);
doThrow(new RuntimeException("simulated writeOptions failure"))
.when(spyChildWriteOptions).close();
childWriteOptionsField.set(store, spyChildWriteOptions);

// Replace parent writeOptions with a spy that throws on close
Field parentWriteOptionsField = TronDatabase.class.getDeclaredField("writeOptions");
parentWriteOptionsField.setAccessible(true);
WriteOptionsWrapper parentWriteOptions =
(WriteOptionsWrapper) parentWriteOptionsField.get(store);
WriteOptionsWrapper spyParentWriteOptions = spy(parentWriteOptions);
doThrow(new RuntimeException("simulated parent writeOptions failure"))
.when(spyParentWriteOptions).close();
parentWriteOptionsField.set(store, spyParentWriteOptions);

// Replace dbSource with a mock
Field dbSourceField = TronDatabase.class.getDeclaredField("dbSource");
dbSourceField.setAccessible(true);
DbSourceInter<byte[]> originalDbSource = (DbSourceInter<byte[]>) dbSourceField.get(store);
DbSourceInter<byte[]> mockDbSource = mock(DbSourceInter.class);
dbSourceField.set(store, mockDbSource);

try {
store.close();

// dbSource.closeDB() must be called even though both writeOptions threw
verify(mockDbSource).closeDB();
} finally {
originalDbSource.closeDB();
}
}
}
52 changes: 52 additions & 0 deletions framework/src/test/java/org/tron/core/db/TronDatabaseTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package org.tron.core.db;

import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import com.google.protobuf.InvalidProtocolBufferException;
import java.io.IOException;
import java.lang.reflect.Field;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
Expand All @@ -12,7 +18,9 @@
import org.junit.rules.TemporaryFolder;
import org.rocksdb.RocksDB;
import org.tron.common.TestConstants;
import org.tron.common.storage.WriteOptionsWrapper;
import org.tron.core.config.args.Args;
import org.tron.core.db.common.DbSourceInter;
import org.tron.core.exception.BadItemException;
import org.tron.core.exception.ItemNotFoundException;

Expand Down Expand Up @@ -99,4 +107,48 @@ public void TestGetFromRoot() throws
Assert.assertEquals(db.getFromRoot("test".getBytes()),
"test");
}

@Test
public void testDoCloseDbSourceCalledWhenWriteOptionsThrows() throws Exception {
TronDatabase<String> db = new TronDatabase<String>("test-do-close") {

@Override
public void put(byte[] key, String item) {
}

@Override
public void delete(byte[] key) {
}

@Override
public String get(byte[] key) {
return null;
}

@Override
public boolean has(byte[] key) {
return false;
}
};

Field writeOptionsField = TronDatabase.class.getDeclaredField("writeOptions");
writeOptionsField.setAccessible(true);
WriteOptionsWrapper spyWriteOptions = spy((WriteOptionsWrapper) writeOptionsField.get(db));
doThrow(new RuntimeException("simulated writeOptions failure")).when(spyWriteOptions).close();
writeOptionsField.set(db, spyWriteOptions);

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();
}
Comment on lines +140 to +152
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.

}
}
Loading