-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Describe the bug
Jackson will leak a file descriptor when using writeValue(File resultFile, Object value) in ObjectMapper and a failure occurs during closing the internal stream that's owned by Jackson (e.g. when flushing buffers). In this case, a file stream is created but the access to the file content is prohibited. This leads to the exceptional case that the stream is never closed and leaked. This bug may affect jackson.core but I think the core issues is located in databind's _writeValueAndClose due to poor resource handling.
Stack Trace:
writeBytes:-1, FileOutputStream (java.io)
write:354, FileOutputStream (java.io)
_flushBuffer:2171, UTF8JsonGenerator (com.fasterxml.jackson.core.json)
close:1214, UTF8JsonGenerator (com.fasterxml.jackson.core.json)
_writeValueAndClose:4573, ObjectMapper (com.fasterxml.jackson.databind)
writeValue:3763, ObjectMapper (com.fasterxml.jackson.databind)
lambda$testJacksonBug2$1:54, MainTest
execute:-1, 1778629809 (MainTest$$Lambda$338)
assertThrows:55, AssertThrows (org.junit.jupiter.api)
assertThrows:37, AssertThrows (org.junit.jupiter.api)
assertThrows:3082, Assertions (org.junit.jupiter.api)
testJacksonBug2:54, MainTest
Version information
v2.13.3
To Reproduce
This bug is a little bit tricky to reproduce but you can lock a file on Windows, where you can successfully create a file stream (with a valid file descriptor) but without access to any file content. On Unix, you need a lot of "luck" to get this case happening.
- Create a demo Java project with both jackson core and databind on Windows!
- Create a JUnit 5 test
- Copy example code
class MainTest {
@TempDir
File tempDir;
@Test
void testJacksonBug2() throws IOException {
Path filePath = Paths.get(tempDir.getAbsolutePath(), "example.json");
try (FileOutputStream outputStream = new FileOutputStream(filePath.toString());
FileChannel channel = outputStream.getChannel();
FileLock lock = channel.lock()) {
assertThrows(IOException.class, () -> new ObjectMapper().writeValue(filePath.toFile(), new JsonEntry()));
}
assertTrue(filePath.toFile().delete());
}
private static class JsonEntry {
public int value = 1;
}
}You'll notice that the the deletion of the example file (last line) will fail but also that JUnit is unable to clearup the temporary directory because a file descriptor is still open. Everything is fine if you comment out the "writeValue"-line or write through the locked channel.
Expected behavior
The file stream should be correctly closed, regardless of whether an exception is thrown or not.
Additional context
This issue is related to #3455 but during close of UTF8JsonGenerator.
Workaround: use an owned stream with try-with-resources and passing the stream into Jackson.