This is an automated email from the ASF dual-hosted git repository. miroslav pushed a commit to branch issue/OAK-12112_unresolved_address in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit e46e4dc7481d2154118dfc1999aaee065ff2f040 Author: smiroslav <[email protected]> AuthorDate: Wed Feb 25 14:16:20 2026 +0100 OAK-12112 treat DNS issue as transient --- .../oak/segment/azure/AzureRepositoryLock.java | 12 +++++-- .../oak/segment/azure/AzureRepositoryLockTest.java | 41 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/AzureRepositoryLock.java b/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/AzureRepositoryLock.java index a34193f742..1594b3b52a 100644 --- a/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/AzureRepositoryLock.java +++ b/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/AzureRepositoryLock.java @@ -28,8 +28,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.channels.UnresolvedAddressException; import java.time.Duration; import java.util.Set; +import java.util.concurrent.TimeoutException; public class AzureRepositoryLock implements RepositoryLock { @@ -222,15 +224,19 @@ public class AzureRepositoryLock implements RepositoryLock { * Per Azure SDK documentation, the timeout parameter causes a RuntimeException to be raised. * Reactor-core throws IllegalStateException with message "Timeout on blocking read" when * the timeout expires (see BlockingSingleSubscriber.blockingGet in reactor-core). + * <p> + * {@link java.nio.channels.UnresolvedAddressException} (extends IllegalArgumentException) + * can occur when DNS resolution temporarily fails. * @param e the exception to check * @return true if this is a transient exception that should be retried */ private boolean isTransientClientSideException(Exception e) { Throwable current = e; while (current != null) { - if (current instanceof java.util.concurrent.TimeoutException || - current instanceof java.io.IOException || - current instanceof IllegalStateException) { + if (current instanceof TimeoutException || + current instanceof IOException || + current instanceof IllegalStateException || + current instanceof UnresolvedAddressException) { return true; } current = current.getCause(); diff --git a/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/AzureRepositoryLockTest.java b/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/AzureRepositoryLockTest.java index ae7504ea76..3e0f82c645 100644 --- a/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/AzureRepositoryLockTest.java +++ b/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/AzureRepositoryLockTest.java @@ -330,6 +330,47 @@ public class AzureRepositoryLockTest { } } + @Test + public void testUnresolvedAddressExceptionIsRecoverable() throws Exception { + BlockBlobClient blockBlobClient = readBlobContainerClient.getBlobClient("oak/repo.lock").getBlockBlobClient(); + BlockBlobClient noRetryBlockBlobClient = noRetryBlobContainerClient.getBlobClient("oak/repo.lock").getBlockBlobClient(); + BlobLeaseClient blobLeaseClient = new BlobLeaseClientBuilder().blobClient(noRetryBlockBlobClient).buildClient(); + + BlockBlobClient blobMocked = Mockito.spy(blockBlobClient); + BlobLeaseClient blobLeaseMocked = Mockito.spy(blobLeaseClient); + + // Simulate DNS resolution failure wrapped in a RuntimeException (as Netty/Reactor does) + RuntimeException dnsError = new RuntimeException( + "Connection failed", + new java.nio.channels.UnresolvedAddressException()); + + // Track if shutdown hook was called + AtomicBoolean shutdownCalled = new AtomicBoolean(false); + Runnable shutdownHook = () -> shutdownCalled.set(true); + + // Instrument the mock to throw the UnresolvedAddressException twice, then succeed + Mockito.doThrow(dnsError) + .doThrow(dnsError) + .doCallRealMethod() + .when(blobLeaseMocked).renewLeaseWithResponse((RequestConditions) any(), any(), any()); + + WriteAccessController writeAccessController = new WriteAccessController(); + + AzureRepositoryLock lock = new AzureRepositoryLock(blobMocked, blobLeaseMocked, shutdownHook, writeAccessController); + try { + lock.lock(); + + // Wait for at least 3 calls (2 failures + 1 success) with a timeout + Mockito.verify(blobLeaseMocked, Mockito.timeout(10000).atLeast(3)) + .renewLeaseWithResponse((RequestConditions) any(), any(), any()); + + // Verify that shutdown hook was NOT called - the UnresolvedAddressException should be treated as recoverable + assertFalse("Shutdown hook should not be called for UnresolvedAddressException", shutdownCalled.get()); + } finally { + lock.unlock(); + } + } + /** * HTTP pipeline policy that injects delays into specific requests. * Used to cause real client-side timeouts in tests.
