diff --git a/CHANGELOG.md b/CHANGELOG.md index bc11f9f18..771b57990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Unite the tx-pool CLI options under the same Tx Pool Options group in UX. [#5466](https://github.com/hyperledger/besu/issues/5466) ### Bug Fixes +- check to ensure storage and transactions are not closed prior to reading/writing [#5527](https://github.com/hyperledger/besu/pull/5527) ### Download Links diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java index a4e439db5..78442732c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java @@ -77,14 +77,23 @@ public abstract class ExecutionEngineJsonRpcMethod implements JsonRpcMethod { cf.complete( resp.otherwise( t -> { - LOG.error( - String.format("failed to exec consensus method %s", this.getName()), - t); + if (LOG.isDebugEnabled()) { + LOG.atDebug() + .setMessage("failed to exec consensus method {}") + .addArgument(this.getName()) + .setCause(t) + .log(); + } else { + LOG.atError() + .setMessage("failed to exec consensus method {}, error: {}") + .addArgument(this.getName()) + .addArgument(t.getMessage()) + .log(); + } return new JsonRpcErrorResponse( request.getRequest().getId(), JsonRpcError.INVALID_REQUEST); }) .result())); - try { return cf.get(); } catch (InterruptedException e) { diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 76752c66f..04784c424 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -69,7 +69,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'gfED3Pzd/+CSSrTtDo1X+lkc7qwffDqF98EZIDMCOIc=' + knownHash = 'V3sh575rrexPv+Ywe8mURT4Z3fREDCDd79PpAISFx8A=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/KeyValueStorage.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/KeyValueStorage.java index a4917a07f..71fc65d69 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/KeyValueStorage.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/KeyValueStorage.java @@ -114,4 +114,11 @@ public interface KeyValueStorage extends Closeable { * @throws StorageException problem encountered when starting a new transaction. */ KeyValueStorageTransaction startTransaction() throws StorageException; + + /** + * Return Whether the underlying storage is closed. + * + * @return boolean indicating whether the storage is closed. + */ + boolean isClosed(); } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java index 4713bb01e..5fc8b3c17 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java @@ -81,7 +81,7 @@ public class OptimisticRocksDBColumnarKeyValueStorage extends RocksDBColumnarKey final WriteOptions writeOptions = new WriteOptions(); writeOptions.setIgnoreMissingColumnFamilies(true); return new SegmentedKeyValueStorageTransactionTransitionValidatorDecorator<>( - new RocksDbTransaction(db.beginTransaction(writeOptions), writeOptions)); + new RocksDbTransaction(db.beginTransaction(writeOptions), writeOptions), this.closed::get); } /** diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java index 1c40716f4..574ece917 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java @@ -108,6 +108,11 @@ public class RocksDBColumnarKeyValueSnapshot implements SnappedKeyValueStorage { return snapTx; } + @Override + public boolean isClosed() { + return closed.get(); + } + @Override public void clear() { throw new UnsupportedOperationException( diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java index 0e98c31c4..cdca868a2 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java @@ -87,7 +87,9 @@ public abstract class RocksDBColumnarKeyValueStorage RocksDbUtil.loadNativeLibrary(); } - private final AtomicBoolean closed = new AtomicBoolean(false); + /** atomic boolean to track if the storage is closed */ + protected final AtomicBoolean closed = new AtomicBoolean(false); + private final WriteOptions tryDeleteOptions = new WriteOptions().setNoSlowdown(true).setIgnoreMissingColumnFamilies(true); private final ReadOptions readOptions = new ReadOptions().setVerifyChecksums(false); @@ -328,6 +330,11 @@ public abstract class RocksDBColumnarKeyValueStorage } } + @Override + public boolean isClosed() { + return closed.get(); + } + void throwIfClosed() { if (closed.get()) { LOG.error("Attempting to use a closed RocksDbKeyValueStorage"); diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java index a3d31cb2a..dbe460b3f 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java @@ -93,10 +93,7 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A * @return the optional data */ public Optional get(final byte[] key) { - if (isClosed.get()) { - LOG.debug("Attempted to access closed snapshot"); - return Optional.empty(); - } + throwIfClosed(); try (final OperationTimer.TimingContext ignored = metrics.getReadLatency().startTimer()) { return Optional.ofNullable(snapTx.get(columnFamilyHandle, readOptions, key)); @@ -107,10 +104,7 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A @Override public void put(final byte[] key, final byte[] value) { - if (isClosed.get()) { - LOG.debug("Attempted to access closed snapshot"); - return; - } + throwIfClosed(); try (final OperationTimer.TimingContext ignored = metrics.getWriteLatency().startTimer()) { snapTx.put(columnFamilyHandle, key, value); @@ -125,10 +119,8 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A @Override public void remove(final byte[] key) { - if (isClosed.get()) { - LOG.debug("Attempted to access closed snapshot"); - return; - } + throwIfClosed(); + try (final OperationTimer.TimingContext ignored = metrics.getRemoveLatency().startTimer()) { snapTx.delete(columnFamilyHandle, key); } catch (final RocksDBException e) { @@ -146,6 +138,8 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A * @return the stream */ public Stream> stream() { + throwIfClosed(); + final RocksIterator rocksIterator = db.newIterator(columnFamilyHandle, readOptions); rocksIterator.seekToFirst(); return RocksDbIterator.create(rocksIterator).toStream(); @@ -157,6 +151,8 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A * @return the stream */ public Stream streamKeys() { + throwIfClosed(); + final RocksIterator rocksIterator = db.newIterator(columnFamilyHandle, readOptions); rocksIterator.seekToFirst(); return RocksDbIterator.create(rocksIterator).toStreamKeys(); @@ -169,6 +165,8 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A @Override public void rollback() { + throwIfClosed(); + try { snapTx.rollback(); metrics.getRollbackCount().inc(); @@ -189,9 +187,7 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A * @return the rocks db snapshot transaction */ public RocksDBSnapshotTransaction copy() { - if (isClosed.get()) { - throw new StorageException("Snapshot already closed"); - } + throwIfClosed(); try { var copyReadOptions = new ReadOptions().setSnapshot(snapshot.markAndUseSnapshot()); var copySnapTx = db.beginTransaction(writeOptions); @@ -213,4 +209,11 @@ public class RocksDBSnapshotTransaction implements KeyValueStorageTransaction, A snapshot.unMarkSnapshot(); isClosed.set(true); } + + private void throwIfClosed() { + if (isClosed.get()) { + LOG.error("Attempting to use a closed RocksDBSnapshotTransaction"); + throw new StorageException("Storage has already been closed"); + } + } } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java index 9dc0cfb6b..b6433ad19 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java @@ -86,6 +86,6 @@ public class TransactionDBRocksDBColumnarKeyValueStorage extends RocksDBColumnar final WriteOptions writeOptions = new WriteOptions(); writeOptions.setIgnoreMissingColumnFamilies(true); return new SegmentedKeyValueStorageTransactionTransitionValidatorDecorator<>( - new RocksDbTransaction(db.beginTransaction(writeOptions), writeOptions)); + new RocksDbTransaction(db.beginTransaction(writeOptions), writeOptions), this.closed::get); } } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java index e75a1b969..579c83249 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/unsegmented/RocksDBKeyValueStorage.java @@ -97,6 +97,7 @@ public class RocksDBKeyValueStorage implements KeyValueStorage { @Override public void clear() throws StorageException { throwIfClosed(); + try (final RocksIterator rocksIterator = db.newIterator()) { rocksIterator.seekToFirst(); if (rocksIterator.isValid()) { @@ -141,6 +142,7 @@ public class RocksDBKeyValueStorage implements KeyValueStorage { @Override public Stream> stream() { throwIfClosed(); + final RocksIterator rocksIterator = db.newIterator(); rocksIterator.seekToFirst(); return RocksDbIterator.create(rocksIterator).toStream(); @@ -149,6 +151,7 @@ public class RocksDBKeyValueStorage implements KeyValueStorage { @Override public Stream streamKeys() { throwIfClosed(); + final RocksIterator rocksIterator = db.newIterator(); rocksIterator.seekToFirst(); return RocksDbIterator.create(rocksIterator).toStreamKeys(); @@ -186,6 +189,11 @@ public class RocksDBKeyValueStorage implements KeyValueStorage { new RocksDBTransaction(db.beginTransaction(options), options, rocksDBMetrics)); } + @Override + public boolean isClosed() { + return closed.get(); + } + @Override public void close() { if (closed.compareAndSet(false, true)) { diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java index 627313f71..d5b5fdaed 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java @@ -154,6 +154,11 @@ public class InMemoryKeyValueStorage return new KeyValueStorageTransactionTransitionValidatorDecorator(new InMemoryTransaction()); } + @Override + public boolean isClosed() { + return false; + } + /** * Key set. * diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java index b8144e8bb..1c12dc4c9 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java @@ -30,11 +30,15 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Streams; import org.apache.commons.lang3.tuple.Pair; import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Key value storage which stores in memory all updates to a parent worldstate storage. */ public class LayeredKeyValueStorage extends InMemoryKeyValueStorage implements SnappedKeyValueStorage { + private static final Logger LOG = LoggerFactory.getLogger(LayeredKeyValueStorage.class); + private final KeyValueStorage parent; /** @@ -65,6 +69,8 @@ public class LayeredKeyValueStorage extends InMemoryKeyValueStorage @Override public Optional get(final byte[] key) throws StorageException { + throwIfClosed(); + final Lock lock = rwLock.readLock(); lock.lock(); try { @@ -82,6 +88,8 @@ public class LayeredKeyValueStorage extends InMemoryKeyValueStorage @Override public Stream> stream() { + throwIfClosed(); + final Lock lock = rwLock.readLock(); lock.lock(); try { @@ -104,6 +112,8 @@ public class LayeredKeyValueStorage extends InMemoryKeyValueStorage @Override public Stream streamKeys() { + throwIfClosed(); + final Lock lock = rwLock.readLock(); lock.lock(); try { @@ -131,6 +141,8 @@ public class LayeredKeyValueStorage extends InMemoryKeyValueStorage @Override public KeyValueStorageTransaction startTransaction() { + throwIfClosed(); + return new KeyValueStorageTransactionTransitionValidatorDecorator( new InMemoryTransaction() { @Override @@ -151,8 +163,20 @@ public class LayeredKeyValueStorage extends InMemoryKeyValueStorage }); } + @Override + public boolean isClosed() { + return parent.isClosed(); + } + @Override public SnappedKeyValueStorage clone() { return new LayeredKeyValueStorage(hashValueStore, parent); } + + private void throwIfClosed() { + if (parent.isClosed()) { + LOG.error("Attempting to use a closed RocksDBKeyValueStorage"); + throw new StorageException("Storage has been closed"); + } + } } diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LimitedInMemoryKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LimitedInMemoryKeyValueStorage.java index cca8d84cc..19ee9868e 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LimitedInMemoryKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LimitedInMemoryKeyValueStorage.java @@ -144,6 +144,11 @@ public class LimitedInMemoryKeyValueStorage implements KeyValueStorage { return new KeyValueStorageTransactionTransitionValidatorDecorator(new MemoryTransaction()); } + @Override + public boolean isClosed() { + return false; + } + private class MemoryTransaction implements KeyValueStorageTransaction { private Map updatedValues = new HashMap<>(); diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorage.java index 90af60538..54e8564d8 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorage.java @@ -123,6 +123,13 @@ public interface SegmentedKeyValueStorage extends Closeable { */ void clear(S segmentHandle); + /** + * Whether the underlying storage is closed. + * + * @return boolean indicating whether the underlying storage is closed. + */ + boolean isClosed(); + /** * Represents a set of changes to be committed atomically. A single transaction is not * thread-safe, but multiple transactions can execute concurrently. diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java index 2f9280535..6504b1885 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java @@ -26,6 +26,8 @@ import java.util.function.Predicate; import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The type Segmented key value storage adapter. @@ -34,6 +36,7 @@ import org.apache.commons.lang3.tuple.Pair; */ public class SegmentedKeyValueStorageAdapter implements KeyValueStorage { + private static final Logger LOG = LoggerFactory.getLogger(SegmentedKeyValueStorageAdapter.class); private final S segmentHandle; private final SegmentedKeyValueStorage storage; @@ -51,41 +54,49 @@ public class SegmentedKeyValueStorageAdapter implements KeyValueStorage { @Override public void clear() { + throwIfClosed(); storage.clear(segmentHandle); } @Override public boolean containsKey(final byte[] key) throws StorageException { + throwIfClosed(); return storage.containsKey(segmentHandle, key); } @Override public Optional get(final byte[] key) throws StorageException { + throwIfClosed(); return storage.get(segmentHandle, key); } @Override public Set getAllKeysThat(final Predicate returnCondition) { + throwIfClosed(); return storage.getAllKeysThat(segmentHandle, returnCondition); } @Override public Set getAllValuesFromKeysThat(final Predicate returnCondition) { + throwIfClosed(); return storage.getAllValuesFromKeysThat(segmentHandle, returnCondition); } @Override public Stream> stream() { + throwIfClosed(); return storage.stream(segmentHandle); } @Override public Stream streamKeys() { + throwIfClosed(); return storage.streamKeys(segmentHandle); } @Override public boolean tryDelete(final byte[] key) { + throwIfClosed(); return storage.tryDelete(segmentHandle, key); } @@ -101,23 +112,39 @@ public class SegmentedKeyValueStorageAdapter implements KeyValueStorage { @Override public void put(final byte[] key, final byte[] value) { + throwIfClosed(); transaction.put(segmentHandle, key, value); } @Override public void remove(final byte[] key) { + throwIfClosed(); transaction.remove(segmentHandle, key); } @Override public void commit() throws StorageException { + throwIfClosed(); transaction.commit(); } @Override public void rollback() { + throwIfClosed(); transaction.rollback(); } }; } + + @Override + public boolean isClosed() { + return storage.isClosed(); + } + + private void throwIfClosed() { + if (storage.isClosed()) { + LOG.error("Attempting to use a closed Storage instance."); + throw new StorageException("Storage has been closed"); + } + } } diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageTransactionTransitionValidatorDecorator.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageTransactionTransitionValidatorDecorator.java index fd9bb4de9..53cacb13e 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageTransactionTransitionValidatorDecorator.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageTransactionTransitionValidatorDecorator.java @@ -19,6 +19,8 @@ import static com.google.common.base.Preconditions.checkState; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.services.kvstore.SegmentedKeyValueStorage.Transaction; +import java.util.function.Supplier; + /** * The Segmented key value storage transaction transition validator decorator. * @@ -28,33 +30,39 @@ public class SegmentedKeyValueStorageTransactionTransitionValidatorDecorator implements Transaction { private final Transaction transaction; + private final Supplier isClosed; private boolean active = true; /** * Instantiates a new Segmented key value storage transaction transition validator decorator. * * @param toDecorate the to decorate + * @param isClosed supplier that returns true if the storage is closed */ public SegmentedKeyValueStorageTransactionTransitionValidatorDecorator( - final Transaction toDecorate) { + final Transaction toDecorate, final Supplier isClosed) { this.transaction = toDecorate; + this.isClosed = isClosed; } @Override public final void put(final S segment, final byte[] key, final byte[] value) { checkState(active, "Cannot invoke put() on a completed transaction."); + checkState(!isClosed.get(), "Cannot invoke put() on a closed storage."); transaction.put(segment, key, value); } @Override public final void remove(final S segment, final byte[] key) { checkState(active, "Cannot invoke remove() on a completed transaction."); + checkState(!isClosed.get(), "Cannot invoke remove() on a closed storage."); transaction.remove(segment, key); } @Override public final void commit() throws StorageException { checkState(active, "Cannot commit a completed transaction."); + checkState(!isClosed.get(), "Cannot invoke commit() on a closed storage."); active = false; transaction.commit(); } @@ -62,6 +70,7 @@ public class SegmentedKeyValueStorageTransactionTransitionValidatorDecorator @Override public final void rollback() { checkState(active, "Cannot rollback a completed transaction."); + checkState(!isClosed.get(), "Cannot invoke rollback() on a closed storage."); active = false; transaction.rollback(); }