| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.geode.internal.cache.map; |
| |
| import org.apache.logging.log4j.Logger; |
| |
| import org.apache.geode.cache.DiskAccessException; |
| import org.apache.geode.cache.query.internal.index.IndexManager; |
| import org.apache.geode.internal.cache.EntryEventImpl; |
| import org.apache.geode.internal.cache.InternalRegion; |
| import org.apache.geode.internal.cache.RegionEntry; |
| import org.apache.geode.internal.cache.Token; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| |
| public abstract class AbstractRegionMapPut { |
| private static final Logger logger = LogService.getLogger(); |
| |
| private final InternalRegion owner; |
| private final FocusedRegionMap focusedRegionMap; |
| private final EntryEventImpl event; |
| private final boolean ownerInitialized; |
| |
| private boolean clearOccurred; |
| private long lastModifiedTime; |
| private RegionEntry regionEntry; |
| private boolean create; |
| private boolean completed; |
| |
| public AbstractRegionMapPut(FocusedRegionMap focusedRegionMap, InternalRegion owner, |
| EntryEventImpl event) { |
| this.focusedRegionMap = focusedRegionMap; |
| this.owner = owner; |
| this.event = event; |
| this.ownerInitialized = owner.isInitialized(); |
| } |
| |
| protected boolean isOwnerInitialized() { |
| return ownerInitialized; |
| } |
| |
| protected boolean isClearOccurred() { |
| return clearOccurred; |
| } |
| |
| protected void setClearOccurred(boolean v) { |
| clearOccurred = v; |
| } |
| |
| protected long getLastModifiedTime() { |
| return lastModifiedTime; |
| } |
| |
| protected void setLastModifiedTime(long v) { |
| lastModifiedTime = v; |
| } |
| |
| protected RegionEntry getRegionEntry() { |
| return regionEntry; |
| } |
| |
| private void setRegionEntry(RegionEntry v) { |
| regionEntry = v; |
| } |
| |
| /** |
| * @return true if put created a new entry; false if it updated an existing one. |
| */ |
| protected boolean isCreate() { |
| return create; |
| } |
| |
| private void setCreate(boolean v) { |
| create = v; |
| } |
| |
| protected EntryEventImpl getEvent() { |
| return event; |
| } |
| |
| protected boolean isCompleted() { |
| return completed; |
| } |
| |
| private void setCompleted(boolean b) { |
| completed = b; |
| } |
| |
| protected InternalRegion getOwner() { |
| return owner; |
| } |
| |
| protected FocusedRegionMap getRegionMap() { |
| return focusedRegionMap; |
| } |
| |
| protected abstract boolean isOnlyExisting(); |
| |
| protected abstract boolean entryExists(RegionEntry regionEntry); |
| |
| protected abstract void serializeNewValueIfNeeded(); |
| |
| protected void runWhileLockedForCacheModification(Runnable r) { |
| final boolean locked = getOwner().lockWhenRegionIsInitializing(); |
| try { |
| r.run(); |
| } finally { |
| if (locked) { |
| getOwner().unlockWhenRegionIsInitializing(); |
| } |
| } |
| } |
| |
| protected abstract void setOldValueForDelta(); |
| |
| protected abstract void setOldValueInEvent(); |
| |
| protected abstract void unsetOldValueForDelta(); |
| |
| protected abstract boolean checkPreconditions(); |
| |
| protected abstract void invokeCacheWriter(); |
| |
| protected abstract void createOrUpdateEntry(); |
| |
| /** |
| * Returns true if getRegionEntry should be removed from the map |
| * because the put did not complete. |
| * Precondition: isCreate() |
| */ |
| protected abstract boolean shouldCreatedEntryBeRemoved(); |
| |
| /** |
| * Called after the put is done but before setCompleted(true) |
| * is called. |
| * Note that the RegionEntry that was modified by the put |
| * is still synchronized when this is called. |
| */ |
| protected abstract void doBeforeCompletionActions(); |
| |
| /** |
| * Called after the put is done. |
| * Always called, even if the put failed. |
| * Note that the RegionEntry that was modified by the put |
| * is no longer synchronized when this is called. |
| * |
| * @param disabledEviction true if caller previously disabled eviction |
| */ |
| protected abstract void doAfterCompletionActions(boolean disabledEviction); |
| |
| /** |
| * @return regionEntry if put completed, otherwise null. |
| */ |
| public RegionEntry put() { |
| serializeNewValueIfNeeded(); |
| runWhileLockedForCacheModification(this::doPut); |
| if (isCompleted()) { |
| return getRegionEntry(); |
| } else { |
| return null; |
| } |
| } |
| |
| private void doPut() { |
| final boolean disabledEviction = getRegionMap().disableLruUpdateCallback(); |
| try { |
| doWithIndexInUpdateMode(this::doPutRetryingIfNeeded); |
| } catch (DiskAccessException dae) { |
| getOwner().handleDiskAccessException(dae); |
| throw dae; |
| } finally { |
| doAfterCompletionActions(disabledEviction); |
| } |
| } |
| |
| private void doWithIndexInUpdateMode(Runnable r) { |
| final IndexManager oqlIndexManager = getInitializedIndexManager(); |
| if (oqlIndexManager != null) { |
| try { |
| r.run(); |
| } finally { |
| oqlIndexManager.countDownIndexUpdaters(); |
| } |
| } else { |
| r.run(); |
| } |
| } |
| |
| private IndexManager getInitializedIndexManager() { |
| final IndexManager oqlIndexManager = getOwner().getIndexManager(); |
| if (oqlIndexManager != null) { |
| oqlIndexManager.waitForIndexInit(); |
| } |
| return oqlIndexManager; |
| } |
| |
| private void doPutRetryingIfNeeded() { |
| do { |
| if (!findAndSaveExistingEntry()) { |
| return; |
| } |
| createNewEntryIfNeeded(); |
| } while (!addRegionEntryToMapAndDoPut()); |
| } |
| |
| /** |
| * If an existing one is found, save it by calling setRegionEntry. |
| * |
| * @return false if an existing entry was not found and this put requires |
| * an existing one; otherwise returns true. |
| */ |
| private boolean findAndSaveExistingEntry() { |
| RegionEntry re = getRegionMap().getEntry(getEvent()); |
| if (isOnlyExisting() && !entryExists(re)) { |
| setRegionEntry(null); |
| return false; |
| } |
| setRegionEntry(re); |
| return true; |
| } |
| |
| private void createNewEntryIfNeeded() { |
| setCreate(getRegionEntry() == null); |
| if (isCreate()) { |
| final Object key = getEvent().getKey(); |
| RegionEntry newEntry = |
| getRegionMap().getEntryFactory().createEntry(getOwner(), key, Token.REMOVED_PHASE1); |
| setRegionEntry(newEntry); |
| } |
| } |
| |
| /** |
| * @return false if caller should retry |
| */ |
| private boolean addRegionEntryToMapAndDoPut() { |
| synchronized (getRegionEntry()) { |
| putIfAbsentNewEntry(); |
| return doPutOnRegionEntryInMap(); |
| } |
| } |
| |
| private void putIfAbsentNewEntry() { |
| if (isCreate()) { |
| RegionEntry oldRe = getRegionMap().putEntryIfAbsent(getEvent().getKey(), getRegionEntry()); |
| if (oldRe != null) { |
| setCreate(false); |
| setRegionEntry(oldRe); |
| } |
| } |
| } |
| |
| /** |
| * @return false if caller should retry because entry is no longer in the map |
| */ |
| private boolean doPutOnRegionEntryInMap() { |
| synchronized (getRegionEntry()) { |
| if (isRegionEntryRemoved()) { |
| return false; |
| } |
| doPutOnSynchronizedRegionEntry(); |
| return true; |
| } |
| } |
| |
| private void doPutOnSynchronizedRegionEntry() { |
| setOldValueForDelta(); |
| try { |
| setOldValueInEvent(); |
| doPutIfPreconditionsSatisified(); |
| } finally { |
| unsetOldValueForDelta(); |
| if (isCreate() && shouldCreatedEntryBeRemoved()) { |
| getRegionMap().removeEntry(getEvent().getKey(), getRegionEntry(), false); |
| } |
| } |
| } |
| |
| private void doPutIfPreconditionsSatisified() { |
| if (!checkPreconditions()) { |
| return; |
| } |
| invokeCacheWriter(); |
| runWithIndexUpdatingInProgress(this::doPutAndDeliverEvent); |
| } |
| |
| private void doPutAndDeliverEvent() { |
| createOrUpdateEntry(); |
| doBeforeCompletionActions(); |
| setCompleted(true); |
| } |
| |
| private void runWithIndexUpdatingInProgress(Runnable r) { |
| notifyIndex(true); |
| try { |
| r.run(); |
| } finally { |
| notifyIndex(false); |
| } |
| } |
| |
| private void notifyIndex(boolean isUpdating) { |
| if (getOwner().getIndexMaintenanceSynchronous()) { |
| getRegionEntry().setUpdateInProgress(isUpdating); |
| } |
| } |
| |
| /** |
| * @return true if the entry is in the final stage of removal |
| */ |
| private boolean isRegionEntryRemoved() { |
| final RegionEntry re = getRegionEntry(); |
| if (re.isRemovedPhase2()) { |
| getOwner().getCachePerfStats().incRetries(); |
| getRegionMap().getEntryMap().remove(getEvent().getKey(), re); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |