Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
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;
Expand Down Expand Up @@ -97,60 +99,97 @@ public void doesNotIncreaseBeyondPerHostMaxOnProbe() {

@Test
public void backoffDoesNotAdjustDuringCoolDownPeriod() {
// Arrange
connPerRoute.setMaxPerRoute(route, 4);

// Act
impl.backOff(route);
final long max = connPerRoute.getMaxPerRoute(route);
final long max1 = 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
final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
lastRouteBackoffs.put(route, Instant.now().minusMillis(1));

// Act again
impl.backOff(route);
assertEquals(max, connPerRoute.getMaxPerRoute(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);

// First probe
impl.probe(route);
final long max = connPerRoute.getMaxPerRoute(route);
final long max1 = 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
final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
lastRouteProbes.put(route, Instant.now().minusMillis(1));

// Second probe
impl.probe(route);
assertEquals(max, connPerRoute.getMaxPerRoute(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);
Expand All @@ -166,19 +205,26 @@ public void backOffFactorIsConfigurable() {
}

@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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,61 +72,79 @@ public void decrementsConnectionsOnProbe() {

@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);
Expand All @@ -132,14 +153,18 @@ public void testSetPerHostConnectionCap() {
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
Expand All @@ -148,8 +173,9 @@ public void probeUpdatesRemainingAttemptsIndirectly() throws InterruptedExceptio
// 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);
Expand All @@ -159,14 +185,23 @@ public void probeUpdatesRemainingAttemptsIndirectly() throws InterruptedExceptio
}

@Test
public void linearIncrementTest() throws InterruptedException {
public void linearIncrementTest() {
final int initialMax = 4;
connPerRoute.setMaxPerRoute(route, initialMax);

// 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);
assertEquals(initialMax + i, connPerRoute.getMaxPerRoute(route));
Thread.sleep(DEFAULT_COOL_DOWN_MS + 1);

// Assert
assertEquals(initialMax + i, connPerRoute.getMaxPerRoute(route));
}
}

}