/*
* 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.ignite.internal.processors.cache;
import java.util.Collection;
import java.util.Set;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cache.eviction.EvictionFilter;
import org.apache.ignite.cache.eviction.EvictionPolicy;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersionManager;
import org.apache.ignite.internal.util.GridBusyLock;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteUuid;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.events.EventType.EVT_CACHE_ENTRY_EVICTED;
/**
*
*/
public class GridCacheEvictionManager extends GridCacheManagerAdapter implements CacheEvictionManager {
/** Eviction policy. */
private EvictionPolicy plc;
/** Eviction filter. */
private EvictionFilter filter;
/** Policy enabled. */
private boolean plcEnabled;
/** Busy lock. */
private final GridBusyLock busyLock = new GridBusyLock();
/** Stopped flag. */
private boolean stopped;
/** First eviction flag. */
private volatile boolean firstEvictWarn;
/** {@inheritDoc} */
@Override public void start0() throws IgniteCheckedException {
CacheConfiguration cfg = cctx.config();
plc = cctx.isNear() ? cfg.getNearConfiguration().getNearEvictionPolicy() : cfg.getEvictionPolicy();
plcEnabled = plc != null;
filter = cfg.getEvictionFilter();
if (log.isDebugEnabled())
log.debug("Eviction manager started on node: " + cctx.nodeId());
}
/** {@inheritDoc} */
@Override protected void onKernalStop0(boolean cancel) {
super.onKernalStop0(cancel);
busyLock.block();
try {
if (log.isDebugEnabled())
log.debug("Eviction manager stopped on node: " + cctx.nodeId());
}
finally {
stopped = true;
busyLock.unblock();
}
}
/**
* @return {@code True} if entered busy.
*/
private boolean enterBusy() {
if (!busyLock.enterBusy())
return false;
if (stopped) {
busyLock.leaveBusy();
return false;
}
return true;
}
/**
* @param cache Cache from which to evict entry.
* @param entry Entry to evict.
* @param obsoleteVer Obsolete version.
* @param filter Filter.
* @param explicit If eviction is initiated by user.
* @return {@code true} if entry has been evicted.
* @throws IgniteCheckedException If failed to evict entry.
*/
private boolean evict0(
GridCacheAdapter cache,
GridCacheEntryEx entry,
GridCacheVersion obsoleteVer,
@Nullable CacheEntryPredicate[] filter,
boolean explicit
) throws IgniteCheckedException {
assert cache != null;
assert entry != null;
assert obsoleteVer != null;
boolean recordable = cctx.events().isRecordable(EVT_CACHE_ENTRY_EVICTED);
CacheObject oldVal = recordable ? entry.rawGet() : null;
boolean hasVal = recordable && entry.hasValue();
boolean evicted = entry.evictInternal(obsoleteVer, filter, false);
if (evicted) {
// Remove manually evicted entry from policy.
if (explicit && plcEnabled)
notifyPolicy(entry);
cache.removeEntry(entry);
if (cache.configuration().isStatisticsEnabled())
cache.metrics0().onEvict();
if (recordable)
cctx.events().addEvent(entry.partition(), entry.key(), cctx.nodeId(), (IgniteUuid)null, null,
EVT_CACHE_ENTRY_EVICTED, null, false, oldVal, hasVal, null, null, null, false);
if (log.isDebugEnabled())
log.debug("Entry was evicted [entry=" + entry + ", localNode=" + cctx.nodeId() + ']');
}
else {
if (log.isDebugEnabled())
log.debug("Entry was not evicted [entry=" + entry + ", localNode=" + cctx.nodeId() + ']');
}
return evicted;
}
/** {@inheritDoc} */
@Override public void touch(IgniteTxEntry txEntry, boolean loc) {
if (!plcEnabled)
return;
if (!loc) {
if (cctx.isNear())
return;
}
GridCacheEntryEx e = txEntry.cached();
if (e.detached() || e.isInternal())
return;
try {
if (e.markObsoleteIfEmpty(null) || e.obsolete())
e.context().cache().removeEntry(e);
}
catch (IgniteCheckedException ex) {
U.error(log, "Failed to evict entry from cache: " + e, ex);
}
notifyPolicy(e);
}
/** {@inheritDoc} */
@Override public void touch(GridCacheEntryEx e, AffinityTopologyVersion topVer) {
if (e.detached() || e.isInternal())
return;
try {
if (e.markObsoleteIfEmpty(null) || e.obsolete())
e.context().cache().removeEntry(e);
}
catch (IgniteCheckedException ex) {
U.error(log, "Failed to evict entry from cache: " + e, ex);
}
if (!plcEnabled)
return;
if (!enterBusy())
return;
try {
if (log.isDebugEnabled())
log.debug("Touching entry [entry=" + e + ", localNode=" + cctx.nodeId() + ']');
notifyPolicy(e);
}
finally {
busyLock.leaveBusy();
}
}
/**
* Warns on first eviction.
*/
private void warnFirstEvict() {
// Do not move warning output to synchronized block (it causes warning in IDE).
synchronized (this) {
if (firstEvictWarn)
return;
firstEvictWarn = true;
}
U.warn(log, "Evictions started (cache may have reached its capacity)." +
" You may wish to increase 'maxSize' on eviction policy being used for cache: " + cctx.name(),
"Evictions started (cache may have reached its capacity): " + cctx.name());
}
/** {@inheritDoc} */
@Override public boolean evict(@Nullable GridCacheEntryEx entry, @Nullable GridCacheVersion obsoleteVer,
boolean explicit, @Nullable CacheEntryPredicate[] filter) throws IgniteCheckedException {
if (entry == null)
return true;
// Do not evict internal entries.
if (entry.key() instanceof GridCacheInternal)
return false;
if (!cctx.isNear() && !explicit && !firstEvictWarn)
warnFirstEvict();
if (obsoleteVer == null)
obsoleteVer = cctx.versions().next();
// Do not touch entry if not evicted:
// 1. If it is call from policy, policy tracks it on its own.
// 2. If it is explicit call, entry is touched on tx commit.
return evict0(cctx.cache(), entry, obsoleteVer, filter, explicit);
}
/** {@inheritDoc} */
@Override public void batchEvict(Collection<?> keys, @Nullable GridCacheVersion obsoleteVer)
throws IgniteCheckedException {
boolean recordable = cctx.events().isRecordable(EVT_CACHE_ENTRY_EVICTED);
GridCacheAdapter cache = cctx.cache();
// Get all participating entries to avoid deadlock.
for (Object k : keys) {
KeyCacheObject cacheKey = cctx.toCacheKeyObject(k);
GridCacheEntryEx entry = cache.peekEx(cacheKey);
if (entry != null && entry.evictInternal(GridCacheVersionManager.EVICT_VER, null, false)) {
if (plcEnabled)
notifyPolicy(entry);
if (recordable)
cctx.events().addEvent(entry.partition(), entry.key(), cctx.nodeId(), (IgniteUuid)null, null,
EVT_CACHE_ENTRY_EVICTED, null, false, entry.rawGet(), entry.hasValue(), null, null, null,
false);
}
}
}
/**
* @param e Entry to notify eviction policy.
*/
@SuppressWarnings({"IfMayBeConditional", "RedundantIfStatement"})
private void notifyPolicy(GridCacheEntryEx e) {
assert plcEnabled;
assert plc != null;
assert !e.isInternal() : "Invalid entry for policy notification: " + e;
if (log.isDebugEnabled())
log.debug("Notifying eviction policy with entry: " + e);
if (filter == null || filter.evictAllowed(e.wrapLazyValue(cctx.keepBinary())))
plc.onEntryAccessed(e.obsoleteOrDeleted(), e.wrapEviction());
}
/** {@inheritDoc} */
@Override public void printMemoryStats() {
X.println(">>> ");
X.println(">>> Eviction manager memory stats [igniteInstanceName=" + cctx.igniteInstanceName() +
", cache=" + cctx.name() + ']');
}
}