From d917ba465805fe690c1e73a19094bc6a4bea0725 Mon Sep 17 00:00:00 2001 From: Adam Wright Date: Thu, 29 Oct 2026 19:37:42 +0000 Subject: [PATCH] Spec 015 T069, from #755's review: check.sh's silent stop, old work, a fair cap check.sh's event selection used `grep && echo` in a loop: under pipefail a last candidate not in its file ended the script silently; it is an if now, and it matches the event's own database-id xref (its comment ends with the id), not any numeric id. The service clears work left in the layout before exports had a folder each (cache/tmp/{uuid}), with a test. The shared cap is fair, so a waiting request of one export is not overtaken by later ones of the other. Node's comment says the queued worst case passes the headers' time and gets the 503, as T069 measured. Co-Authored-By: Claude Opus 5.5 --- deploy/java-library/check.sh | 7 ++++++- .../org/reactome/server/library/ExportFiles.java | 12 +++++++++++- .../reactome/server/library/LibraryApplication.java | 7 +++++-- .../org/reactome/server/library/ExportFilesTest.java | 10 ++++++++++ tools/content-node/exporters-endpoints.ts | 4 +++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/deploy/java-library/check.sh b/deploy/java-library/check.sh index 38932c61..8e4c761d 100755 --- a/deploy/java-library/check.sh +++ b/deploy/java-library/check.sh @@ -142,7 +142,12 @@ candidates=$(docker exec "$neo2" cypher-shell -u neo4j -p fixturepass -d graph.d RETURN kind + " " + e.stId + " " + e.dbId + " " + replace(e.speciesName, " ", "_") ORDER BY kind, e.dbId' | tail -n +2 | tr -d '"') events=$(while read -r kind id dbid species; do - grep -q ">$dbid" "$bci/fixture/expected/$species.owl" && echo "$kind $id $dbid $species" + # The event's own database-id xref, whose comment ends with its id: not any + # numeric id (a PubMed id) that happens to match. An if, not &&: under + # pipefail a last candidate not in its file would end the script silently. + if grep -q "&ID=$dbid" "$bci/fixture/expected/$species.owl"; then + echo "$kind $id $dbid $species" + fi done <<<"$candidates" | sort -u -k1,1 | cut -d' ' -f2-) [ "$(printf '%s\n' "$events" | wc -l)" -eq 5 ] || { echo "Expected one event of each of five kinds in the fixture's files, got: $events" >&2; exit 1; } diff --git a/deploy/java-library/src/main/java/org/reactome/server/library/ExportFiles.java b/deploy/java-library/src/main/java/org/reactome/server/library/ExportFiles.java index 97f8b063..c9fda0ea 100644 --- a/deploy/java-library/src/main/java/org/reactome/server/library/ExportFiles.java +++ b/deploy/java-library/src/main/java/org/reactome/server/library/ExportFiles.java @@ -74,6 +74,8 @@ public DiagramUnavailable(String message, Throwable cause) { } private static final Logger logger = LoggerFactory.getLogger(ExportFiles.class); + private static final java.util.regex.Pattern OLD_WORK = + java.util.regex.Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); private final String kind; private final String extension; @@ -100,8 +102,16 @@ public ExportFiles(String kind, String extension, Graph graph, Converter convert this.diagrams = diagrams; this.cache = cache; this.conversions = conversions; - // Work in progress when the service last stopped, which nothing will finish + // Work in progress when the service last stopped, which nothing will finish: this export's, and any left + // in the layout before exports had a folder each (cache/tmp/{uuid}) delete(work()); + try (DirectoryStream left = Files.newDirectoryStream(cache.resolve("tmp"))) { + for (Path old : left) { + if (OLD_WORK.matcher(old.getFileName().toString()).matches()) delete(old); + } + } catch (IOException e) { + // No tmp folder yet, or none readable: nothing to clear + } } private Path work() { diff --git a/deploy/java-library/src/main/java/org/reactome/server/library/LibraryApplication.java b/deploy/java-library/src/main/java/org/reactome/server/library/LibraryApplication.java index f4995633..8c571bed 100644 --- a/deploy/java-library/src/main/java/org/reactome/server/library/LibraryApplication.java +++ b/deploy/java-library/src/main/java/org/reactome/server/library/LibraryApplication.java @@ -59,10 +59,13 @@ public Optional event(String stId) { }; } - /** The conversions at once, across every export: two, within the heap (spec 015 T069). */ + /** + * The conversions at once, across every export: two, within the heap (spec 015 T069). Fair, so a request + * waiting for a place is not overtaken by later ones of the other export. + */ @Bean public Semaphore conversions() { - return new Semaphore(2); + return new Semaphore(2, true); } @Bean diff --git a/deploy/java-library/src/test/java/org/reactome/server/library/ExportFilesTest.java b/deploy/java-library/src/test/java/org/reactome/server/library/ExportFilesTest.java index 9cecb3b9..eaefae9f 100644 --- a/deploy/java-library/src/test/java/org/reactome/server/library/ExportFilesTest.java +++ b/deploy/java-library/src/test/java/org/reactome/server/library/ExportFilesTest.java @@ -194,6 +194,16 @@ void workLeftByAStoppedServiceIsRemoved() throws Exception { assertTrue(Files.exists(others)); } + @Test + void workLeftInTheLayoutBeforeExportsHadFoldersIsRemoved() throws Exception { + Path old = Files.createDirectories(cache.resolve("tmp/0f8fad5b-d9cb-469f-a165-70867728950e")); + Files.writeString(old.resolve("R-HSA-2.sbml"), " { * deep pool, so a burst of exports cannot starve the queries the pages need, * and from each other, so one export cannot take the other's places here. The * service converts two at once across both (spec 015 T069), for its heap: a - * BioPAX file may wait there behind two SBML ones, within the headers' time. + * BioPAX file may wait there behind two SBML ones, and past the headers' time + * at the worst (T069 measured 300–310 s): the reader then gets the 503 below, + * and the service, finishing, keeps the file for the next request. */ const SBML_SHARE = deepShare(2); const BIOPAX_SHARE = deepShare(2);