This is an automated email from the ASF dual-hosted git repository.
kezhuw pushed a commit to branch branch-3.9
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/branch-3.9 by this push:
new ad858f71c6 ZOOKEEPER-5055: Ensure FileTxnLog.close() closes every
stream
ad858f71c6 is described below
commit ad858f71c61c4696710e5c8020c72e8f6cf0c159
Author: Sanjay Malakar <[email protected]>
AuthorDate: Sun Sep 20 03:58:18 2026 -0700
ZOOKEEPER-5055: Ensure FileTxnLog.close() closes every stream
Reviewers: maoling, kezhuw
Author: iamsanjaymalakar
Closes #2405 from iamsanjaymalakar/ZOOKEEPER-5055-close-all-txn-log-streams
(cherry picked from commit b9818714f4790347c27f37fd49739a91b46c8e5e)
Signed-off-by: Kezhu Wang <[email protected]>
---
.../java/org/apache/zookeeper/common/IOUtils.java | 14 +++++++++
.../zookeeper/server/persistence/FileTxnLog.java | 11 ++++---
.../org/apache/zookeeper/common/IOUtilsTest.java | 24 +++++++++++++++
.../server/persistence/FileTxnLogTest.java | 34 ++++++++++++++++++++++
4 files changed, 77 insertions(+), 6 deletions(-)
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/common/IOUtils.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/IOUtils.java
index 388d3122fc..4f617ff6ac 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/IOUtils.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/IOUtils.java
@@ -23,6 +23,8 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
import org.slf4j.Logger;
/*
@@ -52,6 +54,18 @@ public static void closeStream(Closeable stream) {
* exceptions added as suppressed exceptions
*/
public static void closeAll(Closeable... closeables) throws IOException {
+ closeAll(Arrays.asList(closeables));
+ }
+
+ /**
+ * Closes every non-null object, preserving any {@link IOException} thrown.
+ *
+ * @param closeables
+ * the objects to close, in iteration order
+ * @throws IOException the first exception thrown while closing, with later
+ * exceptions added as suppressed exceptions
+ */
+ public static void closeAll(Collection<? extends Closeable> closeables)
throws IOException {
IOException firstException = null;
for (Closeable closeable : closeables) {
if (closeable != null) {
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/persistence/FileTxnLog.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/persistence/FileTxnLog.java
index e14e510c2b..328654cca0 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/persistence/FileTxnLog.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/persistence/FileTxnLog.java
@@ -43,6 +43,7 @@
import org.apache.jute.InputArchive;
import org.apache.jute.OutputArchive;
import org.apache.jute.Record;
+import org.apache.zookeeper.common.IOUtils;
import org.apache.zookeeper.server.Request;
import org.apache.zookeeper.server.ServerMetrics;
import org.apache.zookeeper.server.ServerStats;
@@ -264,12 +265,10 @@ public synchronized void rollLog() throws IOException {
* @throws IOException
*/
public synchronized void close() throws IOException {
- if (logStream != null) {
- logStream.close();
- }
- for (FileOutputStream log : streamsToFlush) {
- log.close();
- }
+ List<Closeable> toClose = new ArrayList<>(streamsToFlush.size() + 1);
+ toClose.add(logStream);
+ toClose.addAll(streamsToFlush);
+ IOUtils.closeAll(toClose);
}
@Override
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/IOUtilsTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/IOUtilsTest.java
index 42e8e0a7c7..0ac77da640 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/IOUtilsTest.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/IOUtilsTest.java
@@ -22,6 +22,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -108,4 +109,27 @@ public void
testCloseAllDoesNotSuppressAnExceptionOnItself() {
assertEquals(Arrays.asList(3), closed);
}
+ @Test
+ public void
testCloseAllCollectionPreservesFirstFailureAndSuppressesLaterFailures() {
+ IOException first = new IOException("first");
+ IOException second = new IOException("second");
+ List<Integer> closed = new ArrayList<>();
+ List<Closeable> closeables = new ArrayList<>();
+ closeables.add(null);
+ closeables.add(() -> {
+ closed.add(1);
+ throw first;
+ });
+ closeables.add(() -> {
+ closed.add(2);
+ throw second;
+ });
+ closeables.add(() -> closed.add(3));
+
+ IOException failure = assertThrows(IOException.class, () ->
IOUtils.closeAll(closeables));
+
+ assertSame(first, failure);
+ assertArrayEquals(new Throwable[]{second}, failure.getSuppressed());
+ assertEquals(List.of(1, 2, 3), closed);
+ }
}
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/persistence/FileTxnLogTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/persistence/FileTxnLogTest.java
index d7d8f1d447..0fdf8ddffc 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/persistence/FileTxnLogTest.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/persistence/FileTxnLogTest.java
@@ -23,17 +23,24 @@
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Queue;
import java.util.Random;
import java.util.stream.Collectors;
import org.apache.jute.Record;
@@ -64,6 +71,33 @@ public class FileTxnLogTest extends ZKTestCase {
private static final int KB = 1024;
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testCloseAttemptsEveryStream(@TempDir File tmpDir) throws
Exception {
+ FileTxnLog txnLog = new FileTxnLog(tmpDir);
+ BufferedOutputStream logStream = mock(BufferedOutputStream.class);
+ FileOutputStream firstStream = mock(FileOutputStream.class);
+ FileOutputStream secondStream = mock(FileOutputStream.class);
+ IOException logStreamFailure = new IOException("log stream");
+ IOException queuedStreamFailure = new IOException("queued stream");
+ doThrow(logStreamFailure).when(logStream).close();
+ doThrow(queuedStreamFailure).when(firstStream).close();
+ txnLog.logStream = logStream;
+
+ // Inject close failures directly because streamsToFlush is private.
+ Field streamsField =
FileTxnLog.class.getDeclaredField("streamsToFlush");
+ streamsField.setAccessible(true);
+ Queue<FileOutputStream> streams = (Queue<FileOutputStream>)
streamsField.get(txnLog);
+ streams.add(firstStream);
+ streams.add(secondStream);
+
+ IOException thrown = assertThrows(IOException.class, txnLog::close);
+
+ assertEquals(logStreamFailure, thrown);
+ assertArrayEquals(new Throwable[]{queuedStreamFailure},
thrown.getSuppressed());
+ verify(secondStream).close();
+ }
+
@Test
public void testInvalidPreallocSize() {
assertEquals(10 * KB, FilePadding.calculateFileSizeWithPadding(7 * KB,
10 * KB, 0),