//////////////////////////////////////////////////////////////////////////////
// .NET Dynamic Software Load Balancing
// A Draft Implementation of an Idea for .NET Dynamic Software Load Balancing
// (c) 2002, Stoyan Damov (stoyan_damov@hotmail.com)
// The software comes �AS IS�, with all faults and no warranties.
//////////////////////////////////////////////////////////////////////////////
#pragma once
#if !defined (Common_H)
#define Common_H
// .NET method access modifiers
// C# access term:) assembly class
//
#define PUBLIC public public
#define PRIVATE private private
#define PROTECTED public protected
#define INTERNAL private public
// tired of writing "unsigned char buf __gc [] = new unsigned char __gc[...]"
//
typedef unsigned char byte;
// configuration file name -- same for MLMS and MLRS
//
#define CONFIG_FILE_NAME S"LoadBalancingConfiguration.xml"
// Configuration Sections (CS)
//
#define CS_LMS S"LoadMonitoringServer"
#define CS_LRS S"LoadReportingServer"
// Configuration Keys (CK)
//
// for the Load Reporting Server (LRS)
//
// the interval in milliseconds at which the MLRS reports the machine
// load to MLMS
//
#define CK_LRS_INTERVAL S"ReportingInterval"
//
// the IP address of the multicast group, which MLRS joins in order
// to report machine loads to all "listening" MLMS; if multicasting
// is disabled, it is the IP address of a single MLMS server, to which
// MLRS sends machine loads
//
#define CK_LRS_IP_ADDRESS S"IpAddress"
//
// the port, MLRS sends machine loads to, no matter whether it is using
// multicasts, UDP-only or TCP
//
#define CK_LRS_PORT S"Port"
// for the Load Monitoring Server (LMS)
//
// the IP address of the multicast group, MLMS joins in order to receive
// machine loads; if multicasting is disabled, it is not used, as MLMS
// binds to any available IP address on the local machine
//
#define CK_LMS_IP_ADDRESS S"IpAddress"
//
// the port of the multicast group, and the port MLMS listens to if
// multicasting and UDP are disabled
//
#define CK_LMS_COLLECTOR_PORT S"CollectorPort"
//
// the backlog (the maximum number of sockets), MLMS uses if it is
// using TCP (no multicasting and UDP)
//
#define CK_LMS_COLLECTOR_BACKLOG S"CollectorBacklog"
//
// the port on which MLMS listens and accepts TCP queries for the
// least loaded machine; I used it in the first version of the LML
// library, but since I'm using Remoting to publish this information
// you may forget "ReporterPort" and "ReporterBacklog", unless you
// wish to extend the LML library to report machine loads via Remoting
// and by answering TCP queries
//
#define CK_LMS_REPORTER_PORT S"ReporterPort"
//
// the backlog of the ReporterWorker's listener, accepting TCP
// requests
//
#define CK_LMS_REPORTER_BACKLOG S"ReporterBacklog"
//
// the maximum time in milliseconds, at which a machine should report
// a fresh load; i.e. if a machine reports a load now, and it does not
// report a new load in "MachineReportTimeout" milliseconds, the machine
// is considered dead, and is removed from the load balancing, until the
// next time it reports a new load
//
#define CK_LMS_MACHINE_REPORT_TIMEOUT S"MachineReportTimeout"
//
// initially, I thought to register the load balancing object with the
// Remoting runtime, only on a TCP channel; however, later I decided to
// support both TCP and HTTP; so the "RemotingProtocol" is a string,
// which is either "HTTP" or "TCP"
//
#define CK_LMS_REMOTING_PROTOCOL S"RemotingProtocol"
//
// the port, on which the Remoting runtime accepts requests for
// the creation of the load balancing object
//
#define CK_LMS_REMOTING_CHANNEL_PORT S"RemotingChannelPort"
// Remoting channels (protocols)
//
#define CHANNEL_TCP S"tcp"
#define CHANNEL_HTTP S"http"
// the alias of the load balancing object, used by the Remoting runtime
// and the client library LBL
//
#define REMOTED_OBJECT_NAME S"LoadBalancer"
// message parts defined to avoid hardcoding of strings
//
#define MSG_PART_OK S"+OK"
#define MSG_PART_MACH S"MACH"
#define MSG_PART_LOAD S"LOAD"
// I love C++'s macro support! And I really don't like people who
// preach that macros are bad. Macros are good, save a whole lot
// of typing and errors, resulting from Object-Oriented Copy/Pasting:)
// Here's just a simple example of a good macro: the Assert method of
// the Debug class accepts a boolean expression to evaluate, and a
// string which describes the test, so if the test fails, the string
// is output in the assert message box. Well, imagine if I had to write
// every time the same expression in quotes, in order to see it in the
// message box, i.e. Debug::Assert (0 != someObject, S"0 != someObject");
// Now look at the super simple macro, that does the same thing for me
// automatically.
//
#define DEBUG_ASSERT(x) Debug::Assert (x, S#x)
// Here's another good macro, I use for exception handling:
// Rule of a thumb for exception handling: catch only the exceptions
// you can handle, and no more. This means that if you expect the method
// you're calling to throw "ArgumentNullException" and/or
// "ArgumentOutOfRangeException", you should write two catch clauses and
// catch only these exceptions. Another rule is to NEVER swallow an exception
// you caught, but cannot handle. You MUST re-throw it, so the caller of
// YOUR method knows why it failed. There are 2 exceptions you can do nothing
// about but report them to the user and die: these are "OutOfMemoryException",
// and "ExecutionEngineException". I don't know which one is worse -- probably
// the latter, though if you're out of memory, there's almost nothing you can
// do about it. Now, because I'm not writing production code here, I allowed
// myself to catch ALL possible exceptions, when I don't need to handle them
// except to know something wrong has happened. So I catch the base class
// "Exception". This violates both rules, I've written about exception
// handling, but I wrote some code to fit into the second one -- if I catch
// OutOfMemoryException or ExecutionEngineException, I re-throw them
// immediately. Here's the macro I call, after I catch the generic Exception
// class:
//
#define TRACE_EXCEPTION_AND_RETHROW_IF_NEEDED(e) \
System::Type __gc* exType = e->GetType (); \
if (exType == __typeof (OutOfMemoryException) || \
exType == __typeof (ExecutionEngineException)) \
throw; \
Console::WriteLine ( \
S"\n{0}\n{1} ({2}/{3}): {4}\n{0}", \
new String (L'-', 79), \
new String ((char *) __FUNCTION__), \
new String ((char *) __FILE__), \
__box (__LINE__), \
e->Message);
// forward class declaration macro, that "remembers" to wrap the class
// in the namespace I use throughout the solution;
//
#define FORWARD_DECLARE(className) \
namespace SoftwareLoadBalancing \
{ \
public __gc class className; \
}
// I think the macro is self-explanatory, but I need to say, that the
// interval is measured in microseconds, not milliseconds
//
#define SOCKET_POLL_INTERVAL 100
// the time, a "listener" thread waits, before it checks for an arrived
// network requests (in milliseconds)
//
#define SOCKET_WAIT_INTERVAL 50
// long live macros!:)
//
#define SOCKET_CLOSE(sock) \
try \
{ \
if (0 != sock) \
{ \
sock->Shutdown (SocketShutdown::Both); \
sock->Close (); \
sock = 0; \
} \
} \
catch (SocketException __gc*) {} \
catch (ObjectDisposedException __gc*) {}
// undefine the USING_UDP macro to make LRS report to LMS via TCP
//
#define USING_UDP 1
// undefine the USING_MULTICASTS macro to disable multicasting
//
#define USING_MULTICASTS 1
// helper macros for protocol-independent coding
//
#define SOCKET_CREATE_TCP(sock) \
sock = new Socket ( \
AddressFamily::InterNetwork, \
SocketType::Stream, \
ProtocolType::Tcp)
#define SOCKET_CREATE_UDP(sock) \
sock = new Socket ( \
AddressFamily::InterNetwork, \
SocketType::Dgram, \
ProtocolType::Udp)
#define QUEUE_TCP_REQUEST(cls,svr,clientSock) \
ThreadPool::QueueUserWorkItem ( \
new WaitCallback ( \
this, \
&cls::HandleTcpRequest), \
new WorkerTcpState (svr, clientSock))
// there's no need to queue a user work item in the ThreadPool
// in case we use UDP and multicasting, so we will call the method
// synchronously
//
#define QUEUE_UDP_REQUEST(svr,clientSock,listenSock) \
HandleUdpRequest (new WorkerUdpState (svr, listenSock))
#define SOCKET_SEND_TCP(sock,endPoint,buffer) \
sock->Connect (endPoint); \
sock->Send (buffer)
#define SOCKET_SEND_UDP(sock,endPoint,buffer) \
sock->SendTo (buffer, endPoint)
// preprocessor code to enable TCP instead of UDP or disable multicasting
//
#if !defined(USING_UDP) // using TCP
# define SOCKET_CREATE(sock) SOCKET_CREATE_TCP(sock)
# define SOCKET_LISTEN(sock,backlog) sock->Listen (backlog)
# define SOCKET_ACCEPT(sock,clientSock) \
Socket __gc* clientSock = sock->Accept ()
# define QUEUE_SOCKET_REQUEST(cls,svr,clientSock,listenSock) \
QUEUE_TCP_REQUEST (cls, svr, clientSock)
# define SOCKET_SEND(sock,endPoint,buffer) \
SOCKET_SEND_TCP (sock, endPoint, buffer)
# define SOCKET_ADD_MEMBERSHIP(sock,multicastIpAddress) \
(void) 0
# define SOCKET_DROP_MEMBERSHIP(sock,multicastIpAddress) \
(void) 0
#else // using UDP
# define SOCKET_CREATE(sock) SOCKET_CREATE_UDP(sock)
# define SOCKET_LISTEN(sock,backlog) (void) 0
# define SOCKET_ACCEPT(sock,clientSock) (void) 0
# define QUEUE_SOCKET_REQUEST(cls,svr,clientSock,listenSock) \
QUEUE_UDP_REQUEST (svr, clientSock, listenSock)
# define SOCKET_SEND(sock,endPoint,buffer) \
SOCKET_SEND_UDP (sock, endPoint, buffer)
# if !defined (USING_MULTICASTS)
# define SOCKET_ADD_MEMBERSHIP(sock,multicastIpAddress) (void) 0
# define SOCKET_DROP_MEMBERSHIP(sock,multicastIpAddress) (void) 0
# else // multicasting enabled
# define SOCKET_ADD_MEMBERSHIP(sock,multicastIpAddress) \
try \
{ \
sock->SetSocketOption ( \
SocketOptionLevel::IP, \
SocketOptionName::AddMembership, \
new MulticastOption ( \
multicastIpAddress, \
IPAddress::Any)); \
} \
catch (SocketException __gc*) \
{ \
}
# define SOCKET_DROP_MEMBERSHIP(sock,multicastIpAddress) \
try \
{ \
sock->SetSocketOption ( \
SocketOptionLevel::IP, \
SocketOptionName::DropMembership, \
new MulticastOption ( \
multicastIpAddress, \
IPAddress::Any)); \
} \
catch (SocketException __gc*) \
{ \
}
# endif // !USING_MULTICASTS
#endif
#endif // Common_H