Copilot commented on code in PR #4863:
URL: https://github.com/apache/bookkeeper/pull/4863#discussion_r3974830888


##########
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/EntryLogManagerBase.java:
##########
@@ -117,6 +127,21 @@ List<BufferedLogChannel> getRotatedLogChannels() {
         return rotatedLogChannels;
     }
 
+    @Override
+    public void setFatalErrorListener(LedgerDirsListener fatalErrorListener) {
+        this.fatalErrorListener = fatalErrorListener != null ? 
fatalErrorListener : NOOP_FATAL_ERROR_LISTENER;
+    }
+
+    void notifyFatalEntryLogWriteFailure(String message, Throwable cause) {
+        log.error().exception(cause).log(message);
+        fatalErrorListener.fatalError();
+        for (LedgerDirsListener listener : ledgerDirsManager.getListeners()) {
+            if (listener != fatalErrorListener) {
+                listener.fatalError();

Review Comment:
   A single misbehaving `LedgerDirsListener` that throws at `fatalError()` can 
prevent notifying the remaining listeners, which makes shutdown/transition 
behavior less reliable under stress. Wrap each `fatalError()` invocation in its 
own try/catch and continue notifying others (logging any listener failure).



##########
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BufferedChannel.java:
##########
@@ -333,4 +363,17 @@ public synchronized int getNumOfBytesInWriteBuffer() {
     long getUnpersistedBytes() {
         return unpersistedBytes.get();
     }
+
+    final void checkWritable() throws IOException {
+        IOException failure = writeFailure;
+        if (failure != null) {
+            throw new IOException("BufferedChannel is in failed state", 
failure);
+        }
+    }
+
+    final void markWriteFailure(IOException e) {
+        if (writeFailure == null) {
+            writeFailure = e;
+        }
+    }

Review Comment:
   `writeFailure` is `volatile`, but `markWriteFailure()` performs a non-atomic 
check-then-set. If multiple threads hit failures concurrently, the 
original/root failure can be overwritten (or lost), making diagnosis harder and 
potentially changing the thrown cause nondeterministically. Consider using an 
`AtomicReference<IOException>` with `compareAndSet(null, e)` (or synchronizing 
`markWriteFailure`) so the first failure is preserved deterministically.



##########
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/DefaultEntryLogger.java:
##########
@@ -204,7 +207,23 @@ public void accept(long ledgerId, long size) {
             mapInfo.putLong(ledgerMapOffset);
             mapInfo.putInt(numberOfLedgers);
             mapInfo.flip();
-            this.fileChannel.write(mapInfo, LEDGERS_MAP_OFFSET_POSITION);
+            try {
+                writeFully(this.fileChannel, mapInfo, 
LEDGERS_MAP_OFFSET_POSITION);
+            } catch (IOException e) {
+                markWriteFailure(e);
+                throw e;
+            }
+        }
+
+        private static void writeFully(FileChannel fileChannel, ByteBuffer 
buffer, long position) throws IOException {
+            long writePosition = position;
+            while (buffer.hasRemaining()) {
+                int written = fileChannel.write(buffer, writePosition);
+                if (written <= 0) {
+                    throw new IOException("Unable to make progress while 
updating entry log header");
+                }
+                writePosition += written;
+            }
         }

Review Comment:
   The new error message is generic and will be hard to action operationally. 
Including context (e.g., logId/file path if available at the call site, and the 
target position/remaining bytes) would make diagnosing partial/header-write 
issues significantly easier.



##########
bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/storage/ldb/FailOnFlushDbLedgerStorage.java:
##########
@@ -0,0 +1,73 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.bookkeeper.bookie.storage.ldb;
+
+import io.netty.buffer.ByteBufAllocator;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.bookkeeper.bookie.EntryLogWriteException;
+import org.apache.bookkeeper.bookie.LedgerDirsManager;
+import org.apache.bookkeeper.bookie.storage.EntryLogger;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.meta.LedgerManager;
+import org.apache.bookkeeper.stats.StatsLogger;
+
+public class FailOnFlushDbLedgerStorage extends DbLedgerStorage {
+    private static final AtomicBoolean failNextFlushWithEntryLogWriteException 
= new AtomicBoolean(false);
+
+    public static void injectFailureOnNextFlush() {
+        failNextFlushWithEntryLogWriteException.set(true);
+    }
+
+    public static void resetFailure() {
+        failNextFlushWithEntryLogWriteException.set(false);
+    }

Review Comment:
   The failure injection is stored in a static flag, which can leak state 
across tests and can become flaky if the test suite is run with parallelism (or 
multiple clusters/tests in the same JVM). Prefer instance-scoped injection 
(e.g., per-storage field) or tying it to the specific bookie/test instance 
(e.g., via configuration or dependency injection) to avoid cross-test 
interference.



##########
bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/storage/ldb/SingleDirectoryDbLedgerStorageShutdownTest.java:
##########
@@ -0,0 +1,130 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.bookkeeper.bookie.storage.ldb;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.netty.buffer.ByteBufAllocator;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.concurrent.ExecutorService;
+import org.apache.bookkeeper.bookie.BookieImpl;
+import org.apache.bookkeeper.bookie.EntryLogWriteException;
+import org.apache.bookkeeper.bookie.GarbageCollectorThread;
+import org.apache.bookkeeper.bookie.LedgerDirsManager;
+import org.apache.bookkeeper.bookie.storage.EntryLogger;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.conf.TestBKConfiguration;
+import org.apache.bookkeeper.meta.LedgerManager;
+import org.apache.bookkeeper.stats.NullStatsLogger;
+import org.apache.bookkeeper.stats.StatsLogger;
+import org.apache.bookkeeper.util.DiskChecker;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests shutdown cleanup for {@link SingleDirectoryDbLedgerStorage}.
+ */
+public class SingleDirectoryDbLedgerStorageShutdownTest {
+
+    private static final long MB = 1024 * 1024;
+
+    private File tmpDir;
+    private EntryLogger entryLogger;
+    private FailingFlushSingleDirectoryDbLedgerStorage storage;
+
+    @Before
+    public void setup() throws Exception {
+        tmpDir = File.createTempFile("bkTest", ".dir");
+        tmpDir.delete();
+        tmpDir.mkdir();

Review Comment:
   `File.createTempFile()` + `delete()` + `mkdir()` is a brittle pattern (e.g., 
delete can fail on some platforms, `mkdir()` can fail silently here, and it’s 
not guaranteed to be a directory). Prefer `Files.createTempDirectory(...)` (or 
JUnit's `TemporaryFolder`) and assert directory creation succeeds to make the 
test more robust across OS/filesystems.



##########
bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/DefaultEntryLogTest.java:
##########
@@ -1316,6 +1397,53 @@ public void testAppendLedgersMapOnCacheRemoval() throws 
Exception {
         assertEquals((entrySize + 4) * numOfEntries, ledgersMap.get(ledgerId), 
"Total size of entries");
     }
 
+    @Test
+    public void testAppendLedgersMapFailureOnCacheRemovalTriggersFatalError() 
throws Exception {
+        int evictionPeriod = 1;
+
+        ServerConfiguration conf = 
TestBKConfiguration.newServerConfiguration();
+        conf.setEntryLogFilePreAllocationEnabled(false);
+        conf.setEntryLogPerLedgerEnabled(true);
+        conf.setLedgerDirNames(createAndGetLedgerDirs(1));
+        conf.setEntrylogMapAccessExpiryTimeInSeconds(evictionPeriod);
+        LedgerDirsManager ledgerDirsManager = new LedgerDirsManager(conf, 
conf.getLedgerDirs(),
+                new DiskChecker(conf.getDiskUsageThreshold(), 
conf.getDiskUsageWarnThreshold()));
+
+        CountDownLatch fatalLatch = new CountDownLatch(1);
+        ledgerDirsManager.addLedgerDirsListener(new LedgerDirsListener() {
+            @Override
+            public void fatalError() {
+                fatalLatch.countDown();
+            }
+        });
+
+        DefaultEntryLogger entryLogger = new DefaultEntryLogger(conf, 
ledgerDirsManager);
+        EntryLogManagerForEntryLogPerLedger entryLogManager = 
(EntryLogManagerForEntryLogPerLedger) entryLogger
+                .getEntryLogManager();
+
+        long ledgerId = 0L;
+        File tmpFile = File.createTempFile("entrylog", "failed-eviction");
+        tmpFile.deleteOnExit();
+        FileChannel fileChannel = new RandomAccessFile(tmpFile, 
"rw").getChannel();
+        BufferedLogChannel logChannel = new 
BufferedLogChannel(UnpooledByteBufAllocator.DEFAULT, fileChannel, 10, 10,
+                0L, tmpFile, conf.getFlushIntervalInBytes());
+
+        try {
+            entryLogManager.setCurrentLogForLedgerAndAddToRotate(ledgerId, 
logChannel);
+
+            fileChannel.close();
+            Thread.sleep(evictionPeriod * 1000 + 100);
+            entryLogManager.doEntryLogMapCleanup();
+
+            assertTrue(fatalLatch.await(10, TimeUnit.SECONDS),
+                    "Cache removal appendLedgersMap failure should trigger 
fatal error");
+            
assertFalse(entryLogManager.getRotatedLogChannels().contains(logChannel),
+                    "Failed log channel should not be added to rotated logs");
+        } finally {
+            logChannel.close();

Review Comment:
   This test explicitly closes `fileChannel` and then unconditionally calls 
`logChannel.close()` in `finally`. If `BufferedLogChannel.close()` propagates 
an `IOException` when the underlying channel is already closed, the test can 
fail during cleanup and mask the real assertion. Consider either (a) closing 
only via `logChannel` (and inject failure another way), or (b) wrapping 
`logChannel.close()` in a try/catch and ignoring/recording the expected close 
failure.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to