This is an automated email from the ASF dual-hosted git repository.
miroslav pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new 6697d706d9 OAK-12039 test that verifies that lease renewal in
AzureRepositoryLockV8 correctly handles client timeout (#2667)
6697d706d9 is described below
commit 6697d706d92f6928adc9768a3b8f2cb6f070f310
Author: Miroslav Smiljanic <[email protected]>
AuthorDate: Fri Dec 19 11:52:47 2025 +0100
OAK-12039 test that verifies that lease renewal in AzureRepositoryLockV8
correctly handles client timeout (#2667)
Co-authored-by: smiroslav <[email protected]>
---
oak-segment-azure/pom.xml | 12 +++
.../segment/azure/v8/AzureRepositoryLockV8.java | 4 +-
.../azure/v8/AzureRepositoryLockV8Test.java | 91 +++++++++++++++++++++-
3 files changed, 104 insertions(+), 3 deletions(-)
diff --git a/oak-segment-azure/pom.xml b/oak-segment-azure/pom.xml
index fccb98a93d..a71670ae1a 100644
--- a/oak-segment-azure/pom.xml
+++ b/oak-segment-azure/pom.xml
@@ -451,6 +451,18 @@
<artifactId>system-rules</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.wiremock</groupId>
+ <artifactId>wiremock</artifactId>
+ <version>3.9.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <version>4.2.1</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git
a/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/v8/AzureRepositoryLockV8.java
b/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/v8/AzureRepositoryLockV8.java
index 9f58115125..d066f1dc96 100644
---
a/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/v8/AzureRepositoryLockV8.java
+++
b/oak-segment-azure/src/main/java/org/apache/jackrabbit/oak/segment/azure/v8/AzureRepositoryLockV8.java
@@ -37,7 +37,9 @@ public class AzureRepositoryLockV8 implements RepositoryLock {
private static final Logger log =
LoggerFactory.getLogger(AzureRepositoryLockV8.class);
private static final int TIMEOUT_SEC =
Integer.getInteger("oak.segment.azure.lock.timeout", 0);
- private static final Integer LEASE_RENEWAL_TIMEOUT_MS = 5000;
+
+ public static final String LEASE_RENEWAL_TIMEOUT_PROP =
"oak.segment.azure.lock.leaseRenewalTimeoutInMs";
+ private static final int LEASE_RENEWAL_TIMEOUT_MS =
Integer.getInteger(LEASE_RENEWAL_TIMEOUT_PROP, 5000);
public static final String LEASE_DURATION_PROP =
"oak.segment.azure.lock.leaseDurationInSec";
private final int leaseDuration = Integer.getInteger(LEASE_DURATION_PROP,
60);
diff --git
a/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/v8/AzureRepositoryLockV8Test.java
b/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/v8/AzureRepositoryLockV8Test.java
index d645a1743f..59cc74dc43 100644
---
a/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/v8/AzureRepositoryLockV8Test.java
+++
b/oak-segment-azure/src/test/java/org/apache/jackrabbit/oak/segment/azure/v8/AzureRepositoryLockV8Test.java
@@ -18,11 +18,15 @@
*/
package org.apache.jackrabbit.oak.segment.azure.v8;
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+import com.github.tomakehurst.wiremock.stubbing.Scenario;
+import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageException;
+import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
-
import
org.apache.jackrabbit.oak.blob.cloud.azure.blobstorage.AzuriteDockerRule;
import org.apache.jackrabbit.oak.segment.remote.WriteAccessController;
import org.apache.jackrabbit.oak.segment.spi.persistence.RepositoryLock;
@@ -40,7 +44,10 @@ import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
+import static org.awaitility.Awaitility.await;
import static org.junit.Assert.*;
public class AzureRepositoryLockV8Test {
@@ -49,6 +56,7 @@ public class AzureRepositoryLockV8Test {
public static final String LEASE_DURATION = "15";
public static final String RENEWAL_INTERVAL = "3";
public static final String TIME_TO_WAIT_BEFORE_BLOCK = "9";
+ public static final String LEASE_RENEWAL_TIMEOUT = "1000";
@ClassRule
public static AzuriteDockerRule azurite = new AzuriteDockerRule();
@@ -63,7 +71,8 @@ public class AzureRepositoryLockV8Test {
@Rule
public final ProvideSystemProperty systemPropertyRule = new
ProvideSystemProperty(AzureRepositoryLockV8.LEASE_DURATION_PROP, LEASE_DURATION)
.and(AzureRepositoryLockV8.RENEWAL_INTERVAL_PROP, RENEWAL_INTERVAL)
- .and(AzureRepositoryLockV8.TIME_TO_WAIT_BEFORE_WRITE_BLOCK_PROP,
TIME_TO_WAIT_BEFORE_BLOCK);
+ .and(AzureRepositoryLockV8.TIME_TO_WAIT_BEFORE_WRITE_BLOCK_PROP,
TIME_TO_WAIT_BEFORE_BLOCK)
+ .and(AzureRepositoryLockV8.LEASE_RENEWAL_TIMEOUT_PROP,
LEASE_RENEWAL_TIMEOUT);
@Test
public void testFailingLock() throws URISyntaxException, IOException,
StorageException {
@@ -168,4 +177,82 @@ public class AzureRepositoryLockV8Test {
Mockito.doCallRealMethod().when(blobMocked).renewLease(Mockito.any(),
Mockito.any(), Mockito.any());
}
+
+ @Test
+ public void testClientSideTimeoutExceptionIsRecoverable() throws Exception
{
+
+ // Start WireMock as a proxy
+ WireMockServer wireMockServer = new
WireMockServer(WireMockConfiguration.options()
+ .dynamicPort());
+ wireMockServer.start();
+
+ try {
+ int wireMockPort = wireMockServer.port();
+ int azuritePort = azurite.getMappedPort();
+ String azuriteUrl = "http://127.0.0.1:" + azuritePort;
+
+ // Configure WireMock to proxy all requests to Azurite by default
+ wireMockServer.stubFor(any(anyUrl())
+ .willReturn(aResponse().proxiedFrom(azuriteUrl)));
+
+ // Use WireMock scenarios to delay only the first 2 lease renewal
requests
+ // Scenario: Started -> FirstTimeout -> SecondTimeout -> Success
+ String scenarioName = "LeaseRenewalTimeout";
+
+ // First renewal request: delay and transition to SecondTimeout
state
+ wireMockServer.stubFor(put(urlPathMatching(".*/oak/repo\\.lock"))
+ .withQueryParam("comp", equalTo("lease"))
+ .inScenario(scenarioName)
+ .whenScenarioStateIs(Scenario.STARTED)
+
.willReturn(aResponse().proxiedFrom(azuriteUrl).withFixedDelay(2000))
+ .willSetStateTo("SecondTimeout"));
+
+ // Second renewal request: delay and transition to Success state
+ wireMockServer.stubFor(put(urlPathMatching(".*/oak/repo\\.lock"))
+ .withQueryParam("comp", equalTo("lease"))
+ .inScenario(scenarioName)
+ .whenScenarioStateIs("SecondTimeout")
+
.willReturn(aResponse().proxiedFrom(azuriteUrl).withFixedDelay(2000))
+ .willSetStateTo("Success"));
+
+ // Third and subsequent renewal requests: no delay, just proxy
+ wireMockServer.stubFor(put(urlPathMatching(".*/oak/repo\\.lock"))
+ .withQueryParam("comp", equalTo("lease"))
+ .inScenario(scenarioName)
+ .whenScenarioStateIs("Success")
+ .willReturn(aResponse().proxiedFrom(azuriteUrl)));
+
+ // Create a CloudBlockBlob pointing to WireMock instead of Azurite
+ String wireMockEndpoint = "http://127.0.0.1:" + wireMockPort +
"/devstoreaccount1";
+ String connectionString =
"DefaultEndpointsProtocol=http;AccountName=" + AzuriteDockerRule.ACCOUNT_NAME
+ + ";AccountKey=" + AzuriteDockerRule.ACCOUNT_KEY
+ + ";BlobEndpoint=" + wireMockEndpoint;
+
+ CloudStorageAccount storageAccount =
CloudStorageAccount.parse(connectionString);
+ CloudBlobClient blobClient =
storageAccount.createCloudBlobClient();
+ CloudBlobContainer proxyContainer =
blobClient.getContainerReference(container.getName());
+
+ CloudBlockBlob blob =
proxyContainer.getBlockBlobReference("oak/repo.lock");
+
+ AtomicBoolean shutdownCalled = new AtomicBoolean(false);
+ Runnable shutdownHook = () -> shutdownCalled.set(true);
+
+ WriteAccessController writeAccessController = new
WriteAccessController();
+ AzureRepositoryLockV8 lock = new AzureRepositoryLockV8(blob,
shutdownHook, writeAccessController);
+ lock.lock();
+
+ // Wait for at least 3 lease renewal requests (2 timeouts + 1
success)
+ await().atMost(10, java.util.concurrent.TimeUnit.SECONDS)
+ .untilAsserted(() -> wireMockServer.verify(
+ moreThanOrExactly(3),
+
putRequestedFor(urlPathMatching(".*/oak/repo\\.lock"))
+ .withQueryParam("comp",
equalTo("lease"))));
+
+ assertFalse("Shutdown hook should not be called for client-side
timeout exceptions", shutdownCalled.get());
+
+ lock.unlock();
+ } finally {
+ wireMockServer.stop();
+ }
+ }
}