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

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

The 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.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

import com.gemstone.gemfire.CancelException;
import com.gemstone.gemfire.SystemFailure;
import com.gemstone.gemfire.cache.IllegalTransactionStateException;
import com.gemstone.gemfire.cache.Operation;
import com.gemstone.gemfire.cache.RegionDestroyedException;
import com.gemstone.gemfire.cache.TransactionDataRebalancedException;
import com.gemstone.gemfire.cache.TransactionException;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.cache.execute.BucketMovedException;
import com.gemstone.gemfire.internal.cache.locks.ExclusiveSharedSynchronizer;
import com.gemstone.gemfire.internal.cache.locks.LockMode;
import com.gemstone.gemfire.internal.cache.locks.LockingPolicy;
import com.gemstone.gemfire.internal.cache.versions.VersionSource;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.util.ArrayUtils;
import com.gemstone.gemfire.internal.util.concurrent.StoppableReentrantReadWriteLock;
import com.gemstone.gnu.trove.THashMap;
import com.gemstone.gnu.trove.THashSet;
import com.gemstone.gnu.trove.TIntArrayList;
import com.gemstone.gnu.trove.TObjectProcedure;

/**
 * TXRegionState is the entity that tracks all the changes a transaction has
 * made to a region.
 * 
 * 
 * On the remote node, the tx can potentially be accessed by multiple threads,
 * specially with function execution. This class extends lock and should be used
 * to synchronize access to the tx region state.
 * 
 * We _need_ at least region level locks rather than big TXState level locks
 * since GemFireXD global index maintenance can be invoked that will lead to
 * deadlocks with latter.
 * 
 * 
 * @author Darrel Schneider
 * 
 * @since 4.0
 * 
 * @see TXManagerImpl
 */
@SuppressWarnings("serial")
public final class TXRegionState extends ReentrantLock {

  // A map of Objects (entry keys) -> TXEntryState
  private final THashMapWithCreate entryMods;

  TObjectLongHashMapDSFID tailKeysForParallelWAN;

  // A map of Objects (entry keys) -> TXEntryUserAttrState
  //private HashMap uaMods;

  private transient final StoppableReentrantReadWriteLock.StoppableReadLock
      expiryReadLock;

  public transient final LocalRegion region;

  /**
   * The current {@link TXState} that owns this TXRegionState.
   */
  transient final TXState txState;

  private final boolean isPersistent;
  private transient VersionSource versionSource;
  private transient boolean isRemoteVersionSource;

  /** holds the ops for uninitialized regions */
  private final ArrayList pendingTXOps;
  private final TIntArrayList pendingTXLockFlags;
  private List pendingGIITXOps;
  private int pendingGIILockFlag;
  private volatile boolean pendingGIITXLocked;
  /**
   * set to false after {@link #cleanup} has been invoked which disallows any
   * new operations on this TXRegionState
   */
  private boolean isValid;
  /** a sort order for commit/rollback if GII was not done */
  int finishOrder;

  // temporary transient variables
  private transient int tmpEntryCount;
  transient int numChanges;
  private transient TransactionException tmpEx;

  // These three hashmaps are for gemfirexd index maintenance
  private THashMapWithKeyPair transactionalIndexInfo;
  private THashMapWithKeyPair toBeReinstatedIndexesInfo;
  private THashMapWithKeyPair unaffectedIndexInfo;

