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.http.impl.client;
28
29 import java.util.HashMap;
30 import java.util.Map;
31
32 import org.apache.http.client.BackoffManager;
33 import org.apache.http.conn.routing.HttpRoute;
34 import org.apache.http.pool.ConnPoolControl;
35 import org.apache.http.util.Args;
36
37 /**
38 * <p>The <code>AIMDBackoffManager</code> applies an additive increase,
39 * multiplicative decrease (AIMD) to managing a dynamic limit to
40 * the number of connections allowed to a given host. You may want
41 * to experiment with the settings for the cooldown periods and the
42 * backoff factor to get the adaptive behavior you want.</p>
43 *
44 * <p>Generally speaking, shorter cooldowns will lead to more steady-state
45 * variability but faster reaction times, while longer cooldowns
46 * will lead to more stable equilibrium behavior but slower reaction
47 * times.</p>
48 *
49 * <p>Similarly, higher backoff factors promote greater
50 * utilization of available capacity at the expense of fairness
51 * among clients. Lower backoff factors allow equal distribution of
52 * capacity among clients (fairness) to happen faster, at the
53 * expense of having more server capacity unused in the short term.</p>
54 *
55 * @since 4.2
56 */
57 public class AIMDBackoffManager implements BackoffManager {
58
59 private final ConnPoolControl<HttpRoute> connPerRoute;
60 private final Clock clock;
61 private final Map<HttpRoute,Long> lastRouteProbes;
62 private final Map<HttpRoute,Long> lastRouteBackoffs;
63 private long coolDown = 5 * 1000L;
64 private double backoffFactor = 0.5;
65 private int cap = 2; // Per RFC 2616 sec 8.1.4
66
67 /**
68 * Creates an <code>AIMDBackoffManager</code> to manage
69 * per-host connection pool sizes represented by the
70 * given {@link ConnPoolControl}.
71 * @param connPerRoute per-host routing maximums to
72 * be managed
73 */
74 public AIMDBackoffManager(final ConnPoolControl<HttpRoute> connPerRoute) {
75 this(connPerRoute, new SystemClock());
76 }
77
78 AIMDBackoffManager(final ConnPoolControl<HttpRoute> connPerRoute, final Clock clock) {
79 this.clock = clock;
80 this.connPerRoute = connPerRoute;
81 this.lastRouteProbes = new HashMap<HttpRoute,Long>();
82 this.lastRouteBackoffs = new HashMap<HttpRoute,Long>();
83 }
84
85 public void backOff(final HttpRoute route) {
86 synchronized(connPerRoute) {
87 final int curr = connPerRoute.getMaxPerRoute(route);
88 final Long lastUpdate = getLastUpdate(lastRouteBackoffs, route);
89 final long now = clock.getCurrentTime();
90 if (now - lastUpdate.longValue() < coolDown) {
91 return;
92 }
93 connPerRoute.setMaxPerRoute(route, getBackedOffPoolSize(curr));
94 lastRouteBackoffs.put(route, Long.valueOf(now));
95 }
96 }
97
98 private int getBackedOffPoolSize(final int curr) {
99 if (curr <= 1) {
100 return 1;
101 }
102 return (int)(Math.floor(backoffFactor * curr));
103 }
104
105 public void probe(final HttpRoute route) {
106 synchronized(connPerRoute) {
107 final int curr = connPerRoute.getMaxPerRoute(route);
108 final int max = (curr >= cap) ? cap : curr + 1;
109 final Long lastProbe = getLastUpdate(lastRouteProbes, route);
110 final Long lastBackoff = getLastUpdate(lastRouteBackoffs, route);
111 final long now = clock.getCurrentTime();
112 if (now - lastProbe.longValue() < coolDown || now - lastBackoff.longValue() < coolDown) {
113 return;
114 }
115 connPerRoute.setMaxPerRoute(route, max);
116 lastRouteProbes.put(route, Long.valueOf(now));
117 }
118 }
119
120 private Long getLastUpdate(final Map<HttpRoute,Long> updates, final HttpRoute route) {
121 Long lastUpdate = updates.get(route);
122 if (lastUpdate == null) {
123 lastUpdate = Long.valueOf(0L);
124 }
125 return lastUpdate;
126 }
127
128 /**
129 * Sets the factor to use when backing off; the new
130 * per-host limit will be roughly the current max times
131 * this factor. <code>Math.floor</code> is applied in the
132 * case of non-integer outcomes to ensure we actually
133 * decrease the pool size. Pool sizes are never decreased
134 * below 1, however. Defaults to 0.5.
135 * @param d must be between 0.0 and 1.0, exclusive.
136 */
137 public void setBackoffFactor(final double d) {
138 Args.check(d > 0.0 && d < 1.0, "Backoff factor must be 0.0 < f < 1.0");
139 backoffFactor = d;
140 }
141
142 /**
143 * Sets the amount of time, in milliseconds, to wait between
144 * adjustments in pool sizes for a given host, to allow
145 * enough time for the adjustments to take effect. Defaults
146 * to 5000L (5 seconds).
147 * @param l must be positive
148 */
149 public void setCooldownMillis(final long l) {
150 Args.positive(coolDown, "Cool down");
151 coolDown = l;
152 }
153
154 /**
155 * Sets the absolute maximum per-host connection pool size to
156 * probe up to; defaults to 2 (the default per-host max).
157 * @param cap must be >= 1
158 */
159 public void setPerHostConnectionCap(final int cap) {
160 Args.positive(cap, "Per host connection cap");
161 this.cap = cap;
162 }
163
164 }