/* * JBoss, Home of Professional Open Source * Copyright 2011 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.atomic; import org.infinispan.AdvancedCache; import org.infinispan.container.entries.CacheEntry; import org.infinispan.container.entries.DeltaAwareCacheEntry; import org.infinispan.context.Flag; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * A layer of indirection around an {@link FineGrainedAtomicMap} to provide consistency and isolation for concurrent readers * while writes may also be going on. The techniques used in this implementation are very similar to the lock-free * reader MVCC model used in the {@link org.infinispan.container.entries.MVCCEntry} implementations for the core data * container, which closely follow software transactional memory approaches to dealing with concurrency. * <br /><br /> * Typically proxies are only created by the {@link AtomicMapLookup} helper, and would not be created by end-user code * directly. * * @author Manik Surtani * @author Vladimir Blagojevic * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values * @see AtomicHashMap * @since 5.1 */ public class FineGrainedAtomicHashMapProxy<K, V> extends AtomicHashMapProxy<K, V> implements FineGrainedAtomicMap<K,V> { private static final Log log = LogFactory.getLog(FineGrainedAtomicHashMapProxy.class); private static final boolean trace = log.isTraceEnabled(); FineGrainedAtomicHashMapProxy(AdvancedCache<?, ?> cache, Object deltaMapKey) { super((AdvancedCache<Object,AtomicMap<K,V>>) cache, deltaMapKey); } @SuppressWarnings("unchecked") @Override protected AtomicHashMap<K, V> getDeltaMapForWrite() { CacheEntry lookedUpEntry = lookupEntryFromCurrentTransaction(); boolean lockedAndCopied = lookedUpEntry != null && lookedUpEntry.isChanged() && toMap(lookedUpEntry.getValue()).copied; if (lockedAndCopied) { return getDeltaMapForRead(); } else { // acquire WL boolean suppressLocks = flagContainer != null && flagContainer.hasFlag(Flag.SKIP_LOCKING); if (!suppressLocks && flagContainer != null) flagContainer.setFlags(Flag.FORCE_WRITE_LOCK); if (trace) { if (suppressLocks) log.trace("Skip locking flag used. Skipping locking."); else log.trace("Forcing write lock even for reads"); } // reinstate the flag if (suppressLocks) flagContainer.setFlags(Flag.SKIP_LOCKING); AtomicHashMap<K, V> map = getDeltaMapForRead(); boolean insertNewMap = map == null; // copy for write AtomicHashMap<K, V> copy = insertNewMap ? new AtomicHashMap<K, V>(true) : map.copy(); copy.initForWriting(); if (insertNewMap) { cache.put(deltaMapKey, copy); } return copy; } } @Override public Set<K> keySet() { AtomicHashMap<K, V> map = getDeltaMapForRead().copy(); Set<K> result = new HashSet<K>(keySetUncommitted()); if (map != null) { result.addAll(map.keySet()); } return result; } @SuppressWarnings("unchecked") private Set<K> keySetUncommitted() { DeltaAwareCacheEntry entry = lookupEntry(); return entry != null ? (Set<K>) entry.getUncommittedChages().keySet() : Collections.<K>emptySet(); } @Override public Collection<V> values() { AtomicHashMap<K, V> map = getDeltaMapForRead().copy(); Set<V> result = new HashSet<V>(valuesUncommitted()); if (map != null) { result.addAll(map.values()); } return result; } @SuppressWarnings("unchecked") private Collection<V> valuesUncommitted() { DeltaAwareCacheEntry entry = lookupEntry(); return entry != null ? (Collection<V>) entry.getUncommittedChages().values() : Collections.<V>emptySet(); } @Override public Set<Entry<K, V>> entrySet() { AtomicHashMap<K, V> map = getDeltaMapForRead().copy(); Set<Entry<K, V>> result = new HashSet<Entry<K, V>>(entrySetUncommitted()); if (map != null) { result.addAll(map.entrySet()); } return result; } @SuppressWarnings("unchecked") private Set<Entry<K, V>> entrySetUncommitted() { DeltaAwareCacheEntry entry = lookupEntry(); return (Set<Entry<K, V>>) (entry != null ? entry.getUncommittedChages().entrySet(): Collections.<V>emptySet()); } @Override public int size() { AtomicHashMap<K, V> map = getDeltaMapForRead(); int su = sizeUncommitted(); return map == null ? su : su + map.size(); } public int sizeUncommitted() { DeltaAwareCacheEntry entry = lookupEntry(); return entry != null ? entry.getUncommittedChages().size() : 0; } @Override public boolean isEmpty() { AtomicHashMap<K, V> map = getDeltaMapForRead(); return isEmptyUncommitted() && (map == null || map.isEmpty()); } private boolean isEmptyUncommitted() { DeltaAwareCacheEntry entry = lookupEntry(); return entry != null && entry.getUncommittedChages().isEmpty(); } @Override public boolean containsKey(Object key) { AtomicHashMap<K, V> map = getDeltaMapForRead(); return containsKeyUncommitted(key) || (map != null && map.containsKey(key)); } private boolean containsKeyUncommitted(Object key) { DeltaAwareCacheEntry entry = lookupEntry(); return entry != null && entry.getUncommittedChages().containsKey(key); } @Override public boolean containsValue(Object value) { AtomicHashMap<K, V> map = getDeltaMapForRead(); return containsValueUncommitted(value) || (map != null && map.containsValue(value)); } private boolean containsValueUncommitted(Object value) { DeltaAwareCacheEntry entry = lookupEntry(); return entry != null && entry.getUncommittedChages().containsValue(value); } @Override public V get(Object key) { V result = getUncommitted(key); if (result == null) { AtomicHashMap<K, V> map = getDeltaMapForRead(); result = map == null ? null : map.get(key); } return result; } @SuppressWarnings("unchecked") public V getUncommitted(Object key) { DeltaAwareCacheEntry entry = lookupEntry(); return entry != null ? (V)entry.getUncommittedChages().get(key): null; } // writers @Override public V put(K key, V value) { AtomicHashMap<K, V> deltaMapForWrite = null; try { startAtomic(); deltaMapForWrite = getDeltaMapForWrite(); V toReturn = deltaMapForWrite.put(key, value); invokeApplyDelta(deltaMapForWrite.getDelta()); return toReturn; } finally { endAtomic(); } } @Override public V remove(Object key) { AtomicHashMap<K, V> deltaMapForWrite = null; try { startAtomic(); deltaMapForWrite = getDeltaMapForWrite(); V toReturn = deltaMapForWrite.remove(key); invokeApplyDelta(deltaMapForWrite.getDelta()); return toReturn; } finally { endAtomic(); } } @Override public void putAll(Map<? extends K, ? extends V> m) { AtomicHashMap<K, V> deltaMapForWrite = null; try { startAtomic(); deltaMapForWrite = getDeltaMapForWrite(); deltaMapForWrite.putAll(m); invokeApplyDelta(deltaMapForWrite.getDelta()); } finally { endAtomic(); } } @Override public void clear() { AtomicHashMap<K, V> deltaMapForWrite = null; try { startAtomic(); deltaMapForWrite = getDeltaMapForWrite(); deltaMapForWrite.clear(); invokeApplyDelta(deltaMapForWrite.getDelta()); } finally { endAtomic(); } } private DeltaAwareCacheEntry lookupEntry() { CacheEntry entry = lookupEntryFromCurrentTransaction(); if (entry instanceof DeltaAwareCacheEntry) { return (DeltaAwareCacheEntry)entry; } else { return null; } } private void invokeApplyDelta(AtomicHashMapDelta delta) { Collection<?> keys = Collections.emptyList(); if (delta.hasClearOperation()) { // if it has clear op we need to lock all keys AtomicHashMap<?, ?> map = (AtomicHashMap<?, ?>) cache.get(deltaMapKey); if (map != null) { keys = new ArrayList(map.keySet()); } } else { keys = delta.getKeys(); } cache.applyDelta(deltaMapKey, delta, keys); } @Override public String toString() { StringBuilder sb = new StringBuilder("FineGrainedAtomicHashMapProxy{deltaMapKey="); sb.append(deltaMapKey); sb.append("}"); return sb.toString(); } }