diff --git a/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java b/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java index 3cbf5a1028a7..ee7b43059187 100644 --- a/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java +++ b/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java @@ -171,4 +171,90 @@ private static boolean isAndroid() { private static boolean isWindows() { return OS_NAME.value().startsWith("Windows"); } + + /** + * Test that verifies the resource leak fix for Issue #5756. + * + * This test covers a scenario where we write a smaller amount of data first, + * then write a large amount that crosses the threshold (transitioning from + * "not at threshold" to "over the threshold"). This differs from the existing + * testThreshold() which writes exactly enough bytes to fill the buffer, then + * immediately writes more bytes. + * + * Note: Direct testing of the IOException scenario during write/flush is challenging + * without mocking. This test verifies that normal operation with threshold crossing + * still works correctly with the fix in place. + */ + public void testThresholdCrossing_ResourceManagement() throws Exception { + // Test data that will cross the threshold + int threshold = 50; + byte[] beforeThreshold = newPreFilledByteArray(40); + byte[] afterThreshold = newPreFilledByteArray(30); + + FileBackedOutputStream out = new FileBackedOutputStream(threshold); + ByteSource source = out.asByteSource(); + + // Write data that doesn't cross threshold + out.write(beforeThreshold); + assertNull(out.getFile()); + + // Write data that crosses threshold - this exercises the fixed code path + if (!JAVA_IO_TMPDIR.value().equals("/sdcard")) { + out.write(afterThreshold); + File file = out.getFile(); + assertNotNull(file); + assertTrue(file.exists()); + + // Verify all data was written correctly + byte[] expected = new byte[70]; + System.arraycopy(beforeThreshold, 0, expected, 0, 40); + System.arraycopy(afterThreshold, 0, expected, 40, 30); + assertTrue(Arrays.equals(expected, source.read())); + + // Clean up + out.close(); + out.reset(); + assertFalse(file.exists()); + } + } + + /** + * Test that verifies writes after crossing the threshold work correctly. + * + * Once the threshold is crossed, subsequent writes go to the file. This test + * ensures that continued writing after the initial threshold crossing works + * properly with the resource management fix in place. + */ + public void testWriteAfterThresholdCrossing() throws Exception { + // Use a small threshold to force multiple file operations + int threshold = 10; + FileBackedOutputStream out = new FileBackedOutputStream(threshold); + ByteSource source = out.asByteSource(); + + // Write data in chunks: first below threshold, then crossing it, then after crossing + byte[] chunk1 = newPreFilledByteArray(8); // Below threshold + byte[] chunk2 = newPreFilledByteArray(5); // Crosses threshold + byte[] chunk3 = newPreFilledByteArray(20); // More data to file + + out.write(chunk1); + assertNull(out.getFile()); + + if (!JAVA_IO_TMPDIR.value().equals("/sdcard")) { + out.write(chunk2); + File file = out.getFile(); + assertNotNull(file); + + out.write(chunk3); + + // Verify all data is correct + byte[] expected = new byte[33]; + System.arraycopy(chunk1, 0, expected, 0, 8); + System.arraycopy(chunk2, 0, expected, 8, 5); + System.arraycopy(chunk3, 0, expected, 13, 20); + assertTrue(Arrays.equals(expected, source.read())); + + out.close(); + out.reset(); + } + } } diff --git a/guava/src/com/google/common/io/FileBackedOutputStream.java b/guava/src/com/google/common/io/FileBackedOutputStream.java index ee7cc83c5d1d..4fe78aac11cd 100644 --- a/guava/src/com/google/common/io/FileBackedOutputStream.java +++ b/guava/src/com/google/common/io/FileBackedOutputStream.java @@ -238,13 +238,22 @@ private void update(int len) throws IOException { // this is insurance. temp.deleteOnExit(); } + // Create and populate the file, ensuring proper resource management + FileOutputStream transfer = null; try { - FileOutputStream transfer = new FileOutputStream(temp); + transfer = new FileOutputStream(temp); transfer.write(memory.getBuffer(), 0, memory.getCount()); transfer.flush(); // We've successfully transferred the data; switch to writing to file out = transfer; } catch (IOException e) { + if (transfer != null) { + try { + transfer.close(); + } catch (IOException closeException) { + e.addSuppressed(closeException); + } + } temp.delete(); throw e; }