Fix features BackoffManager Unit Tests in Resource-Constrained Environments. (#494)

This commit improves the reliability of BackoffManager unit tests by replacing the use of Thread.sleep() with a more robust approach that manipulates lastRouteProbes to simulate the cooldown period. This enhancement ensures that the tests run successfully even in resource-constrained environments, making them more resilient and reliable.
This commit is contained in:
Arturo Bernal 2023-10-20 12:52:20 +02:00 committed by Oleg Kalnichevski
parent f6a37780cf
commit 5c69779f7d
2 changed files with 139 additions and 58 deletions

View File

@ -30,6 +30,8 @@ package org.apache.hc.client5.http.impl.classic;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Instant;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
@ -97,60 +99,97 @@ public class TestAIMDBackoffManager {
@Test
public void backoffDoesNotAdjustDuringCoolDownPeriod() {
// Arrange
connPerRoute.setMaxPerRoute(route, 4);
impl.backOff(route);
final long max = connPerRoute.getMaxPerRoute(route);
// Replace Thread.sleep(1) with busy waiting
final long end = System.currentTimeMillis() + 1;
while (System.currentTimeMillis() < end) {
// Busy waiting
}
// Act
impl.backOff(route);
assertEquals(max, connPerRoute.getMaxPerRoute(route));
final long max1 = connPerRoute.getMaxPerRoute(route);
// Manipulate lastRouteBackoffs to simulate that not enough time has passed
final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
lastRouteBackoffs.put(route, Instant.now().minusMillis(1));
// Act again
impl.backOff(route);
final long max2 = connPerRoute.getMaxPerRoute(route);
// Assert
assertEquals(max1, max2);
}
@Test
public void backoffStillAdjustsAfterCoolDownPeriod() throws InterruptedException {
public void backoffStillAdjustsAfterCoolDownPeriod() {
// Arrange: Initialize the maximum number of connections for a route to 8
connPerRoute.setMaxPerRoute(route, 8);
// Act: Perform the first backoff operation
impl.backOff(route);
final long max = connPerRoute.getMaxPerRoute(route);
Thread.sleep(DEFAULT_COOL_DOWN_MS + 100); // Sleep for cooldown period + 100 ms
final long initialMax = connPerRoute.getMaxPerRoute(route);
// Act: Simulate that the cooldown period has passed
final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
lastRouteBackoffs.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS + 1));
// Act: Perform the second backoff operation
impl.backOff(route);
assertTrue(max == 1 || max > connPerRoute.getMaxPerRoute(route));
final long finalMax = connPerRoute.getMaxPerRoute(route);
// Assert: Verify that the maximum number of connections has decreased or reached the minimum limit (1)
if (initialMax != 1) {
assertTrue(finalMax < initialMax, "Max connections should decrease after cooldown");
} else {
assertEquals(1, finalMax, "Max connections should remain 1 if it's already at the minimum");
}
}
@Test
public void probeDoesNotAdjustDuringCooldownPeriod() {
// Arrange
connPerRoute.setMaxPerRoute(route, 4);
impl.probe(route);
final long max = connPerRoute.getMaxPerRoute(route);
// Replace Thread.sleep(1) with busy waiting
final long end = System.currentTimeMillis() + 1;
while (System.currentTimeMillis() < end) {
// Busy waiting
}
// First probe
impl.probe(route);
assertEquals(max, connPerRoute.getMaxPerRoute(route));
final long max1 = connPerRoute.getMaxPerRoute(route);
// Manipulate lastRouteProbes to simulate that not enough time has passed
final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
lastRouteProbes.put(route, Instant.now().minusMillis(1));
// Second probe
impl.probe(route);
final long max2 = connPerRoute.getMaxPerRoute(route);
// Assert
assertEquals(max1, max2);
}
@Test
public void probeStillAdjustsAfterCoolDownPeriod() throws InterruptedException {
public void probeStillAdjustsAfterCoolDownPeriod() {
connPerRoute.setMaxPerRoute(route, 8);
// First probe
impl.probe(route);
final long max = connPerRoute.getMaxPerRoute(route);
Thread.sleep(DEFAULT_COOL_DOWN_MS + 100); // Sleep for cooldown period + 1 ms
// Manipulate lastRouteProbes to simulate that enough time has passed for the cooldown period
final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
lastRouteProbes.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS + 1));
// Second probe
impl.probe(route);
// Assert that the max connections have increased
assertTrue(max < connPerRoute.getMaxPerRoute(route));
}
@Test
public void willBackoffImmediatelyEvenAfterAProbe() {
connPerRoute.setMaxPerRoute(route, 8);
final long now = System.currentTimeMillis();
impl.probe(route);
final long max = connPerRoute.getMaxPerRoute(route);
impl.backOff(route);
@ -166,19 +205,26 @@ public class TestAIMDBackoffManager {
}
@Test
public void coolDownPeriodIsConfigurable() throws InterruptedException {
public void coolDownPeriodIsConfigurable() {
final long cd = new Random().nextInt(500) + 500; // Random cooldown period between 500 and 1000 milliseconds
impl.setCoolDown(TimeValue.ofMilliseconds(cd));
// Probe and check if the connection count remains the same during the cooldown period
impl.probe(route);
final int max0 = connPerRoute.getMaxPerRoute(route);
Thread.sleep(cd / 2 + 100); // Sleep for half the cooldown period + 100 ms buffer
// Manipulate lastRouteProbes to simulate that not enough time has passed
final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
lastRouteProbes.put(route, Instant.now().minusMillis(cd / 2));
// Probe again
impl.probe(route);
assertEquals(max0, connPerRoute.getMaxPerRoute(route));
// Probe and check if the connection count increases after the cooldown period
Thread.sleep(cd / 2 + 100); // Sleep for the remaining half of the cooldown period + 100 ms buffer
// Manipulate lastRouteProbes to simulate that enough time has passed
lastRouteProbes.put(route, Instant.now().minusMillis(cd + 1));
// Probe again
impl.probe(route);
assertTrue(max0 < connPerRoute.getMaxPerRoute(route));
}

View File

@ -27,15 +27,18 @@
package org.apache.hc.client5.http.impl.classic;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Instant;
import java.util.Map;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.util.TimeValue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestLinearBackoffManager {
private LinearBackoffManager impl;
@ -69,61 +72,79 @@ public class TestLinearBackoffManager {
@Test
public void backoffDoesNotAdjustDuringCoolDownPeriod() {
// Arrange
connPerRoute.setMaxPerRoute(route, 4);
impl.backOff(route);
final long max = connPerRoute.getMaxPerRoute(route);
// Replace Thread.sleep(1) with busy waiting
final long end = System.currentTimeMillis() + 1;
while (System.currentTimeMillis() < end) {
// Busy waiting
}
// Manipulate lastRouteBackoffs to simulate that not enough time has passed for the cooldown period
final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
lastRouteBackoffs.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS - 1));
// Act
impl.backOff(route);
// Assert
assertEquals(max, connPerRoute.getMaxPerRoute(route));
}
@Test
public void backoffStillAdjustsAfterCoolDownPeriod() throws InterruptedException {
public void backoffStillAdjustsAfterCoolDownPeriod() {
// Arrange
final LinearBackoffManager impl = new LinearBackoffManager(connPerRoute);
impl.setCoolDown(TimeValue.ofMilliseconds(DEFAULT_COOL_DOWN_MS)); // Set the cool-down period
connPerRoute.setMaxPerRoute(route, 4);
impl.backOff(route);
final int max1 = connPerRoute.getMaxPerRoute(route);
Thread.sleep(DEFAULT_COOL_DOWN_MS + 1); // Sleep for cooldown period + 1 ms
// Manipulate lastRouteBackoffs to simulate that enough time has passed for the cooldown period
final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
lastRouteBackoffs.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS + 1));
// Act
impl.backOff(route);
final int max2 = connPerRoute.getMaxPerRoute(route);
// Assert
assertTrue(max2 > max1);
}
@Test
public void probeDoesNotAdjustDuringCooldownPeriod() {
// Arrange
connPerRoute.setMaxPerRoute(route, 4);
impl.probe(route);
final long max = connPerRoute.getMaxPerRoute(route);
// Replace Thread.sleep(1) with busy waiting
final long end = System.currentTimeMillis() + 1;
while (System.currentTimeMillis() < end) {
// Busy waiting
}
// Manipulate lastRouteProbes to simulate that not enough time has passed for the cooldown period
final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
lastRouteProbes.put(route, Instant.now());
// Act
impl.probe(route);
// Assert
assertEquals(max, connPerRoute.getMaxPerRoute(route));
}
@Test
public void probeStillAdjustsAfterCoolDownPeriod() throws InterruptedException {
public void probeStillAdjustsAfterCoolDownPeriod() {
// Arrange
connPerRoute.setMaxPerRoute(route, 4);
impl.probe(route);
Thread.sleep(DEFAULT_COOL_DOWN_MS + 1); // Sleep for cooldown period + 1 ms
// Manipulate lastRouteProbes to simulate that enough time has passed for the cooldown period
final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
lastRouteProbes.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS + 1));
// Act
impl.probe(route);
final long newMax = connPerRoute.getMaxPerRoute(route);
// Assert
assertEquals(2, newMax); // The cap is set to 2 by default
}
@Test
public void testSetPerHostConnectionCap() {
connPerRoute.setMaxPerRoute(route, 5);
@ -132,14 +153,18 @@ public class TestLinearBackoffManager {
assertEquals(6, connPerRoute.getMaxPerRoute(route));
}
@Test
public void probeUpdatesRemainingAttemptsIndirectly() throws InterruptedException {
public void probeUpdatesRemainingAttemptsIndirectly() {
// Set initial max per route
connPerRoute.setMaxPerRoute(route, 4);
// Apply backOff twice
impl.backOff(route);
Thread.sleep(DEFAULT_COOL_DOWN_MS + 1);
// Manipulate lastRouteBackoffs to simulate that enough time has passed for the cooldown period
final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
lastRouteBackoffs.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS + 1));
impl.backOff(route);
// Ensure that connection pool size has increased
@ -148,8 +173,9 @@ public class TestLinearBackoffManager {
// Apply probe once
impl.probe(route);
// Wait for a longer cool down period
Thread.sleep(DEFAULT_COOL_DOWN_MS * 2);
// Manipulate lastRouteProbes to simulate that enough time has passed for the cooldown period
final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
lastRouteProbes.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS * 2));
// Apply probe once more
impl.probe(route);
@ -159,14 +185,23 @@ public class TestLinearBackoffManager {
}
@Test
public void linearIncrementTest() throws InterruptedException {
public void linearIncrementTest() {
final int initialMax = 4;
connPerRoute.setMaxPerRoute(route, initialMax);
for (int i = 1; i <= 5; i++) {
impl.backOff(route);
assertEquals(initialMax + i, connPerRoute.getMaxPerRoute(route));
Thread.sleep(DEFAULT_COOL_DOWN_MS + 1);
// Manipulate lastRouteBackoffs to simulate that enough time has passed for the cooldown period
final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
for (int i = 1; i <= 5; i++) {
// Simulate that enough time has passed for the cooldown period
lastRouteBackoffs.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS + 1));
// Act
impl.backOff(route);
// Assert
assertEquals(initialMax + i, connPerRoute.getMaxPerRoute(route));
}
}
}