  public TXRegionState(final LocalRegion r, final TXState tx) {
    // don't allow for expiration on replicated regions or eviction with local
    // destroy since some copies may have been expired/evicted while others may
    // not have been when the distributed transactional locks are taken
    if ((r.isEntryEvictionPossible() && !r.getEvictionAttributes().getAction()
        .isOverflowToDisk()) || (r.getDataPolicy().withReplication()
            && r.isEntryExpiryPossible())) {
      throw new UnsupportedOperationException(LocalizedStrings
          .TXRegionState_OPERATIONS_ON_EVICT_EXPIRY_REGION_ARE_NOT_ALLOWED_BECAUSE_THIS_THREAD_HAS_AN_ACTIVE_TRANSACTION
              .toLocalizedString(r.getFullPath()));
    }
    if (r.getPersistBackup() && !TXManagerImpl.ALLOW_PERSISTENT_TRANSACTIONS) {
      throw new UnsupportedOperationException(LocalizedStrings
          .TXRegionState_OPERATIONS_ON_PERSISTBACKUP_REGIONS_ARE_NOT_ALLOWED_BECAUSE_THIS_THREAD_HAS_AN_ACTIVE_TRANSACTION
              .toLocalizedString(r.getFullPath()));
    }
    // TODO: TX: why are global regions not supported?
    if (r.getScope().isGlobal()) {
      throw new UnsupportedOperationException(LocalizedStrings
          .TXRegionState_OPERATIONS_ON_GLOBAL_REGIONS_ARE_NOT_ALLOWED_BECAUSE_THIS_THREAD_HAS_AN_ACTIVE_TRANSACTION
              .toLocalizedString(r.getFullPath()));
    }

    if (r.isDestroyed()) {
      throw new TransactionDataRebalancedException(LocalizedStrings
          .PartitionedRegion_TRANSACTIONAL_DATA_MOVED_DUE_TO_REBALANCING
              .toLocalizedString("", r.getFullPath()), r
              .getFullPath());
    }

    this.region = r;
    this.isPersistent = r.getDataPolicy().withPersistence();
    this.txState = tx;
    this.entryMods = new THashMapWithCreate(4);
    this.expiryReadLock = r.getTxEntryExpirationReadLock();
    this.isValid = true;

    if (r.isInitialized() || !r.getImageState().addPendingTXRegionState(this)) {
      this.pendingTXOps = null;
      this.pendingTXLockFlags = null;
    }
    else {
      this.pendingTXOps = new ArrayList();
      this.pendingTXLockFlags = new TIntArrayList();
    }
  }

  /**
   * NOT THREAD-SAFE; ONLY FOR TESTS
   */
  @SuppressWarnings("unchecked")
  public final Set getEntryKeys() {
    final THashSet keys = new THashSet(this.entryMods.size());
    this.entryMods.forEachKey(new TObjectProcedure() {
      public final boolean execute(final Object obj) {
        if (obj instanceof RegionEntry) {
          keys.add(((RegionEntry)obj).getKeyCopy());
        }
        else {
          keys.add(obj);
        }
        return true;
      }
    });
    return keys;
  }

  /**
   * Get the key to {@link TXEntryState} map.
   */
  final THashMapWithCreate getEntryMap() {
    if (this.isValid) {
      return this.entryMods;
    }
    else {
      throw new IllegalTransactionStateException(
          LocalizedStrings.TRANSACTION_0_IS_NO_LONGER_ACTIVE
              .toLocalizedString(this.txState.getTransactionId()));
    }
  }

  /**
   * Gets raw handle to the map returned by {@link #getEntryMap()}. Do not use
   * unless sure that it is being used for read-only.
   */
  final THashMapWithCreate getInternalEntryMap() {
    return this.entryMods;
  }

  /**
   * NOT THREAD-SAFE.
   * This is called in case of hdfs enabled table to get newly
   * created entries in this tx.
   * As the iterator is on queue, this entry is not in the queue+hdfs.
   */
  
  @SuppressWarnings("unchecked")
  public final Set getCreatedEntryKeys() {
    final THashSet keys = new THashSet(this.entryMods.size());
    this.entryMods.forEachValue(new TObjectProcedure() {
      public final boolean execute(final Object obj) {
        if(obj instanceof TXEntryState) {
          if(((TXEntryState)obj).wasCreatedByTX()) {
            keys.add(((TXEntryState)obj).getUnderlyingRegionEntry());
          }
        }
        return true;
      }
    });
    return keys;
  }

  /**
   * Not thread-safe. Invoke only under the {@link #lock()} that takes a higher
   * level lock or in tests where only single thread is known to access the
   * region.
   * 
   * Returns either a {@link TXEntryState} object for a transactional entry
   * locked for write, while returns {@link RegionEntry} object for a
   * transactional entry locked for read.
   */
  public final Object readEntry(final Object entryKey) {
    return readEntry(entryKey, true);
  }

