| // Copyright 2014 The Bazel Authors. 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. |
| package com.google.devtools.build.lib.actions; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadHostile; |
| import java.util.Iterator; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentMap; |
| import javax.annotation.Nullable; |
| |
| /** |
| * A Multimap-like object that is actually a {@link ConcurrentMap} of {@code SmallSet}s to avoid |
| * the memory penalties of a {@code Multimap} while preserving concurrency guarantees, and |
| * retrieving a consistent "head" element. Operations are guaranteed to reflect a consistent view of |
| * a {@code SetMultimap}, although most methods are not implemented. |
| */ |
| final class ConcurrentMultimapWithHeadElement<K, V> { |
| private final ConcurrentMap<K, SmallSet<V>> map = Maps.newConcurrentMap(); |
| |
| /** |
| * Remove (key, val) pair from the multimap. If this removes the current 'head' element |
| * for a key, then another randomly chosen element becomes the current head. |
| * |
| * <p>Until the next (possibly concurrent) {@link #putAndGet}(key, val) call, {@link #get}(key) |
| * will never return val. |
| */ |
| void remove(K key, V val) { |
| SmallSet<V> entry = getEntry(key); |
| if (entry != null) { |
| entry.remove(val); |
| if (entry.get() == null) { |
| // Remove entry completely from map if dead. |
| map.remove(key, entry); |
| } |
| } |
| } |
| |
| /** |
| * Return some value val such that (key, val) is in the multimap. If there is always at least one |
| * entry for key in the multimap during the lifetime of this method call, it will not return null. |
| */ |
| @Nullable V get(K key) { |
| SmallSet<V> entry = getEntry(key); |
| return (entry != null) ? entry.get() : null; |
| } |
| |
| /** |
| * Adds (key, val) to the multimap. Returns the head element for key, either val or another |
| * already-stored value. |
| */ |
| V putAndGet(K key, V val) { |
| V result = null; |
| while (result == null) { |
| // If another thread concurrently removes the only remaining value from the entry, this |
| // putAndGet will return null, since the entry is about to be removed from the map. In that |
| // case, we obtain a fresh entry from the map and do the put on it. |
| result = getOrCreateEntry(key).putAndGet(val); |
| } |
| return result; |
| } |
| |
| /** |
| * Obtain the entry for key, adding it to the underlying map if no entry was previously present. |
| */ |
| private SmallSet<V> getOrCreateEntry(K key) { |
| SmallSet<V> entry = new SmallSet<V>(); |
| SmallSet<V> oldEntry = map.putIfAbsent(key, entry); |
| if (oldEntry != null) { |
| return oldEntry; |
| } |
| return entry; |
| } |
| |
| /** |
| * Obtain the entry for key, returning null if no entry was present in the underlying map. |
| */ |
| private SmallSet<V> getEntry(K key) { |
| return map.get(key); |
| } |
| |
| /** |
| * Clears the multimap. May not be called concurrently with any other methods. |
| */ |
| @ThreadHostile |
| void clear() { |
| map.clear(); |
| } |
| |
| /** |
| * Wrapper for a {@code #Set} that will probably have at most one element. Keeps the first element |
| * in a separate variable for fast reading/writing and to save space if more than one element is |
| * never written to this set. We always have the invariant that {@link #first} is null only if |
| * {@link #rest} is null. |
| */ |
| private static class SmallSet<T> { |
| /* |
| * What is this 'volatile' on first and where's the lock on the read path? |
| * |
| * Volatile is an alternative to locking that works only in very limited situations, such as |
| * simple field reads and writes. Writes from one thread to 'first' happen before reads from |
| * other threads. When used correctly, it can have the same correctness properties as a |
| * 'synchronized' but is much faster on most hardware. |
| * |
| * Here, volatile is used to eliminate locks on the read path. Since get() is merely fetching |
| * the contents of 'first', it meets the criteria for a safe volatile read. In the mutator |
| * methods, care is taken to write only correct values to 'first'; intermediate and incomplete |
| * values do not get written to the field. This means that whenever 'first' is replaced, it is |
| * immediately replaced with the next correct value. Therefore, it is a safe volatile write. |
| * |
| * Other more complex relationships that need to be maintained during the mutate are maintained |
| * with the Object monitor. Since they do not impact the read path (only 'first' matters), the |
| * lock is sufficient for writes and unnecessary for 'first' reads. |
| * |
| * Documentation on volatile: |
| * http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility |
| * (java.util.concurrent package docs) |
| */ |
| |
| private volatile T first = null; |
| private Set<T> rest = null; |
| |
| /* |
| * We may have a race where one thread tries to remove a small set from the map while another |
| * thread tries to add to it. If the second thread loses the race, it will add to a set that is |
| * no longer in the map. To prevent that, once a small set is ever empty, we mark it "dead" by |
| * setting {@code rest} to a {@code TOMBSTONE} value, and (and subsequently remove it from the |
| * map). No modifications to a set can happen after the {@code TOMBSTONE} value is set. Thus, |
| * the thread trying to add a new value to a set will fail, and knows to retrieve the entry anew |
| * from the map and try again. |
| */ |
| private static final ImmutableSet<Object> TOMBSTONE = ImmutableSet.of(); |
| |
| /** |
| * Return some value in the SmallSet. |
| * |
| * <p>If there is always at least one value in the SmallSet during the lifetime of this call, |
| * it will not return null, since by the invariant, {@link #first} must be non-null. |
| */ |
| private T get() { |
| return first; |
| } |
| |
| /** |
| * Adds val to the SmallSet. Returns some element of the SmallSet. |
| */ |
| private synchronized T putAndGet(T elt) { |
| Preconditions.checkNotNull(elt); |
| if (isDead()) { |
| return null; |
| } |
| if (elt.equals(first)) { |
| return first; |
| } |
| if (first == null) { |
| Preconditions.checkState(rest == null, elt); |
| first = elt; |
| return first; |
| } |
| if (rest == null) { |
| rest = Sets.newHashSet(); |
| } |
| rest.add(elt); |
| return first; |
| } |
| |
| /** |
| * Remove val from the SmallSet, if it is present. |
| */ |
| private synchronized void remove(T elt) { |
| Preconditions.checkNotNull(elt); |
| if (isDead()) { |
| return; |
| } |
| if (elt.equals(first)) { |
| // Normalize to enforce invariant "first is null only if rest is empty." |
| if (rest != null) { |
| Iterator<T> it = rest.iterator(); |
| first = it.next(); |
| it.remove(); |
| if (!it.hasNext()) { |
| rest = null; |
| } |
| } else { |
| first = null; |
| markDead(); |
| } |
| } else if ((rest != null) && rest.remove(elt) && rest.isEmpty()) { // side-effect: remove |
| rest = null; |
| } |
| } |
| |
| private boolean isDead() { |
| Preconditions.checkState(rest != TOMBSTONE || first == null, |
| "%s present in tombstoned SmallSet, but tombstoned SmallSets should be empty", first); |
| return rest == TOMBSTONE; |
| } |
| |
| @SuppressWarnings("unchecked") // Cast of TOMBSTONE. Ok since TOMBSTONE is empty immutable set. |
| private void markDead() { |
| rest = (Set<T>) TOMBSTONE; |
| } |
| } |
| } |