
jetbrains.exodus.entitystore.PersistentStoreTransaction Maven / Gradle / Ivy
/**
* Copyright 2010 - 2020 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.exodus.entitystore;
import jetbrains.exodus.ByteIterable;
import jetbrains.exodus.ExodusException;
import jetbrains.exodus.OutOfDiskSpaceException;
import jetbrains.exodus.bindings.IntegerBinding;
import jetbrains.exodus.bindings.LongBinding;
import jetbrains.exodus.core.dataStructures.FakeObjectCache;
import jetbrains.exodus.core.dataStructures.NonAdjustableConcurrentObjectCache;
import jetbrains.exodus.core.dataStructures.ObjectCacheBase;
import jetbrains.exodus.core.dataStructures.ObjectCacheDecorator;
import jetbrains.exodus.core.dataStructures.hash.LongHashMap;
import jetbrains.exodus.core.dataStructures.hash.LongHashSet;
import jetbrains.exodus.core.dataStructures.hash.LongSet;
import jetbrains.exodus.entitystore.iterate.*;
import jetbrains.exodus.env.*;
import jetbrains.exodus.util.StringBuilderSpinAllocator;
import kotlin.jvm.functions.Function0;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@SuppressWarnings({"RawUseOfParameterizedType", "rawtypes"})
public class PersistentStoreTransaction implements StoreTransaction, TxnGetterStrategy, TxnProvider {
private static final Logger logger = LoggerFactory.getLogger(PersistentStoreTransaction.class);
enum TransactionType {
Regular,
Exclusive,
Readonly
}
private static final int LOCAL_CACHE_GENERATIONS = 2;
@NotNull
private static final ByteIterable ZERO_VERSION_ENTRY = IntegerBinding.intToCompressedEntry(0);
@NotNull
protected final PersistentEntityStoreImpl store;
@NotNull
protected final Transaction txn;
private final ObjectCacheBase propsCache;
@NotNull
private final ObjectCacheBase linksCache;
@NotNull
private final ObjectCacheBase blobStringsCache;
private EntityIterableCacheAdapter localCache;
private int localCacheAttempts;
private int localCacheHits;
@Nullable
private EntityIterableCacheAdapterMutable mutableCache;
private List mutatedInTxn;
@Nullable
private LongHashMap blobStreams;
@Nullable
private LongHashMap blobFiles;
private LongSet deferredBlobsToDelete;
private QueryCancellingPolicy queryCancellingPolicy;
PersistentStoreTransaction(@NotNull final PersistentEntityStoreImpl store) {
this(store, TransactionType.Regular);
}
/**
* Ctor for creating snapshot transactions.
*
* @param source source txn which snapshot should be created of.
* @param txnType type of snapshot transaction, only {@linkplain TransactionType#Regular} or
* {@linkplain TransactionType#Readonly} are allowed
*/
PersistentStoreTransaction(@NotNull final PersistentStoreTransaction source,
@NotNull final TransactionType txnType) {
this.store = source.store;
final PersistentEntityStoreConfig config = store.getConfig();
propsCache = createObjectCache(config.getTransactionPropsCacheSize());
linksCache = createObjectCache(config.getTransactionLinksCacheSize());
blobStringsCache = createObjectCache(config.getTransactionBlobStringsCacheSize());
localCache = source.localCache;
localCacheAttempts = localCacheHits = 0;
switch (txnType) {
case Regular:
txn = source.txn.getSnapshot(getRevertCachesBeginHook());
break;
case Readonly:
txn = source.txn.getReadonlySnapshot();
break;
default:
throw new EntityStoreException("Can't create exclusive snapshot transaction");
}
}
protected PersistentStoreTransaction(@NotNull final PersistentEntityStoreImpl store,
@NotNull final TransactionType txnType) {
this.store = store;
final PersistentEntityStoreConfig config = store.getConfig();
propsCache = createObjectCache(config.getTransactionPropsCacheSize());
linksCache = createObjectCache(config.getTransactionLinksCacheSize());
blobStringsCache = createObjectCache(config.getTransactionBlobStringsCacheSize());
localCacheAttempts = localCacheHits = 0;
final Runnable beginHook = getRevertCachesBeginHook();
final Environment env = store.getEnvironment();
switch (txnType) {
case Regular:
txn = env.beginTransaction(beginHook);
break;
case Exclusive:
txn = env.beginExclusiveTransaction(beginHook);
mutableCache = createMutableCache();
break;
case Readonly:
txn = env.beginReadonlyTransaction(beginHook);
break;
default:
throw new EntityStoreException("Can't create " + txnType + " transaction");
}
}
@Override
@NotNull
public PersistentEntityStoreImpl getStore() {
return store;
}
@Override
public boolean isIdempotent() {
return getEnvironmentTransaction().isIdempotent() &&
(blobStreams == null || blobStreams.isEmpty()) &&
(blobFiles == null || blobFiles.isEmpty());
}
@Override
public boolean isReadonly() {
return txn.isReadonly();
}
@Override
public boolean isFinished() {
return txn.isFinished();
}
@Override
public boolean commit() {
// txn can be read-only if Environment is in read-only mode
if (!isReadonly()) {
apply();
return doCommit();
} else if (txn.isExclusive()) {
applyExclusiveTransactionCaches();
}
return true;
}
public boolean isCurrent() {
return true;
}
private boolean doCommit() {
if (txn.commit()) {
store.unregisterTransaction(this);
flushNonTransactionalBlobs();
revertCaches();
return true;
}
revert();
return false;
}
@Override
public void abort() {
try {
store.unregisterTransaction(this);
revertCaches();
} finally {
txn.abort();
}
}
@Override
public boolean flush() {
// txn can be read-only if Environment is in read-only mode
if (!isReadonly()) {
apply();
return doFlush();
} else if (txn.isExclusive()) {
applyExclusiveTransactionCaches();
}
return true;
}
// exposed only for tests
boolean doFlush() {
if (txn.flush()) {
flushNonTransactionalBlobs();
revertCaches(false); // do not clear props & links caches
return true;
}
revert();
return false;
}
@Override
public void revert() {
txn.revert();
mutableCache = null;
mutatedInTxn = new ArrayList<>();
}
public PersistentStoreTransaction getSnapshot() {
// this snapshots should not be registered in store, hence no de-registration
return new PersistentStoreTransactionSnapshot(this);
}
@Override
@NotNull
public PersistentEntity newEntity(@NotNull final String entityType) {
try {
final int entityTypeId = store.getEntityTypeId(this, entityType, true);
final long entityLocalId = store.getEntitiesSequence(this, entityTypeId).increment();
store.getEntitiesTable(this, entityTypeId).putRight(
txn, LongBinding.longToCompressedEntry(entityLocalId), ZERO_VERSION_ENTRY);
final PersistentEntityId id = new PersistentEntityId(entityTypeId, entityLocalId);
// update iterables' cache
new EntityAddedHandleCheckerImpl(this, id, mutableCache(), mutatedInTxn).updateCache();
return new PersistentEntity(store, id);
} catch (Exception e) {
throw ExodusException.toEntityStoreException(e);
}
}
@Override
public void saveEntity(@NotNull final Entity entity) {
try {
final EntityId entityId = entity.getId();
final Store entitiesTable = store.getEntitiesTable(this, entityId.getTypeId());
entitiesTable.put(txn, LongBinding.longToCompressedEntry(entityId.getLocalId()), ZERO_VERSION_ENTRY);
new EntityAddedHandleCheckerImpl(this, entityId, mutableCache(), mutatedInTxn).updateCache();
} catch (Exception e) {
throw ExodusException.toEntityStoreException(e);
}
}
@Override
@NotNull
public PersistentEntity getEntity(@NotNull final EntityId id) {
final int version = store.getLastVersion(this, id);
if (version < 0) {
throw new EntityRemovedInDatabaseException(store.getEntityType(this, id.getTypeId()));
}
return new PersistentEntity(store, (PersistentEntityId) id);
}
@Override
@NotNull
public List getEntityTypes() {
return store.getEntityTypes(this);
}
// TODO: remove ASAP
private static int traceGetAllForEntityType = Integer.getInteger("jetbrains.exodus.entitystore.traceGetAllForEntityType", -1);
@Override
@NotNull
public EntityIterable getAll(@NotNull final String entityType) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
if (entityTypeId == traceGetAllForEntityType) {
if (logger.isErrorEnabled()) {
logger.error("txn.getAll() for entityTypeId = " + entityTypeId, new Throwable());
}
}
return new EntitiesOfTypeIterable(this, entityTypeId);
}
@Override
@NotNull
public EntityIterable getSingletonIterable(@NotNull final Entity entity) {
return new SingleEntityIterable(this, entity.getId());
}
@Override
@NotNull
public EntityIterable find(@NotNull final String entityType,
@NotNull final String propertyName,
@NotNull final Comparable value) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int propertyId = store.getPropertyId(this, propertyName, false);
if (propertyId < 0) {
return EntityIterableBase.EMPTY;
}
return new PropertyValueIterable(this, entityTypeId, propertyId, value);
}
@Override
@NotNull
public EntityIterable find(@NotNull final String entityType, @NotNull final String propertyName,
@NotNull final Comparable minValue, @NotNull final Comparable maxValue) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int propertyId = store.getPropertyId(this, propertyName, false);
if (propertyId < 0) {
return EntityIterableBase.EMPTY;
}
return new PropertyRangeIterable(this, entityTypeId, propertyId, minValue, maxValue);
}
@Override
@NotNull
public EntityIterable findIds(@NotNull final String entityType, final long minValue, final long maxValue) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
return new EntitiesOfTypeRangeIterable(this, entityTypeId, minValue, maxValue);
}
@NotNull
@Override
public EntityIterableBase findWithProp(@NotNull final String entityType, @NotNull final String propertyName) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int propertyId = store.getPropertyId(this, propertyName, false);
if (propertyId < 0) {
return EntityIterableBase.EMPTY;
}
return new EntitiesWithPropertyIterable(this, entityTypeId, propertyId);
}
public EntityIterableBase findWithPropSortedByValue(@NotNull final String entityType, @NotNull final String propertyName) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int propertyId = store.getPropertyId(this, propertyName, false);
if (propertyId < 0) {
return EntityIterableBase.EMPTY;
}
return new PropertiesIterable(this, entityTypeId, propertyId);
}
@Override
@NotNull
public EntityIterable findStartingWith(@NotNull final String entityType,
@NotNull final String propertyName,
@NotNull final String value) {
final int len = value.length();
if (len == 0) {
return getAll(entityType);
}
return find(entityType, propertyName, value, value + Character.MAX_VALUE);
}
@NotNull
@Override
public EntityIterable findWithBlob(@NotNull final String entityType, @NotNull final String blobName) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int blobId = store.getPropertyId(this, blobName, false);
if (blobId < 0) {
return EntityIterableBase.EMPTY;
}
return new EntitiesWithBlobIterable(this, entityTypeId, blobId);
}
@Override
@NotNull
public EntityIterable findLinks(@NotNull final String entityType,
@NotNull final Entity entity,
@NotNull final String linkName) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int linkId = store.getLinkId(this, linkName, false);
if (linkId < 0) {
return EntityIterableBase.EMPTY;
}
if (entity instanceof PersistentEntity) {
return new EntityToLinksIterable(this, ((PersistentEntity) entity).getId(), entityTypeId, linkId);
}
EntityId id = entity.getId();
if (id instanceof PersistentEntityId) {
return new EntityToLinksIterable(this, id, entityTypeId, linkId);
}
return EntityIterableBase.EMPTY;
}
@Override
@NotNull
public EntityIterable findLinks(@NotNull final String entityType,
@NotNull final EntityIterable entities,
@NotNull final String linkName) {
// create balanced union tree
List links = null;
for (final Entity entity : entities) {
if (links == null) {
links = new ArrayList<>();
}
links.add(findLinks(entityType, entity, linkName));
}
if (links == null) {
return EntityIterableBase.EMPTY;
}
if (links.size() > 1) {
for (int i = 0; i < links.size() - 1; i += 2) {
links.add(links.get(i).union(links.get(i + 1)));
}
}
return links.get(links.size() - 1);
}
@Override
@NotNull
public EntityIterable findWithLinks(@NotNull final String entityType, @NotNull final String linkName) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int linkId = store.getLinkId(this, linkName, false);
if (linkId < 0) {
return EntityIterableBase.EMPTY;
}
return new EntitiesWithLinkIterable(this, entityTypeId, linkId);
}
@Override
@NotNull
public EntityIterable findWithLinks(@NotNull final String entityType,
@NotNull final String linkName,
@NotNull final String oppositeEntityType,
@NotNull final String oppositeLinkName) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int linkId = store.getLinkId(this, linkName, false);
if (linkId < 0) {
return EntityIterableBase.EMPTY;
}
final int oppositeEntityId = store.getEntityTypeId(this, oppositeEntityType, false);
if (oppositeEntityId < 0) {
return EntityIterableBase.EMPTY;
}
final int oppositeLinkId = store.getLinkId(this, oppositeLinkName, false);
if (oppositeLinkId < 0) {
return EntityIterableBase.EMPTY;
}
return new EntitiesWithLinkSortedIterable(this, entityTypeId, linkId, oppositeEntityId, oppositeLinkId);
}
@Override
@NotNull
public EntityIterable sort(@NotNull final String entityType,
@NotNull final String propertyName,
final boolean ascending) {
return sort(entityType, propertyName, getAll(entityType), ascending);
}
@Override
@NotNull
public EntityIterable sort(@NotNull final String entityType,
@NotNull final String propertyName,
@NotNull final EntityIterable rightOrder,
final boolean ascending) {
final int entityTypeId = store.getEntityTypeId(this, entityType, false);
if (entityTypeId < 0) {
return EntityIterableBase.EMPTY;
}
final int propertyId = store.getPropertyId(this, propertyName, false);
if (propertyId < 0 || rightOrder == EntityIterableBase.EMPTY) {
return rightOrder;
}
return new SortIterable(this, findWithPropSortedByValue(
entityType, propertyName), (EntityIterableBase) rightOrder, entityTypeId, propertyId, ascending);
}
@Override
@NotNull
public EntityIterable sortLinks(@NotNull final String entityType,
@NotNull final EntityIterable sortedLinks,
final boolean isMultiple,
@NotNull final String linkName,
@NotNull final EntityIterable rightOrder) {
final EntityIterable result = new SortIndirectIterable(this, store, entityType,
((EntityIterableBase) sortedLinks).getSource(), linkName, (EntityIterableBase) rightOrder, null, null);
return isMultiple ? result.distinct() : result;
}
@Override
@NotNull
public EntityIterable sortLinks(@NotNull final String entityType,
@NotNull final EntityIterable sortedLinks,
final boolean isMultiple,
@NotNull final String linkName,
@NotNull final EntityIterable rightOrder,
@NotNull final String oppositeEntityType,
@NotNull final String oppositeLinkName) {
final EntityIterable result = new SortIndirectIterable(this, store, entityType,
((EntityIterableBase) sortedLinks).getSource(), linkName, (EntityIterableBase) rightOrder, oppositeEntityType, oppositeLinkName);
return isMultiple ? result.distinct() : result;
}
@Override
@Deprecated
@NotNull
public EntityIterable mergeSorted(@NotNull final List sorted,
@NotNull final Comparator comparator) {
List filtered = null;
for (final EntityIterable it : sorted) {
if (it != EntityIterableBase.EMPTY) {
if (filtered == null) {
filtered = new ArrayList<>();
}
filtered.add(it);
}
}
return filtered == null ? EntityIterableBase.EMPTY : new MergeSortedIterable(this, filtered, comparator);
}
@NotNull
public EntityIterable mergeSorted(@NotNull final List sorted,
@NotNull ComparableGetter valueGetter,
@NotNull final Comparator> comparator) {
List filtered = null;
for (final EntityIterable it : sorted) {
if (it != EntityIterableBase.EMPTY) {
if (filtered == null) {
filtered = new ArrayList<>();
}
filtered.add(it);
}
}
return filtered == null ? EntityIterableBase.EMPTY : new MergeSortedIterableWithValueGetter(this, filtered, valueGetter, comparator);
}
@Override
@NotNull
public EntityId toEntityId(@NotNull final String representation) {
return PersistentEntityId.toEntityId(representation);
}
@Override
@NotNull
public Sequence getSequence(@NotNull final String sequenceName) {
try {
return store.getSequence(this, sequenceName);
} catch (Exception e) {
throw ExodusException.wrap(e);
}
}
@Override
public void setQueryCancellingPolicy(QueryCancellingPolicy policy) {
queryCancellingPolicy = policy;
}
@Override
@Nullable
public QueryCancellingPolicy getQueryCancellingPolicy() {
return queryCancellingPolicy;
}
public void registerMutatedHandle(@NotNull final EntityIterableHandle handle, @NotNull final CachedInstanceIterable iterable) {
final EntityIterableCacheAdapterMutable cache = this.mutableCache;
if (cache == null) {
throw new IllegalStateException("Transaction wasn't mutated");
}
// cache new mutated iterable instance not affecting HandlesDistribution
cache.cacheObjectNotAffectingHandleDistribution(handle, iterable);
}
public void registerStickyObject(@NotNull final EntityIterableHandle handle, Updatable object) {
mutableCache().registerStickyObject(handle, object);
}
@NotNull
public Updatable getStickyObject(@NotNull final EntityIterableHandle handle) {
return getLocalCache().getStickyObject(handle);
}
@SuppressWarnings({"HardCodedStringLiteral"})
public String toString() {
final StringBuilder builder = StringBuilderSpinAllocator.alloc();
try {
builder.append(store.getLocation());
builder.append(", thread = ");
builder.append(Thread.currentThread().toString());
return builder.toString();
} finally {
StringBuilderSpinAllocator.dispose(builder);
}
}
@NotNull
public Transaction getEnvironmentTransaction() {
return txn;
}
@Override
public PersistentStoreTransaction getTxn(@NotNull EntityIterableBase iterable) {
return this; // this is fun
}
@Override
public @NotNull PersistentStoreTransaction getTransaction() {
return this;
}
@Nullable
public CachedInstanceIterable getCachedInstance(@NotNull final EntityIterableBase sample) {
final EntityIterableHandle handle = sample.getHandle();
final EntityIterableCacheAdapter localCache = getLocalCache();
return localCache.tryKey(handle);
}
@Nullable
public CachedInstanceIterable getCachedInstanceFast(@NotNull final EntityIterableBase sample) {
final EntityIterableHandle handle = sample.getHandle();
final EntityIterableCacheAdapter localCache = getLocalCache();
return localCache.getObject(handle);
}
public void addCachedInstance(@NotNull final CachedInstanceIterable cached) {
final EntityIterableCacheAdapter localCache = getLocalCache();
final EntityIterableHandle handle = cached.getHandle();
if (localCache.getObject(handle) == null) {
localCache.cacheObject(handle, cached);
store.getEntityIterableCache().setCachedCount(handle, cached.size());
}
}
@NotNull
EntityIterableCacheAdapter getLocalCache() {
return mutableCache != null ? mutableCache : localCache;
}
void localCacheAttempt() {
++localCacheAttempts;
}
void localCacheHit() {
++localCacheHits;
}
boolean isCachingRelevant() {
// HEURISTICS:
// caching is irrelevant for the transaction if there are more than a quarter of the EntityIterablesCache's size
// attempts to query an EntityIterable with local cache hit rate less than 25%
return localCacheAttempts <= localCache.size() >> 2 || localCacheHits >= localCacheAttempts >> 2;
}
void cacheProperty(@NotNull final PersistentEntityId fromId, final int propId, @NotNull final Comparable value) {
final PropertyId fromEnd = new PropertyId(fromId, propId);
if (propsCache.getObject(fromEnd) == null) {
propsCache.cacheObject(fromEnd, value);
}
}
Comparable getCachedProperty(@NotNull final PersistentEntity from, final int propId) {
return propsCache.tryKey(new PropertyId(from.getId(), propId));
}
void cacheLink(@NotNull final PersistentEntity from, final int linkId, @NotNull final PersistentEntityId to) {
final PropertyId fromEnd = new PropertyId(from.getId(), linkId);
if (linksCache.getObject(fromEnd) != null) {
throw new IllegalStateException("Link is already cached, at first it should be invalidated");
}
linksCache.cacheObject(fromEnd, to);
}
PersistentEntityId getCachedLink(@NotNull final PersistentEntity from, final int linkId) {
return linksCache.tryKey(new PropertyId(from.getId(), linkId));
}
void cacheBlobString(@NotNull final PersistentEntity from, final int blobId, @NotNull final String value) {
final PropertyId fromEnd = new PropertyId(from.getId(), blobId);
if (blobStringsCache.getObject(fromEnd) != null) {
throw new IllegalStateException("Blob string is already cached, at first it should be invalidated");
}
blobStringsCache.cacheObject(fromEnd, value);
}
String getCachedBlobString(@NotNull final PersistentEntity from, final int blobId) {
return blobStringsCache.tryKey(new PropertyId(from.getId(), blobId));
}
void invalidateCachedBlobString(@NotNull final PersistentEntity from, final int blobId) {
blobStringsCache.remove(new PropertyId(from.getId(), blobId));
}
boolean isMutable() {
return mutableCache != null;
}
void entityDeleted(@NotNull final PersistentEntityId id) {
new EntityDeletedHandleCheckerImpl(this, id, mutableCache(), mutatedInTxn).updateCache();
}
void propertyChanged(@NotNull final PersistentEntityId id,
final int propertyId,
@Nullable final Comparable oldValue,
@Nullable final Comparable newValue) {
final PropertyId propId = new PropertyId(id, propertyId);
propsCache.remove(propId);
new PropertyChangedHandleCheckerImpl(this, id, propertyId,
oldValue, newValue, mutableCache(), mutatedInTxn).updateCache();
}
void linkAdded(@NotNull final PersistentEntityId sourceId,
@NotNull final PersistentEntityId targetId,
final int linkId) {
final PropertyId propId = new PropertyId(sourceId, linkId);
linksCache.remove(propId);
new LinkAddedHandleChecker(this, sourceId, targetId, linkId, mutableCache(), mutatedInTxn).updateCache();
}
void linkDeleted(@NotNull final PersistentEntityId sourceId,
@NotNull final PersistentEntityId targetId,
final int linkId) {
final PropertyId propId = new PropertyId(sourceId, linkId);
linksCache.remove(propId);
new LinkDeletedHandleChecker(this, sourceId, targetId, linkId, mutableCache(), mutatedInTxn).updateCache();
}
void addBlob(final long blobHandle, @NotNull final InputStream stream) {
LongHashMap blobStreams = this.blobStreams;
if (blobStreams == null) {
blobStreams = new LongHashMap<>();
this.blobStreams = blobStreams;
}
if (!stream.markSupported()) {
throw new EntityStoreException("Blob input stream should support the mark and reset methods");
}
stream.mark(Integer.MAX_VALUE);
blobStreams.put(blobHandle, stream);
}
void addBlob(final long blobHandle, @NotNull final File file) {
LongHashMap blobFiles = this.blobFiles;
if (blobFiles == null) {
blobFiles = new LongHashMap<>();
this.blobFiles = blobFiles;
}
blobFiles.put(blobHandle, file);
}
void deleteBlob(final long blobHandle) {
if (blobStreams != null) {
blobStreams.remove(blobHandle);
}
if (blobFiles != null) {
blobFiles.remove(blobHandle);
}
}
long getBlobSize(final long blobHandle) throws IOException {
final LongHashMap blobStreams = this.blobStreams;
if (blobStreams != null) {
final InputStream stream = blobStreams.get(blobHandle);
if (stream != null) {
stream.reset();
return stream.skip(Long.MAX_VALUE); // warning, this may return inaccurate results
}
}
final LongHashMap blobFiles = this.blobFiles;
if (blobFiles != null) {
final File file = blobFiles.get(blobHandle);
if (file != null) {
return file.length();
}
}
return -1;
}
@Nullable
InputStream getBlobStream(final long blobHandle) throws IOException {
final LongHashMap blobStreams = this.blobStreams;
if (blobStreams != null) {
final InputStream stream = blobStreams.get(blobHandle);
if (stream != null) {
stream.reset();
return stream;
}
}
final LongHashMap blobFiles = this.blobFiles;
if (blobFiles != null) {
final File file = blobFiles.get(blobHandle);
if (file != null) {
return store.getBlobVault().cloneFile(file);
}
}
return null;
}
void deferBlobDeletion(final long blobHandle) {
if (deferredBlobsToDelete == null) {
deferredBlobsToDelete = new LongHashSet();
}
deferredBlobsToDelete.add(blobHandle);
}
void closeCaches() {
propsCache.close();
linksCache.close();
blobStringsCache.close();
}
public void revertCaches() {
revertCaches(true);
}
private void revertCaches(final boolean clearPropsAndLinksCache) {
if (clearPropsAndLinksCache) {
propsCache.clear();
linksCache.clear();
blobStringsCache.clear();
}
localCache = store.getEntityIterableCache().getCacheAdapter();
mutableCache = null;
mutatedInTxn = null;
blobStreams = null;
blobFiles = null;
deferredBlobsToDelete = null;
}
// exposed only for tests
void apply() {
final FlushLog log = new FlushLog();
store.logOperations(txn, log);
final BlobVault blobVault = store.getBlobVault();
if (blobVault.requiresTxn()) {
try {
blobVault.flushBlobs(blobStreams, blobFiles, deferredBlobsToDelete, txn);
} catch (Exception e) {
// out of disk space not expected there
throw ExodusException.toEntityStoreException(e);
}
}
txn.setCommitHook(new Runnable() {
@Override
public void run() {
log.flushed();
final EntityIterableCacheAdapterMutable cache = PersistentStoreTransaction.this.mutableCache;
if (cache != null) { // mutableCache can be null if only blobs are modified
applyAtomicCaches(cache);
}
}
});
}
private void applyAtomicCaches(@NotNull EntityIterableCacheAdapterMutable cache) {
final EntityIterableCache entityIterableCache = store.getEntityIterableCache();
for (final Updatable it : mutatedInTxn) {
it.endUpdate(PersistentStoreTransaction.this);
}
if (!entityIterableCache.compareAndSetCacheAdapter(localCache, cache.endWrite())) {
throw new EntityStoreException("This exception should never be thrown");
}
}
private void applyExclusiveTransactionCaches() {
final EntityIterableCacheAdapterMutable cache = this.mutableCache;
if (cache != null) {
UnsafeKt.executeInMetaWriteLock((EnvironmentImpl) store.getEnvironment(), new Function0
© 2015 - 2025 Weber Informatics LLC | Privacy Policy