commit 2c92a238e2fe0a66b074434dc725437a6f133d07
parent c726b6b6ec94874d005b2b0ee4fdc045a6dde3d6
Author: finwo <finwo@pm.me>
Date: Sat, 3 Jan 2026 19:37:25 +0100
2.47
Diffstat:
15 files changed, 119 insertions(+), 61 deletions(-)
diff --git a/ChangeLog b/ChangeLog
@@ -1,3 +1,10 @@
+2.47 [KK 2009-03-04]
+- Stored-ip dispatching was enhanced to "anticipate" reconnects from
+ previously seen clients. The number of anticipated connections is taken
+ into account when dispatching a new client.
+- Bugfix in command line parsing of dispatch mode lax-stored-ip (would
+ be recognized as strict).
+
2.46 [KK 2009-02-18]
- Dispatcher-related classes moved under directory xr/Dispatchers/.
- UDP balancer implementation started (code stubs in place).
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@
# Top-level Makefile for XR
# -------------------------
-VER = 2.46
+VER = 2.47
PREFIX = $(DESTDIR)/usr
BINDIR = $(PREFIX)/sbin
MANDIR = $(PREFIX)/share/man
diff --git a/xr/DispatchAlgorithms/leastconn/target.cc b/xr/DispatchAlgorithms/leastconn/target.cc
@@ -6,25 +6,28 @@ unsigned Leastconn::target(struct in_addr clientip,
PROFILE("Leastconn::target");
bool found = false;
- unsigned nconn = 0, t = 0;
+ unsigned best_weighted = 0, t = 0;
for (unsigned i = 0; i < targetlist.size(); i++) {
if (! balancer.backend(targetlist[i]).available())
continue;
- unsigned weighted_conn =
- balancer.backend(targetlist[i]).connections() *
+ unsigned this_weight =
+ (balancer.backend(targetlist[i]).connections() +
+ balancer.backend(targetlist[i]).anticipated()) *
balancer.backend(targetlist[i]).adjustedweight();
msg ("Back end " + balancer.backend(targetlist[i]).description() +
(Mstr(": connections ")
+ balancer.backend(targetlist[i]).connections()) +
+ (Mstr(", anticipated ")
+ + balancer.backend(targetlist[i]).anticipated()) +
(Mstr(", adjusted weight ") +
balancer.backend(targetlist[i]).adjustedweight()) +
- (Mstr(", weighted connections ") + weighted_conn) +
+ (Mstr(", weighted ") + this_weight) +
"\n");
- if (!found || weighted_conn < nconn) {
+ if (!found || this_weight < best_weighted) {
t = targetlist[i];
- nconn = balancer.backend(t).connections();
+ best_weighted = this_weight;
found = true;
}
}
diff --git a/xr/DispatchAlgorithms/storedip/target.cc b/xr/DispatchAlgorithms/storedip/target.cc
@@ -19,71 +19,73 @@ static StoreMap store;
unsigned StoredIp::target(struct in_addr clientip,
BackendVector const &targetlist) {
+ PROFILE("StoredIP::target");
+
+ msg(Mstr("Starting stored-ip dispatcher\n"));
+
unsigned target;
time_t now = time(0);
- if (store.count(clientip) > 0) {
- // Client already known, maybe timed out.
- time_t diff = now - store[clientip].lastaccess;
+ // Weed out store. Done first, because the store should be up to date
+ // for some decisions below.
+ for (StoreMap::iterator iter = store.begin(); iter != store.end();
+ iter++) {
+ if (config.debug()) {
+ Timestamp tm((*iter).second.lastaccess);
+ debugmsg (Mstr(inet_ntoa(iter->first)) + Mstr(" visited on ") +
+ tm.desc() + "\n");
+ }
+ if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) {
+ debugmsg (" Erasing stale entry\n");
+ store.erase(iter);
+ }
+ }
+ // Let's see if we know the client.
+ if (store.count(clientip) > 0) {
if (config.verbose()) {
Timestamp tm(store[clientip].lastaccess);
msg(Mstr("Client IP ") + Mstr(inet_ntoa(clientip)) +
- " last visited on " + tm.desc() +
- Mstr(Mstr(", ") + diff) + " sec ago, and went to " +
+ " last visited on " + tm.desc() + " and went to " +
balancer.backend(store[clientip].targetbackend).description() +
"\n");
}
-
- if (diff <= config.ipstoretimeout()) {
- // Recent 'nuff
- target = store[clientip].targetbackend;
- if (! balancer.backend(target).available()) {
- // Historical target down - get new one if in lax mode
- if (config.dispatchmode() == Dispatchmode::m_strict_stored_ip)
- throw Error("Stored-IP algorithm: target back end " +
- balancer.backend(target).description() +
- "unavailable");
- else {
- msg ("Stored IP algorithm: target back end " +
- balancer.backend(target).description() +
- " unavailable, falling back to least-connections\n");
- Leastconn l;
- target = l.target(clientip, targetlist);
- }
- }
- } else {
- // Not recent anymore
- msg ("Visit too long ago, re-dispatching with least-connections\n");
- Leastconn l;
- target = l.target(clientip, targetlist);
+ target = store[clientip].targetbackend;
+ if (balancer.backend(target).available()) {
+ // Historical target is up, go there!
+ msg(Mstr("Sending ") + Mstr(inet_ntoa(clientip)) + " to " +
+ balancer.backend(target).description() + "\n");
+ ClientData entry = {target, now};
+ store[clientip] = entry;
+ return target;
}
- } else {
- // Historical target unknown, fetch new one
- msg ("New visit from " + static_cast<string>(inet_ntoa(clientip)) +
- "\n");
- Leastconn l;
- target = l.target(clientip, targetlist);
+ msg (Mstr("Historical target ") +
+ balancer.backend(target).description() + " unavailable\n");
+ if (config.dispatchmode() == Dispatchmode::m_strict_stored_ip)
+ throw Error("Stored-IP algorithm: target back end " +
+ balancer.backend(target).description() +
+ "unavailable");
}
- // Update the info.
- ClientData entry = {target, now};
- store[clientip] = entry;
+ // Client is seen for the first time, or after the timout period, or
+ // their preferred back end is down (and we're in lax mode ofc).
+ // Treat as new connection.
- // Weed out store.
+ // Preload anticipated connections.
for (StoreMap::iterator iter = store.begin(); iter != store.end();
iter++) {
- if (config.debug()) {
- Timestamp tm((*iter).second.lastaccess);
- debugmsg (Mstr(inet_ntoa(iter->first)) + Mstr(" visited on ") +
- tm.desc() + "\n");
- }
- if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) {
- debugmsg (" Erasing stale entry, stale\n");
- store.erase(iter);
- }
+ msg(Mstr("Anticipating connection for back end ") +
+ balancer.backend((*iter).second.targetbackend).description() +
+ "\n");
+ balancer.backend((*iter).second.targetbackend).anticipate_more();
}
+ // Now get a target and store the dispatch result.
+ Leastconn l;
+ target = l.target(clientip, targetlist);
+ ClientData entry = {target, now};
+ store[clientip] = entry;
+
// Return target to caller
- return (target);
+ return target;
}
diff --git a/xr/Dispatchers/tcpdispatcher/dispatch.cc b/xr/Dispatchers/tcpdispatcher/dispatch.cc
@@ -8,6 +8,11 @@ void TcpDispatcher::dispatch() {
bool connected = false;
+ // Reset the expectancy of back ends. Dispatchers down the line (stored-ip)
+ // will update that later.
+ for (unsigned i = 0; i < balancer.nbackends(); i++)
+ balancer.backend(i).anticipated(0);
+
// Build up the target list, if not yet done so. The HTTP dispatcher
// might've created it already for host-based matching (in which case
// we won't bother here).
diff --git a/xr/backend/anticipated.cc b/xr/backend/anticipated.cc
@@ -0,0 +1,9 @@
+#include "backend"
+
+void Backend::anticipated(unsigned a) {
+ Mutex::lock(&nanticipated);
+ msg((Mstr("Backend ") + description()) +
+ (Mstr(" now anticipates ") + a) + " connections\n");
+ nanticipated = a;
+ Mutex::unlock(&nanticipated);
+}
diff --git a/xr/backend/anticipateless.cc b/xr/backend/anticipateless.cc
@@ -0,0 +1,9 @@
+#include "backend"
+
+void Backend::anticipate_less() {
+ if (nanticipated) {
+ Mutex::lock(&nanticipated);
+ nanticipated--;
+ Mutex::unlock(&nanticipated);
+ }
+}
diff --git a/xr/backend/anticipatemore.cc b/xr/backend/anticipatemore.cc
@@ -0,0 +1,7 @@
+#include "backend"
+
+void Backend::anticipate_more() {
+ Mutex::lock(&nanticipated);
+ nanticipated++;
+ Mutex::unlock(&nanticipated);
+}
diff --git a/xr/backend/available.cc b/xr/backend/available.cc
@@ -8,9 +8,12 @@ bool Backend::available() const {
(Mstr(": ") + livestr()) +
(Mstr(", ") + upstr()) +
(Mstr(", ") + connections()) +
- (Mstr(" connections of ") + maxconn()) +
+ (Mstr(" connections, ") + anticipated()) +
+ (Mstr(" anticipated, of ") + maxconn()) +
" max\n");
if (!maxconn())
return (islive && isup);
- return (islive && isup && connections() < maxconn());
+ return (islive &&
+ isup &&
+ (connections() + anticipated()) < maxconn());
}
diff --git a/xr/backend/backend b/xr/backend/backend
@@ -53,6 +53,12 @@ public:
unsigned adjustedweight() const { return (bdef.adjustedweight()); }
unsigned connections() const { return (nconn); }
+
+ unsigned anticipated() const { return nanticipated; }
+ void anticipated(unsigned n);
+ void anticipate_more();
+ void anticipate_less();
+
double bytesserved() const { return (bytes_served); }
unsigned clientsserved() const { return (totconn); }
@@ -80,7 +86,7 @@ private:
bool islive;
bool isup;
int clsocket;
- unsigned nconn, totconn;
+ unsigned nconn, totconn, nanticipated;
double bytes_served;
double loadaverage;
DNSEntry dnsentry;
diff --git a/xr/backend/backend1.cc b/xr/backend/backend1.cc
@@ -2,6 +2,6 @@
Backend::Backend () :
islive(true), isup(true), clsocket(-1),
- nconn(0), totconn(0), bytes_served(0),
+ nconn(0), totconn(0), nanticipated(0), bytes_served(0),
loadaverage(0.1), dnsentry() {
}
diff --git a/xr/backend/backend2.cc b/xr/backend/backend2.cc
@@ -2,5 +2,6 @@
Backend::Backend (BackendDef const &b) :
bdef(b), islive(true), isup(true), clsocket(-1), nconn(0), totconn(0),
+ nanticipated(0),
bytes_served(0), loadaverage(0.1), dnsentry() {
}
diff --git a/xr/config/setdispatcmode.cc b/xr/config/setdispatcmode.cc
@@ -33,7 +33,7 @@ void Config::setdispatchmode (string s) {
dmode.mode (Dispatchmode::m_lax_stored_ip);
ipstoretimeout(setinteger(s.substr(2)));
} else if (s.substr(0, 14) == "lax-stored-ip:") {
- dmode.mode (Dispatchmode::m_strict_stored_ip);
+ dmode.mode (Dispatchmode::m_lax_stored_ip);
ipstoretimeout(setinteger(s.substr(14)));
} else
throw Error("Bad dispatch mode -d" + s);
diff --git a/xr/etc/status.xslt b/xr/etc/status.xslt
@@ -567,7 +567,12 @@
<tr>
<td></td>
<td>Connections</td>
- <td colspan="2"><xsl:value-of select="connections"/></td>
+ <td colspan="2">
+ <xsl:value-of select="connections"/>
+ <xsl:if test="anticipated > 0">
+ (anticipating <xsl:value-of select="anticipated"/>)
+ </xsl:if>
+ </td>
</tr>
<tr>
<td></td>
diff --git a/xr/webinterface/answerstatus.cc b/xr/webinterface/answerstatus.cc
@@ -98,6 +98,7 @@ void Webinterface::answer_status() {
" <live>" << balancer.backend(i).livestr() << "</live>\n"
" <available>" << balancer.backend(i).availablestr() << "</available>\n"
" <connections>" << balancer.backend(i).connections() << "</connections>\n"
+ " <anticipated>" << balancer.backend(i).anticipated() << "</anticipated>\n"
" <bytesserved>" << balancer.backend(i).bytesserved() << "</bytesserved>\n"
" <clientsserved>" << balancer.backend(i).clientsserved() << "</clientsserved>\n"
" <hostmatch>" << balancer.backend(i).hostmatch() << "</hostmatch>\n"