Skip to content

Commit dede93d

Browse files
committed
prepare geotiff zips in blocking endpoint
Previously prepared in background task action because it was very slow. Reverses r5 PR #938 which is hopefully no longer necessary after PR #986
1 parent 6637912 commit dede93d

1 file changed

Lines changed: 35 additions & 57 deletions

File tree

src/main/java/com/conveyal/analysis/controllers/RegionalAnalysisController.java

Lines changed: 35 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,6 @@ private HumanKey getSingleCutoffGrid (
270270
return new HumanKey(singleCutoffFileStorageKey, resultHumanFilename);
271271
}
272272

273-
// Prevent multiple requests from creating the same files in parallel.
274-
// This could potentially be integrated into FileStorage with enum return values or an additional boolean method.
275-
private Set<String> filesBeingPrepared = Collections.synchronizedSet(new HashSet<>());
276-
277273
private Object getAllRegionalResults (Request req, Response res) throws IOException {
278274
final String regionalAnalysisId = req.params("_id");
279275
final UserPermissions userPermissions = UserPermissions.from(req);
@@ -282,62 +278,44 @@ private Object getAllRegionalResults (Request req, Response res) throws IOExcept
282278
throw AnalysisServerException.badRequest("Batch result download only available for gridded origins.");
283279
}
284280
FileStorageKey zippedResultsKey = new FileStorageKey(RESULTS, analysis._id + "_ALL.zip");
285-
if (fileStorage.exists(zippedResultsKey)) {
286-
res.type(APPLICATION_JSON.asString());
287-
String analysisHumanName = humanNameForEntity(analysis);
288-
return fileStorage.getJsonUrl(zippedResultsKey, analysisHumanName, "zip");
289-
}
290-
if (filesBeingPrepared.contains(zippedResultsKey.path)) {
291-
res.type(TEXT_PLAIN.asString());
292-
res.status(HttpStatus.ACCEPTED_202);
293-
return "Geotiff zip is already being prepared in the background.";
294-
}
295-
// File did not exist. Create it in the background and ask caller to request it later.
296-
filesBeingPrepared.add(zippedResultsKey.path);
297-
Task task = Task.create("Zip all geotiffs for regional analysis " + analysis.name)
298-
.forUser(userPermissions)
299-
.withAction(progressListener -> {
300-
int nSteps = analysis.destinationPointSetIds.length * analysis.cutoffsMinutes.length *
301-
analysis.travelTimePercentiles.length * 2 + 1;
302-
progressListener.beginTask("Creating and archiving geotiffs...", nSteps);
303-
// Iterate over all dest, cutoff, percentile combinations and generate one geotiff for each combination.
304-
List<HumanKey> humanKeys = new ArrayList<>();
305-
for (String destinationPointSetId : analysis.destinationPointSetIds) {
306-
OpportunityDataset destinations = getDestinations(destinationPointSetId, userPermissions);
307-
// TODO handle dual access
308-
for (int cutoffMinutes : analysis.cutoffsMinutes) {
309-
for (int percentile : analysis.travelTimePercentiles) {
310-
HumanKey gridKey = getSingleCutoffGrid(
311-
analysis, destinations, cutoffMinutes, percentile, GridResultType.ACCESS, FileStorageFormat.GEOTIFF
312-
);
313-
humanKeys.add(gridKey);
314-
progressListener.increment();
315-
}
281+
if (!fileStorage.exists(zippedResultsKey)) {
282+
// File did not exist, so create it. This endpoint blocks:
283+
// it is no longer done in the background as it's reasonably fast after PR #986.
284+
// Nothing prevents multiple requests from creating the same files in parallel.
285+
// Other than wasted compute that should be harmless, with all actions taken idempotent.
286+
// Iterate over all dest, cutoff, percentile combinations and generate one geotiff for each combination.
287+
List<HumanKey> humanKeys = new ArrayList<>();
288+
for (String destinationPointSetId : analysis.destinationPointSetIds) {
289+
OpportunityDataset destinations = getDestinations(destinationPointSetId, userPermissions);
290+
// TODO handle dual access
291+
for (int cutoffMinutes : analysis.cutoffsMinutes) {
292+
for (int percentile : analysis.travelTimePercentiles) {
293+
HumanKey gridKey = getSingleCutoffGrid(analysis, destinations, cutoffMinutes,
294+
percentile, GridResultType.ACCESS, FileStorageFormat.GEOTIFF);
295+
humanKeys.add(gridKey);
316296
}
317297
}
318-
File tempZipFile = File.createTempFile("regional", ".zip");
319-
// Zipfs can't open existing empty files, the file has to not exist. FIXME: Non-dangerous race condition
320-
// Examining ZipFileSystemProvider reveals a "useTempFile" env parameter, but this is for the individual
321-
// entries. May be better to just use zipOutputStream which would also allow gzip - zip CSV conversion.
322-
tempZipFile.delete();
323-
Map<String, String> env = Map.of("create", "true");
324-
URI uri = URI.create("jar:file:" + tempZipFile.getAbsolutePath());
325-
try (FileSystem zipFilesystem = FileSystems.newFileSystem(uri, env)) {
326-
for (HumanKey key : humanKeys) {
327-
Path storagePath = fileStorage.getFile(key.storageKey).toPath();
328-
Path zipPath = zipFilesystem.getPath(key.humanName);
329-
Files.copy(storagePath, zipPath, StandardCopyOption.REPLACE_EXISTING);
330-
progressListener.increment();
331-
}
298+
}
299+
File tempZipFile = File.createTempFile("regional", ".zip");
300+
// Zipfs can't open existing empty files, the file has to not exist.
301+
// Deleting the temp file and then immediately re-creating it is a race condition but apparently harmless.
302+
// Examining ZipFileSystemProvider reveals a "useTempFile" env parameter, but this is for the individual
303+
// entries. May be better to just use zipOutputStream which would also allow gzip - zip CSV conversion.
304+
tempZipFile.delete();
305+
Map<String, String> env = Map.of("create", "true");
306+
URI uri = URI.create("jar:file:" + tempZipFile.getAbsolutePath());
307+
try (FileSystem zipFilesystem = FileSystems.newFileSystem(uri, env)) {
308+
for (HumanKey key : humanKeys) {
309+
Path storagePath = fileStorage.getFile(key.storageKey).toPath();
310+
Path zipPath = zipFilesystem.getPath(key.humanName);
311+
Files.copy(storagePath, zipPath, StandardCopyOption.REPLACE_EXISTING);
332312
}
333-
fileStorage.moveIntoStorage(zippedResultsKey, tempZipFile);
334-
progressListener.increment();
335-
filesBeingPrepared.remove(zippedResultsKey.path);
336-
});
337-
taskScheduler.enqueue(task);
338-
res.type(TEXT_PLAIN.asString());
339-
res.status(HttpStatus.ACCEPTED_202);
340-
return "Building geotiff zip in background.";
313+
}
314+
fileStorage.moveIntoStorage(zippedResultsKey, tempZipFile);
315+
}
316+
res.type(APPLICATION_JSON.asString());
317+
String analysisHumanName = humanNameForEntity(analysis);
318+
return fileStorage.getJsonUrl(zippedResultsKey, analysisHumanName, "zip");
341319
}
342320

343321
/**

0 commit comments

Comments
 (0)