/*
* 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.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.lang.IgniteUuid;
import org.jetbrains.annotations.Nullable;
import org.jsr166.ConcurrentHashMap8;
import static org.apache.ignite.events.EventType.EVT_CACHE_ENTRY_CREATED;
import static org.apache.ignite.events.EventType.EVT_CACHE_ENTRY_DESTROYED;
/**
* Implementation of concurrent cache map.
*/
public abstract class GridCacheConcurrentMapImpl implements GridCacheConcurrentMap {
/** Default load factor. */
private static final float DFLT_LOAD_FACTOR = 0.75f;
/** Default concurrency level. */
private static final int DFLT_CONCUR_LEVEL = Runtime.getRuntime().availableProcessors() * 2;
/** Internal map. */
private final ConcurrentMap<KeyCacheObject, GridCacheMapEntry> map;
/** Map entry factory. */
private final GridCacheMapEntryFactory factory;
/** Cache context. */
private final GridCacheContext ctx;
/**
* Creates a new, empty map with the specified initial
* capacity.
*
* @param ctx Cache context.
* @param factory Entry factory.
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements.
* @throws IllegalArgumentException if the initial capacity is
* negative.
*/
public GridCacheConcurrentMapImpl(GridCacheContext ctx, GridCacheMapEntryFactory factory, int initialCapacity) {
this(ctx, factory, initialCapacity, DFLT_LOAD_FACTOR, DFLT_CONCUR_LEVEL);
}
/**
* Creates a new, empty map with the specified initial
* capacity, load factor and concurrency level.
*
* @param ctx Cache context.
* @param factory Entry factory.
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements.
* @param loadFactor the load factor threshold, used to control resizing.
* Resizing may be performed when the average number of elements per
* bin exceeds this threshold.
* @param concurrencyLevel the estimated number of concurrently
* updating threads. The implementation performs internal sizing
* to try to accommodate this many threads.
* @throws IllegalArgumentException if the initial capacity is
* negative or the load factor or concurrencyLevel are
* non-positive.
*/
public GridCacheConcurrentMapImpl(
GridCacheContext ctx,
GridCacheMapEntryFactory factory,
int initialCapacity,
float loadFactor,
int concurrencyLevel
) {
this.ctx = ctx;
this.factory = factory;
map = new ConcurrentHashMap8<>(initialCapacity, loadFactor, concurrencyLevel);
}
/** {@inheritDoc} */
@Nullable @Override public GridCacheMapEntry getEntry(KeyCacheObject key) {
return map.get(key);
}
/** {@inheritDoc} */
@Nullable @Override public GridCacheMapEntry putEntryIfObsoleteOrAbsent(final AffinityTopologyVersion topVer,
KeyCacheObject key,
final boolean create,
final boolean touch) {
GridCacheMapEntry cur = null;
GridCacheMapEntry created = null;
GridCacheMapEntry created0 = null;
GridCacheMapEntry doomed = null;
boolean done = false;
boolean reserved = false;
int sizeChange = 0;
try {
while (!done) {
GridCacheMapEntry entry = map.get(key);
created = null;
doomed = null;
if (entry == null) {
if (create) {
if (created0 == null) {
if (!reserved) {
if (!reserve())
return null;
reserved = true;
}
created0 = factory.create(ctx, topVer, key);
}
cur = created = created0;
done = map.putIfAbsent(created.key(), created) == null;
}
else
done = true;
}
else {
if (entry.obsolete()) {
doomed = entry;
if (create) {
if (created0 == null) {
if (!reserved) {
if (!reserve())
return null;
reserved = true;
}
created0 = factory.create(ctx, topVer, key);
}
cur = created = created0;
done = map.replace(entry.key(), doomed, created);
}
else
done = map.remove(entry.key(), doomed);
}
else {
cur = entry;
done = true;
}
}
}
sizeChange = 0;
if (doomed != null) {
synchronized (doomed) {
if (!doomed.deleted())
sizeChange--;
}
if (ctx.events().isRecordable(EVT_CACHE_ENTRY_DESTROYED))
ctx.events().addEvent(doomed.partition(),
doomed.key(),
ctx.localNodeId(),
(IgniteUuid)null,
null,
EVT_CACHE_ENTRY_DESTROYED,
null,
false,
null,
false,
null,
null,
null,
true);
}
if (created != null) {
sizeChange++;
if (ctx.events().isRecordable(EVT_CACHE_ENTRY_CREATED))
ctx.events().addEvent(created.partition(),
created.key(),
ctx.localNodeId(),
(IgniteUuid)null,
null,
EVT_CACHE_ENTRY_CREATED,
null,
false,
null,
false,
null,
null,
null,
true);
if (touch)
ctx.evicts().touch(
cur,
topVer);
}
assert Math.abs(sizeChange) <= 1;
return cur;
}
finally {
if (reserved)
release(sizeChange, cur);
else {
if (sizeChange != 0) {
assert sizeChange == -1;
decrementPublicSize(cur);
}
}
}
}
/**
*
*/
protected boolean reserve() {
return true;
}
/**
*
*/
protected void release() {
// No-op.
}
/**
* @param sizeChange Size delta.
* @param e Map entry.
*/
protected void release(int sizeChange, GridCacheEntryEx e) {
if (sizeChange == 1)
incrementPublicSize(e);
else if (sizeChange == -1)
decrementPublicSize(e);
}
/** {@inheritDoc} */
@Override public boolean removeEntry(final GridCacheEntryEx entry) {
boolean removed = map.remove(entry.key(), entry);
if (removed) {
if (ctx.events().isRecordable(EVT_CACHE_ENTRY_DESTROYED))
// Event notification.
ctx.events().addEvent(entry.partition(), entry.key(), ctx.localNodeId(), (IgniteUuid)null, null,
EVT_CACHE_ENTRY_DESTROYED, null, false, null, false, null, null, null, false);
synchronized (entry) {
if (!entry.deleted())
decrementPublicSize(entry);
}
}
return removed;
}
/** {@inheritDoc} */
@Override public int size() {
return map.size();
}
/** {@inheritDoc} */
@Override public Set<KeyCacheObject> keySet(final CacheEntryPredicate... filter) {
final IgnitePredicate<KeyCacheObject> p = new IgnitePredicate<KeyCacheObject>() {
@Override public boolean apply(KeyCacheObject key) {
GridCacheMapEntry entry = map.get(key);
return entry != null && entry.visitable(filter);
}
};
return new AbstractSet<KeyCacheObject>() {
@Override public Iterator<KeyCacheObject> iterator() {
return F.iterator0(map.keySet(), true, p);
}
@Override public int size() {
return F.size(iterator());
}
@Override public boolean contains(Object o) {
if (!(o instanceof KeyCacheObject))
return false;
return map.keySet().contains(o) && p.apply((KeyCacheObject)o);
}
};
}
/** {@inheritDoc} */
@Override public Collection<GridCacheMapEntry> entries(final CacheEntryPredicate... filter) {
final IgnitePredicate<GridCacheMapEntry> p = new IgnitePredicate<GridCacheMapEntry>() {
@Override public boolean apply(GridCacheMapEntry entry) {
return entry.visitable(filter);
}
};
return F.viewReadOnly(map.values(), F.<GridCacheMapEntry>identity(), p);
}
/** {@inheritDoc} */
@Override public Collection<GridCacheMapEntry> allEntries(final CacheEntryPredicate... filter) {
final IgnitePredicate<GridCacheMapEntry> p = new IgnitePredicate<GridCacheMapEntry>() {
@Override public boolean apply(GridCacheMapEntry entry) {
return F.isAll(entry, filter);
}
};
return F.viewReadOnly(map.values(), F.<GridCacheMapEntry>identity(), p);
}
/** {@inheritDoc} */
@Override public Set<GridCacheMapEntry> entrySet(final CacheEntryPredicate... filter) {
final IgnitePredicate<GridCacheMapEntry> p = new IgnitePredicate<GridCacheMapEntry>() {
@Override public boolean apply(GridCacheMapEntry entry) {
return entry.visitable(filter);
}
};
return new AbstractSet<GridCacheMapEntry>() {
@Override public Iterator<GridCacheMapEntry> iterator() {
return F.iterator0(map.values(), true, p);
}
@Override public int size() {
return F.size(iterator());
}
@Override public boolean contains(Object o) {
if (!(o instanceof GridCacheMapEntry))
return false;
GridCacheMapEntry entry = (GridCacheMapEntry)o;
return entry.equals(map.get(entry.key())) && p.apply(entry);
}
};
}
}