28
28
* "value is present in map".
29
29
*
30
30
* Potential problem: if we try to return immediately saying whether the needed data are available,
31
- * there are some cases where preparing the reqeusted object might take only a few hundred milliseconds or less.
31
+ * there are some cases where preparing the requested object might take only a few hundred milliseconds or less.
32
32
* In that case then we don't want the caller to have to re-poll. In this case a Future.get() with timeout is good.
33
33
*
34
34
* Created by abyrd on 2018-09-14
@@ -97,10 +97,23 @@ public String toString() {
97
97
}
98
98
}
99
99
100
+ /**
101
+ * This has been factored out of the executor runnables so subclasses can force a blocking (non-async) load.
102
+ * Any exceptions that occur while building the value will escape this method, leaving the status as BUILDING.
103
+ */
104
+ protected V getBlocking (K key ) {
105
+ V value = buildValue (key );
106
+ synchronized (map ) {
107
+ map .put (key , new LoaderState (Status .PRESENT , "Loaded" , 100 , value ));
108
+ }
109
+ return value ;
110
+ }
111
+
100
112
/**
101
113
* Attempt to fetch the value for the supplied key.
102
114
* If the value is not yet present, and not yet being computed / fetched, enqueue a task to do so.
103
115
* Return a response that reports status, and may or may not contain the value.
116
+ * Any exception that occurs while building the value is caught and associated with the key with a status of ERROR.
104
117
*/
105
118
public LoaderState <V > get (K key ) {
106
119
LoaderState <V > state = null ;
@@ -109,7 +122,7 @@ public LoaderState<V> get (K key) {
109
122
state = map .get (key );
110
123
if (state == null ) {
111
124
// Only enqueue a task to load the value for this key if another call hasn't already done it.
112
- state = new LoaderState <V >(Status .WAITING , "Enqueued task..." , 0 , null );
125
+ state = new LoaderState <>(Status .WAITING , "Enqueued task..." , 0 , null );
113
126
map .put (key , state );
114
127
enqueueLoadTask = true ;
115
128
}
@@ -120,16 +133,16 @@ public LoaderState<V> get (K key) {
120
133
// Enqueue task outside the above block (synchronizing the fewest lines possible).
121
134
if (enqueueLoadTask ) {
122
135
executor .execute (() -> {
123
- setProgress (key , 0 , "Starting..." );
124
136
try {
125
- V value = buildValue (key );
126
- synchronized (map ) {
127
- map .put (key , new LoaderState (Status .PRESENT , null , 100 , value ));
128
- }
137
+ setProgress (key , 0 , "Starting..." );
138
+ getBlocking (key );
129
139
} catch (Throwable t ) {
130
140
// It's essential to trap Throwable rather than just Exception. Otherwise the executor
131
- // threads can be killed by any Error that happens, stalling the executor.
132
- setError (key , t );
141
+ // threads can be killed by any Error that happens, stalling the executor. The below permanently
142
+ // associates an error with the key. No further attempt will ever be made to create the value.
143
+ synchronized (map ) {
144
+ map .put (key , new LoaderState (t ));
145
+ }
133
146
LOG .error ("Async load failed: " + ExceptionUtils .stackTraceString (t ));
134
147
}
135
148
});
@@ -139,12 +152,13 @@ public LoaderState<V> get (K key) {
139
152
140
153
/**
141
154
* Override this method in concrete subclasses to specify the logic to build/calculate/fetch a value.
142
- * Implementations may call setProgress to report progress on long operations.
155
+ * Implementations may call setProgress to report progress on long operations; if they do so, any callers of this
156
+ * method are responsible for also calling setComplete() to ensure loaded objects are marked as PRESENT.
143
157
* Throw an exception to indicate an error has occurred and the building process cannot complete.
144
158
* It's not entirely clear this should return a value - might be better to call setValue within the overridden
145
159
* method, just as we call setProgress or setError.
146
160
*/
147
- protected abstract V buildValue (K key ) throws Exception ;
161
+ protected abstract V buildValue (K key );
148
162
149
163
/**
150
164
* Call this method inside the buildValue method to indicate progress.
@@ -155,13 +169,4 @@ public void setProgress(K key, int percentComplete, String message) {
155
169
}
156
170
}
157
171
158
- /**
159
- * Call this method inside the buildValue method to indicate that an unrecoverable error has happened.
160
- * FIXME this will permanently associate an error with the key. No further attempt will ever be made to create the value.
161
- */
162
- protected void setError (K key , Throwable throwable ) {
163
- synchronized (map ) {
164
- map .put (key , new LoaderState (throwable ));
165
- }
166
- }
167
172
}
0 commit comments