Обновить

Комментарии 6

Это все конечно замечательно, но что мешало обновить версию зависимости, в которой 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 тесты, которые воспроизводят проблему тоже

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации