[groovy] implement NotNullCachedComputableWrapper without blocking (IDEA-154705)
[idea/community.git] / plugins / groovy / groovy-psi / src / org / jetbrains / plugins / groovy / util / NotNullCachedComputableWrapper.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
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 org.jetbrains.plugins.groovy.util;
17
18 import com.intellij.openapi.util.NotNullComputable;
19 import com.intellij.openapi.util.RecursionGuard;
20 import com.intellij.openapi.util.RecursionManager;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.TestOnly;
23
24 import java.util.concurrent.atomic.AtomicReference;
25
26 public class NotNullCachedComputableWrapper<T> implements NotNullComputable<T> {
27
28   private static final RecursionGuard ourGuard = RecursionManager.createGuard(NotNullCachedComputableWrapper.class.getName());
29
30   private volatile NotNullComputable<T> myComputable;
31   private final AtomicReference<T> myValueRef = new AtomicReference<>();
32
33   public NotNullCachedComputableWrapper(@NotNull NotNullComputable<T> computable) {
34     myComputable = computable;
35   }
36
37   @NotNull
38   @Override
39   public T compute() {
40     while (true) {
41       T value = myValueRef.get();
42       if (value != null) return value;                // value already computed and cached
43
44       final NotNullComputable<T> computable = myComputable;
45       if (computable == null) continue;               // computable is null only after some thread succeeds CAS
46
47       final RecursionGuard.StackStamp stamp = ourGuard.markStack();
48       value = computable.compute();
49       if (stamp.mayCacheNow()) {
50         if (myValueRef.compareAndSet(null, value)) {  // try to cache value
51           myComputable = null;                        // if ok, allow gc to clean computable
52           return value;
53         }                                             // if not ok then another thread already set cached value
54       }
55       else {
56         return value;                                 // do not try to cache, just return value
57       }
58     }
59   }
60
61   @TestOnly
62   public boolean isComputed() {
63     return myValueRef.get() != null;
64   }
65 }