  /**
   * Not thread-safe. Invoke only under the {@link #lock()} that takes a higher
   * level lock or in tests where only single thread is known to access the
   * region.
   * 
   * Returns either a {@link TXEntryState} object for a transactional entry
   * locked for write, while returns {@link RegionEntry} object for a
   * transactional entry locked for read.
   */
  public final Object readEntry(final Object entryKey,
      final boolean checkValid) {
    if (!checkValid || this.isValid) {
      final THashMapWithCreate entryMap = this.entryMods;
      final Object txEntry = entryMap.size() > 0 ? entryMap.get(entryKey)
          : null;
      if (TXStateProxy.LOG_FINEST) {
        final LogWriterI18n logger = this.txState.getCache().getLoggerI18n();
        logger.info(LocalizedStrings.DEBUG,
            "TXRegionState#readEntry: return TX entry for key [" + entryKey
                + ",type=" + entryKey.getClass().getSimpleName() + "] for "
                + toString() + ": " + txEntry);
      }
      return txEntry;
    }
    else {
      throw new IllegalTransactionStateException(
          LocalizedStrings.TRANSACTION_0_IS_NO_LONGER_ACTIVE
              .toLocalizedString(this.txState.getTransactionId()));
    }
  }

  public final TXEntryState createEntry(final Object entryKey,
      final RegionEntry re, final Object val, final boolean doFullValueFlush) {
    final TXEntryState txes = GemFireCacheImpl.FactoryStatics.txEntryStateFactory
        .createEntry(entryKey, re, val, doFullValueFlush, this);
    if (TXStateProxy.LOG_FINE) {
      final LogWriterI18n logger = this.txState.getCache().getLoggerI18n();
      logger.info(LocalizedStrings.DEBUG, "TXRegionState#createEntry: for TX "
          + txState.txId.shortToString() + " created new TX entry for key ["
          + entryKey + ",type=" + entryKey.getClass().getName() + "] in region "
          + this.region.getFullPath() + " doFullValueFlush=" + doFullValueFlush
          + " with value=" + ArrayUtils.objectStringNonRecursive(val) + ": "
          + ArrayUtils.objectRefString(txes));
    }
    return txes;
  }

  public static final TXEntryState createEmptyEntry(final TXId txId,
      final Object entryKey, final RegionEntry re, final Object val,
      final String regionPath, final boolean doFullValueFlush) {
    final TXEntryState txes = GemFireCacheImpl.FactoryStatics.txEntryStateFactory
        .createEntry(entryKey, re, val, doFullValueFlush, null);
    if (TXStateProxy.LOG_FINE) {
      final LogWriterI18n logger = GemFireCacheImpl.getExisting()
          .getLoggerI18n();
      logger.info(LocalizedStrings.DEBUG, "TXRegionState#createEmptyEntry: "
          + "for TX " + txId.shortToString()
          + " created new TX entry for key [" + entryKey + "] in region "
          + regionPath + " doFullValueFlush=" + doFullValueFlush
          + " with value=" + ArrayUtils.objectStringNonRecursive(val) + ": "
          + ArrayUtils.objectRefString(txes));
    }
    return txes;
  }

  public final TXEntryState createReadEntry(final Object entryKey,
      final RegionEntry re, final Object val, final boolean doFullValueFlush) {
    final TXEntryState result = createEntry(entryKey, re, val,
        doFullValueFlush);
    getEntryMap().put(entryKey, result);
    return result;
  }

  public final TXState getTXState() {
    return this.txState;
  }

  /*
  public void rmEntry(Object entryKey, TXState txState, LocalRegion r) {
    rmEntryUserAttr(entryKey);
    TXEntryState e = (TXEntryState)this.entryMods.remove(entryKey);
    if (e != null) {
      e.cleanup(r);
    }
    if (this.uaMods == null && this.entryMods.size() == 0) {
      txState.rmRegion(r);
    }
  }

  public TXEntryUserAttrState readEntryUserAttr(Object entryKey) {
    TXEntryUserAttrState result = null;
    if (this.uaMods != null) {
      result = (TXEntryUserAttrState)this.uaMods.get(entryKey);
    }
    return result;
  }

  public TXEntryUserAttrState writeEntryUserAttr(Object entryKey, LocalRegion r) {
    if (this.uaMods == null) {
      this.uaMods = new HashMap();
    }
    TXEntryUserAttrState result = (TXEntryUserAttrState)this.uaMods
        .get(entryKey);
    if (result == null) {
      result = new TXEntryUserAttrState(r.basicGetEntryUserAttribute(entryKey));
      this.uaMods.put(entryKey, result);
    }
    return result;
  }

  public void rmEntryUserAttr(Object entryKey) {
    if (this.uaMods != null) {
      if (this.uaMods.remove(entryKey) != null) {
        if (this.uaMods.size() == 0) {
          this.uaMods = null;
        }
      }
    }
  }
  */

