Comments 8
Это все конечно замечательно, но что мешало обновить версию зависимости, в которой dispose делается автоматически?
Да я не знал, что у меня вообще есть проблема в проде - диск тек тихо, а узнал по инциденту, а не по changelog’у POI. «Просто обновись» работает, когда заранее знаешь, что искать
Плюс апгрейд POI в живом проекте — не однострочник: поведение форматтеров/стилей, конфликты версий с другими либами. Защитный try/finally + ArchUnit + тест на tmp-файлы дают гарантию здесь и сейчас, независимо от версии.
Но большое спасибо за наводку, посмотрю свежие версии либы!
Это поведение по доке, а не баг библиотеки.
Немного странное поведение, ИМХО. Насколько я вижу в https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/streaming/SXSSFWorkbook.html#close-- написано, что
Once this has been called, no further operations, updates or reads should be performed on the Workbook.
Т. е. смысла держать временные файлы нет, ведь исходный том уже протух и при его повторной обработке всё начнётся с самого начала.
Да, тут как раз и прикол, что дока сама себе противоречит. С одной стороны после close() с воркбуком работать уже нельзя. С другой - tmp-файлы спокойно остаются лежать. По-факту это баг, по доке это фича.
В общем, починил у себя через dispose() и докинул в functional-тесты прямо проверку этого контракта. Получилось четыре теста:
с dispose() после успешной записи - tmp-файлов нет
с dispose() в finally после исключения - tmp-файлов нет
без dispose() после успешной записи - tmp-файл остался
без dispose() после исключения - tmp-файл остался
Последние два и есть пруф, что без dispose() файлы реально текут. Если в будущей версии POI причешут поведение и close() сам начнёт чистить - эти два теста покраснеют, и сразу станет видно, что костыль с dispose() уже не нужен
Вод код тестов
@Test
void withoutDisposeShouldKeepPoiTempFilesAfterSuccessfulClose(@TempDir Path poiTempDir) throws IOException {
TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(poiTempDir.toFile()));
// window=1 POI флашит каждую строку на диск, tmp-файл гарантирован
try (SXSSFWorkbook wb = new SXSSFWorkbook(1)) {
wb.setCompressTempFiles(true);
var sheet = wb.createSheet("contract-doc");
for (int i = 0; i < 100; i++) {
sheet.createRow(i).createCell(0).setCellValue("row " + i);
}
wb.write(new ByteArrayOutputStream());
} // try-with-resources вызвал close() но НЕ dispose()
assertPoiSheetFilesPresent(poiTempDir); // tmp-файл всё ещё на диске
}
@Test
void withoutDisposeShouldKeepPoiTempFilesAfterFailedWrite(@TempDir Path poiTempDir) throws IOException {
TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(poiTempDir.toFile()));
SXSSFWorkbook wb = new SXSSFWorkbook(1);
try (OutputStream failing = new OutputStream() {
@Override public void write(int b) throws IOException { throw new IOException("boom"); }
}) {
wb.setCompressTempFiles(true);
var sheet = wb.createSheet("contract-doc");
for (int i = 0; i < 100; i++) {
sheet.createRow(i).createCell(0).setCellValue("row " + i);
}
assertThatThrownBy(() -> wb.write(failing)).isInstanceOf(IOException.class);
} finally {
wb.close(); // close без dispose — симулируем «забытый» dispose
}
assertPoiSheetFilesPresent(poiTempDir);
}Спасибо за уточнение, докинул дополнительно 2 тесты, которые воспроизводят проблему тоже
Давно не смотрел Apache POI, как у вас реализована потоковая обработка файлов? Раньше была какая-то доп.библиотека, потом ее перестали поддерживать и она перестала работать с новыми версиями POI. А потом я ушел из проекта, где надо было парсить excel ))
На запись как раз тот самый SXSSFWorkbook из статьи. Параметры у нас такие: rowAccessWindowSize = 500 (сколько строк держим в памяти), setCompressTempFiles(true) (gzip временных листов на диске) и руками sheet.flushRows(500) каждые 5000 строк, чтобы окно гарантированно не разъезжалось при больших файлах. Плюс особенность - мы пишем поверх XLSX-шаблона (new SXSSFWorkbook(xssfTemplate, 500)), так что в finally закрываем оба воркбука (со всеми нюансами dispose(), про которые в статье)
На чтение используем Poiji. Это обёртка над POI, которая по аннотациям @ExcelRow / @ExcelCell маппит строки в POJO. Под капотом тот же POI, но без ручного хождения по Row/Cell. Поддерживается, с актуальной POI 5.x работает
Доп. библиотека, про которую вспоминаешь это, видимо, excel-streaming-reader. Сейчас живой форк есть- pjfanning/excel-streaming-reader, нормально дружит с POI 5.x, его можно брать, если нужен именно стриминговый reader в SAX-режие. У нас объёмы на чтение небольшие, поэтому хватает Poiji’я и стандартного POI
Шёл за утечкой памяти, нашёл утечку диска: SXSSFWorkbook без dispose() в Apache POI