All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gemstone.gemfire.internal.cache.DiskEntry Maven / Gradle / Ivy

There is a newer version: 2.0-BETA
Show newest version
/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * 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. See accompanying
 * LICENSE file.
 */
package com.gemstone.gemfire.internal.cache;

import java.io.IOException;

import com.gemstone.gemfire.cache.CacheClosedException;
import com.gemstone.gemfire.cache.DiskAccessException;
import com.gemstone.gemfire.cache.query.internal.IndexUpdater;
import com.gemstone.gemfire.distributed.internal.DM;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.Assert;
import com.gemstone.gemfire.internal.ByteArrayDataInput;
import com.gemstone.gemfire.internal.HeapDataOutputStream;
import com.gemstone.gemfire.internal.cache.DiskStoreImpl.AsyncDiskEntry;
import com.gemstone.gemfire.internal.cache.GemFireCacheImpl.StaticSystemCallbacks;
import com.gemstone.gemfire.internal.cache.lru.EnableLRU;
import com.gemstone.gemfire.internal.cache.lru.LRUClockNode;
import com.gemstone.gemfire.internal.cache.lru.LRUEntry;
import com.gemstone.gemfire.internal.cache.persistence.BytesAndBits;
import com.gemstone.gemfire.internal.cache.persistence.DiskRecoveryStore;
import com.gemstone.gemfire.internal.cache.persistence.DiskRegionView;
import com.gemstone.gemfire.internal.cache.versions.VersionStamp;
import com.gemstone.gemfire.internal.cache.versions.VersionTag;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.offheap.ByteSource;
import com.gemstone.gemfire.internal.offheap.OffHeapHelper;
import com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl;
import com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.Chunk;
import com.gemstone.gemfire.internal.offheap.StoredObject;
import com.gemstone.gemfire.internal.offheap.annotations.Released;
import com.gemstone.gemfire.internal.offheap.annotations.Retained;
import com.gemstone.gemfire.internal.offheap.annotations.Unretained;
import com.gemstone.gemfire.internal.shared.Version;
import com.gemstone.gemfire.internal.util.BlobHelper;