  /**
   * Returns the total number of modifications made by this transaction to this
   * region's entry count. The result will have a 1 for every create, a 0 for
   * every destroy of entry created in TX itself and -1 for every other destroy.
   */
  final int entryCountMod() {
    this.tmpEntryCount = 0;
    this.entryMods.forEachValue(new TObjectProcedure() {
      public final boolean execute(final Object txes) {
        if (txes instanceof TXEntryState) {
          tmpEntryCount += ((TXEntryState)txes).entryCountMod();
        }
        return true;
      }
    });
    return this.tmpEntryCount;
  }

  public final void setTransactionalIndexInfoMap(
      final THashMapWithKeyPair map) {
    this.transactionalIndexInfo = map;
  }
  
  public final void setUnaffectedIndexInfoMap(
      final THashMapWithKeyPair map) {
    this.unaffectedIndexInfo = map;
  }

  public final void setToBeReinstatedIndexMap(final THashMapWithKeyPair map) {
    this.toBeReinstatedIndexesInfo = map;
  }

  public final THashMapWithKeyPair getTransactionalIndexInfoMap() {
    return this.transactionalIndexInfo;
  }
  
  public final THashMapWithKeyPair getUnaffectedIndexInfoMap() {
    return this.unaffectedIndexInfo;
  }

  public final THashMapWithKeyPair getToBeReinstatedIndexMap() {
    return this.toBeReinstatedIndexesInfo;
  }

  /**
   * Check if GII for the underlying region including for TXRegionState, if any,
   * is complete. In case it is not complete, then it will also acquire the lock
   * to sync against a concurrent GII for this TXRegionState. The lock should be
   * released by invoking {@link #unlockGII()} or
   * {@link #addPendingTXOpAndUnlockGII}.
   * 
   * @return true if the underlying region has been initialized at least for
   *         this TXRegionState, and false otherwise in which case it will lock
   *         GII ops on this TXRegionState until {@link #unlockGII()} is invoked
   */
  public final boolean isInitializedAndLockGII() {
    if (this.pendingTXOps == null) {
      return true;
    }
    // if region is in the process of final initialization, then wait
    // for it since GII will also bring TXRegionState up-to-date
    final ImageState imgState = this.region.getImageState();
    return !imgState.lockPendingTXRegionStates(false, false);
  }

  public final void unlockGII() {
    final ImageState imgState = this.region.getImageState();
    imgState.unlockPendingTXRegionStates(false);
  }

  public final void addPendingTXOpAndUnlockGII(Object entry, int lockFlags) {
    lock();
    this.pendingTXOps.add(entry);
    this.pendingTXLockFlags.add(lockFlags);
    unlock();
    unlockGII();
  }

  public final void setGIITXOps(List entries, int lockFlags) {
    lock();
    // set the TXRegionState field
    for (Object entry : entries) {
      if (entry instanceof TXEntryState) {
        ((TXEntryState)entry).txRegionState = this;
      }
    }
    this.pendingGIITXOps = entries;
    this.pendingGIILockFlag = lockFlags;
    unlock();
  }

