|
18 | 18 | package org.apache.spark.util |
19 | 19 |
|
20 | 20 | import java.lang.ref.WeakReference |
21 | | -import java.util |
22 | | -import java.util.concurrent.ConcurrentHashMap |
23 | | -import java.util.concurrent.atomic.AtomicInteger |
24 | 21 |
|
25 | | -import scala.collection.JavaConversions |
26 | | - |
27 | | -import org.apache.spark.Logging |
28 | | - |
29 | | -private[util] case class TimeStampedWeakValue[T](timestamp: Long, weakValue: WeakReference[T]) { |
30 | | - def this(timestamp: Long, value: T) = this(timestamp, new WeakReference[T](value)) |
31 | | -} |
| 22 | +import scala.collection.{immutable, mutable} |
32 | 23 |
|
33 | 24 | /** |
34 | | - * A map that stores the timestamp of when a key was inserted along with the value, |
35 | | - * while ensuring that the values are weakly referenced. If the value is garbage collected and |
36 | | - * the weak reference is null, get() operation returns the key be non-existent. However, |
37 | | - * the key is actually not removed in the current implementation. Key-value pairs whose |
38 | | - * timestamps are older than a particular threshold time can then be removed using the |
39 | | - * clearOldValues method. It exposes a scala.collection.mutable.Map interface to allow it to be a |
40 | | - * drop-in replacement for Scala HashMaps. |
| 25 | + * A wrapper of TimeStampedHashMap that ensures the values are weakly referenced and timestamped. |
| 26 | + * |
| 27 | + * If the value is garbage collected and the weak reference is null, get() operation returns |
| 28 | + * a non-existent value. However, the corresponding key is actually not removed in the current |
| 29 | + * implementation. Key-value pairs whose timestamps are older than a particular threshold time |
| 30 | + * can then be removed using the clearOldValues method. It exposes a scala.collection.mutable.Map |
| 31 | + * interface to allow it to be a drop-in replacement for Scala HashMaps. |
41 | 32 | * |
42 | 33 | * Internally, it uses a Java ConcurrentHashMap, so all operations on this HashMap are thread-safe. |
| 34 | + * |
| 35 | + * @param updateTimeStampOnGet Whether timestamp of a pair will be updated when it is accessed. |
43 | 36 | */ |
| 37 | +private[spark] class TimeStampedWeakValueHashMap[A, B](updateTimeStampOnGet: Boolean = false) |
| 38 | + extends mutable.Map[A, B]() { |
| 39 | + |
| 40 | + import TimeStampedWeakValueHashMap._ |
44 | 41 |
|
45 | | -private[spark] class TimeStampedWeakValueHashMap[A, B]() |
46 | | - extends WrappedJavaHashMap[A, B, A, TimeStampedWeakValue[B]] with Logging { |
| 42 | + private val internalMap = new TimeStampedHashMap[A, WeakReference[B]](updateTimeStampOnGet) |
47 | 43 |
|
48 | | - /** Number of inserts after which keys whose weak ref values are null will be cleaned */ |
49 | | - private val CLEANUP_INTERVAL = 1000 |
| 44 | + def get(key: A): Option[B] = internalMap.get(key) |
50 | 45 |
|
51 | | - /** Counter for counting the number of inserts */ |
52 | | - private val insertCounts = new AtomicInteger(0) |
| 46 | + def iterator: Iterator[(A, B)] = internalMap.iterator |
| 47 | + |
| 48 | + override def + [B1 >: B](kv: (A, B1)): mutable.Map[A, B1] = { |
| 49 | + val newMap = new TimeStampedWeakValueHashMap[A, B1] |
| 50 | + newMap.internalMap += kv |
| 51 | + newMap |
| 52 | + } |
53 | 53 |
|
54 | | - private[util] val internalJavaMap: util.Map[A, TimeStampedWeakValue[B]] = { |
55 | | - new ConcurrentHashMap[A, TimeStampedWeakValue[B]]() |
| 54 | + override def - (key: A): mutable.Map[A, B] = { |
| 55 | + val newMap = new TimeStampedWeakValueHashMap[A, B] |
| 56 | + newMap.internalMap -= key |
| 57 | + newMap |
56 | 58 | } |
57 | 59 |
|
58 | | - private[util] def newInstance[K1, V1](): WrappedJavaHashMap[K1, V1, _, _] = { |
59 | | - new TimeStampedWeakValueHashMap[K1, V1]() |
| 60 | + override def += (kv: (A, B)): this.type = { |
| 61 | + internalMap += kv |
| 62 | + this |
60 | 63 | } |
61 | 64 |
|
62 | | - override def +=(kv: (A, B)): this.type = { |
63 | | - // Cleanup null value at certain intervals |
64 | | - if (insertCounts.incrementAndGet() % CLEANUP_INTERVAL == 0) { |
65 | | - cleanNullValues() |
66 | | - } |
67 | | - super.+=(kv) |
| 65 | + override def -= (key: A): this.type = { |
| 66 | + internalMap -= key |
| 67 | + this |
68 | 68 | } |
69 | 69 |
|
70 | | - override def get(key: A): Option[B] = { |
71 | | - Option(internalJavaMap.get(key)).flatMap { weakValue => |
72 | | - val value = weakValue.weakValue.get |
73 | | - if (value == null) { |
74 | | - internalJavaMap.remove(key) |
75 | | - } |
76 | | - Option(value) |
77 | | - } |
| 70 | + override def update(key: A, value: B) = this += ((key, value)) |
| 71 | + |
| 72 | + override def apply(key: A): B = internalMap.apply(key) |
| 73 | + |
| 74 | + override def filter(p: ((A, B)) => Boolean): mutable.Map[A, B] = internalMap.filter(p) |
| 75 | + |
| 76 | + override def empty: mutable.Map[A, B] = new TimeStampedWeakValueHashMap[A, B]() |
| 77 | + |
| 78 | + override def size: Int = internalMap.size |
| 79 | + |
| 80 | + override def foreach[U](f: ((A, B)) => U) = internalMap.foreach(f) |
| 81 | + |
| 82 | + def putIfAbsent(key: A, value: B): Option[B] = internalMap.putIfAbsent(key, value) |
| 83 | + |
| 84 | + def toMap: immutable.Map[A, B] = iterator.toMap |
| 85 | + |
| 86 | + /** |
| 87 | + * Remove old key-value pairs that have timestamp earlier than `threshTime`. |
| 88 | + */ |
| 89 | + def clearOldValues(threshTime: Long) = internalMap.clearOldValues(threshTime) |
| 90 | + |
| 91 | +} |
| 92 | + |
| 93 | +/** |
| 94 | + * Helper methods for converting to and from WeakReferences. |
| 95 | + */ |
| 96 | +private[spark] object TimeStampedWeakValueHashMap { |
| 97 | + |
| 98 | + /* Implicit conversion methods to WeakReferences */ |
| 99 | + |
| 100 | + implicit def toWeakReference[V](v: V): WeakReference[V] = new WeakReference[V](v) |
| 101 | + |
| 102 | + implicit def toWeakReferenceTuple[K, V](kv: (K, V)): (K, WeakReference[V]) = { |
| 103 | + kv match { case (k, v) => (k, toWeakReference(v)) } |
78 | 104 | } |
79 | 105 |
|
80 | | - @inline override protected def externalValueToInternalValue(v: B): TimeStampedWeakValue[B] = { |
81 | | - new TimeStampedWeakValue(currentTime, v) |
| 106 | + implicit def toWeakReferenceFunction[K, V, R](p: ((K, V)) => R): ((K, WeakReference[V])) => R = { |
| 107 | + (kv: (K, WeakReference[V])) => p(kv) |
82 | 108 | } |
83 | 109 |
|
84 | | - @inline override protected def internalValueToExternalValue(iv: TimeStampedWeakValue[B]): B = { |
85 | | - iv.weakValue.get |
| 110 | + /* Implicit conversion methods from WeakReferences */ |
| 111 | + |
| 112 | + implicit def fromWeakReference[V](ref: WeakReference[V]): V = ref.get |
| 113 | + |
| 114 | + implicit def fromWeakReferenceOption[V](v: Option[WeakReference[V]]): Option[V] = { |
| 115 | + v.map(fromWeakReference) |
86 | 116 | } |
87 | 117 |
|
88 | | - override def iterator: Iterator[(A, B)] = { |
89 | | - val iterator = internalJavaMap.entrySet().iterator() |
90 | | - JavaConversions.asScalaIterator(iterator).flatMap(kv => { |
91 | | - val (key, value) = (kv.getKey, kv.getValue.weakValue.get) |
92 | | - if (value != null) Seq((key, value)) else Seq.empty |
93 | | - }) |
| 118 | + implicit def fromWeakReferenceTuple[K, V](kv: (K, WeakReference[V])): (K, V) = { |
| 119 | + kv match { case (k, v) => (k, fromWeakReference(v)) } |
94 | 120 | } |
95 | 121 |
|
96 | | - /** |
97 | | - * Removes old key-value pairs that have timestamp earlier than `threshTime`, |
98 | | - * calling the supplied function on each such entry before removing. |
99 | | - */ |
100 | | - def clearOldValues(threshTime: Long, f: (A, B) => Unit = null) { |
101 | | - val iterator = internalJavaMap.entrySet().iterator() |
102 | | - while (iterator.hasNext) { |
103 | | - val entry = iterator.next() |
104 | | - if (entry.getValue.timestamp < threshTime) { |
105 | | - val value = entry.getValue.weakValue.get |
106 | | - if (f != null && value != null) { |
107 | | - f(entry.getKey, value) |
108 | | - } |
109 | | - logDebug("Removing key " + entry.getKey) |
110 | | - iterator.remove() |
111 | | - } |
112 | | - } |
| 122 | + implicit def fromWeakReferenceIterator[K, V]( |
| 123 | + it: Iterator[(K, WeakReference[V])]): Iterator[(K, V)] = { |
| 124 | + it.map(fromWeakReferenceTuple) |
113 | 125 | } |
114 | 126 |
|
115 | | - /** |
116 | | - * Removes keys whose weak referenced values have become null. |
117 | | - */ |
118 | | - private def cleanNullValues() { |
119 | | - val iterator = internalJavaMap.entrySet().iterator() |
120 | | - while (iterator.hasNext) { |
121 | | - val entry = iterator.next() |
122 | | - if (entry.getValue.weakValue.get == null) { |
123 | | - logDebug("Removing key " + entry.getKey) |
124 | | - iterator.remove() |
125 | | - } |
126 | | - } |
| 127 | + implicit def fromWeakReferenceMap[K, V]( |
| 128 | + map: mutable.Map[K, WeakReference[V]]) : mutable.Map[K, V] = { |
| 129 | + mutable.Map(map.mapValues(fromWeakReference).toSeq: _*) |
127 | 130 | } |
128 | 131 |
|
129 | | - private def currentTime = System.currentTimeMillis() |
130 | 132 | } |
0 commit comments