Class SourceImagePrewarmService
SourceImageCache.
Without prewarm, the first viewer of a cold-cache page pays the full source-decode latency
spread across the parallel tile requests: every tile thread either ROI-decodes the source
itself (cache disabled) or waits for the burst-coalesce decoder (cache enabled but cold —
the first tile triggers the decode, every subsequent tile waits). With prewarm, the decoder
runs once during HTML render — by the time the browser starts requesting tiles, the cache
already holds the decoded BufferedImage and every tile is served via cheap
BufferedImage.getSubimage(int, int, int, int).
The service is a JVM-wide singleton because the underlying SourceImageCache is
itself a singleton and a single bounded executor avoids one Tomcat session monopolising
decode threads at the expense of others. Tasks are submitted to a small pool (4 threads) with
a bounded queue; overflow is dropped silently — prewarm is best-effort, never load-bearing.
The decode runs on a worker thread that calls getRenderedImage(null) on a fresh
ImageManager. That call hits AbstractImageInterpreter's cache hook which —
after the prewarm thread's own registerRequestAndIsBurst primer — takes the
burst-coalesce path and lands in SourceImageCache.getOrDecodeFull(java.net.URI, de.unigoettingen.sub.commons.cache.SourceImageCache.ImageDecoder). The first thread
(us) wins the race, decodes the master, and populates the cache. Tile threads arriving
concurrently see the in-flight CompletableFuture and wait,
which is exactly the desired coalescing behavior.
-
Method Summary
Modifier and TypeMethodDescriptionlonglonglongstatic SourceImagePrewarmServicelonglongvoidprewarm(PhysicalElement page) Resolves the source-image URI of the given page and submits an async prewarm.voidSubmits an async prewarm for the given source-image URI.voidprewarmByPiAndPage(String pi, int pageOrder) Submits an async prewarm for the page identified by record PI and 1-based page order.voidshutdown()Stops the prewarm executor cleanly so Tomcat does not report a memory-leak warning at webapp undeploy.
-
Method Details
-
getInstance
-
prewarm
Resolves the source-image URI of the given page and submits an async prewarm. Silently returns when the page is null, has no usable file path, or fails to resolve — prewarm is best-effort and never throws to the caller.- Parameters:
page- page whose master image should be pre-decoded; may be null
-
prewarmByPiAndPage
Submits an async prewarm for the page identified by record PI and 1-based page order. Returns immediately. The Solr lookup that resolves PI+order to the master file path runs on a worker thread, never on the caller's thread (typically a servlet request thread that must hand the response back to the user as fast as possible).Used by
PrewarmRequestFilterwhich fires before JSF is even involved — earliest possible point in the request lifecycle to start an async master-image decode.- Parameters:
pi- record PI; null/blank short-circuitspageOrder- 1-based page order; values < 1 short-circuit
-
prewarm
Submits an async prewarm for the given source-image URI. Returns immediately. The actual decode happens on a worker thread some time later. Safe to call repeatedly with the same URI: a duplicate submission while a previous prewarm is still in flight short-circuits inSourceImageCache.getOrDecodeFull(java.net.URI, de.unigoettingen.sub.commons.cache.SourceImageCache.ImageDecoder)(one decoder runs, the other waits and serves the same image), and a submission for an already-cached URI returns without doing work.- Parameters:
sourceUri- URI of the master image file (typicallyfile://...). May be null — null is silently ignored to keep callers free of preconditions.
-
shutdown
public void shutdown()Stops the prewarm executor cleanly so Tomcat does not report a memory-leak warning at webapp undeploy. Discards any queued tasks (prewarm is best-effort, so dropping pending decodes is fine), interrupts in-flight worker threads and waits up to 5 seconds for them to terminate.Called from
ContextListener#contextDestroyedBEFORE the Solr client is closed, becauseresolveAndPrewarm(java.lang.String, int)performs a Solr lookup; shutting down the executor first guarantees no worker is still running when Solr disappears. -
getSubmittedCount
public long getSubmittedCount()- Returns:
- number of prewarm tasks ever submitted to the executor
-
getCompletedCount
public long getCompletedCount()- Returns:
- number of prewarm tasks that ran to a successful decode
-
getDroppedCount
public long getDroppedCount()- Returns:
- number of prewarm submissions rejected by the executor (e.g. saturated queue)
-
getSkippedAlreadyCachedCount
public long getSkippedAlreadyCachedCount()- Returns:
- number of prewarm calls short-circuited because the URI was already cached
-
getFailedCount
public long getFailedCount()- Returns:
- number of prewarm tasks that failed (file missing, decode error, Solr lookup)
-