  public void applyPendingTXOps() {
    final TXState tx = this.txState;
    // reusable EntryEvent
    final EntryEventImpl eventTemplate = EntryEventImpl.create(null,
        Operation.UPDATE, null, null, null, true, null);
    eventTemplate.setTXState(tx);
    // apply as PUT DML so duplicate entry inserts etc. will go through fine
    // [sumedh] this is a strange duplication of functionality to combine
    // fetchFromHDFS with PUT DML -- should have separate flag
    //eventTemplate.setFetchFromHDFS(false);
    final LocalRegion region = this.region;
    final LocalRegion baseRegion;
    if (region.isUsedForPartitionedRegionBucket()) {
      baseRegion = region.getPartitionedRegion();
    }
    else {
      baseRegion = region;
    }

    lock();
    try {
      // first the GII ops, if any
      if (this.pendingGIITXOps != null) {
        final int lockFlags = this.pendingGIILockFlag;
        for (Object event : this.pendingGIITXOps) {
          if (TXStateProxy.LOG_FINE) {
            final LogWriterI18n logger = this.txState.getCache()
                .getLoggerI18n();
            logger.info(LocalizedStrings.DEBUG,
                "applyPendingTXOps: applying GII entry " + getEntryString(
                    event, TXStateProxy.LOG_FINEST) + " on " + toString());
          }
          tx.applyPendingOperation(event, lockFlags, this, region, baseRegion,
              eventTemplate, false, Boolean.FALSE, null);
        }
        this.pendingGIITXOps = null;
      }
      // next the pending TX ops in this list
      final int size = this.pendingTXOps.size();
      for (int index = 0; index < size; index++) {
        Object event = this.pendingTXOps.get(index);
        int lockFlags = this.pendingTXLockFlags.getQuick(index);
        if (TXStateProxy.LOG_FINE) {
          final LogWriterI18n logger = this.txState.getCache()
              .getLoggerI18n();
          logger.info(LocalizedStrings.DEBUG,
              "applyPendingTXOps: applying entry " + getEntryString(
                  event, TXStateProxy.LOG_FINEST) + " on " + toString());
        }
        tx.applyPendingOperation(event, lockFlags, this, region, baseRegion,
            eventTemplate, false, Boolean.FALSE, null);
      }
      this.pendingTXOps.clear();
      this.pendingTXLockFlags.clear();
    } finally {
      eventTemplate.release();
      unlock();
    }
  }

  private String getEntryString(Object entry, boolean verbose) {
    if (verbose) {
      return entry.toString();
    }
    else if (entry instanceof TXEntryState) {
      return ((TXEntryState)entry).shortToString();
    }
    else {
      return ArrayUtils.objectRefString(entry);
    }
  }

  public final VersionSource getVersionSource() {
    VersionSource vs = this.versionSource;
    if (vs != null) {
      return vs;
    }
    final LocalRegion r = this.region;
    if (!r.getConcurrencyChecksEnabled()) {
      return null;
    }
    final TXState tx = this.txState;
    // for non-persistent region, use the coordinator
    if (!this.isPersistent) {
      this.isRemoteVersionSource = !tx.isCoordinator();
      return (this.versionSource = tx.getCoordinator());
    }
    // else set from TXStateProxy
    THashMap versionSources = tx.getProxy().regionDiskVersionSources;
    if (versionSources != null) {
      vs = (VersionSource)versionSources.get(r);
      if (vs != null) {
        this.isRemoteVersionSource = !vs.equals(r.getDiskStore()
            .getDiskStoreID());
        return (this.versionSource = vs);
      }
    }
    // version source not set in proxy for some reason?
    final LogWriterI18n logger = tx.getTxMgr().getLogger();
    logger.convertToLogWriter().warning(
        "Missing VersionSource for " + toString());
    // fallback to local diskId
    this.isRemoteVersionSource = false;
    return (this.versionSource = r.getDiskStore().getDiskStoreID());
  }

  public final boolean isRemoteVersionSource() {
    return this.isRemoteVersionSource;
  }

