Skip to content

Commit 5648f0b

Browse files
ansoncfitabyrd
andauthored
Fix preloading
* Set Status.PRESENT after synchronous preload Fixes #949 * single underlying get method in AsyncLoader The nonblocking version of get just runs getBlocking in a thread. NetworkPreloader has corresponding preload and preloadBlocking methods. * inline setError method Method comments said it was called in the buildValue implementation methods but it was actually only ever called in AsyncLoader error handling code. * add comments explaining current exception behavior * move initial message back to async runnable Just to keep behavior more similar to previous versions. This could be changed in the future if we do a more significant refactor of how exceptions are handled and passed up to the backend. --------- Co-authored-by: Andrew Byrd <[email protected]>
1 parent 9ba72c3 commit 5648f0b

File tree

3 files changed

+33
-27
lines changed

3 files changed

+33
-27
lines changed

src/main/java/com/conveyal/r5/analyst/NetworkPreloader.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public NetworkPreloader(TransportNetworkCache transportNetworkCache) {
7979
this.transportNetworkCache = transportNetworkCache;
8080
}
8181

82-
public LoaderState<TransportNetwork> preloadData (AnalysisWorkerTask task) {
82+
public LoaderState<TransportNetwork> preload (AnalysisWorkerTask task) {
8383
if (task.scenario != null) {
8484
transportNetworkCache.rememberScenario(task.scenario);
8585
}
@@ -94,10 +94,11 @@ public LoaderState<TransportNetwork> preloadData (AnalysisWorkerTask task) {
9494
* similar tasks will make interleaved calls to setProgress (with superficial map synchronization). Other than
9595
* causing a value to briefly revert from PRESENT to BUILDING this doesn't seem deeply problematic.
9696
* This is provided specifically for regional tasks, to ensure that they remain in preloading mode while all this
97-
* data is prepared.
97+
* data is prepared.
98+
* Any exceptions that occur while building the network will escape this method, leaving the status as BUILDING.
9899
*/
99-
public TransportNetwork synchronousPreload (AnalysisWorkerTask task) {
100-
return buildValue(Key.forTask(task));
100+
public TransportNetwork preloadBlocking (AnalysisWorkerTask task) {
101+
return getBlocking(Key.forTask(task));
101102
}
102103

103104
@Override
@@ -140,7 +141,7 @@ protected TransportNetwork buildValue(Key key) {
140141
linkedPointSet.getEgressCostTable(progressListener);
141142
}
142143
}
143-
// Finished building all needed inputs for analysis, return the completed network to the AsyncLoader code.
144+
// Finished building all needed inputs for analysis, return the completed network
144145
return scenarioNetwork;
145146
}
146147

src/main/java/com/conveyal/r5/analyst/cluster/AnalysisWorker.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public static void sleepSeconds (int seconds) {
314314
protected byte[] handleAndSerializeOneSinglePointTask (TravelTimeSurfaceTask task) throws IOException {
315315
LOG.debug("Handling single-point task {}", task.toString());
316316
// Get all the data needed to run one analysis task, or at least begin preparing it.
317-
final AsyncLoader.LoaderState<TransportNetwork> networkLoaderState = networkPreloader.preloadData(task);
317+
final AsyncLoader.LoaderState<TransportNetwork> networkLoaderState = networkPreloader.preload(task);
318318

319319
// If loading is not complete, bail out of this function.
320320
// Ideally we'd stall briefly using something like Future.get(timeout) in case loading finishes quickly.
@@ -467,7 +467,7 @@ protected void handleOneRegionalTask (RegionalTask task) throws Throwable {
467467
// LoadingCaches behind it. Specifically, the TransportNetworkCache and LinkageCache enforce turn-taking and
468468
// prevent redundant work. If those are ever removed, we would need either async regional task preparation, or
469469
// a synchronous mode with per-key blocking on AsyncLoader (kind of reinventing the wheel of LoadingCache).
470-
TransportNetwork transportNetwork = networkPreloader.synchronousPreload(task);
470+
TransportNetwork transportNetwork = networkPreloader.preloadBlocking(task);
471471

472472
// If we are generating a static site, there must be a single metadata file for an entire batch of results.
473473
// Arbitrarily we create this metadata as part of the first task in the job.

src/main/java/com/conveyal/r5/util/AsyncLoader.java

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* "value is present in map".
2929
*
3030
* 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.
3232
* 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.
3333
*
3434
* Created by abyrd on 2018-09-14
@@ -97,10 +97,23 @@ public String toString() {
9797
}
9898
}
9999

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+
100112
/**
101113
* Attempt to fetch the value for the supplied key.
102114
* If the value is not yet present, and not yet being computed / fetched, enqueue a task to do so.
103115
* 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.
104117
*/
105118
public LoaderState<V> get (K key) {
106119
LoaderState<V> state = null;
@@ -109,7 +122,7 @@ public LoaderState<V> get (K key) {
109122
state = map.get(key);
110123
if (state == null) {
111124
// 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);
113126
map.put(key, state);
114127
enqueueLoadTask = true;
115128
}
@@ -120,16 +133,16 @@ public LoaderState<V> get (K key) {
120133
// Enqueue task outside the above block (synchronizing the fewest lines possible).
121134
if (enqueueLoadTask) {
122135
executor.execute(() -> {
123-
setProgress(key, 0, "Starting...");
124136
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);
129139
} catch (Throwable t) {
130140
// 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+
}
133146
LOG.error("Async load failed: " + ExceptionUtils.stackTraceString(t));
134147
}
135148
});
@@ -139,12 +152,13 @@ public LoaderState<V> get (K key) {
139152

140153
/**
141154
* 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.
143157
* Throw an exception to indicate an error has occurred and the building process cannot complete.
144158
* It's not entirely clear this should return a value - might be better to call setValue within the overridden
145159
* method, just as we call setProgress or setError.
146160
*/
147-
protected abstract V buildValue(K key) throws Exception;
161+
protected abstract V buildValue(K key);
148162

149163
/**
150164
* Call this method inside the buildValue method to indicate progress.
@@ -155,13 +169,4 @@ public void setProgress(K key, int percentComplete, String message) {
155169
}
156170
}
157171

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-
}
167172
}

0 commit comments

Comments
 (0)