Copilot commented on code in PR #3067:
URL: https://github.com/apache/tika/pull/3067#discussion_r3860503252


##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/detect/microsoft/POIFSContainerDetector.java:
##########
@@ -583,16 +597,54 @@ private Set<String> getTopLevelNames(TikaInputStream 
stream) throws IOException
         }
     }
 
+    /**
+     * Opens the OLE2 container from memory instead of spooling it to a temp 
file. Returns
+     * null when the object is too large for the in-memory limit or POI cannot 
load it from a
+     * stream (the caller then spools, as before).
+     */
+    private Set<String> getTopLevelNamesInMemory(TikaInputStream stream, 
ParseContext context)
+            throws IOException {
+        CacheMemoryBudget budget = context == null ? null : 
context.get(CacheMemoryBudget.class);
+        long limit = budget == null ? DEFAULT_IN_MEMORY_POIFS :
+                Math.min(MAX_IN_MEMORY_POIFS, budget.getMaxBytes());
+        // attach the budget first: without it the drain below caches only the 
per-object
+        // default (1MB) in memory and spills the rest before we can even 
check the size
+        stream.enableRewind(budget);
+        // the channel is served from memory while the content fits the 
cache/budget
+        try (SeekableByteChannel channel = stream.getSeekableByteChannel()) {
+            long size = channel.size();
+            if (size > limit) {
+                return null;
+            }
+            if (budget != null) {
+                if (budget.tryReserve(size) == 0) {
+                    return null;
+                }
+                stream.addCloseableResource(() -> budget.release(size));
+            }
+            POIFSFileSystem fs = new 
POIFSFileSystem(Channels.newInputStream(channel));
+            stream.setOpenContainer(fs);
+            return getTopLevelNames(fs.getRoot());

Review Comment:
   When POI's stream loader throws (IOException/RuntimeException), this method 
returns null and falls back to spooling, but the CacheMemoryBudget reservation 
added at `stream.addCloseableResource(() -> budget.release(size))` is still 
held until the TikaInputStream is closed. That can artificially reduce the 
shared budget and force unrelated caches to spill even though the in-memory 
POIFS path was abandoned.
   
   Consider only registering the release callback after successfully 
constructing the POIFSFileSystem, and explicitly releasing the reservation on 
failure before returning null.



##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java:
##########
@@ -315,10 +321,25 @@ private void scanXRefOffsets(PDFParserConfig localConfig,
         List<StartXRefOffset> xRefOffsets = new ArrayList<>();
         //TODO -- can we use the PDFBox parser's RandomAccessRead
         //so that we don't have to reopen from file?
-        try (RandomAccessRead ra =
-                     new 
RandomAccessReadBufferedFile(tikaInputStream.getFile())) {
-            StartXRefScanner xRefScanner = new StartXRefScanner(ra);
-            xRefOffsets.addAll(xRefScanner.scan());
+        // In-memory input is scanned from memory rather than spooled to a 
file for this
+        // pass. Rewind support is enabled first so the later parse (and a 
renderer's
+        // getPath(), if rendering is on) can re-read the content from the 
cache: the old
+        // getFile() spool used to provide that as a side effect.
+        boolean fileBacked = tikaInputStream.hasFile();
+        try {
+            if (!fileBacked) {
+                
tikaInputStream.enableRewind(parseContext.get(CacheMemoryBudget.class));
+            }
+            try (RandomAccessRead ra = fileBacked ?
+                    new 
RandomAccessReadBufferedFile(tikaInputStream.getFile()) :
+                    new 
RandomAccessReadBuffer(CloseShieldInputStream.wrap(tikaInputStream))) {
+                StartXRefScanner xRefScanner = new StartXRefScanner(ra);

Review Comment:
   For non-file-backed input, this path switches to 
`RandomAccessReadBuffer(...)`, which is an in-memory random-access 
implementation. For large PDFs provided via stream-backed TikaInputStream, this 
can substantially increase peak heap usage (potentially duplicating the PDF 
bytes), whereas the previous `RandomAccessReadBufferedFile` path kept the scan 
largely off-heap/disk-backed.
   
   It may be worth adding a size/budget-based cutoff (e.g., fall back to 
spooling and `RandomAccessReadBufferedFile` when the PDF exceeds the 
CacheMemoryBudget or some fixed threshold) to avoid unexpected heap pressure on 
large inputs.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to