diff --git a/actuator/src/main/java/org/tron/core/vm/repository/WriteOptionsWrapper.java b/actuator/src/main/java/org/tron/core/vm/repository/WriteOptionsWrapper.java deleted file mode 100644 index f9e819f9716..00000000000 --- a/actuator/src/main/java/org/tron/core/vm/repository/WriteOptionsWrapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.tron.core.vm.repository; - -import lombok.Getter; - -public class WriteOptionsWrapper { - - @Getter - private org.rocksdb.WriteOptions rocks = null; - @Getter - private org.iq80.leveldb.WriteOptions level = null; - - public static WriteOptionsWrapper getInstance() { - WriteOptionsWrapper wrapper = new WriteOptionsWrapper(); - wrapper.level = new org.iq80.leveldb.WriteOptions(); - wrapper.rocks = new org.rocksdb.WriteOptions(); - return wrapper; - } - - public WriteOptionsWrapper sync(boolean bool) { - this.level.sync(bool); - this.rocks.setSync(bool); - return this; - } -} diff --git a/chainbase/src/main/java/org/tron/common/storage/WriteOptionsWrapper.java b/chainbase/src/main/java/org/tron/common/storage/WriteOptionsWrapper.java index 11277eafe75..bd6cacc6481 100644 --- a/chainbase/src/main/java/org/tron/common/storage/WriteOptionsWrapper.java +++ b/chainbase/src/main/java/org/tron/common/storage/WriteOptionsWrapper.java @@ -1,6 +1,8 @@ package org.tron.common.storage; -public class WriteOptionsWrapper { +import java.io.Closeable; + +public class WriteOptionsWrapper implements Closeable { public org.rocksdb.WriteOptions rocks = null; public org.iq80.leveldb.WriteOptions level = null; @@ -9,6 +11,23 @@ private WriteOptionsWrapper() { } + /** + * Returns an WriteOptionsWrapper. + * + *
CRITICAL: The returned WriteOptionsWrapper holds native resources + * and MUST be closed + * after use to prevent memory leaks. It is strongly recommended to use a try-with-resources + * statement. + * + *
Example of correct usage: + *
{@code
+ * try ( WriteOptionsWrapper readOptions = WriteOptionsWrapper.getInstance()) {
+ * // do something
+ * }
+ * }
+ *
+ * @return a new WriteOptionsWrapper that must be closed.
+ */
public static WriteOptionsWrapper getInstance() {
WriteOptionsWrapper wrapper = new WriteOptionsWrapper();
wrapper.level = new org.iq80.leveldb.WriteOptions();
@@ -23,4 +42,12 @@ public WriteOptionsWrapper sync(boolean bool) {
this.rocks.setSync(bool);
return this;
}
+
+ @Override
+ public void close() {
+ if (rocks != null) {
+ rocks.close();
+ }
+ // leveldb WriteOptions has no close method, and does not need to be closed
+ }
}
diff --git a/chainbase/src/main/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImpl.java b/chainbase/src/main/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImpl.java
index 63191b6ac01..c7ca698cc3d 100644
--- a/chainbase/src/main/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImpl.java
+++ b/chainbase/src/main/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImpl.java
@@ -52,6 +52,7 @@ public class RocksDbDataSourceImpl extends DbStat implements DbSourceInterCRITICAL: The returned iterator holds native resources and MUST be closed + * after use to prevent memory leaks. It is strongly recommended to use a try-with-resources + * statement. + * + *
Example of correct usage: + *
{@code
+ * try ( ReadOptions readOptions = new ReadOptions().setFillCache(false);
+ * RocksIterator iterator = getRocksIterator(readOptions)) {
+ * iterator.seekToFirst();
+ * // do something
+ * }
+ * }
+ *
+ * @return a new database iterator that must be closed.
+ */
+ private RocksIterator getRocksIterator(ReadOptions readOptions) {
+ throwIfNotAlive();
+ return database.newIterator(readOptions);
+ }
+
+ /**
+ * Returns an ReadOptions.
+ *
+ * CRITICAL: The returned ReadOptions holds native resources and MUST be closed + * after use to prevent memory leaks. It is strongly recommended to use a try-with-resources + * statement. + * + *
Example of correct usage: + *
{@code
+ * try (ReadOptions readOptions = getReadOptions();
+ * RocksIterator iterator = getRocksIterator(readOptions)) {
+ * iterator.seekToFirst();
+ * // do something
+ * }
+ * }
+ *
+ * @return a new database iterator that must be closed.
+ */
+ private ReadOptions getReadOptions() {
+ throwIfNotAlive();
+ return new ReadOptions().setFillCache(false);
}
public boolean deleteDbBakPath(String dir) {
diff --git a/chainbase/src/main/java/org/tron/core/db/TronDatabase.java b/chainbase/src/main/java/org/tron/core/db/TronDatabase.java
index e699675408f..40762568c82 100644
--- a/chainbase/src/main/java/org/tron/core/db/TronDatabase.java
+++ b/chainbase/src/main/java/org/tron/core/db/TronDatabase.java
@@ -27,7 +27,7 @@ public abstract class TronDatabaseCRITICAL: Must be closed after use to prevent native memory leaks. + * Use try-with-resources. + * + *
{@code
+ * try (Options options = getOptionsByDbName(dbName)) {
+ * // do something
+ * }
+ * }
+ *
+ * @param dbName db name
+ * @return a new Options instance that must be closed
+ */
public static Options getOptionsByDbName(String dbName) {
RocksDbSettings settings = getSettings();
diff --git a/framework/src/test/java/org/tron/common/storage/leveldb/LevelDbDataSourceImplTest.java b/framework/src/test/java/org/tron/common/storage/leveldb/LevelDbDataSourceImplTest.java
index 8fc05746fc8..78cbba3d079 100644
--- a/framework/src/test/java/org/tron/common/storage/leveldb/LevelDbDataSourceImplTest.java
+++ b/framework/src/test/java/org/tron/common/storage/leveldb/LevelDbDataSourceImplTest.java
@@ -172,7 +172,9 @@ public void testupdateByBatchInner() {
rows.clear();
rows.put(key1.getBytes(), null);
rows.put(key2.getBytes(), null);
- dataSource.updateByBatch(rows, WriteOptionsWrapper.getInstance());
+ try (WriteOptionsWrapper options = WriteOptionsWrapper.getInstance()) {
+ dataSource.updateByBatch(rows, options);
+ }
assertEquals(0, dataSource.allKeys().size());
rows.clear();
diff --git a/framework/src/test/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImplTest.java b/framework/src/test/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImplTest.java
index bf71b024541..86543db19fb 100644
--- a/framework/src/test/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImplTest.java
+++ b/framework/src/test/java/org/tron/common/storage/rocksdb/RocksDbDataSourceImplTest.java
@@ -147,7 +147,9 @@ public void testupdateByBatchInner() {
rows.clear();
rows.put(key1.getBytes(), null);
rows.put(key2.getBytes(), null);
- dataSource.updateByBatch(rows, WriteOptionsWrapper.getInstance());
+ try (WriteOptionsWrapper options = WriteOptionsWrapper.getInstance()) {
+ dataSource.updateByBatch(rows, options);
+ }
assertEquals(0, dataSource.allKeys().size());
rows.clear();
diff --git a/framework/src/test/java/org/tron/core/db/DBIteratorTest.java b/framework/src/test/java/org/tron/core/db/DBIteratorTest.java
index b4f7ca424c0..100502428d0 100644
--- a/framework/src/test/java/org/tron/core/db/DBIteratorTest.java
+++ b/framework/src/test/java/org/tron/core/db/DBIteratorTest.java
@@ -14,6 +14,7 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
+import org.rocksdb.ReadOptions;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.tron.core.db.common.iterator.RockStoreIterator;
@@ -83,7 +84,7 @@ public void testRocksDb() throws RocksDBException, IOException {
RocksDB db = RocksDB.open(options, file.toString())) {
db.put("1".getBytes(StandardCharsets.UTF_8), "1".getBytes(StandardCharsets.UTF_8));
db.put("2".getBytes(StandardCharsets.UTF_8), "2".getBytes(StandardCharsets.UTF_8));
- RockStoreIterator iterator = new RockStoreIterator(db.newIterator());
+ RockStoreIterator iterator = new RockStoreIterator(db.newIterator(), new ReadOptions());
iterator.seekToFirst();
Assert.assertArrayEquals("1".getBytes(StandardCharsets.UTF_8), iterator.getKey());
Assert.assertArrayEquals("1".getBytes(StandardCharsets.UTF_8), iterator.next().getValue());
@@ -99,7 +100,7 @@ public void testRocksDb() throws RocksDBException, IOException {
Assert.assertTrue(e instanceof IllegalStateException);
}
- iterator = new RockStoreIterator(db.newIterator());
+ iterator = new RockStoreIterator(db.newIterator(), new ReadOptions());
iterator.seekToLast();
Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.getKey());
Assert.assertArrayEquals("2".getBytes(StandardCharsets.UTF_8), iterator.getValue());
diff --git a/plugins/src/main/java/common/org/tron/plugins/DbConvert.java b/plugins/src/main/java/common/org/tron/plugins/DbConvert.java
index 37ea6bdeca4..bcf6e1e7afc 100644
--- a/plugins/src/main/java/common/org/tron/plugins/DbConvert.java
+++ b/plugins/src/main/java/common/org/tron/plugins/DbConvert.java
@@ -223,8 +223,8 @@ private void batchInsert(RocksDB rocks, ListCRITICAL: Must be closed after use to prevent native memory leaks. + * Use try-with-resources. + * + *
{@code
+ * try (Options options = newDefaultRocksDbOptions(false, name)) {
+ * // do something
+ * }
+ * }
+ *
+ * @param forBulkLoad if true, optimizes for bulk loading
+ * @param name db name
+ * @return a new Options instance that must be closed
+ */
+ public static Options newDefaultRocksDbOptions(boolean forBulkLoad, String name) {
Options options = new Options();
options.setCreateIfMissing(true);
options.setIncreaseParallelism(1);
@@ -111,35 +125,10 @@ private static Options newDefaultRocksDbOptions(boolean forBulkLoad) {
if (forBulkLoad) {
options.prepareForBulkLoad();
}
- return options;
- }
-
- public static RocksDB newRocksDb(Path db) throws RocksDBException {
- try (Options options = newDefaultRocksDbOptions(false)) {
- if (MARKET_PAIR_PRICE_TO_ORDER.equalsIgnoreCase(db.getFileName().toString())) {
- options.setComparator(new MarketOrderPriceComparatorForRocksDB(new ComparatorOptions()));
- }
- return RocksDB.open(options, db.toString());
- }
- }
-
- public static RocksDB newRocksDbForBulkLoad(Path db) throws RocksDBException {
- try (Options options = newDefaultRocksDbOptions(true)) {
- if (MARKET_PAIR_PRICE_TO_ORDER.equalsIgnoreCase(db.getFileName().toString())) {
- options.setComparator(new MarketOrderPriceComparatorForRocksDB(new ComparatorOptions()));
- }
- return RocksDB.open(options, db.toString());
- }
- }
-
-
- public static RocksDB newRocksDbReadOnly(Path db) throws RocksDBException {
- try (Options options = newDefaultRocksDbOptions(false)) {
- if (MARKET_PAIR_PRICE_TO_ORDER.equalsIgnoreCase(db.getFileName().toString())) {
- options.setComparator(new MarketOrderPriceComparatorForRocksDB(new ComparatorOptions()));
- }
- return RocksDB.openReadOnly(options, db.toString());
+ if (MARKET_PAIR_PRICE_TO_ORDER.equalsIgnoreCase(name)) {
+ options.setComparator(new MarketOrderPriceComparatorForRocksDB(new ComparatorOptions()));
}
+ return options;
}
public static String simpleDecode(byte[] bytes) {
diff --git a/plugins/src/main/java/common/org/tron/plugins/utils/db/DBInterface.java b/plugins/src/main/java/common/org/tron/plugins/utils/db/DBInterface.java
index 513e021c83c..13a195f9347 100644
--- a/plugins/src/main/java/common/org/tron/plugins/utils/db/DBInterface.java
+++ b/plugins/src/main/java/common/org/tron/plugins/utils/db/DBInterface.java
@@ -12,9 +12,25 @@ public interface DBInterface extends Closeable {
void delete(byte[] key);
+ /**
+ * Returns an iterator over the database.
+ *
+ * CRITICAL: The returned iterator holds native resources and MUST be closed + * after use to prevent memory leaks. It is strongly recommended to use a try-with-resources + * statement. + * + *
Example of correct usage: + *
{@code
+ * try (DBIterator iterator = db.iterator()) {
+ * // do something
+ * }
+ * }
+ *
+ * @return a new database iterator that must be closed.
+ */
DBIterator iterator();
- long size();
+ long size() throws IOException;
void close() throws IOException;
diff --git a/plugins/src/main/java/common/org/tron/plugins/utils/db/DbTool.java b/plugins/src/main/java/common/org/tron/plugins/utils/db/DbTool.java
index cf4c69505bc..127b8f97db5 100644
--- a/plugins/src/main/java/common/org/tron/plugins/utils/db/DbTool.java
+++ b/plugins/src/main/java/common/org/tron/plugins/utils/db/DbTool.java
@@ -181,7 +181,7 @@ public static LevelDBImpl openLevelDb(Path db, String name) throws IOException {
}
public static RocksDBImpl openRocksDb(Path db, String name) throws RocksDBException {
- RocksDBImpl rocksdb = new RocksDBImpl(DBUtils.newRocksDb(db), name);
+ RocksDBImpl rocksdb = new RocksDBImpl(db, name);
tryInitEngineFile(db, ROCKSDB);
return rocksdb;
}
diff --git a/plugins/src/main/java/common/org/tron/plugins/utils/db/LevelDBImpl.java b/plugins/src/main/java/common/org/tron/plugins/utils/db/LevelDBImpl.java
index 511f4dfd5b4..1c7f22eff1a 100644
--- a/plugins/src/main/java/common/org/tron/plugins/utils/db/LevelDBImpl.java
+++ b/plugins/src/main/java/common/org/tron/plugins/utils/db/LevelDBImpl.java
@@ -40,8 +40,11 @@ public DBIterator iterator() {
}
@Override
- public long size() {
- return Streams.stream(leveldb.iterator()).count();
+ public long size() throws IOException {
+ try (DBIterator iterator = this.iterator()) {
+ iterator.seekToFirst();
+ return Streams.stream(iterator).count();
+ }
}
@Override
diff --git a/plugins/src/main/java/common/org/tron/plugins/utils/db/RockDBIterator.java b/plugins/src/main/java/common/org/tron/plugins/utils/db/RockDBIterator.java
index d3e17d9173f..17ecca4a4c1 100644
--- a/plugins/src/main/java/common/org/tron/plugins/utils/db/RockDBIterator.java
+++ b/plugins/src/main/java/common/org/tron/plugins/utils/db/RockDBIterator.java
@@ -2,14 +2,19 @@
import java.io.IOException;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.rocksdb.ReadOptions;
import org.rocksdb.RocksIterator;
public class RockDBIterator implements DBIterator {
private final RocksIterator iterator;
+ private final ReadOptions readOptions;
+ private final AtomicBoolean closed = new AtomicBoolean(false);
- public RockDBIterator(RocksIterator iterator) {
+ public RockDBIterator(RocksIterator iterator, ReadOptions readOptions) {
this.iterator = iterator;
+ this.readOptions = readOptions;
}
@Override
@@ -72,6 +77,9 @@ public byte[] setValue(byte[] value) {
@Override
public void close() throws IOException {
- iterator.close();
+ if (closed.compareAndSet(false, true)) {
+ readOptions.close();
+ iterator.close();
+ }
}
}
diff --git a/plugins/src/main/java/common/org/tron/plugins/utils/db/RocksDBImpl.java b/plugins/src/main/java/common/org/tron/plugins/utils/db/RocksDBImpl.java
index 50957bbe61b..236d0a847b3 100644
--- a/plugins/src/main/java/common/org/tron/plugins/utils/db/RocksDBImpl.java
+++ b/plugins/src/main/java/common/org/tron/plugins/utils/db/RocksDBImpl.java
@@ -1,69 +1,97 @@
package org.tron.plugins.utils.db;
+import com.google.common.collect.Streams;
import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicBoolean;
import lombok.Getter;
+import org.rocksdb.Options;
+import org.rocksdb.ReadOptions;
+import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
-import org.rocksdb.RocksIterator;
+import org.tron.plugins.utils.DBUtils;
public class RocksDBImpl implements DBInterface {
- private org.rocksdb.RocksDB rocksDB;
+ private final RocksDB rocksDB;
@Getter
private final String name;
+ private final AtomicBoolean closed = new AtomicBoolean(false);
+ private Options options = null;
- public RocksDBImpl(org.rocksdb.RocksDB rocksDB, String name) {
- this.rocksDB = rocksDB;
- this.name = name;
+ public RocksDBImpl(Path path, String name) throws RocksDBException {
+ try {
+ this.options = DBUtils.newDefaultRocksDbOptions(false, name);
+ this.name = name;
+ this.rocksDB = RocksDB.open(options, path.toString());
+ } catch (RocksDBException e) {
+ if (this.options != null) {
+ this.options.close();
+ }
+ throw e;
+ }
}
@Override
public byte[] get(byte[] key) {
+ throwIfClosed();
try {
return rocksDB.get(key);
} catch (RocksDBException e) {
- e.printStackTrace();
+ throw new RuntimeException(name, e);
}
- return null;
}
@Override
public void put(byte[] key, byte[] value) {
+ throwIfClosed();
try {
rocksDB.put(key, value);
} catch (RocksDBException e) {
- e.printStackTrace();
+ throw new RuntimeException(name, e);
}
}
@Override
public void delete(byte[] key) {
+ throwIfClosed();
try {
rocksDB.delete(key);
} catch (RocksDBException e) {
- e.printStackTrace();
+ throw new RuntimeException(name, e);
}
}
@Override
public DBIterator iterator() {
- return new RockDBIterator(rocksDB.newIterator(
- new org.rocksdb.ReadOptions().setFillCache(false)));
+ throwIfClosed();
+ ReadOptions readOptions = new ReadOptions().setFillCache(false);
+ return new RockDBIterator(rocksDB.newIterator(readOptions), readOptions);
}
@Override
- public long size() {
- RocksIterator iterator = rocksDB.newIterator();
- long size = 0;
- for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) {
- size++;
+ public long size() throws IOException {
+ throwIfClosed();
+ try (DBIterator iterator = this.iterator()) {
+ iterator.seekToFirst();
+ return Streams.stream(iterator).count();
}
- iterator.close();
- return size;
}
@Override
public void close() throws IOException {
- rocksDB.close();
+ if (closed.compareAndSet(false, true)) {
+ if (this.options != null) {
+ this.options.close();
+ }
+ rocksDB.close();
+ }
+ }
+
+ private void throwIfClosed() {
+ if (closed.get()) {
+ throw new IllegalStateException("db " + name + " has been closed");
+ }
}
}
diff --git a/plugins/src/test/java/org/tron/plugins/DbTest.java b/plugins/src/test/java/org/tron/plugins/DbTest.java
index da693a720c2..bbcc1a0bbf7 100644
--- a/plugins/src/test/java/org/tron/plugins/DbTest.java
+++ b/plugins/src/test/java/org/tron/plugins/DbTest.java
@@ -5,6 +5,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.UUID;
+import org.junit.Assert;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.rocksdb.RocksDBException;
@@ -77,11 +78,13 @@ private static void initDB(String sourceDir, String dbName, DbTool.DbType dbType
db.put(pairPriceKey1, "1".getBytes(StandardCharsets.UTF_8));
db.put(pairPriceKey2, "2".getBytes(StandardCharsets.UTF_8));
db.put(pairPriceKey3, "3".getBytes(StandardCharsets.UTF_8));
+ Assert.assertEquals(3, db.size());
} else {
for (int i = 0; i < 100; i++) {
byte[] bytes = UUID.randomUUID().toString().getBytes();
db.put(bytes, bytes);
}
+ Assert.assertEquals(100, db.size());
}
}
}