• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Guava Authors
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 
17 package com.google.common.cache;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertThrows;
21 
22 import com.google.common.util.concurrent.UncheckedExecutionException;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Queue;
26 import java.util.concurrent.ConcurrentLinkedQueue;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.TimeUnit;
29 import java.util.function.IntConsumer;
30 import java.util.stream.IntStream;
31 import junit.framework.TestCase;
32 
33 /** Test Java8 map.compute in concurrent cache context. */
34 public class LocalCacheMapComputeTest extends TestCase {
35   final int count = 10000;
36   final String delimiter = "-";
37   final String key = "key";
38   Cache<String, String> cache;
39 
40   // helper
doParallelCacheOp(int count, IntConsumer consumer)41   private static void doParallelCacheOp(int count, IntConsumer consumer) {
42     IntStream.range(0, count).parallel().forEach(consumer);
43   }
44 
45   @Override
setUp()46   public void setUp() throws Exception {
47     super.setUp();
48     this.cache =
49         CacheBuilder.newBuilder()
50             .expireAfterAccess(500000, TimeUnit.MILLISECONDS)
51             .maximumSize(count)
52             .build();
53   }
54 
testComputeIfAbsent()55   public void testComputeIfAbsent() {
56     // simultaneous insertion for same key, expect 1 winner
57     doParallelCacheOp(
58         count,
59         n -> {
60           cache.asMap().computeIfAbsent(key, k -> "value" + n);
61         });
62     assertEquals(1, cache.size());
63   }
64 
testComputeIfAbsentEviction()65   public void testComputeIfAbsentEviction() {
66     // b/80241237
67 
68     Cache<String, String> c = CacheBuilder.newBuilder().maximumSize(1).build();
69 
70     assertThat(c.asMap().computeIfAbsent("hash-1", k -> "")).isEqualTo("");
71     assertThat(c.asMap().computeIfAbsent("hash-1", k -> "")).isEqualTo("");
72     assertThat(c.asMap().computeIfAbsent("hash-1", k -> "")).isEqualTo("");
73     assertThat(c.size()).isEqualTo(1);
74     assertThat(c.asMap().computeIfAbsent("hash-2", k -> "")).isEqualTo("");
75   }
76 
testComputeEviction()77   public void testComputeEviction() {
78     // b/80241237
79 
80     Cache<String, String> c = CacheBuilder.newBuilder().maximumSize(1).build();
81 
82     assertThat(c.asMap().compute("hash-1", (k, v) -> "a")).isEqualTo("a");
83     assertThat(c.asMap().compute("hash-1", (k, v) -> "b")).isEqualTo("b");
84     assertThat(c.asMap().compute("hash-1", (k, v) -> "c")).isEqualTo("c");
85     assertThat(c.size()).isEqualTo(1);
86     assertThat(c.asMap().computeIfAbsent("hash-2", k -> "")).isEqualTo("");
87   }
88 
testComputeIfPresent()89   public void testComputeIfPresent() {
90     cache.put(key, "1");
91     // simultaneous update for same key, expect count successful updates
92     doParallelCacheOp(
93         count,
94         n -> {
95           cache.asMap().computeIfPresent(key, (k, v) -> v + delimiter + n);
96         });
97     assertEquals(1, cache.size());
98     assertThat(cache.getIfPresent(key).split(delimiter)).hasLength(count + 1);
99   }
100 
testComputeIfPresentRemove()101   public void testComputeIfPresentRemove() {
102     List<RemovalNotification<Integer, Integer>> notifications = new ArrayList<>();
103     Cache<Integer, Integer> cache =
104         CacheBuilder.newBuilder()
105             .removalListener(
106                 new RemovalListener<Integer, Integer>() {
107                   @Override
108                   public void onRemoval(RemovalNotification<Integer, Integer> notification) {
109                     notifications.add(notification);
110                   }
111                 })
112             .build();
113     cache.put(1, 2);
114 
115     // explicitly remove the existing value
116     cache.asMap().computeIfPresent(1, (key, value) -> null);
117     assertThat(notifications).hasSize(1);
118     CacheTesting.checkEmpty(cache);
119 
120     // ensure no zombie entry remains
121     cache.asMap().computeIfPresent(1, (key, value) -> null);
122     assertThat(notifications).hasSize(1);
123     CacheTesting.checkEmpty(cache);
124   }
125 
testUpdates()126   public void testUpdates() {
127     cache.put(key, "1");
128     // simultaneous update for same key, some null, some non-null
129     doParallelCacheOp(
130         count,
131         n -> {
132           cache.asMap().compute(key, (k, v) -> n % 2 == 0 ? v + delimiter + n : null);
133         });
134     assertTrue(1 >= cache.size());
135   }
136 
testCompute()137   public void testCompute() {
138     cache.put(key, "1");
139     // simultaneous deletion
140     doParallelCacheOp(
141         count,
142         n -> {
143           cache.asMap().compute(key, (k, v) -> null);
144         });
145     assertEquals(0, cache.size());
146   }
147 
testComputeWithLoad()148   public void testComputeWithLoad() {
149     Queue<RemovalNotification<String, String>> notifications = new ConcurrentLinkedQueue<>();
150     cache =
151         CacheBuilder.newBuilder()
152             .removalListener(
153                 new RemovalListener<String, String>() {
154                   @Override
155                   public void onRemoval(RemovalNotification<String, String> notification) {
156                     notifications.add(notification);
157                   }
158                 })
159             .expireAfterAccess(500000, TimeUnit.MILLISECONDS)
160             .maximumSize(count)
161             .build();
162 
163     cache.put(key, "1");
164     // simultaneous load and deletion
165     doParallelCacheOp(
166         count,
167         n -> {
168           try {
169             String unused = cache.get(key, () -> key);
170             cache.asMap().compute(key, (k, v) -> null);
171           } catch (ExecutionException e) {
172             throw new UncheckedExecutionException(e);
173           }
174         });
175 
176     CacheTesting.checkEmpty(cache);
177     for (RemovalNotification<String, String> entry : notifications) {
178       assertThat(entry.getKey()).isNotNull();
179       assertThat(entry.getValue()).isNotNull();
180     }
181   }
182 
testComputeExceptionally()183   public void testComputeExceptionally() {
184     assertThrows(
185         RuntimeException.class,
186         () ->
187             doParallelCacheOp(
188                 count,
189                 n -> {
190                   cache
191                       .asMap()
192                       .compute(
193                           key,
194                           (k, v) -> {
195                             throw new RuntimeException();
196                           });
197                 }));
198   }
199 }
200