  public void commitPendingTXOps() {
    // first apply into TXRegionState and then commit it if required
    applyPendingTXOps();
    // initialize the base event offsets
    final TXState tx = getTXState();
    tx.initBaseEventOffsetsForCommit();
    // then commit to region
    lock();
    try {
      final LockingPolicy lockPolicy = tx.getLockingPolicy();
      final TransactionObserver observer = tx.getObserver();
      final LogWriterI18n logger = tx.getTxMgr().getLogger();
      final Collection txess = this.entryMods.values();
        applyChangesStart(this.region);
        // we don't expect any conflicts etc. here due to pre-GII state
        for (Object entry : txess) {
          if (entry instanceof TXEntryState) {
            TXEntryState txes = (TXEntryState)entry;
            try {
              tx.commitEntryPhase1(txes, lockPolicy, null, observer);
            } catch (Throwable t) {
              handleGIICommitException(t, logger);
            }
          }
        }

      // now phase2, then cleanup
      List eventsToFree = null;
      try {
        final boolean logFine = TXStateProxy.LOG_FINE;
        boolean firstTime = true;
        EntryEventImpl cbEvent = tx.getProxy().newEntryEventImpl();
        GemFireCacheImpl cache = GemFireCacheImpl.getExisting();
        final boolean reuseEV = !(cache.getOffHeapStore() != null && cache
            .isGFXDSystem());
        if (!reuseEV) {
          eventsToFree = new ArrayList();
        }
        for (Object entry : txess) {
          if (entry instanceof TXEntryState) {
            TXEntryState txes = (TXEntryState)entry;
            try {
              cbEvent = tx.commitEntryPhase2(txes, cbEvent, eventsToFree,
                  firstTime, reuseEV, logger, logFine);
              if (cbEvent == null) {
                break;
              }
              firstTime = false;
            } catch (Throwable t) {
              handleGIICommitException(t, logger);
            } finally {
              if (cbEvent != null) {
                if (reuseEV) {
                  cbEvent.release();
                }
                else {
                  eventsToFree.add(cbEvent);
                }
              }
            }
          }
        }
      } finally {
        try {
          tx.cleanupTXRS(new TXRegionState[] { this }, lockPolicy, LockMode.EX,
              true, false, observer);
        } catch (Throwable t) {
          handleGIICommitException(t, logger);
        }
        if (observer != null) {
          observer.afterIndividualCommit(tx.getProxy(), null);
        }
        if (eventsToFree != null) {
          for (EntryEventImpl ev : eventsToFree) {
            ev.release();
          }
        }
      }
    } finally {
      unlock();
    }
  }

