-
Notifications
You must be signed in to change notification settings - Fork 0
/
StackOverflow63752162Test.java
132 lines (109 loc) · 3.29 KB
/
StackOverflow63752162Test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import org.cache2k.Cache;
import org.cache2k.Cache2kBuilder;
import org.cache2k.CacheEntry;
import org.cache2k.expiry.ExpiryTimeValues;
import org.cache2k.integration.AdvancedCacheLoader;
import org.cache2k.processor.EntryProcessor;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* Two ideas how to switch the cache on bypass during a database update.
*
* @see <a href="https://stackoverflow.com/questions/63752162"/>Stackoverflow question</a>
* @author Jens Wilke
*/
public class StackOverflow63752162Test {
@Test
public void test() {
CacheWrapper<Long, String> cw = new CacheWrapper<>(
new Cache2kBuilder<Long, ValueAndState<String>>() {
}
.loader(new AdvancedCacheLoader<Long, ValueAndState<String>>() {
@Override
public ValueAndState<String> load(Long key, long now,
CacheEntry<Long, ValueAndState<String>> oldEntry) {
return new ValueAndState<>(
realLoad(key),
oldEntry != null && oldEntry.getValue().updating);
}
})
.keepDataAfterExpired(true)
.eternal(true)
.expiryPolicy(
(key, value, loadTime, oldEntry) ->
value.updating ? ExpiryTimeValues.NOW : ExpiryTimeValues.ETERNAL)
.build()
);
cw.startUpdate(123L);
assertEquals(0, loaderCalled.get());
cw.cache.get(123L);
cw.cache.get(123L);
cw.cache.get(4711L);
cw.cache.get(4711L);
assertEquals(3, loaderCalled.get());
}
AtomicLong loaderCalled = new AtomicLong();
String realLoad(long key) {
loaderCalled.incrementAndGet();
return key * 2 + "";
}
static class ValueAndState<V> {
V value;
boolean updating;
public ValueAndState(boolean value) {
this.updating = value;
}
public ValueAndState(V value, boolean updating) {
this.value = value;
this.updating = updating;
}
}
static class CacheWrapper<K, V> {
Cache<K, ValueAndState<V>> cache;
public CacheWrapper(Cache<K, ValueAndState<V>> cache) {
this.cache = cache;
}
V read (K key) {
return cache.get(key).value;
}
/**
* @return {@code false} if update is already in progress.
*/
boolean startUpdate(K key) {
return cache.invoke(key, (EntryProcessor<K, ValueAndState<V>, Boolean>) entry -> {
// check whether entry is existing to avoid trigger a load
// we only need to load, if the data is actually read
ValueAndState<V> v = entry.exists() ? entry.getValue() : null;
if (v != null) {
if (v.updating) {
return false;
}
entry.setValue(new ValueAndState<>(v.value, true));
} else {
entry.setValue(new ValueAndState<>(true));
}
return true;
});
}
}
abstract class ValueOrUpdating<V> { }
class Value<V> extends ValueOrUpdating<V> {
V value;
}
class Updating extends ValueOrUpdating<Void> {
}
class SimpleCacheWrapper<K, V> {
Cache<K, ValueOrUpdating<V>> cache;
V read(K key) {
ValueOrUpdating<V> v = cache.get(key);
if (v instanceof Updating) {
return load(key);
}
return ((Value<V>) v).value;
}
V load(K key) {
return null;
}
}
}