1818package com .uber .cadence .internal .replay ;
1919
2020import com .google .common .base .Preconditions ;
21- import com .google .common .base .Throwables ;
2221import com .google .common .cache .CacheBuilder ;
2322import com .google .common .cache .CacheLoader ;
2423import com .google .common .cache .LoadingCache ;
25- import com .google .common .util .concurrent .ExecutionError ;
26- import com .google .common .util .concurrent .UncheckedExecutionException ;
2724import com .uber .cadence .PollForDecisionTaskResponse ;
28- import com .uber .cadence .internal .common .ThrowableFunc1 ;
2925import com .uber .cadence .internal .metrics .MetricsType ;
3026import com .uber .m3 .tally .Scope ;
31- import java .util .Iterator ;
32- import java .util .Objects ;
33- import java .util .Random ;
34- import java .util .Set ;
35- import java .util .concurrent .TimeUnit ;
36- import java .util .concurrent .atomic .AtomicBoolean ;
27+ import java .util .*;
28+ import java .util .concurrent .Callable ;
3729import java .util .concurrent .locks .Lock ;
3830import java .util .concurrent .locks .ReentrantLock ;
39- import org .slf4j .Logger ;
40- import org .slf4j .LoggerFactory ;
4131
4232public final class DeciderCache {
4333 private final Scope metricsScope ;
4434 private LoadingCache <String , Decider > cache ;
45- private Lock evictionLock = new ReentrantLock ();
46- Random rand = new Random ();
47-
48- private static final Logger log = LoggerFactory .getLogger (DeciderCache .class );
35+ private Lock cacheLock = new ReentrantLock ();
36+ private Set <String > inProcessing = new HashSet <>();
4937
5038 public DeciderCache (int maxCacheSize , Scope scope ) {
5139 Preconditions .checkArgument (maxCacheSize > 0 , "Max cache size must be greater than 0" );
@@ -70,83 +58,79 @@ public Decider load(String key) {
7058 }
7159
7260 public Decider getOrCreate (
73- PollForDecisionTaskResponse decisionTask ,
74- ThrowableFunc1 <PollForDecisionTaskResponse , Decider , Exception > createReplayDecider )
75- throws Exception {
61+ PollForDecisionTaskResponse decisionTask , Callable <Decider > deciderFunc ) throws Exception {
7662 String runId = decisionTask .getWorkflowExecution ().getRunId ();
77- metricsScope .gauge (MetricsType .STICKY_CACHE_SIZE ).update (size ());
7863 if (isFullHistory (decisionTask )) {
79- invalidate (decisionTask );
80- return cache .get (runId , () -> createReplayDecider .apply (decisionTask ));
64+ invalidate (runId );
65+ return deciderFunc .call ();
66+ }
67+
68+ Decider decider = getForProcessing (runId );
69+ if (decider != null ) {
70+ return decider ;
8171 }
82- AtomicBoolean miss = new AtomicBoolean ();
83- Decider result = null ;
72+ return deciderFunc .call ();
73+ }
74+
75+ private Decider getForProcessing (String runId ) throws Exception {
76+ cacheLock .lock ();
8477 try {
85- result =
86- cache .get (
87- runId ,
88- () -> {
89- miss .set (true );
90- return createReplayDecider .apply (decisionTask );
91- });
92- } catch (UncheckedExecutionException | ExecutionError e ) {
93- Throwables .throwIfUnchecked (e .getCause ());
78+ Decider decider = cache .get (runId );
79+ inProcessing .add (runId );
80+ metricsScope .counter (MetricsType .STICKY_CACHE_HIT ).inc (1 );
81+ return decider ;
82+ } catch (CacheLoader .InvalidCacheLoadException e ) {
83+ // We don't have a default loader and don't want to have one. So it's ok to get null value.
84+ metricsScope .counter (MetricsType .STICKY_CACHE_MISS ).inc (1 );
85+ return null ;
9486 } finally {
95- if (miss .get ()) {
96- metricsScope .counter (MetricsType .STICKY_CACHE_MISS ).inc (1 );
97- } else {
98- metricsScope .counter (MetricsType .STICKY_CACHE_HIT ).inc (1 );
99- }
87+ cacheLock .unlock ();
10088 }
101- return result ;
10289 }
10390
104- public void evictAny (String runId ) throws InterruptedException {
105- // Timeout is to guard against workflows trying to evict each other.
106- if (!evictionLock .tryLock (rand .nextInt (4 ), TimeUnit .SECONDS )) {
107- return ;
91+ void markProcessingDone (PollForDecisionTaskResponse decisionTask ) {
92+ String runId = decisionTask .getWorkflowExecution ().getRunId ();
93+
94+ cacheLock .lock ();
95+ try {
96+ inProcessing .remove (runId );
97+ } finally {
98+ cacheLock .unlock ();
10899 }
100+ }
101+
102+ public void addToCache (PollForDecisionTaskResponse decisionTask , Decider decider ) {
103+ String runId = decisionTask .getWorkflowExecution ().getRunId ();
104+ cache .put (runId , decider );
105+ }
106+
107+ public boolean evictAnyNotInProcessing (String runId ) {
108+ cacheLock .lock ();
109109 try {
110110 metricsScope .gauge (MetricsType .STICKY_CACHE_SIZE ).update (size ());
111- Set <String > set = cache .asMap ().keySet ();
112- if (set .isEmpty ()) {
113- return ;
114- }
115- Iterator <String > iter = cache .asMap ().keySet ().iterator ();
116- String key = "" ;
117- while (iter .hasNext ()) {
118- key = iter .next ();
119- if (!key .equals (runId )) {
120- break ;
111+ for (String key : cache .asMap ().keySet ()) {
112+ if (!key .equals (runId ) && !inProcessing .contains (key )) {
113+ cache .invalidate (key );
114+ metricsScope .gauge (MetricsType .STICKY_CACHE_SIZE ).update (size ());
115+ metricsScope .counter (MetricsType .STICKY_CACHE_THREAD_FORCED_EVICTION ).inc (1 );
116+ return true ;
121117 }
122118 }
123119
124- if (key .equals (runId )) {
125- log .warn (String .format ("%s attempted to self evict. Ignoring eviction" , runId ));
126- return ;
127- }
128- cache .invalidate (key );
129- metricsScope .gauge (MetricsType .STICKY_CACHE_SIZE ).update (size ());
130- metricsScope .counter (MetricsType .STICKY_CACHE_THREAD_FORCED_EVICTION ).inc (1 );
120+ return false ;
131121 } finally {
132- evictionLock .unlock ();
122+ cacheLock .unlock ();
133123 }
134124 }
135125
136- public void invalidate (PollForDecisionTaskResponse decisionTask ) throws InterruptedException {
137- String runId = decisionTask .getWorkflowExecution ().getRunId ();
138- invalidate (runId );
139- }
140-
141- private void invalidate (String runId ) throws InterruptedException {
142- if (!evictionLock .tryLock (rand .nextInt (4 ), TimeUnit .SECONDS )) {
143- return ;
144- }
126+ void invalidate (String runId ) {
127+ cacheLock .lock ();
145128 try {
146129 cache .invalidate (runId );
130+ inProcessing .remove (runId );
147131 metricsScope .counter (MetricsType .STICKY_CACHE_TOTAL_FORCED_EVICTION ).inc (1 );
148132 } finally {
149- evictionLock .unlock ();
133+ cacheLock .unlock ();
150134 }
151135 }
152136
@@ -163,11 +147,4 @@ private boolean isFullHistory(PollForDecisionTaskResponse decisionTask) {
163147 public void invalidateAll () {
164148 cache .invalidateAll ();
165149 }
166-
167- public static class EvictedException extends Exception {
168-
169- public EvictedException (String runId ) {
170- super (String .format ("cache was evicted for the decisionTask. RunId: %s" , runId ));
171- }
172- }
173150}
0 commit comments