001 /*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.jet.lang.resolve.lazy.storage;
018
019 import com.google.common.collect.ImmutableMap;
020 import com.intellij.openapi.util.Computable;
021 import com.intellij.util.Consumer;
022 import com.intellij.util.Function;
023 import com.intellij.util.containers.ConcurrentWeakValueHashMap;
024 import org.jetbrains.annotations.NotNull;
025 import org.jetbrains.annotations.Nullable;
026 import org.jetbrains.annotations.TestOnly;
027 import org.jetbrains.jet.lang.diagnostics.Diagnostic;
028 import org.jetbrains.jet.lang.resolve.BindingContext;
029 import org.jetbrains.jet.lang.resolve.BindingTrace;
030 import org.jetbrains.jet.util.slicedmap.ReadOnlySlice;
031 import org.jetbrains.jet.util.slicedmap.WritableSlice;
032 import org.jetbrains.jet.utils.ExceptionUtils;
033 import org.jetbrains.jet.utils.WrappedValues;
034
035 import java.util.Collection;
036 import java.util.concurrent.ConcurrentHashMap;
037 import java.util.concurrent.ConcurrentMap;
038
039 public class LockBasedStorageManager implements StorageManager {
040
041 private final Object lock = new Object() {
042 @Override
043 public String toString() {
044 return "LockBasedStorageManager centralized lock";
045 }
046 };
047
048 @NotNull
049 @Override
050 public <K, V> MemoizedFunctionToNotNull<K, V> createMemoizedFunction(
051 @NotNull Function<K, V> compute, @NotNull ReferenceKind valuesReferenceKind
052 ) {
053 ConcurrentMap<K, Object> map = createConcurrentMap(valuesReferenceKind);
054 return new MapBasedMemoizedFunctionToNotNull<K, V>(lock, map, compute);
055 }
056
057 @NotNull
058 @Override
059 public <K, V> MemoizedFunctionToNullable<K, V> createMemoizedFunctionWithNullableValues(
060 @NotNull Function<K, V> compute, @NotNull ReferenceKind valuesReferenceKind
061 ) {
062 ConcurrentMap<K, Object> map = createConcurrentMap(valuesReferenceKind);
063 return new MapBasedMemoizedFunction<K, V>(lock, map, compute);
064 }
065
066 private static <K, V> ConcurrentMap<K, V> createConcurrentMap(ReferenceKind referenceKind) {
067 return (referenceKind == ReferenceKind.WEAK) ? new ConcurrentWeakValueHashMap<K, V>() : new ConcurrentHashMap<K, V>();
068 }
069
070 @NotNull
071 @Override
072 public <T> NotNullLazyValue<T> createLazyValue(@NotNull Computable<T> computable) {
073 return new LockBasedNotNullLazyValue<T>(lock, computable);
074 }
075
076 @NotNull
077 @Override
078 public <T> NotNullLazyValue<T> createLazyValueWithPostCompute(@NotNull Computable<T> computable, @NotNull final Consumer<T> postCompute) {
079 return new LockBasedNotNullLazyValue<T>(lock, computable) {
080 @Override
081 protected void postCompute(@NotNull T value) {
082 postCompute.consume(value);
083 }
084 };
085 }
086
087 @NotNull
088 @Override
089 public <T> NullableLazyValue<T> createNullableLazyValue(@NotNull Computable<T> computable) {
090 return new LockBasedLazyValue<T>(lock, computable);
091 }
092
093 @NotNull
094 @Override
095 public <T> NullableLazyValue<T> createNullableLazyValueWithPostCompute(
096 @NotNull Computable<T> computable, @NotNull final Consumer<T> postCompute
097 ) {
098 return new LockBasedLazyValue<T>(lock, computable) {
099 @Override
100 protected void postCompute(@Nullable T value) {
101 postCompute.consume(value);
102 }
103 };
104 }
105
106 @NotNull
107 @Override
108 public BindingTrace createSafeTrace(@NotNull BindingTrace originalTrace) {
109 // It seems safe to have a separate lock for traces:
110 // no other locks will be acquired inside the trace operations
111 return new LockProtectedTrace(lock, originalTrace);
112 }
113
114 @Override
115 public <T> T compute(@NotNull Computable<T> computable) {
116 synchronized (lock) {
117 return computable.compute();
118 }
119 }
120
121 private static class LockBasedLazyValue<T> implements NullableLazyValue<T> {
122 private final Object lock;
123 private final Computable<T> computable;
124
125 @Nullable
126 private volatile Object value = null;
127
128 public LockBasedLazyValue(@NotNull Object lock, @NotNull Computable<T> computable) {
129 this.lock = lock;
130 this.computable = computable;
131 }
132
133 @Override
134 public T compute() {
135 Object _value = value;
136 if (_value != null) return WrappedValues.unescapeExceptionOrNull(_value);
137
138 synchronized (lock) {
139 _value = value;
140 if (_value != null) return WrappedValues.unescapeExceptionOrNull(_value);
141
142 try {
143 T typedValue = computable.compute();
144 value = WrappedValues.escapeNull(typedValue);
145 postCompute(typedValue);
146 return typedValue;
147 }
148 catch (Throwable throwable) {
149 value = WrappedValues.escapeThrowable(throwable);
150 throw ExceptionUtils.rethrow(throwable);
151 }
152 }
153 }
154
155 protected void postCompute(T value) {
156 // Doing something in post-compute helps prevent infinite recursion
157 }
158 }
159
160 private static class LockBasedNotNullLazyValue<T> extends LockBasedLazyValue<T> implements NotNullLazyValue<T> {
161
162 public LockBasedNotNullLazyValue(@NotNull Object lock, @NotNull Computable<T> computable) {
163 super(lock, computable);
164 }
165
166 @Override
167 @NotNull
168 public T compute() {
169 T result = super.compute();
170 assert result != null : "compute() returned null";
171 return result;
172 }
173 }
174
175 private static class MapBasedMemoizedFunction<K, V> implements MemoizedFunctionToNullable<K, V> {
176 private final Object lock;
177 private final ConcurrentMap<K, Object> cache;
178 private final Function<K, V> compute;
179
180 public MapBasedMemoizedFunction(@NotNull Object lock, @NotNull ConcurrentMap<K, Object> map, @NotNull Function<K, V> compute) {
181 this.lock = lock;
182 this.cache = map;
183 this.compute = compute;
184 }
185
186 @Override
187 @Nullable
188 public V fun(@NotNull K input) {
189 Object value = cache.get(input);
190 if (value != null) return WrappedValues.unescapeExceptionOrNull(value);
191
192 synchronized (lock) {
193 value = cache.get(input);
194 if (value != null) return WrappedValues.unescapeExceptionOrNull(value);
195
196 try {
197 V typedValue = compute.fun(input);
198 Object oldValue = cache.put(input, WrappedValues.escapeNull(typedValue));
199 assert oldValue == null : "Race condition detected";
200
201 return typedValue;
202 }
203 catch (Throwable throwable) {
204 Object oldValue = cache.put(input, WrappedValues.escapeThrowable(throwable));
205 assert oldValue == null : "Race condition detected";
206
207 throw ExceptionUtils.rethrow(throwable);
208 }
209 }
210 }
211 }
212
213 private static class MapBasedMemoizedFunctionToNotNull<K, V> extends MapBasedMemoizedFunction<K, V> implements MemoizedFunctionToNotNull<K, V> {
214
215 public MapBasedMemoizedFunctionToNotNull(
216 @NotNull Object lock,
217 @NotNull ConcurrentMap<K, Object> map,
218 @NotNull Function<K, V> compute
219 ) {
220 super(lock, map, compute);
221 }
222
223 @NotNull
224 @Override
225 public V fun(@NotNull K input) {
226 V result = super.fun(input);
227 assert result != null : "compute() returned null";
228 return result;
229 }
230 }
231
232 private static class LockProtectedContext implements BindingContext {
233 private final Object lock;
234 private final BindingContext context;
235
236 private LockProtectedContext(Object lock, BindingContext context) {
237 this.lock = lock;
238 this.context = context;
239 }
240
241 @Override
242 public Collection<Diagnostic> getDiagnostics() {
243 synchronized (lock) {
244 return context.getDiagnostics();
245 }
246 }
247
248 @Nullable
249 @Override
250 public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
251 synchronized (lock) {
252 return context.get(slice, key);
253 }
254 }
255
256 @NotNull
257 @Override
258 public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
259 synchronized (lock) {
260 return context.getKeys(slice);
261 }
262 }
263
264 @NotNull
265 @Override
266 @TestOnly
267 public <K, V> ImmutableMap<K, V> getSliceContents(@NotNull ReadOnlySlice<K, V> slice) {
268 synchronized (lock) {
269 return context.getSliceContents(slice);
270 }
271 }
272 }
273
274 private static class LockProtectedTrace implements BindingTrace {
275 private final Object lock;
276 private final BindingTrace trace;
277 private final BindingContext context;
278
279 public LockProtectedTrace(@NotNull Object lock, @NotNull BindingTrace trace) {
280 this.lock = lock;
281 this.trace = trace;
282 this.context = new LockProtectedContext(lock, trace.getBindingContext());
283 }
284
285 @Override
286 public BindingContext getBindingContext() {
287 return context;
288 }
289
290 @Override
291 public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
292 synchronized (lock) {
293 trace.record(slice, key, value);
294 }
295 }
296
297 @Override
298 public <K> void record(WritableSlice<K, Boolean> slice, K key) {
299 synchronized (lock) {
300 trace.record(slice, key);
301 }
302 }
303
304 @Override
305 @Nullable
306 public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
307 synchronized (lock) {
308 return trace.get(slice, key);
309 }
310 }
311
312 @Override
313 @NotNull
314 public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
315 synchronized (lock) {
316 return trace.getKeys(slice);
317 }
318 }
319
320 @Override
321 public void report(@NotNull Diagnostic diagnostic) {
322 synchronized (lock) {
323 trace.report(diagnostic);
324 }
325 }
326 }
327
328 }