  void handleGIICommitException(Throwable t, final LogWriterI18n logger) {
    Error err;
    if (t instanceof Error && SystemFailure.isJVMFailureError(err = (Error)t)) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    }
    logger.warning(LocalizedStrings.DEBUG,
        "Unexpected exception in TX GII commit", t);
  }

  final void applyChangesStart(LocalRegion r) {
    try {
      r.txLRUStart();
    } catch (RegionDestroyedException ex) {
      // Region was destroyed out from under us. So act as if the region
      // destroy happened right after the commit. We act this way by doing
      // nothing.
    } catch (BucketMovedException ex) {
      // Region was destroyed out from under us. So act as if the region
      // destroy happened right after the commit. We act this way by doing
      // nothing.
    } catch (CancelException ex) {
      // cache was closed out from under us; after conflict checking
      // passed. So do nothing.
    }
  }

  final void applyChangesEnd(LocalRegion r, boolean commit) {
    try {
      /*
      try {
        if (this.uaMods != null) {
          Iterator it = this.uaMods.entrySet().iterator();
          while (it.hasNext()) {
            Map.Entry me = (Map.Entry)it.next();
            Object eKey = me.getKey();
            TXEntryUserAttrState txes = (TXEntryUserAttrState)me.getValue();
            txes.applyChanges(r, eKey);
          }
        }
      } finally {
        r.txLRUEnd();
      }
      */
      r.txLRUEnd(commit);
    } catch (RegionDestroyedException ex) {
      // Region was destroyed out from under us. So act as if the region
      // destroy happened right after the commit. We act this way by doing
      // nothing.
    } catch (BucketMovedException ex) {
      // Region was destroyed out from under us. So act as if the region
      // destroy happened right after the commit. We act this way by doing
      // nothing.
    } catch (CancelException ex) {
      // cache was closed out from under us; after conflict checking
      // passed. So do nothing.
    }
  }

  final void lockPendingGII() {
    if (!this.pendingGIITXLocked) {
      this.pendingGIITXLocked = this.region.getImageState()
          .lockPendingTXRegionStates(false, false);
    }
  }

  final void unlockPendingGII() {
    if (this.pendingGIITXLocked) {
      this.pendingGIITXLocked = false;
      unlockGII();
    }
  }

  final TransactionException cleanup(final LockingPolicy lockPolicy,
      final LockMode writeMode, final Boolean forCommit,
      final boolean removeFromList, final TransactionException te) {
    final boolean rollback = (forCommit != null && !forCommit.booleanValue());
    final LocalRegion rgn = this.region;
    final TXState tx = getTXState();
    this.tmpEx = te;
    // check for GII in progress for the region
    if (this.pendingTXOps != null && this.pendingGIITXLocked) {
      // set the order for commit/rollback
      rgn.getImageState().setTXOrderForFinish(this);
    }
    else if (this.entryMods.size() > 0) {
      // cleanup indexes before releasing the locks
      // keep going even in case of exceptions
      try {
        tx.getProxy().updateIndexes(rollback, this.transactionalIndexInfo,
            this.toBeReinstatedIndexesInfo, this.unaffectedIndexInfo);
      } catch (Throwable t) {
        Error err;
        if (t instanceof Error && SystemFailure.isJVMFailureError(
            err = (Error)t)) {
          SystemFailure.initiateFailure(err);
          // If this ever returns, rethrow the error. We're poisoned
          // now, so don't let this thread continue.
          throw err;
        }
        this.tmpEx = TXState.processCleanupException(t, this.tmpEx);
      }
      final TXId txId = tx.txId;
      final LockMode readMode = lockPolicy.getReadLockMode();
      this.numChanges = 0;
      this.entryMods.forEachValue(new TObjectProcedure() {
        public final boolean execute(final Object obj) {
          if (obj instanceof TXEntryState) {
            final TXEntryState txes = (TXEntryState)obj;
            try {
              // Now release all entry locks and for rollback also cleanup lock
              // entries created during operations
              final RegionEntry entry = txes.getUnderlyingRegionEntry();
              // remove the entry from map in case it was added due to put for
              // locking and we have to rollback
              if (entry != null && txes.entryCreatedForLock()
                  && (rollback || (!txes.hasPendingValue() && entry
                      .isDestroyedOrRemoved()))) {
                rgn.entries.removeEntry(entry.getKey(), entry, false);
              }
              if (txes.isDirty()) {
                numChanges++;
              }
              txes.cleanup(tx, rgn, lockPolicy, writeMode, removeFromList,
                  false, forCommit);
            } catch (Throwable t) {
              Error err;
              if (t instanceof Error && SystemFailure.isJVMFailureError(
                  err = (Error)t)) {
                SystemFailure.initiateFailure(err);
                // If this ever returns, rethrow the error. We're poisoned
                // now, so don't let this thread continue.
                throw err;
              }
              tmpEx = TXState.processCleanupException(t, tmpEx);
            }
            return true;
          }
          else {
            // case of read locked entry
            lockPolicy.releaseLock((AbstractRegionEntry)obj, readMode, txId,
                false, rgn);
            return true;
          }
        }
      });
    }
    this.transactionalIndexInfo = null;
    this.toBeReinstatedIndexesInfo = null;
    this.unaffectedIndexInfo = null;
    if (this.expiryReadLock != null) {
      this.expiryReadLock.unlock();
    }
    this.isValid = false;
    return this.tmpEx;
  }

  public final int getFinishOrder() {
    return this.finishOrder;
  }

  void processPendingExpires() {
    if (this.expiryReadLock != null) {
      this.region.processPendingExpires();
    }
  }

  int getChanges() {
    this.tmpEntryCount = 0;
    this.entryMods.forEachValue(new TObjectProcedure() {
      @Override
      public final boolean execute(final Object txes) {
        if (txes instanceof TXEntryState && ((TXEntryState)txes).isDirty()) {
          tmpEntryCount++;
        }
        return true;
      }
    });
    /*
    if (this.uaMods != null) {
      changes += this.uaMods.size();
    }
    */
    return this.tmpEntryCount;
  }

  @Override
  public final void lock() {
    super.lock();
    if (ExclusiveSharedSynchronizer.TRACE_LOCK_COMPACT) {
      final LogWriterI18n logger = this.region.getLogWriterI18n();
      logger.info(LocalizedStrings.DEBUG, "acquired lock on " + toString());
    }
  }

  @Override
  public final void unlock() {
    super.unlock();
    if (ExclusiveSharedSynchronizer.TRACE_LOCK_COMPACT) {
      final LogWriterI18n logger = this.region.getLogWriterI18n();
      logger.info(LocalizedStrings.DEBUG, "released lock on " + toString());
    }
  }

  @Override
  public final int hashCode() {
    return this.region.hashCode();
  }

  @Override
  public final String toString() {
    return ArrayUtils.objectRefString(this) + ",valid=" + this.isValid
        + ",dataRegion=" + this.region.getFullPath() + ','
        + this.txState.txId.shortToString() + ",TXState@0x"
        + Integer.toHexString(System.identityHashCode(txState));
  }
}