1
+ /*
2
+ Copyright 2012 Twitter, Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ package com .twitter .algebird .util .summer
17
+
18
+ import com .twitter .algebird ._
19
+ import com .twitter .util .{Duration , Future , FuturePool }
20
+ import java .util .concurrent .ConcurrentHashMap
21
+ import java .util .concurrent .atomic .AtomicInteger
22
+ import scala .collection .JavaConverters ._
23
+ import scala .collection .mutable .{Set => MSet }
24
+ /**
25
+ * @author Ian O Connell
26
+ */
27
+
28
+ class AsyncListSum [Key , Value ](bufferSize : BufferSize ,
29
+ override val flushFrequency : FlushFrequency ,
30
+ override val softMemoryFlush : MemoryFlushPercent ,
31
+ workPool : FuturePool )
32
+ (implicit semigroup : Semigroup [Value ])
33
+ extends AsyncSummer [(Key , Value ), Map [Key , Value ]]
34
+ with WithFlushConditions [(Key , Value ), Map [Key , Value ]] {
35
+
36
+ require(bufferSize.v > 0 , " Use the Null summer for an empty async summer" )
37
+
38
+ protected override val emptyResult = Map .empty[Key , Value ]
39
+
40
+ private [this ] final val queueMap = new ConcurrentHashMap [Key , IndexedSeq [Value ]]()
41
+ private [this ] final val elementsInCache = new AtomicInteger (0 )
42
+
43
+ override def isFlushed : Boolean = elementsInCache.get == 0
44
+
45
+ override def flush : Future [Map [Key , Value ]] =
46
+ workPool {
47
+ didFlush // bumps timeout on the flush conditions
48
+ // Take a copy of the keyset into a scala set (toSet forces the copy)
49
+ // We want this to be safe around uniqueness in the keys coming out of the keys.flatMap
50
+ val keys = queueMap.keySet.asScala.toSet
51
+ keys.flatMap { k =>
52
+ val retV = queueMap.remove(k)
53
+ if (retV != null ) {
54
+ val newRemaining = elementsInCache.addAndGet(retV.size * - 1 )
55
+ Semigroup .sumOption(retV).map(v => (k, v))
56
+ }
57
+ else None
58
+ }.toMap
59
+ }
60
+
61
+ @ annotation.tailrec
62
+ private [this ] final def doInsert (key : Key , vals : IndexedSeq [Value ]) {
63
+ require(key != null , " Key can not be null" )
64
+ val success = if (queueMap.containsKey(key)) {
65
+ val oldValue = queueMap.get(key)
66
+ if (oldValue != null ) {
67
+ val newValue = vals ++ oldValue
68
+ queueMap.replace(key, oldValue, newValue)
69
+ } else {
70
+ false // Removed between the check above and fetching
71
+ }
72
+ } else {
73
+ // Test if something else has raced into our spot.
74
+ queueMap.putIfAbsent(key, vals) == null
75
+ }
76
+
77
+ if (success) {
78
+ // Successful insert
79
+ elementsInCache.addAndGet(vals.size)
80
+ } else {
81
+ return doInsert(key, vals)
82
+ }
83
+ }
84
+
85
+ def addAll (vals : TraversableOnce [(Key , Value )]): Future [Map [Key , Value ]] = {
86
+ val prepVals = vals.map { case (k, v) =>
87
+ require(k != null , " Inserting a null key?" )
88
+ (k -> IndexedSeq (v))
89
+ } : TraversableOnce [(Key , IndexedSeq [Value ])]
90
+
91
+ val curData = MapAlgebra .sumByKey(prepVals)
92
+
93
+ curData.foreach { case (k, v) =>
94
+ doInsert(k, v)
95
+ }
96
+
97
+ if (elementsInCache.get >= bufferSize.v) {
98
+ flush
99
+ } else {
100
+ Future .value(Map .empty)
101
+ }
102
+ }
103
+ }
0 commit comments