/**
 * Represents an entry in an {@link RegionMap} whose value may be
 * stored on disk.  This interface provides accessor and mutator
 * methods for a disk entry's state.  This allows us to abstract all
 * of the interesting behavior into a {@linkplain DiskEntry.Helper
 * helper class} that we only need to implement once.
 *
 * 

* * Each DiskEntry has a unique id that is * used by the {@link DiskRegion} to identify the key/value pair. * Before the disk entry is written to disk, the value of the * id is {@link DiskRegion#INVALID_ID invalid}. Once the * object has been written to disk, the id is a positive * number. If the value is {@linkplain Helper#update updated}, then the * id is negated to signify that the value on disk is * dirty. * * @see DiskRegion * * @author David Whitlock * * @since 3.2 */ public interface DiskEntry extends RegionEntry { /** * Sets the value with a {@link RegionEntryContext}. * @param context the value's context. * @param value an entry value. */ public void setValueWithContext(RegionEntryContext context,Object value); /** * In some cases we need to do something just before we drop the value * from a DiskEntry that is being moved (i.e. overflowed) to disk. * @param context */ public void handleValueOverflow(RegionEntryContext context); /** * In some cases we need to do something just after we unset the value * from a DiskEntry that has been moved (i.e. overflowed) to disk. * @param context */ public void afterValueOverflow(RegionEntryContext context); /** * Returns true if the DiskEntry value is equal to {@link Token#DESTROYED}, {@link Token#REMOVED_PHASE1}, or {@link Token#REMOVED_PHASE2}. */ public boolean isRemovedFromDisk(); /** * Returns the id of this DiskEntry */ public DiskId getDiskId(); public void _removePhase1(); public int updateAsyncEntrySize(EnableLRU capacityController); public DiskEntry getPrev(); public DiskEntry getNext(); public void setPrev(DiskEntry v); public void setNext(DiskEntry v); /** * Used as the entry value if it was invalidated. */ public static final byte[] INVALID_BYTES = new byte[0]; /** * Used as the entry value if it was locally invalidated. */ public static final byte[] LOCAL_INVALID_BYTES = new byte[0]; /** * Used as the entry value if it was tombstone. */ public static final byte[] TOMBSTONE_BYTES = new byte[0]; /////////////////////// Inner Classes ////////////////////// /** * A Helper class for performing functions common to all * DiskEntrys. */ public static class Helper { /** * Testing purpose only * Get the value of an entry that is on disk without faulting * it in and without looking in the io buffer. * @since 3.2.1 */ static Object getValueOnDisk(DiskEntry entry, DiskRegion dr) { DiskId id = entry.getDiskId(); if (id == null) { return null; } dr.acquireReadLock(); try { synchronized (id) { if (id == null || (dr.isBackup() && id.getKeyId() == DiskRegion.INVALID_ID) || (!entry.isValueNull() && id.needsToBeWritten() && !EntryBits.isRecoveredFromDisk(id.getUserBits()))/*fix for bug 41942*/) { return null; } return dr.getNoBuffer(id); } } finally { dr.releaseReadLock(); } } /** * Get the serialized value directly from disk. Returned object may be * a {@link CachedDeserializable}. Goes straight to disk without faulting * into memory. Only looks at the disk storage, not at heap storage. * @param entry the entry used to identify the value to fetch * @param dr the persistent storage from which to fetch the value * @return either null, byte array, or CacheDeserializable * @since gemfire57_hotfix */ public static Object getSerializedValueOnDisk( DiskEntry entry, DiskRegion dr) { DiskId did = entry.getDiskId(); if (did == null) { return null; } dr.acquireReadLock(); try { synchronized (did) { if (did == null || (dr.isBackup() && did.getKeyId() == DiskRegion.INVALID_ID)) { return null; } else if (!entry.isValueNull() && did.needsToBeWritten() && !EntryBits.isRecoveredFromDisk(did.getUserBits())/*fix for bug 41942*/) { return null; } return dr.getSerializedData(did); } } finally { dr.releaseReadLock(); } } /** * Get the value of an entry that is on disk without * faulting it in . It checks for the presence in the buffer also. * This method is used for concurrent map operations, GemFireXD and CQ processing * * @throws DiskAccessException * @since 5.1 */ static Object getValueOnDiskOrBuffer(DiskEntry entry, DiskRegion dr, RegionEntryContext context) { @Released Object v = getOffHeapValueOnDiskOrBuffer(entry, dr, context, true); if (v instanceof CachedDeserializable) { if (v instanceof Chunk) { @Released Chunk ohv = (Chunk) v; try { v = ohv.getDeserializedValue(null, null); if (v == ohv) { throw new IllegalStateException("gfxd tried to use getValueOnDiskOrBuffer"); } } finally { ohv.release(); // OFFHEAP the offheap ref is decremented here } } else { v = ((CachedDeserializable)v).getDeserializedValue(null, null); } } return v; } @Retained static Object getOffHeapValueOnDiskOrBuffer(DiskEntry entry, DiskRegionView dr, RegionEntryContext context, boolean faultin) { DiskId did = entry.getDiskId(); Object syncObj = did; if (syncObj == null) { syncObj = entry; } if (syncObj == did) { dr.acquireReadLock(); } try { synchronized (syncObj) { if (did != null && did.isPendingAsync()) { @Retained Object v = entry._getValueRetain(context, true); // TODO:KIRK:OK Rusty had Object v = entry.getValueWithContext(context); if (Token.isRemovedFromDisk(v)) { v = null; } return v; } if (did == null || ( dr.isBackup() && did.getKeyId() == DiskRegion.INVALID_ID) || (!entry.isValueNull() && did.needsToBeWritten() && !EntryBits.isRecoveredFromDisk(did.getUserBits()))/*fix for bug 41942*/) { return null; } return dr.getDiskStore().getSerializedDataWithoutLock(dr, did, faultin); } } finally { if (syncObj == did) { dr.releaseReadLock(); } } } /** * Returns false if the entry is INVALID (or LOCAL_INVALID). Determines this * without faulting in the value from disk. * * @since 3.2.1 */ /* TODO prpersist - Do we need this method? It was added by the gfxd merge static boolean isValid(DiskEntry entry, DiskRegion dr) { synchronized (entry) { if (entry.isRecovered()) { // We have a recovered entry whose value is still on disk. // So take a peek at it without faulting it in. //long id = entry.getDiskId().getKeyId(); //entry.getDiskId().setKeyId(-id); byte bits = dr.getBits(entry.getDiskId()); //TODO Asif:Check if resetting is needed return !EntryBits.isInvalid(bits) && !EntryBits.isLocalInvalid(bits); } } }*/ static boolean isOverflowedToDisk(DiskEntry de, DiskRegionView dr, DistributedRegion.DiskPosition dp, RegionEntryContext context) { Object v = null; DiskId did; synchronized (de) { did = de.getDiskId(); } Object syncObj = did; if (syncObj == null) { syncObj = de; } if (syncObj == did) { dr.acquireReadLock(); } try { synchronized (syncObj) { if (de.isValueNull()) { if (did == null) { synchronized (de) { did = de.getDiskId(); } assert did != null; return isOverflowedToDisk(de, dr, dp, context); } else { dp.setPosition(did.getOplogId(), did.getOffsetInOplog()); return true; } } else { return false; } } } finally { if (syncObj == did) { dr.releaseReadLock(); } } } /** * Get the value of an entry that is on disk without faulting * it in. * @param targetVersion the destination where this filled in value will be sent. * @since 3.2.1 */ static boolean fillInValue(DiskEntry de, InitialImageOperation.Entry entry, LocalRegion lr, ByteArrayDataInput in, DM mgr, RegionEntryContext context, Version targetVersion) { LogWriterI18n logger = mgr.getLoggerI18n(); DiskRegion dr = lr.getDiskRegion(); @Retained @Released Object v = null; DiskId did; synchronized (de) { did = de.getDiskId(); } Object syncObj = did; if (syncObj == null) { syncObj = de; } if (syncObj == did) { dr.acquireReadLock(); } try { synchronized (syncObj) { entry.setLastModified(mgr, de.getLastModified()); SimpleMemoryAllocatorImpl.setReferenceCountOwner(entry); v = de._getValueRetain(context, true); // OFFHEAP copied to heap entry; todo allow entry to refer to offheap since it will be copied to network. SimpleMemoryAllocatorImpl.setReferenceCountOwner(null); if (v == null) { if (did == null) { // fix for bug 41449 synchronized (de) { did = de.getDiskId(); } assert did != null; // do recursive call to get readLock on did return fillInValue(de, entry, lr, in, mgr, context, targetVersion); } if (logger.finerEnabled()) { logger.finer("DiskEntry.Helper.fillInValue, key=" + entry.key + "; getting value from disk, disk id=" + did); } BytesAndBits bb = null; try { bb = dr.getBytesAndBits(did, false); }catch(DiskAccessException dae){ return false; } if (EntryBits.isInvalid(bb.getBits())) { entry.setInvalid(); } else if (EntryBits.isLocalInvalid(bb.getBits())) { entry.setLocalInvalid(); } else if (EntryBits.isTombstone(bb.getBits())) { entry.setTombstone(); } else { // in case of older version entry do the transformation first // before shipping final Version version = bb.getVersion(); boolean deserializeForTarget = !Version.CURRENT .equals(targetVersion); if (deserializeForTarget) { // no need to deserialize for user regions as of now // TODO: upgrade: need to determine the cases when // deserialization is needed and when not (e.g. when Gem+GemXD // converge then certain internal types like BigInteger have // different serialization which might be embedded inside a user // object) if (!lr.isUsedForMetaRegion() && !lr.isUsedForPartitionedRegionAdmin()) { deserializeForTarget = false; } } // TODO: upgrade: also need to handle cases of GemFire internal // objects (e.g. gateway events, _PR objects) changing // serialization below via a versioned CachedDeserializable if ((version == null && !deserializeForTarget) || !CachedDeserializableFactory.preferObject()) { entry.value = bb.getBytes(); entry.setSerialized(EntryBits.isSerialized(bb.getBits())); } else if (EntryBits.isSerialized(bb.getBits())) { entry.value = readSerializedValue(bb.getBytes(), version, in, true); entry.setSerialized(false); entry.setEagerDeserialize(); } else { entry.value = readRawValue(bb.getBytes(), version, in); entry.setSerialized(false); } } return true; } } } finally { if (syncObj == did) { dr.releaseReadLock(); } } entry.setSerialized(false); // starting default value final Class vclass; if (Token.isRemovedFromDisk(v)) { // fix for bug 31757 return false; } else if ((vclass = v.getClass()) == byte[].class) { entry.value = v; } else if (vclass == byte[][].class) { if (CachedDeserializableFactory.preferObject()) { entry.value = v; entry.setEagerDeserialize(); } else { serializeForEntry(entry, v); } } else if (v == Token.INVALID) { entry.setInvalid(); } else if (v == Token.LOCAL_INVALID) { // fix for bug 31107 entry.setLocalInvalid(); } else if (v == Token.TOMBSTONE) { entry.setTombstone(); } else if (CachedDeserializable.class.isAssignableFrom(vclass)) { try { if (StoredObject.class.isAssignableFrom(vclass) && !((StoredObject)v).isSerialized()) { entry.setSerialized(false); entry.value = ((StoredObject)v).getDeserializedForReading(); // For GemFireXD we prefer eager deserialized if (CachedDeserializableFactory.preferObject()) { entry.setEagerDeserialize(); } } else { // don't serialize here if it is not already serialized Object tmp = ((CachedDeserializable)v).getValue(); // For GemFireXD we prefer eager deserialized if (CachedDeserializableFactory.preferObject()) { entry.setEagerDeserialize(); } if (tmp instanceof byte[]) { byte[] bb = (byte[])tmp; entry.value = bb; entry.setSerialized(true); } else { serializeForEntry(entry, tmp); } } } finally { // If v == entry.value then v is assumed to be an OffHeapByteSource // and release() will be called on v after the bytes have been read from // off-heap. if (v != entry.value) { OffHeapHelper.releaseWithNoTracking(v); } } } else { Object preparedValue = v; if (preparedValue != null) { preparedValue = AbstractRegionEntry.prepareValueForGII(preparedValue); if (preparedValue == null) { return false; } } if (CachedDeserializableFactory.preferObject()) { entry.value = preparedValue; entry.setEagerDeserialize(); } else { serializeForEntry(entry, preparedValue); } } return true; } private static void serializeForEntry(InitialImageOperation.Entry entry, Object v) { try { HeapDataOutputStream hdos = new HeapDataOutputStream(Version.CURRENT); BlobHelper.serializeTo(v, hdos); hdos.trim(); entry.value = hdos; entry.setSerialized(true); } catch (IOException e) { RuntimeException e2 = new IllegalArgumentException( LocalizedStrings.DiskEntry_AN_IOEXCEPTION_WAS_THROWN_WHILE_SERIALIZING .toLocalizedString()); e2.initCause(e); throw e2; } } /** * Used to initialize a new disk entry */ public static void initialize(DiskEntry entry, DiskRecoveryStore r, Object newValue) { DiskRegionView drv = null; if (r instanceof LocalRegion) { drv = ((LocalRegion)r).getDiskRegion(); } else if (r instanceof DiskRegionView) { drv = (DiskRegionView)r; } if (drv == null) { throw new IllegalArgumentException(LocalizedStrings.DiskEntry_DISK_REGION_IS_NULL.toLocalizedString()); } if (Token.isRemovedFromDisk(newValue)) { // it is not in vm and it is not on disk DiskId did = entry.getDiskId(); if (did != null) { did.setKeyId(DiskRegion.INVALID_ID); } } else if (newValue instanceof RecoveredEntry) { // Set the id directly, the value will also be set if RECOVER_VALUES RecoveredEntry re = (RecoveredEntry)newValue; DiskId did = entry.getDiskId(); did.setOplogId(re.getOplogId()); did.setOffsetInOplog(re.getOffsetInOplog()); did.setKeyId(re.getRecoveredKeyId()); did.setUserBits(re.getUserBits()); did.setValueLength(re.getValueLength()); if (re.getRecoveredKeyId() < 0) { drv.incNumOverflowOnDisk(1L); incrementBucketStats(r, 0/*InVM*/, 1/*OnDisk*/, did.getValueLength()); } else { entry.setValueWithContext(drv, entry.prepareValueForCache((RegionEntryContext) r, re.getValue(), false, false)); drv.incNumEntriesInVM(1L); incrementBucketStats(r, 1/*InVM*/, 0/*OnDisk*/, 0); } } else { DiskId did = entry.getDiskId(); if (did != null) { did.setKeyId(DiskRegion.INVALID_ID); } drv.incNumEntriesInVM(1L); incrementBucketStats(r, 1/*InVM*/, 0/*OnDisk*/, 0); } } private static final ValueWrapper INVALID_VW = new ValueWrapper(true, INVALID_BYTES); private static final ValueWrapper LOCAL_INVALID_VW = new ValueWrapper(true, LOCAL_INVALID_BYTES); private static final ValueWrapper TOMBSTONE_VW = new ValueWrapper(true, TOMBSTONE_BYTES); private static class ValueWrapper { public final boolean isSerializedObject; public final byte[] bytes; public static ValueWrapper create(Object value) { if (value == Token.INVALID) { // even though it is not serialized we say it is because // bytes will never be an empty array when it is serialized // so that gives us a way to specify the invalid value // given a byte array and a boolean flag. return INVALID_VW; } else if (value == Token.LOCAL_INVALID) { // even though it is not serialized we say it is because // bytes will never be an empty array when it is serialized // so that gives us a way to specify the local-invalid value // given a byte array and a boolean flag. return LOCAL_INVALID_VW; } else if (value == Token.TOMBSTONE) { return TOMBSTONE_VW; } else { byte[] bytes; boolean isSerializedObject = true; if (value instanceof CachedDeserializable) { CachedDeserializable proxy = (CachedDeserializable)value; if (proxy instanceof StoredObject) { StoredObject ohproxy = (StoredObject) proxy; isSerializedObject = ohproxy.isSerialized(); if (isSerializedObject) { bytes = ohproxy.getSerializedValue(); } else { //TODO:Asif: Speak to Darrel for cleaner way if(ohproxy instanceof ByteSource) { bytes = ((ByteSource)ohproxy).getRowBytes(); }else { bytes = (byte[]) ohproxy.getDeserializedForReading(); } } } else { bytes = proxy.getSerializedValue(); } } else if (value instanceof byte[]) { isSerializedObject = false; bytes = (byte[])value; } else { Assert.assertTrue(!Token.isRemovedFromDisk(value)); bytes = EntryEventImpl.serialize(value); if (bytes.length == 0) { throw new IllegalStateException("serializing <" + value + "> produced empty byte array"); } } return new ValueWrapper(isSerializedObject, bytes); } } private ValueWrapper(boolean isSerializedObject, byte[] bytes) { this.isSerializedObject = isSerializedObject; this.bytes = bytes; } } /** * Writes the key/value object stored in the given entry to disk * @throws RegionClearedException * * @see DiskRegion#put */ private static void writeToDisk(DiskEntry entry, LocalRegion region, boolean async) throws RegionClearedException { DiskRegion dr = region.getDiskRegion(); @Retained @Released Object value = entry._getValueRetain(region, true); // TODO:KIRK:OK Rusty had Object value = entry.getValueWithContext(region); ValueWrapper vw; try { vw = ValueWrapper.create(value); } finally { OffHeapHelper.release(value); } writeBytesToDisk(entry, region, async, vw); } private static void writeBytesToDisk(DiskEntry entry, LocalRegion region, boolean async, ValueWrapper vw) throws RegionClearedException { // @todo does the following unmark need to be called when an async // write is scheduled or is it ok for doAsyncFlush to do it? entry.getDiskId().unmarkForWriting(); region.getDiskRegion().put(entry, region, vw.bytes, vw.isSerializedObject, async); } /** * Updates the value of the disk entry with a new value. This allows us to * free up disk space in the non-backup case. * * @throws RegionClearedException */ public static void update(DiskEntry entry, LocalRegion region, Object newValue) throws RegionClearedException { DiskRegion dr = region.getDiskRegion(); if (newValue == null) { throw new NullPointerException(LocalizedStrings.DiskEntry_ENTRYS_VALUE_SHOULD_NOT_BE_NULL.toLocalizedString()); } //If we have concurrency checks enabled for a persistent region, we need //to add an entry to the async queue for every update to maintain the RVV boolean maintainRVV = region.concurrencyChecksEnabled && dr.isBackup(); Token oldValue = null; int oldValueLength = 0; boolean scheduleAsync = false; boolean callRemoveFromDisk = false; DiskId did = entry.getDiskId(); VersionTag tag = null; Object syncObj = did; if (syncObj == null) { syncObj = entry; } if (syncObj == did) { dr.acquireReadLock(); } try { synchronized (syncObj) { oldValue = entry.getValueAsToken(); if (Token.isRemovedFromDisk(newValue)) { if (dr.isBackup()) { dr.testIsRecovered(did, true); // fixes bug 41409 } RuntimeException rte = null; try { if (!Token.isRemovedFromDisk(oldValue)) { // removeFromDisk takes care of oldValueLength if (dr.isSync()) { removeFromDisk(entry, region, false); } else { callRemoveFromDisk = true; // do it outside the sync } } } catch (RuntimeException e) { rte = e; throw e; } finally { if (rte != null && (rte instanceof CacheClosedException)) { // 47616: not to set the value to be removedFromDisk since it failed to persist } else { // Asif Ensure that the value is rightly set despite clear so // that it can be distributed correctly entry.setValueWithContext(region, newValue); // OFFHEAP newValue was already preparedForCache } } } else if (newValue instanceof RecoveredEntry) { // Now that oplog creates are immediately put in cache // a later oplog modify will get us here RecoveredEntry re = (RecoveredEntry)newValue; long oldKeyId = did.getKeyId(); long oldOplogId = did.getOplogId(); long newOplogId = re.getOplogId(); if (newOplogId != oldOplogId) { did.setOplogId(newOplogId); re.setOplogId(oldOplogId); // so caller knows oldoplog id } did.setOffsetInOplog(re.getOffsetInOplog()); // id already set did.setUserBits(re.getUserBits()); oldValueLength = did.getValueLength(); did.setValueLength(re.getValueLength()); // The following undo and then do fixes bug 41849 // First, undo the stats done for the previous recovered value if (oldKeyId < 0) { dr.incNumOverflowOnDisk(-1L); incrementBucketStats(region, 0/*InVM*/, -1/*OnDisk*/, -oldValueLength); } else { dr.incNumEntriesInVM(-1L); incrementBucketStats(region, -1/*InVM*/, 0/*OnDisk*/, 0); } // Second, do the stats done for the current recovered value if (re.getRecoveredKeyId() < 0) { if (!entry.isValueNull()) { try { entry.handleValueOverflow(region); entry.setValueWithContext(region, null); // fixes bug 41119 }finally { entry.afterValueOverflow(region); } } dr.incNumOverflowOnDisk(1L); incrementBucketStats(region, 0/*InVM*/, 1/*OnDisk*/, did.getValueLength()); } else { entry.setValueWithContext(region, entry.prepareValueForCache(region, re.getValue(), false, false)); dr.incNumEntriesInVM(1L); incrementBucketStats(region, 1/*InVM*/, 0/*OnDisk*/, 0); } } else { //The new value in the entry needs to be set after the disk writing // has succeeded. If not , for GemFireXD , it is possible that other thread // may pick this transient value from region entry ( which for //offheap will eventually be released ) as index key, //given that this operation is bound to fail in case of //disk access exception. //entry.setValueWithContext(region, newValue); // OFFHEAP newValue already prepared if (dr.isBackup()) { dr.testIsRecovered(did, true); // fixes bug 41409 oldValueLength = getValueLength(did); if (dr.isSync()) { //In case of compression the value is being set first // because atleast for now , GemFireXD does not support compression // if and when it does support, this needs to be taken care of else // we risk Bug 48965 if (AbstractRegionEntry.isCompressible(dr, newValue)) { entry.setValueWithContext(region, newValue); // OFFHEAP newValue already prepared // newValue is prepared and compressed. We can't write compressed values to disk. writeToDisk(entry, region, false); } else { writeBytesToDisk(entry, region, false, ValueWrapper.create(newValue)); entry.setValueWithContext(region, newValue); // OFFHEAP newValue already prepared } } else if (did.isPendingAsync() && !maintainRVV) { entry.setValueWithContext(region, newValue); // OFFHEAP newValue already prepared // nothing needs to be done except // fixing up LRU stats // @todo fixup LRU stats if needed // I'm not sure anything needs to be done here. // If we have overflow and it decided to evict this entry // how do we handle that case when we are async? // Seems like the eviction code needs to leave the value // in memory until the pendingAsync is done. } else { //if the entry is not async, we need to schedule it //for regions with concurrency checks enabled, we add an entry //to the queue for every entry. scheduleAsync = true; did.setPendingAsync(true); VersionStamp stamp = entry.getVersionStamp(); if(stamp != null) { tag = stamp.asVersionTag(); } entry.setValueWithContext(region, newValue); } } else if (did != null) { entry.setValueWithContext(region, newValue); // OFFHEAP newValue already prepared // Mark the id as needing to be written // The disk remove that this section used to do caused bug 30961 // @todo this seems wrong. How does leaving it on disk fix the bug? did.markForWriting(); //did.setValueSerializedSize(0); }else { entry.setValueWithContext(region, newValue); } if (Token.isRemovedFromDisk(oldValue)) { // Note we now initialize entries removed and then set their // value once we find no existing entry. // So this is the normal path for a brand new entry. dr.incNumEntriesInVM(1L); incrementBucketStats(region, 1/*InVM*/, 0/*OnDisk*/, 0); } } if (entry instanceof LRUEntry) { LRUEntry le = (LRUEntry)entry; boolean wasEvicted = le.testEvicted(); le.unsetEvicted(); if (!Token.isRemovedFromDisk(newValue)) { if (oldValue == null // added null check for bug 41759 || wasEvicted && did != null && did.isPendingAsync()) { // Note we do not append this entry because that will be // done by lruEntryUpdate dr.incNumEntriesInVM(1L); dr.incNumOverflowOnDisk(-1L); incrementBucketStats(region, 1/*InVM*/, -1/*OnDisk*/, -oldValueLength); } } } } } finally { if (syncObj == did) { dr.releaseReadLock(); } } if (callRemoveFromDisk) { removeFromDisk(entry, region, false, oldValue == null, false); } else if (scheduleAsync && did.isPendingAsync()) { // this needs to be done outside the above sync scheduleAsyncWrite(new AsyncDiskEntry(region, entry, tag)); } } private static int getValueLength(DiskId did) { int result = 0; if (did != null) { synchronized (did) { result = did.getValueLength(); } } return result; } public static void updateRecoveredEntry(PlaceHolderDiskRegion drv, DiskEntry entry, RecoveredEntry newValue,RegionEntryContext context) { if (newValue == null) { throw new NullPointerException(LocalizedStrings.DiskEntry_ENTRYS_VALUE_SHOULD_NOT_BE_NULL.toLocalizedString()); } DiskId did = entry.getDiskId(); synchronized (did) { boolean oldValueWasNull = entry.isValueNull(); // Now that oplog creates are immediately put in cache // a later oplog modify will get us here long oldOplogId = did.getOplogId(); long newOplogId = newValue.getOplogId(); if (newOplogId != oldOplogId) { did.setOplogId(newOplogId); newValue.setOplogId(oldOplogId); // so caller knows oldoplog id } did.setOffsetInOplog(newValue.getOffsetInOplog()); // id already set did.setUserBits(newValue.getUserBits()); did.setValueLength(newValue.getValueLength()); if (newValue.getRecoveredKeyId() >= 0) { entry.setValueWithContext(context, entry.prepareValueForCache(drv, newValue.getValue(), false, false)); } else { if (!oldValueWasNull) { try { entry.handleValueOverflow(context); entry.setValueWithContext(context,null); // fixes bug 41119 }finally { entry.afterValueOverflow(context); } } } if (entry instanceof LRUEntry) { LRUEntry le = (LRUEntry)entry; assert !le.testEvicted(); // we don't allow eviction during recovery if (oldValueWasNull) { // Note we do not append this entry because that will be // done by lruEntryUpdate drv.incNumEntriesInVM(1L); drv.incNumOverflowOnDisk(-1L); } } } } public static Object getValueInVMOrDiskWithoutFaultIn(DiskEntry entry, LocalRegion region) { Object result = OffHeapHelper.copyAndReleaseIfNeeded(getValueOffHeapOrDiskWithoutFaultIn(entry, region)); if (result instanceof CachedDeserializable) { result = ((CachedDeserializable)result).getDeserializedValue(null, null); } if (result instanceof StoredObject) { ((StoredObject) result).release(); throw new IllegalStateException("gfxd tried to use getValueInVMOrDiskWithoutFaultIn"); } return result; } public static Object getValueInVMOrDiskWithoutFaultIn(DiskEntry entry, DiskRegionView dr, RegionEntryContext context) { Object result = OffHeapHelper .copyAndReleaseIfNeeded(getValueOffHeapOrDiskWithoutFaultIn(entry, dr, context)); if (result instanceof CachedDeserializable) { result = ((CachedDeserializable)result) .getDeserializedValue(null, null); } if (result instanceof StoredObject) { ((StoredObject) result).release(); throw new IllegalStateException("gfxd tried to use getValueInVMOrDiskWithoutFaultIn"); } return result; } @Retained public static Object getValueOffHeapOrDiskWithoutFaultIn(DiskEntry entry, LocalRegion region) { @Retained Object v = entry._getValueRetain(region, true); // TODO:KIRK:OK Object v = entry.getValueWithContext(region); final boolean isRemovedFromDisk = Token.isRemovedFromDisk(v); if ((v == null || isRemovedFromDisk) && !region.isIndexCreationThread()) { synchronized (entry) { v = entry._getValueRetain(region, true); // TODO:KIRK:OK v = entry.getValueWithContext(region); if (v == null) { v = Helper.getOffHeapValueOnDiskOrBuffer(entry, region.getDiskRegion(), region, false); } } } if (isRemovedFromDisk) { // fix for bug 31800 v = null; } else if (v instanceof ByteSource) { // If the ByteSource contains a Delta or ListOfDelta then we want to deserialize it final ByteSource bs = (ByteSource)v; Object deserVal = bs.getDeserializedForReading(); if (deserVal != v) { bs.release(); v = deserVal; } } return v; } @Retained public static Object getValueHeapOrDiskWithoutFaultIn(DiskEntry entry, LocalRegion region) { Object v; if (region.compressor == null) { v = entry._getValue(); if (v != null && !Token.isRemovedFromDisk(v)) { return v; } } else { v = AbstractRegionEntry.decompress(region, entry._getValue()); if (v != null && !Token.isRemovedFromDisk(v)) { return v; } } if (!region.isIndexCreationThread()) { synchronized (entry) { if (region.compressor == null) { v = entry._getValue(); } else { v = AbstractRegionEntry.decompress(region, entry._getValue()); } if (v == null) { v = Helper.getOffHeapValueOnDiskOrBuffer(entry, region.getDiskRegion(), region, false); } } return v; } else { return null; } } @Retained public static Object getValueOffHeapOrDiskWithoutFaultIn(DiskEntry entry, DiskRegionView dr, RegionEntryContext context) { @Retained Object v = entry._getValueRetain(context, true); if (v == null || Token.isRemovedFromDisk(v)) { synchronized (entry) { v = entry._getValueRetain(context, true); if (v == null) { v = Helper.getOffHeapValueOnDiskOrBuffer(entry, dr, context, false); } } } if (Token.isRemovedFromDisk(v)) { // fix for bug 31800 v = null; } else if (v instanceof ByteSource) { // If the ByteSource contains a Delta or ListOfDelta then we want to deserialize it final ByteSource bs = (ByteSource)v; Object deserVal = bs.getDeserializedForReading(); if (deserVal != v) { bs.release(); v = deserVal; } } return v; } /** * * @param entry * @param region * @return Value * @throws DiskAccessException */ @Retained public static Object faultInValue(DiskEntry entry, LocalRegion region) { DiskRegion dr = region.getDiskRegion(); @Retained Object v = entry._getValueRetain(region, true); // TODO:KIRK:OK Object v = entry.getValueWithContext(region); boolean lruFaultedIn = false; boolean done = false; try { //Asif: If the entry is instance of LRU then DidkRegion cannot be null. //Since GemFireXD is accessing this method direcly & it passes the owning region, //if the region happens to be persistent PR type, the owning region passed is PR, // but it will have DiskRegion as null. GemFireXD takes care of passing owning region // as BucketRegion in case of Overflow type entry. This is fix for Bug # 41804 if ( entry instanceof LRUEntry && !dr.isSync() ) { synchronized (entry) { DiskId did = entry.getDiskId(); if (did != null && did.isPendingAsync()) { done = true; // See if it is pending async because of a faultOut. // If so then if we are not a backup then we can unschedule the pending async. // In either case we need to do the lruFaultIn logic. boolean evicted = ((LRUEntry)entry).testEvicted(); if (evicted) { if (!dr.isBackup()) { // @todo do we also need a bit that tells us if it is in the async queue? // Seems like we could end up adding it to the queue multiple times. did.setPendingAsync(false); } // since it was evicted fix the stats here dr.incNumEntriesInVM(1L); dr.incNumOverflowOnDisk(-1L); // no need to dec overflowBytesOnDisk because it was not inced in this case. incrementBucketStats(region, 1/*InVM*/, -1/*OnDisk*/, 0); } lruEntryFaultIn((LRUEntry) entry, region); lruFaultedIn = true; } } } if (!done && (v == null || Token.isRemovedFromDisk(v) && !region.isIndexCreationThread())) { synchronized (entry) { v = entry._getValueRetain(region, true); // TODO:KIRK:OK v = entry.getValueWithContext(region); if (v == null) { v = readValueFromDisk(entry, region); if (entry instanceof LRUEntry) { if (v != null && !Token.isInvalid(v)) { lruEntryFaultIn((LRUEntry) entry, region); lruFaultedIn = true; } } } } } } finally { v = OffHeapHelper.copyAndReleaseIfNeeded(v); // At this point v should be either a heap object or a retained gfxd off-heap reference. } if (Token.isRemoved(v)) { // fix for bug 31800 v = null; } else { ((RegionEntry)entry).setRecentlyUsed(); } if (lruFaultedIn) { lruUpdateCallback(region); } // dr.getOwner().getCache().getLogger().info("DEBUG: faulted in entry " + entry.getKey()); return v; // OFFHEAP: the value ends up being returned by RegionEntry.getValue } public static void recoverValue(DiskEntry entry, long oplogId, DiskRecoveryStore recoveryStore, ByteArrayDataInput in) { boolean lruFaultedIn = false; synchronized (entry) { if (entry.isValueNull()) { DiskId did = entry.getDiskId(); if (did != null) { Object value = null; DiskRecoveryStore region = recoveryStore; DiskRegionView dr = region.getDiskRegionView(); dr.acquireReadLock(); try { synchronized (did) { // don't read if the oplog has changed. if (oplogId == did.getOplogId()) { value = getValueFromDisk(dr, did, in); if (value != null) { setValueOnFaultIn(value, did, entry, dr, region); } } } } finally { dr.releaseReadLock(); } if (entry instanceof LRUEntry) { if (value != null && !Token.isInvalid(value)) { lruEntryFaultIn((LRUEntry) entry, recoveryStore); lruFaultedIn = true; } } } } } if (lruFaultedIn) { lruUpdateCallback(recoveryStore); } } /** * Caller must have "did" synced. */ private static Object getValueFromDisk(DiskRegionView dr, DiskId did, ByteArrayDataInput in) { Object value; if (dr.isBackup() && did.getKeyId() == DiskRegion.INVALID_ID) { // must have been destroyed value = null; } else { if (did.isKeyIdNegative()) { did.setKeyId(- did.getKeyId()); } // if a bucket region then create a CachedDeserializable here instead of object value = dr.getRaw(did); // fix bug 40192 if (value instanceof BytesAndBits) { BytesAndBits bb = (BytesAndBits)value; if (EntryBits.isInvalid(bb.getBits())) { value = Token.INVALID; } else if (EntryBits.isLocalInvalid(bb.getBits())) { value = Token.LOCAL_INVALID; } else if (EntryBits.isTombstone(bb.getBits())) { value = Token.TOMBSTONE; } else if (EntryBits.isSerialized(bb.getBits())) { value = readSerializedValue(bb.getBytes(), bb.getVersion(), in, false); } else { value = readRawValue(bb.getBytes(), bb.getVersion(), in); } } } return value; } private static void lruUpdateCallback(DiskRecoveryStore recoveryStore) { /* * Used conditional check to see if * if its a LIFO Enabled, * yes then disable lruUpdateCallback() * and called updateStats() * its keep track of actual entries * present in memory - useful when * checking capacity constraint */ try { if (recoveryStore.getEvictionAttributes() != null && recoveryStore.getEvictionAttributes().getAlgorithm().isLIFO()) { ((VMLRURegionMap) recoveryStore.getRegionMap()).updateStats(); return; } // this must be done after releasing synchronization recoveryStore.getRegionMap().lruUpdateCallback(); }catch( DiskAccessException dae) { recoveryStore.handleDiskAccessException(dae, true/* stop bridge servers*/); throw dae; } } private static void lruEntryFaultIn(LRUEntry entry, DiskRecoveryStore recoveryStore) { RegionMap rm = recoveryStore.getRegionMap(); try { rm.lruEntryFaultIn(entry); // Notify the GemFireXD IndexManager if present final IndexUpdater indexUpdater = rm.getIndexUpdater(); if (indexUpdater != null) { indexUpdater.onFaultInFromDisk(entry); } } catch (DiskAccessException dae) { recoveryStore.handleDiskAccessException(dae, true/* stop bridge servers*/); throw dae; } } /** * Returns the value of this map entry, reading it from disk, if necessary. * Sets the value in the entry. * This is only called by the faultIn code once it has determined that * the value is no longer in memory. * return the result will only be off-heap if the value is a gfxd ByteSource. Otherwise result will be on-heap. * Caller must have "entry" synced. */ @Retained public static Object readValueFromDisk(DiskEntry entry, DiskRecoveryStore region) { DiskRegionView dr = region.getDiskRegionView(); DiskId did = entry.getDiskId(); if (did == null) { return null; } dr.acquireReadLock(); try { synchronized (did) { Object value = getValueFromDisk(dr, did, null); if (value == null) return null; @Unretained Object preparedValue = setValueOnFaultIn(value, did, entry, dr, region); // For Gemfirexd we want to return the offheap representation. // So we need to retain it for the caller to release. if (preparedValue instanceof ByteSource) { // This is the only case in which we return a retained off-heap ref. ((ByteSource)preparedValue).retain(); return preparedValue; } else { return value; } } } finally { dr.releaseReadLock(); } } /** * Caller must have "entry" and "did" synced and "dr" readLocked. * @return the unretained result must be used by the caller before it releases the sync on "entry". */ @Unretained private static Object setValueOnFaultIn(Object value, DiskId did, DiskEntry entry, DiskRegionView dr, DiskRecoveryStore region) { // dr.getOwner().getCache().getLogger().info("DEBUG: faulting in entry with key " + entry.getKey()); int bytesOnDisk = getValueLength(did); // Retained by the prepareValueForCache call for the region entry. // NOTE that we return this value unretained because the retain is owned by the region entry not the caller. @Retained Object preparedValue = entry.prepareValueForCache((RegionEntryContext) region, value, false, false); region.updateSizeOnFaultIn(entry.getKey(), region.calculateValueSize(preparedValue), bytesOnDisk); //did.setValueSerializedSize(0); // I think the following assertion is true but need to run // a regression with it. Reenable this post 6.5 //Assert.assertTrue(entry._getValue() == null); entry.setValueWithContext((RegionEntryContext) region, preparedValue); dr.incNumEntriesInVM(1L); dr.incNumOverflowOnDisk(-1L); incrementBucketStats(region, 1/*InVM*/, -1/*OnDisk*/, -bytesOnDisk); return preparedValue; } static Object readSerializedValue(byte[] valueBytes, Version version, ByteArrayDataInput in, boolean forceDeserialize) { if (forceDeserialize || CachedDeserializableFactory.preferObject()) { // deserialize checking for product version change return EntryEventImpl.deserialize(valueBytes, version, in); } else { // TODO: upgrades: is there a case where GemFire values are internal // ones that need to be upgraded transparently; probably messages // being persisted (gateway events?) return CachedDeserializableFactory.create(valueBytes); } } static Object readRawValue(byte[] valueBytes, Version version, ByteArrayDataInput in) { final StaticSystemCallbacks sysCb; if (version != null && (sysCb = GemFireCacheImpl.FactoryStatics .systemCallbacks) != null) { // may need to change serialized shape for GemFireXD return sysCb.fromVersion(valueBytes, valueBytes.length, false, version, in); } else { return valueBytes; } } public static void incrementBucketStats(Object owner, int entriesInVmDelta, int overflowOnDiskDelta, int overflowBytesOnDiskDelta) { if (owner instanceof BucketRegion) { ((BucketRegion)owner).incNumEntriesInVM(entriesInVmDelta); ((BucketRegion)owner).incNumOverflowOnDisk(overflowOnDiskDelta); ((BucketRegion)owner).incNumOverflowBytesOnDisk(overflowBytesOnDiskDelta); } else if (owner instanceof DiskRegionView) { ((DiskRegionView)owner).incNumOverflowBytesOnDisk(overflowBytesOnDiskDelta); } } /** * Writes the value of this DiskEntry to disk and * null s out the reference to the value to free up VM space. *

* Note that if the value had already been written to disk, it is not * written again. *

* Caller must synchronize on entry and it is assumed the entry is evicted * * see #writeToDisk * @throws RegionClearedException */ public static int overflowToDisk(DiskEntry entry, LocalRegion region, EnableLRU ccHelper) throws RegionClearedException { { Token entryVal = entry.getValueAsToken(); if (entryVal == null || Token.isRemovedFromDisk(entryVal)) { // Note it could be removed token now because // freeAllEntriesOnDisk is not able to sync on entry return 0; } } DiskRegion dr = region.getDiskRegion(); final int oldSize = region.calculateRegionEntryValueSize(entry);; // dr.getOwner().getCache().getLogger().info("DEBUG: overflowing entry with key " + entry.getKey()); //Asif:Get diskID . If it is null, it implies it is // overflow only mode. //long id = entry.getDiskId().getKeyId(); DiskId did = entry.getDiskId(); if (did == null) { ((AbstractDiskLRURegionEntry)entry).setDelayedDiskId(region); did = entry.getDiskId(); } // Notify the GemFireXD IndexManager if present /* final IndexUpdater indexUpdater = region.getIndexUpdater(); if(indexUpdater != null && dr.isSync()) { indexUpdater.onOverflowToDisk(entry); }*/ int change = 0; boolean scheduledAsyncHere = false; dr.acquireReadLock(); try { synchronized (did) { // check for a concurrent freeAllEntriesOnDisk if (entry.isRemovedFromDisk()) { return 0; } //TODO:Asif: Check if we need to overflow even when id is = 0 boolean wasAlreadyPendingAsync = did.isPendingAsync(); if (did.needsToBeWritten()) { if (dr.isSync()) { writeToDisk(entry, region, false); } else if (!wasAlreadyPendingAsync) { scheduledAsyncHere = true; did.setPendingAsync(true); } else { // it may have been scheduled to be written (isBackup==true) // and now we are faulting it out } } boolean movedValueToDisk = false; // added for bug 41849 // If async then if it does not need to be written (because it already was) // then treat it like the sync case. This fixes bug 41310 if (scheduledAsyncHere || wasAlreadyPendingAsync) { // we call _setValue(null) after it is actually written to disk change = entry.updateAsyncEntrySize(ccHelper); // do the stats when it is actually written to disk } else { region.updateSizeOnEvict(entry.getKey(), oldSize); //did.setValueSerializedSize(byteSizeOnDisk); try { entry.handleValueOverflow(region); entry.setValueWithContext(region,null); }finally { entry.afterValueOverflow(region); } movedValueToDisk = true; change = ((LRUClockNode)entry).updateEntrySize(ccHelper); } dr.incNumEntriesInVM(-1L); dr.incNumOverflowOnDisk(1L); int valueLength = 0; if (movedValueToDisk) { valueLength = getValueLength(did); } incrementBucketStats(region, -1/*InVM*/, 1/*OnDisk*/, valueLength); } } finally { dr.releaseReadLock(); } if (scheduledAsyncHere && did.isPendingAsync()) { // this needs to be done outside the above sync // the version tag is null here because this method only needs // to write to disk for overflow only regions, which do not need // to maintain an RVV on disk. scheduleAsyncWrite(new AsyncDiskEntry(region, entry, null)); } return change; } private static void scheduleAsyncWrite(AsyncDiskEntry ade) { DiskRegion dr = ade.region.getDiskRegion(); dr.scheduleAsyncWrite(ade); } public static void handleFullAsyncQueue(DiskEntry entry, LocalRegion region, VersionTag tag) { DiskRegion dr = region.getDiskRegion(); DiskId did = entry.getDiskId(); synchronized (entry) { dr.acquireReadLock(); try { synchronized (did) { if (did.isPendingAsync()) { did.setPendingAsync(false); final Token entryVal = entry.getValueAsToken(); final int entryValSize = region.calculateRegionEntryValueSize(entry); boolean remove = false; try { if (Token.isRemovedFromDisk(entryVal)) { // onDisk was already deced so just do the valueLength here incrementBucketStats(region, 0/*InVM*/, 0/*OnDisk*/, -did.getValueLength()); dr.remove(region, entry, true, false); if (dr.isBackup()) { did.setKeyId(DiskRegion.INVALID_ID); // fix for bug 41340 } remove = true; } else if (Token.isInvalid(entryVal) && !dr.isBackup()) { // no need to write invalid to disk if overflow only } else if (entryVal != null) { writeToDisk(entry, region, true); } else { //if we have a version tag we need to record the operation //to update the RVV if(tag != null) { DiskEntry.Helper.doAsyncFlush(tag, region); } return; } assert !dr.isSync(); // Only setValue to null if this was an evict. // We could just be a backup that is writing async. if (!remove && !Token.isInvalid(entryVal) && entry instanceof LRUEntry && ((LRUEntry)entry).testEvicted()) { // Moved this here to fix bug 40116. region.updateSizeOnEvict(entry.getKey(), entryValSize); // note the old size was already accounted for // onDisk was already inced so just do the valueLength here incrementBucketStats(region, 0/*InVM*/, 0/*OnDisk*/, did.getValueLength()); try { entry.handleValueOverflow(region); entry.setValueWithContext(region,null); }finally { entry.afterValueOverflow(region); } } //See if we the entry we wrote to disk has the same tag //as this entry. If not, write the tag as a conflicting operation. //to update the RVV. VersionStamp stamp = entry.getVersionStamp(); if(tag != null && stamp != null && (stamp.getMemberID() != tag.getMemberID() || stamp.getRegionVersion() != tag.getRegionVersion())) { DiskEntry.Helper.doAsyncFlush(tag, region); } } catch (RegionClearedException ignore) { // no need to do the op since it was clobbered by a region clear } } else { //if we have a version tag we need to record the operation //to update the RVV, even if we don't write the entry if(tag != null) { DiskEntry.Helper.doAsyncFlush(tag, region); } } } } finally { dr.releaseReadLock(); } } // sync entry } public static void doAsyncFlush(VersionTag tag, LocalRegion region) { if (region.isThisRegionBeingClosedOrDestroyed()) return; DiskRegion dr = region.getDiskRegion(); if (!dr.isBackup()) { return; } assert !dr.isSync(); dr.acquireReadLock(); try { dr.getDiskStore().putVersionTagOnly(region, tag, true); } finally { dr.releaseReadLock(); } } /** * Flush an entry that was previously scheduled to be written to disk. * @param tag * @since prPersistSprint1 */ public static void doAsyncFlush(DiskEntry entry, LocalRegion region, VersionTag tag) { if (region.isThisRegionBeingClosedOrDestroyed()) return; DiskRegion dr = region.getDiskRegion(); dr.setClearCountReference(); synchronized (entry) { // fixes 40116 // If I don't sync the entry and this method ends up doing an eviction // thus setting value to null // some other thread is free to fetch the value while the entry is synced // and think it has removed it or replaced it. This results in updateSizeOn* // being called twice for the same value (once when it is evicted and once // when it is removed/updated). try { dr.acquireReadLock(); try { DiskId did = entry.getDiskId(); synchronized (did) { if (did.isPendingAsync()) { did.setPendingAsync(false); final Token entryVal = entry.getValueAsToken(); final int entryValSize = region.calculateRegionEntryValueSize(entry); boolean remove = false; try { if (Token.isRemovedFromDisk(entryVal)) { if (region.isThisRegionBeingClosedOrDestroyed()) return; // onDisk was already deced so just do the valueLength here incrementBucketStats(region, 0/*InVM*/, 0/*OnDisk*/, -did.getValueLength()); dr.remove(region, entry, true, false); if (dr.isBackup()) { did.setKeyId(DiskRegion.INVALID_ID); // fix for bug 41340 } remove = true; } else if ((Token.isInvalid(entryVal) || entryVal == Token.TOMBSTONE) && !dr.isBackup()) { // no need to write invalid or tombstones to disk if overflow only } else if (entryVal != null) { writeToDisk(entry, region, true); // dr.getOwner().getCache().getLogger().info("DEBUG: overflowing async entry with key " + entry.getKey()); } else { // @todo why would we have a null value here? // I'm seeing it show up in tests: // java.lang.IllegalArgumentException: Must not serialize null in this context. // at com.gemstone.gemfire.internal.cache.EntryEventImpl.serialize(EntryEventImpl.java:1024) // at com.gemstone.gemfire.internal.cache.DiskEntry$Helper.writeToDisk(DiskEntry.java:351) // at com.gemstone.gemfire.internal.cache.DiskEntry$Helper.doAsyncFlush(DiskEntry.java:683) // at com.gemstone.gemfire.internal.cache.DiskRegion$FlusherThread.run(DiskRegion.java:1055) //if we have a version tag we need to record the operation //to update the RVV if(tag != null) { DiskEntry.Helper.doAsyncFlush(tag, region); } return; } assert !dr.isSync(); // Only setValue to null if this was an evict. // We could just be a backup that is writing async. if (!remove && !Token.isInvalid(entryVal) && (entryVal != Token.TOMBSTONE) && entry instanceof LRUEntry && ((LRUEntry)entry).testEvicted()) { // Moved this here to fix bug 40116. region.updateSizeOnEvict(entry.getKey(), entryValSize); // note the old size was already accounted for // onDisk was already inced so just do the valueLength here incrementBucketStats(region, 0/*InVM*/, 0/*OnDisk*/, did.getValueLength()); try { entry.handleValueOverflow(region); entry.setValueWithContext(region,null); }finally { entry.afterValueOverflow(region); } } } catch (RegionClearedException ignore) { // no need to do the op since it was clobbered by a region clear } //See if we the entry we wrote to disk has the same tag //as this entry. If not, write the tag as a conflicting operation. //to update the RVV. VersionStamp stamp = entry.getVersionStamp(); if(tag != null && stamp != null && (stamp.getMemberID() != tag.getMemberID() || stamp.getRegionVersion() != tag.getRegionVersion())) { DiskEntry.Helper.doAsyncFlush(tag, region); } } else { //if we have a version tag we need to record the operation //to update the RVV if(tag != null) { DiskEntry.Helper.doAsyncFlush(tag, region); } } } } finally { dr.releaseReadLock(); } } finally { dr.removeClearCountReference(); } } // sync entry } /** * Removes the key/value pair in the given entry from disk * * @throws RegionClearedException If the operation is aborted due to a clear * @see DiskRegion#remove */ public static void removeFromDisk(DiskEntry entry, LocalRegion region, boolean isClear) throws RegionClearedException { removeFromDisk(entry, region, true, false, isClear); } private static void removeFromDisk(DiskEntry entry, LocalRegion region, boolean checkValue, boolean valueWasNull, boolean isClear) throws RegionClearedException { DiskRegion dr = region.getDiskRegion(); //If we have concurrency checks enabled for a persistent region, we need //to add an entry to the async queue for every update to maintain the RVV boolean maintainRVV = region.concurrencyChecksEnabled && dr.isBackup(); DiskId did = entry.getDiskId(); VersionTag tag = null; Object syncObj = did; if (did == null) { syncObj = entry; } boolean scheduledAsyncHere = false; if (syncObj == did) { dr.acquireReadLock(); } try { synchronized (syncObj) { if (did == null || (dr.isBackup() && did.getKeyId()== DiskRegion.INVALID_ID)) { // Not on disk yet // InternalDistributedSystem.getLoggerI18n().fine("DEBUG: removing Entry " + entry.getKey() // + (did==null? " that is not on disk" // : "with dr.isBackup and invalid keyID")); dr.incNumEntriesInVM(-1L); incrementBucketStats(region, -1/*InVM*/, 0/*OnDisk*/, 0); dr.unscheduleAsyncWrite(did); return; } //Asif: This will convert the -ve OplogKeyId to positive as part of fixing //Bug # 39989 did.unmarkForWriting(); //System.out.println("DEBUG: removeFromDisk doing remove(" + id + ")"); int oldValueLength = 0; if (dr.isSync() || isClear) { oldValueLength = did.getValueLength(); dr.remove(region, entry, false, isClear); if (dr.isBackup()) { did.setKeyId(DiskRegion.INVALID_ID); // fix for bug 41340 } //If this is a clear, we should unschedule the async write for this //entry did.setPendingAsync(false); } else { if (!did.isPendingAsync() || maintainRVV) { scheduledAsyncHere = true; did.setPendingAsync(true); VersionStamp stamp = entry.getVersionStamp(); if(stamp != null) { tag = stamp.asVersionTag(); } } } // InternalDistributedSystem.getLoggerI18n().fine("DEBUG: removing Entry " + entry.getKey() // + " with value " + entry._getValue() + "checkValue=" + checkValue); if (checkValue) { valueWasNull = entry.isValueNull(); entry._removePhase1(); } if (valueWasNull) { dr.incNumOverflowOnDisk(-1L); incrementBucketStats(region, 0/*InVM*/, -1/*OnDisk*/, -oldValueLength); } else { dr.incNumEntriesInVM(-1L); incrementBucketStats(region, -1/*InVM*/, 0/*OnDisk*/, 0); if (!dr.isSync()) { // we are going to do an async remove of an entry that is not currently // overflowed to disk so we don't want to count its value length as being // on disk when we finally do the async op. So we clear it here. did.setValueLength(0); } } } } finally { if (syncObj == did) { dr.releaseReadLock(); } } if (scheduledAsyncHere && did.isPendingAsync()) { // do this outside the sync scheduleAsyncWrite(new AsyncDiskEntry(region, entry, tag)); } } /** * @param entry * @param region * @param tag */ public static void updateVersionOnly(DiskEntry entry, LocalRegion region, VersionTag tag) { DiskRegion dr = region.getDiskRegion(); if (!dr.isBackup()) { return; } assert tag != null && tag.getMemberID()!=null; boolean scheduleAsync = false; DiskId did = entry.getDiskId(); Object syncObj = did; if (syncObj == null) { syncObj = entry; } if (syncObj == did) { dr.acquireReadLock(); } try { synchronized (syncObj) { if (dr.isSync()) { dr.getDiskStore().putVersionTagOnly(region, tag, false); } else { scheduleAsync = true; } } } finally { if (syncObj == did) { dr.releaseReadLock(); } } if (scheduleAsync) { // this needs to be done outside the above sync scheduleAsyncWrite(new AsyncDiskEntry(region, tag)); } } } /** * A marker object for an entry that has been recovered from disk. * It is handled specially when it is placed in a region. */ public static final class RecoveredEntry { /** The disk id of the entry being recovered */ private final long recoveredKeyId; /** The value of the recovered entry */ private final Object value; private final long offsetInOplog; private final byte userBits; private final int valueLength; private long oplogId; private VersionTag tag; /** last modified timestamp for no-versions (#45397) */ private long lastModifiedTime; /** * Only for this constructor, the value is not loaded into the region & it is lying * on the oplogs. Since Oplogs rely on DiskId to furnish user bits so as to correctly * interpret bytes, the userbit needs to be set correctly here. */ public RecoveredEntry(long keyId, long oplogId, long offsetInOplog, byte userBits, int valueLength) { this(-keyId, oplogId, offsetInOplog, userBits, valueLength, null); } public RecoveredEntry(long keyId, long oplogId, long offsetInOplog, byte userBits, int valueLength, Object value) { this.recoveredKeyId = keyId; this.value = value; this.oplogId = oplogId; this.offsetInOplog = offsetInOplog; this.userBits = EntryBits.setRecoveredFromDisk(userBits, true); this.valueLength = valueLength; } /** * Returns the disk id of the entry being recovered */ public long getRecoveredKeyId() { return this.recoveredKeyId; } /** * Returns the value of the recovered entry. Note that if the * disk id is < 0 then the value has not been faulted in and * this method will return null. */ public Object getValue() { return this.value; } /** * * @return byte indicating the user bits. The correct value is returned only in the specific case of * entry recovered from oplog ( & not rolled to Htree) & the RECOVER_VALUES flag is false . In other cases * the exact value is not needed */ public byte getUserBits() { return this.userBits; } public int getValueLength() { return this.valueLength; } public long getOffsetInOplog() { return offsetInOplog; } public long getOplogId() { return this.oplogId; } public void setOplogId(long v) { this.oplogId = v; } public VersionTag getVersionTag() { return this.tag; } public void setVersionTag(VersionTag tag) { this.tag = tag; } public final long getLastModifiedTime() { return this.lastModifiedTime; } public final void setLastModifiedTime(long timestamp) { this.lastModifiedTime = timestamp; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy