commit f0cb14bcd2b19ee5dd0abbc223c9d25581c8b4c1
parent e08f730be61b2d2c337b15ef201d6d327759fce8
Author: finwo <finwo@pm.me>
Date: Sat, 3 Jan 2026 19:37:49 +0100
2.57
Diffstat:
22 files changed, 279 insertions(+), 51 deletions(-)
diff --git a/ChangeLog b/ChangeLog
@@ -1,3 +1,15 @@
+2.57 [KK 2009-09-14]
+- Output of "xrctl status" colorized when a service is not running,
+ thanks Frederik D. for the suggestion and the code!
+- Small cleanup of xrctl's usage information.
+
+2.56 [KK 2009-05-26]
+- Bugfix in cleaning of IPStore map
+- Implemented flag --remove-reservations (xrctl: tag: removereservations)
+- Timeouts adjusted: all read time outs set to 30, client-write set to
+ 5, backend-write set to 3
+- Implemented tag prefixtimestamp in system block to force timestamping.
+
2.55 [KK 2009-05-13]
- Implemented connection error counting of back ends.
- Select-handling revised: atomic readability and writeability checks,
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@
# Top-level Makefile for XR
# -------------------------
-VER = 2.55
+VER = 2.57
PREFIX = $(DESTDIR)/usr
BINDIR = $(PREFIX)/sbin
MANDIR = $(PREFIX)/share/man
diff --git a/doc/xr.odt b/doc/xr.odt
Binary files differ.
diff --git a/doc/xr.pdf b/doc/xr.pdf
Binary files differ.
diff --git a/doc/xrctl.xml.5 b/doc/xrctl.xml.5
@@ -45,6 +45,9 @@ distributed with the sources for a full description.
<maxlogsize>100000</maxlogsize>
- How many history logs to keep?
<loghistory>10</loghistory> -->
+ <!-- To force log line timestamping, add:
+ <prefixtimestamp>true</prefixtimestamp>
+ Or turn it off with value "false". -->
</system>
<!-- Service descriptions: This section defines all balancing
@@ -151,6 +154,9 @@ distributed with the sources for a full description.
<denyfrom>192.168.1.100</denyfrom>
</acl>
+ <!-- For a nonstandard buffer size (default is 2k), use: -->
+ <buffersize>4096</buffersize>
+
<dosprotection>
<!-- Here is some basic DOS protection. Connections from IP's
are counted over timeinterval seconds (here: 2 sec). When a
diff --git a/test/multi-ip.xml b/test/multi-ip.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+ <system>
+ <uselogger>true</uselogger>
+ <logger>clpipe /var/log/xr.clog</logger>
+ <path>/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/opt/local/bin:/opt/local/sbin</path>
+ </system>
+
+ <service>
+ <name>first</name>
+ <server>
+ <type>tcp</type>
+ <address>192.168.2.5:10000</address>
+ <dispatchmode>first-available</dispatchmode>
+ <webinterface>192.168.2.5:10001</webinterface>
+ <verbose>yes</verbose>
+ <debug>yes</debug>
+ </server>
+ <backend>
+ <address>www.google.nl:80</address>
+ </backend>
+ <backend>
+ <address>www.thedailywtf.com:80</address>
+ </backend>
+ </service>
+
+ <service>
+ <name>second</name>
+ <server>
+ <type>tcp</type>
+ <address>172.16.89.1:10000</address>
+ <dispatchmode>first-available</dispatchmode>
+ <webinterface>172.16.89.1:10001</webinterface>
+ <verbose>yes</verbose>
+ <debug>yes</debug>
+ </server>
+ <backend>
+ <address>kubat.nl:80</address>
+ </backend>
+ <backend>
+ <address>slashdot.org:80</address>
+ </backend>
+ </service>
+
+</configuration>
diff --git a/xr/DispatchAlgorithms/leastconn/target.cc b/xr/DispatchAlgorithms/leastconn/target.cc
@@ -5,25 +5,46 @@ unsigned Leastconn::target(struct in_addr clientip,
PROFILE("Leastconn::target");
msg (Mstr("Starting least-connections dispatcher\n"));
+
+ if (config.debug()) {
+ ostringstream o;
+ o << "Back end target list:";
+ for (unsigned i = 0; i < targetlist.size(); i++)
+ o << ' ' << targetlist[i];
+ o << '\n';
+ _debugmsg(o.str());
+ }
bool found = false;
unsigned best_weighted = 0, t = 0;
for (unsigned i = 0; i < targetlist.size(); i++) {
- if (! balancer.backend(targetlist[i]).available())
+ if (! balancer.backend(targetlist[i]).available()) {
+ debugmsg(Mstr("Back end ") +
+ Mstr(balancer.backend(targetlist[i]).description()) +
+ " is NOT available\n");
continue;
- unsigned this_weight =
- (balancer.backend(targetlist[i]).connections() +
- IPStore::anticipated(targetlist[i])) *
- balancer.backend(targetlist[i]).adjustedweight();
+ }
+ unsigned connections = balancer.backend(targetlist[i]).connections();
+ unsigned anticipated = IPStore::anticipated(targetlist[i]);
+ unsigned adjweight = balancer.backend(targetlist[i]).adjustedweight();
+ unsigned this_weight = (connections + anticipated) * adjweight;
+ ostringstream o;
+
+ if (config.debug())
+ o << "Back end " << balancer.backend(targetlist[i]).description()
+ << ": connections=" << connections << ", anticipated=" << anticipated
+ << ", adjweight=" << adjweight << ", thisweight=" << this_weight;
+
if (!found || this_weight < best_weighted) {
t = targetlist[i];
best_weighted = this_weight;
found = true;
- }
+ debugmsg(Mstr(o.str()) + Mstr(" is best so far\n"));
+ } else
+ debugmsg(Mstr(o.str()) + Mstr(" skipped, got a better one\n"));
}
-
if (!found)
throw Error("Least-connections algorithm: no available back ends");
return (t);
diff --git a/xr/DispatchAlgorithms/storedip/target.cc b/xr/DispatchAlgorithms/storedip/target.cc
@@ -29,5 +29,28 @@ unsigned StoredIp::target(struct in_addr clientip,
// their preferred back end is down (and we're in lax mode ofc).
// Treat as new connection.
Leastconn l;
- return l.target(clientip, targetlist);
+
+ if (!config.removereservations())
+ return l.target(clientip, targetlist);
+ else {
+ BackendVector tlist = targetlist;
+ while (true) {
+ try {
+ return l.target(clientip, tlist);
+ } catch (...) {
+ // We're out of back ends and need to clear up the IP store to retry.
+ // We give it a sec, remove the oldest entry, and rebuild the
+ // target list for the least-connections dispatch algorithm.
+ warnmsg("Out of back ends, releasing oldest client entry and retrying\n");
+ IPStore::clearoldest();
+ sleep(1);
+ BackendVector newlist;
+ for (unsigned int i = 0; i < balancer.nbackends(); i++)
+ if (balancer.backend(i).available())
+ newlist.add(i);
+ tlist = newlist;
+ }
+ }
+ }
}
+
diff --git a/xr/balancer/serve.cc b/xr/balancer/serve.cc
@@ -3,6 +3,8 @@
#include "Dispatchers/httpdispatcher/httpdispatcher"
#include "Dispatchers/udpdispatcher/udpdispatcher"
+// #define SHOWDEBUG
+
void Balancer::serve() {
int clsock = -1;
@@ -143,6 +145,11 @@ void Balancer::serve() {
_debugmsg (Mstr("Allocation boundary at dispatcher start: ") +
mem + "\n");
}
+ #ifdef SHOWDEBUG
+ void *mem = malloc(16);
+ free (mem);
+ cout << "XR allocation at dispatcher start: " << mem << '\n';
+ #endif
d->start();
} else {
diff --git a/xr/config/config b/xr/config/config
@@ -158,6 +158,12 @@ public:
string dispatchmodestr() const {
return (dmode.modestr());
}
+ bool removereservations() const {
+ return remove_reservations;
+ }
+ void removereservations(bool b) {
+ remove_reservations = b;
+ }
string const &softmaxconnexcess() const {
return soft_maxconn_excess_prog;
@@ -227,6 +233,7 @@ private:
static string hard_maxconn_excess_prog;
static unsigned dns_cache_timeout;
static string on_start, on_end, on_fail;
+ static bool remove_reservations;
};
extern Config config;
diff --git a/xr/config/config1.cc b/xr/config/config1.cc
@@ -7,8 +7,8 @@ string Config::sip = "0";
vector<BackendDef> Config::blist;
Dispatchmode Config::dmode;
unsigned Config::c_timeout = 30;
-unsigned Config::b_timeout = 3;
-unsigned Config::c_write_timeout = 30;
+unsigned Config::c_write_timeout = 5;
+unsigned Config::b_timeout = 30;
unsigned Config::b_write_timeout = 3;
unsigned Config::wakeup = 5;
unsigned Config::checkup = 0;
@@ -43,6 +43,7 @@ unsigned Config::dns_cache_timeout = 3600;
string Config::on_start = "";
string Config::on_end = "";
string Config::on_fail = "";
+bool Config::remove_reservations = false;
Config::Config () {
}
diff --git a/xr/config/parsecmdline.cc b/xr/config/parsecmdline.cc
@@ -15,7 +15,7 @@ void Config::parsecmdline (int ac, char **av) {
if (ac == 1)
throw Error("Bad command line '" + cmdline + "'\n" + USAGE);
-# define OPTSTRING "?a:A:B:b:c:CDd:E:e:fF:g:hH:Il:" \
+# define OPTSTRING "?a:A:B:b:c:CDd:E:e:fF:Gg:hH:Il:" \
"m:M:nPp:Q:r:R:Ss:t:T:u:U:vVW:w:xXy:z:Z:"
# ifdef HAVE_GETOPT_LONG
static struct option longopts[] = {
@@ -31,6 +31,7 @@ void Config::parsecmdline (int ac, char **av) {
{ "soft-maxconn-excess", required_argument, 0, 'e' },
{ "dns-cache-timeout", required_argument, 0, 'F' },
{ "foreground", no_argument, 0, 'f' },
+ { "remove-reservations", no_argument, 0, 'G' },
{ "backend-check", required_argument, 0, 'g' },
{ "help", no_argument, 0, 'h' },
{ "add-server-header", required_argument, 0, 'H' },
@@ -115,6 +116,9 @@ void Config::parsecmdline (int ac, char **av) {
case 'f':
foreground_mode = true;
break;
+ case 'G':
+ removereservations(true);
+ break;
case 'g':
current_backendcheck.parse(optarg);
break;
diff --git a/xr/etc/usage.txt b/xr/etc/usage.txt
@@ -76,6 +76,9 @@ may not exist on your platform):
The default behavior is a TCP connect, to the back end's IP, at
the back end's port. Use "-g connect::" to reset previous flags
to the default.
+ -G, --remove-reservations
+ In stored-ip algorithms, outstanding reservations for expected
+ clients are removed when no more back ends are available.
-h, -?, --help
This text.
-H HDR, --add-server-header HDR
@@ -123,13 +126,13 @@ may not exist on your platform):
is 0, XR will listen to stdin (inetd-mode, not available for udp).
Default: tcp:0:10000 (TCP balancing, on all interfaces, via port 10000).
-t SEC, --backend-timeout SEC
- Defines network timeouts for back ends, default 3 sec. Use 0 to
- prevent timing out. SEC is the timeout for reads and writes.
- Use -t RSEC:WSEC to specify separate timeouts for reads and writes.
+ Defines network read timeouts for back ends, default 30 sec. Use 0 to
+ prevent timing out. Use -t RSEC:WSEC to specify separate timeouts
+ for reads and writes, default 30:3.
-T SEC, --client-timeout SEC
- Defines network timeouts for clients, default 30 sec. Use 0 to
+ Defines network read timeouts for clients, default 30 sec. Use 0 to
prevent timing out. Use -T RSEC:WSEC to specify separate
- timeouts for reads and writes.
+ timeouts for reads and writes, default 30:5
-u USEC, --defer-time USEC
If a connection is going to be deferred due to hitting the "soft" rate
(see --soft-maxconnrate), then this option sets how long the deferral
diff --git a/xr/fdset/wait.cc b/xr/fdset/wait.cc
@@ -53,13 +53,27 @@ void Fdset::wait(bool wait_read, bool wait_write) {
throw Error(o.str());
}
- // More debugging
+ // More debugging: What has become readable, what has become
+ // writeable, also state if no change was seen
if (config.debug()) {
+ bool statechanged = false;
for (unsigned int i = 0; i < FD_SETSIZE; i++) {
- if (FD_ISSET(i, &readset))
+ if (FD_ISSET(i, &readset)) {
_debugmsg(Mstr("Fd ") + Mstr(i) + " is readable\n");
- if (FD_ISSET(i, &writeset))
+ statechanged = true;
+ }
+ if (FD_ISSET(i, &writeset)) {
_debugmsg(Mstr("Fd ") + Mstr(i) + " is writeable\n");
+ statechanged = true;
+ }
}
+ if (!statechanged) {
+ ostringstream o;
+ o << "Select timeout: neither of the fd's ";
+ for (unsigned int i = 0; i < set.size(); i++)
+ o << set[i] << ' ';
+ o << "has shown activity in " << tsec << " sec\n";
+ _debugmsg(o.str());
+ }
}
}
diff --git a/xr/ipstore/anticipated.cc b/xr/ipstore/anticipated.cc
@@ -3,7 +3,11 @@
unsigned IPStore::anticipated(unsigned b) {
if (!onoff || b >= balancer.nbackends())
return 0;
-
+
+ // Weed store for decisions later
+ weed();
+
+ // Get number of anticipated clients for given back end
unsigned ret = 0;
Mutex::lock(&store);
@@ -13,7 +17,9 @@ unsigned IPStore::anticipated(unsigned b) {
if ((*iter).second.targetbackend == (int)b)
ret++;
Mutex::unlock(&store);
-
+
+ debugmsg(Mstr("Anticipated connections for back end ") + Mstr(b) + ": " +
+ Mstr(ret) + "\n");
return ret;
}
diff --git a/xr/ipstore/clearoldest.cc b/xr/ipstore/clearoldest.cc
@@ -0,0 +1,36 @@
+#include "ipstore"
+
+void IPStore::clearoldest() {
+ time_t oldest_time = time(0) + 100;
+ StoreMap::iterator oldest_entry;
+ bool found = false;
+
+ Mutex::lock(&store);
+
+ dump();
+
+ // Find oldest entry.
+ for (StoreMap::iterator iter = store.begin();
+ iter != store.end();
+ iter++) {
+ if ((*iter).second.lastaccess < oldest_time) {
+ oldest_time = (*iter).second.lastaccess;
+ oldest_entry = iter;
+ found = true;
+ }
+ }
+
+ // Kill it if we got it.
+ if (found) {
+ if (config.debug()) {
+ Timestamp tm((*oldest_entry).second.lastaccess);
+ _debugmsg(Mstr("Erasing oldest IP store entry: ") +
+ Mstr(inet_ntoa(oldest_entry->first)) + " on " +
+ tm.desc() + "\n");
+ }
+ store.erase(oldest_entry);
+ }
+
+ Mutex::unlock(&store);
+ dump();
+}
diff --git a/xr/ipstore/dump.cc b/xr/ipstore/dump.cc
@@ -0,0 +1,18 @@
+#include "ipstore"
+
+void IPStore::dump() {
+ if (!config.debug())
+ return;
+
+ _debugmsg(Mstr("IPStore dump:\n"));
+ for (StoreMap::iterator iter = store.begin();
+ iter != store.end();
+ iter++) {
+ Timestamp tm((*iter).second.lastaccess);
+ ostringstream o;
+ o << "Client IP " << inet_ntoa(iter->first) << " on "
+ << tm.desc() << " to back end " << (*iter).second.targetbackend
+ << '\n';
+ _debugmsg(o.str());
+ }
+}
diff --git a/xr/ipstore/ipstore b/xr/ipstore/ipstore
@@ -29,11 +29,15 @@ public:
static void activity(struct in_addr clientip, unsigned curbackend);
static unsigned anticipated(unsigned bckend);
static void clear(struct in_addr clientip);
+ static void clearoldest();
static void on() { onoff = true; }
static void off() { onoff = false; }
private:
+ static void dump();
+ static void weed();
+
static StoreMap store;
static bool onoff;
};
diff --git a/xr/ipstore/target.cc b/xr/ipstore/target.cc
@@ -4,30 +4,8 @@ IPStore::StoreMap IPStore::store;
bool IPStore::onoff = false;
int IPStore::target(struct in_addr clientip) {
- time_t now = time(0);
-
- // Weed out store. Done first, because the store should be up to date
- // for some decisions below.
- bool done = false;
- Mutex::lock(&store);
- while (!done) {
- done = true;
- for (StoreMap::iterator iter = store.begin();
- iter != store.end();
- iter++) {
- if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) {
- if (config.debug()) {
- done = false;
- Timestamp tm((*iter).second.lastaccess);
- debugmsg (Mstr(inet_ntoa(iter->first)) +
- Mstr(" visited on ") + tm.desc() + ", erasing\n");
- }
- store.erase(iter);
- break;
- }
- }
- }
- Mutex::unlock(&store);
+ // Weed out the store, for decisions later
+ weed();
// Let's see if we know the client.
if (store.count(clientip) > 0) {
diff --git a/xr/ipstore/weed.cc b/xr/ipstore/weed.cc
@@ -0,0 +1,29 @@
+#include "ipstore"
+
+void IPStore::weed() {
+ time_t now = time(0);
+ bool done = false;
+
+ Mutex::lock(&store);
+
+ while (!done) {
+ done = true;
+ for (StoreMap::iterator iter = store.begin();
+ iter != store.end();
+ iter++) {
+ if (now - ((*iter).second.lastaccess) > config.ipstoretimeout()) {
+ if (config.debug()) {
+ Timestamp tm((*iter).second.lastaccess);
+ _debugmsg (Mstr("Stale entry: ") + Mstr(inet_ntoa(iter->first)) +
+ Mstr(" visited on ") + tm.desc() +
+ ", erasing\n");
+ }
+ done = false;
+ store.erase(iter);
+ break;
+ }
+ }
+ }
+
+ Mutex::unlock(&store);
+}
diff --git a/xr/webinterface/answerstatus.cc b/xr/webinterface/answerstatus.cc
@@ -23,6 +23,7 @@ void Webinterface::answer_status() {
" <backendreadtimeout>" << config.backend_read_timeout() << "</backendreadtimeout>\n"
" <backendwritetimeout>" << config.backend_write_timeout() << "</backendwritetimeout>\n"
" <dispatchmode>" << config.dispatchmodestr() << "</dispatchmode>\n"
+ " <removereservations>" << config.removereservations() << "</removereservations>\n"
" <webinterface>" << config.webinterfaceip() << ':' << config.webinterfaceport() << "</webinterface>\n"
" <dnscachetimeout>" << config.dnscachetimeout() << "</dnscachetimeout>\n"
" <buffersize>" << config.buffersize() << "</buffersize>\n"
diff --git a/xrctl/xrctl b/xrctl/xrctl
@@ -1,6 +1,7 @@
#!/usr/bin/perl
use strict;
use Getopt::Std;
+use Term::ANSIColor qw(:constants);
# Versioning
my $VER = "__VER__";
@@ -11,6 +12,7 @@ my $VER = "__VER__";
# Default configuration file to read and default logging facility
my $default_conf = '/etc/xrctl.xml';
my $default_logger = 'logger';
+my $default_prefixtimestamp = undef;
# Default settings, must match xr's defaults
my $default_dispatchmode = 'least-connections';
@@ -57,7 +59,7 @@ my $sysblock = $xp->data('system');
if ($sysblock ne '') {
my $sysxp = new XMLParser($xp->data('system'));
for my $tag qw(pscmd logger uselogger logdir
- maxlogsize loghistory path) {
+ maxlogsize loghistory path prefixtimestamp) {
$sysconf{$tag} = $sysxp->data($tag);
msg("System config $tag: $sysconf{$tag}\n") if ($sysconf{$tag} ne '');
}
@@ -78,6 +80,15 @@ if ($sysblock ne '') {
}
}
msg ("PS command: $sysconf{pscmd}\n");
+
+ if ($sysconf{prefixtimestamp}) {
+ $default_prefixtimestamp = 1 if istrue($sysconf{prefixtimestamp});
+ } else {
+ $default_prefixtimestamp = 1
+ if (!istrue($sysconf{uselogger}) or !find_bin('logger'));
+ }
+ msg ("Log lines will be prefixed with a timestamp\n")
+ if ($default_prefixtimestamp);
}
# Load up the service names.
@@ -227,7 +238,7 @@ sub cmd_killstart {
sub cmd_status {
for my $s (@_) {
print ("Service $s: ");
- print ("not ") unless (is_running($s));
+ print (BOLD, RED, "not ", RESET) unless (is_running($s));
print ("running\n");
}
}
@@ -325,7 +336,7 @@ Flags are:
-v increases verbosity
-c CONFIG specifies the configuration, default $default_conf
Actions are:
- configtest builds invocations from the configuration file and validates them
+ configtest validates the configuration
list shows the xr command line
start starts the service(s) if they are not yet running
stop gracefully stops the service(s) if they are running
@@ -472,7 +483,7 @@ sub xr_cmdarr {
my @cmd;
push (@cmd, "xr-$service");
push (@cmd, '--prefix-timestamp')
- if (!istrue($sysconf{uselogger}) or !find_bin('logger'));
+ if ($default_prefixtimestamp);
# Fetch the <service> block for this service
my $sp = xml_serviceparser($service)
@@ -490,7 +501,8 @@ sub xr_cmdarr {
# Flags that should go on the command line if the bool-tag is true
my %boolflags = (closesocketsfast => '--close-sockets-fast',
verbose => '--verbose',
- debug => '--debug');
+ debug => '--debug',
+ removereservations => '--remove-reservations');
# Handle general flags and boolflags
push (@cmd,