View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.classic;
28  
29  import org.apache.hc.client5.http.HttpRoute;
30  import org.apache.hc.core5.http.HttpHost;
31  import org.apache.hc.core5.util.TimeValue;
32  import org.junit.jupiter.api.BeforeEach;
33  import org.junit.jupiter.api.Test;
34  
35  
36  import static org.junit.jupiter.api.Assertions.assertEquals;
37  import static org.junit.jupiter.api.Assertions.assertTrue;
38  
39  public class TestExponentialBackoffManager {
40  
41      private ExponentialBackoffManager impl;
42      private MockConnPoolControl connPerRoute;
43      private HttpRoute route;
44      private static final long DEFAULT_COOL_DOWN_MS = 5000; // Adjust this value to match the default cooldown period in ExponentialBackoffManager
45  
46      @BeforeEach
47      public void setUp() {
48          connPerRoute = new MockConnPoolControl();
49          route = new HttpRoute(new HttpHost("localhost", 80));
50          impl = new ExponentialBackoffManager(connPerRoute);
51          impl.setPerHostConnectionCap(10);
52          impl.setCoolDown(TimeValue.ofMilliseconds(DEFAULT_COOL_DOWN_MS));
53          impl.setBackoffFactor(1.75); // Adjust this value to match the default growth rate in ExponentialBackoffManager
54      }
55  
56      @Test
57      public void exponentialBackoffApplied() {
58          connPerRoute.setMaxPerRoute(route, 4);
59          impl.setBackoffFactor(2); // Sets the growth rate to 2 for this test
60          impl.backOff(route);
61          assertEquals(1, connPerRoute.getMaxPerRoute(route)); // Corrected expected value
62      }
63  
64      @Test
65      public void exponentialGrowthRateIsConfigurable() throws InterruptedException {
66          final int customCoolDownMs = 500;
67          connPerRoute.setMaxPerRoute(route, 4);
68          impl.setBackoffFactor(0.5);
69          impl.setCoolDown(TimeValue.ofMilliseconds(customCoolDownMs));
70          impl.backOff(route);
71          assertEquals(2, connPerRoute.getMaxPerRoute(route));
72          Thread.sleep(customCoolDownMs + 100); // Sleep for a slightly longer than the custom cooldown period
73          impl.backOff(route);
74          assertEquals(1, connPerRoute.getMaxPerRoute(route));
75      }
76  
77      @Test
78      public void doesNotIncreaseBeyondPerHostMaxOnProbe() {
79          connPerRoute.setDefaultMaxPerRoute(5);
80          connPerRoute.setMaxPerRoute(route, 5);
81          impl.setPerHostConnectionCap(5);
82          impl.probe(route);
83          assertEquals(5, connPerRoute.getMaxPerRoute(route));
84      }
85  
86      @Test
87      public void backoffDoesNotAdjustDuringCoolDownPeriod() throws InterruptedException {
88          connPerRoute.setMaxPerRoute(route, 4);
89          impl.backOff(route);
90          final long max = connPerRoute.getMaxPerRoute(route);
91          Thread.sleep(1); // Sleep for 1 ms
92          impl.backOff(route);
93          assertEquals(max, connPerRoute.getMaxPerRoute(route));
94      }
95  
96      @Test
97      public void backoffStillAdjustsAfterCoolDownPeriod() throws InterruptedException {
98          connPerRoute.setMaxPerRoute(route, 8);
99          impl.backOff(route);
100         final long max = connPerRoute.getMaxPerRoute(route);
101         Thread.sleep(DEFAULT_COOL_DOWN_MS + 1); // Sleep for cooldown period + 1 ms
102         impl.backOff(route);
103         assertTrue(max == 1 || max > connPerRoute.getMaxPerRoute(route));
104     }
105 
106     @Test
107     public void probeDoesNotAdjustDuringCooldownPeriod() throws InterruptedException {
108         connPerRoute.setMaxPerRoute(route, 4);
109         impl.probe(route);
110         final long max = connPerRoute.getMaxPerRoute(route);
111         Thread.sleep(1); // Sleep for 1 ms
112         impl.probe(route);
113         assertEquals(max, connPerRoute.getMaxPerRoute(route));
114     }
115 
116     @Test
117     public void probeStillAdjustsAfterCoolDownPeriod() throws InterruptedException {
118         connPerRoute.setMaxPerRoute(route, 8);
119         impl.probe(route);
120         final long max = connPerRoute.getMaxPerRoute(route);
121         Thread.sleep(DEFAULT_COOL_DOWN_MS + 1); // Sleep for cooldown period + 1 ms
122         impl.probe(route);
123         assertTrue(max < connPerRoute.getMaxPerRoute(route));
124     }
125 
126     @Test
127     public void willBackoffImmediatelyEvenAfterAProbe() {
128         connPerRoute.setMaxPerRoute(route, 8);
129         final long now = System.currentTimeMillis();
130         impl.probe(route);
131         final long max = connPerRoute.getMaxPerRoute(route);
132         impl.backOff(route);
133         assertTrue(connPerRoute.getMaxPerRoute(route) < max);
134     }
135 
136     @Test
137     public void coolDownPeriodIsConfigurable() throws InterruptedException {
138         final long cd = 500; // Fixed cooldown period of 500 milliseconds
139         impl.setCoolDown(TimeValue.ofMilliseconds(cd));
140 
141         // Sleep for a short duration before starting the test to reduce potential timing issues
142         Thread.sleep(100);
143 
144         // Probe and check if the connection count remains the same during the cooldown period
145         impl.probe(route);
146         final int max0 = connPerRoute.getMaxPerRoute(route);
147         Thread.sleep(cd / 2); // Sleep for half the cooldown period
148         impl.probe(route);
149         assertEquals(max0, connPerRoute.getMaxPerRoute(route));
150 
151         // Probe and check if the connection count increases after the cooldown period
152         Thread.sleep(cd / 2 + 1); // Sleep for the remaining half of the cooldown period + 1 ms
153         impl.probe(route);
154         assertTrue(max0 < connPerRoute.getMaxPerRoute(route));
155     }
156 
157 }