@@ -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