|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
ContentsIntroductionEvery model of the Distributed Application needs to handle invoking a method between the object and its consumer in the properly way using either the synch or asynch design pattern. The state transition in this model is the time driven depending on many factors such as connectivity, resources, etc. Typically example can be a model using the database resources, where a database connection is under the time "watcher" to allow effectively shares its expensive resource. Another example is the model using the web services, which needs to handle an unreliable connectivity. Coming to the point, the model needs an asynchronously design pattern to allow watching the state transition in the business processing. This article describes a design and implementation of the "Watchdog" timer incorporated into the remoting infrastructure using the custom channel design pattern. The usage of the Watchdog is very generic such as free-run timer to ping a remoting object, retrying a remote call, delaying a remote call, counter of the remote call, watchdog timer and etc. From the consumer side using the Watchdog features in the remoting is full transparently to the remote object and suitable for the distributed and deployment patterns. To make easy managing the Watchdogs, I built the MMC snap-in to create and manage their host process and configurations. Let's starting with Watchdog application features. I assume you are familiarly using the .Net Remoting. FeaturesThe watchdog behavior:
Managing the watchdog state:
Watchdog objectThe Watchdog object is a stateful object derived from the The Watchdogs incorporated into the Watchdog custom channel are using all advantages of the remoting infrastructure included publishing itself where the channel name is used as its endpoint. This design pattern allows accessing a Watchdog object using the remoting paradigm on the run-time. The Watchdog state can be configured administratively from the config file or programmatically using the IWatchdogControlThe public interface IWatchdogControl { void Start(string id); void Start(string id, object state); void Restart(string id); void Restart(string id, object state); void Stop(string id); bool Exists(string id); bool IsRun(string id); void Add(WatchdogSettings wds); void Remove(string id); int RemoveAll(); int RemoveAll(string name); int RemoveAll(string name, string notifyurl); WatchdogSettings GetSettings(string id); object GetSettingsAll(); object GetSettingsAllSorted(); } The watchdog is identified by its unique Id (string type) within the channel. Its Id can be assigned either explicitly or random by using the Guid class. IWatchdogThe public interface IWatchdog { object WatchdogNotify(WatchdogSettings wds, DateTime signalTime); bool WatchdogException(WatchdogSettings wds, Exception ex); void WatchdogAck(WatchdogSettings wds, DateTime signalTime); } Every method is passing the source of the notification - Watchdog state described by the class
WatchdogSettingsThe WatchdogSettings class is a simple class with smart properties and helpers to get/set the Watchdog state. The following snippet shows their public signatures: [Serializable] public class WatchdogSettings { //access to the properties public string Name // watchdog name public string Id // watchdog unique Id public string Description // description public string Message // notify message public string NotifyObjectUrl // notify endpoint public string AcknowledgeObjectUrl // acknowledge endpoint public string ExceptionObjectUrl // exception endpoint public string ChannelUrl // watchdog channel endpoint public string LastErrMsg // last error message public bool Destroy // flag to allow destroy it public bool Retry // flag of the retry mechanism public int Downcounter // action down counter public bool TimerAutoReset // timer AutoReset flag public bool TimerEnable // timer Enable flag public int TimerInterval // timer interval public int TimerErrorInterval // timer interval during the // exception public object Tag // application tag public object State // application state //helpers public IMethodReturnMessage MethodReturnMessage() public IMethodCallMessage MethodCallMessage() } There are two special properties in the
To de-serialize the ConfigurationUsing the Watchdog requires to make its setup in the host process config file such as a config section and channel. See the following example: <configuration>
<configSections>
<section name="watchdogManagement"
type="RKiss.WatchdogManagement.WatchdogSectionHandler,
WatchdogChannel, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=2f2c272b6c14cde0" />
</configSections>
<watchdogManagement mode="on">
<watchdog mode="on"
name="AutoPing"
interval="60"
notifyurl="tcp://localhost:3333/endpoint"
description="Watchdog to ping a remote object" />
</watchdogManagement>
<appSettings>
<add key="RKiss.Watchdog" value="tcp://localhost:3344/wd" />
</appSettings>
<system.runtime.remoting>
<application>
<service>
</service>
<channels>
<channel ref="wd" name="wd" />
<channel ref="tcp" port="3344" />
</channels>
</application>
<channelSinkProviders>
</channelSinkProviders>
<channels>
<channel id="wd" type="RKiss.WatchdogManagement.Sender,
WatchdogChannel, Version=1.0.0.0,Culture=neutral,
PublicKeyToken=2f2c272b6c14cde0" />
</channels>
</system.runtime.remoting>
</configuration>
The above configuration example will create one Watchdog (name = AutoPing) to notify a remote object specified by its url address (notifyurl="tcp://localhost:3333/endpoint"). The "ping" interval is every 60 seconds from the time when the watchdog custom channel has been constructed. This type of the Watchdog can be used to handle a business state persisted in the database, clean-up tables, pinging a host process lifetime, etc. To configure another watchdog is very simple, just adding a <watchdog . /> element into the watchdogManagement config section, for example: <watchdogManagement mode="on">
<watchdog mode="on"
name="AutoPing"
interval="60"
notifyurl="tcp://localhost:3333/endpoint"
description="Watchdog to ping a remote object" />
<watchdog mode="on"
name="MyWatchdog"
id="911"
interval="20"
autoreset="false"
message="The time to finish a process has been expired."
notifyurl="tcp://localhost:3333/endpoint"
exceptionurl="tcp://localhost:3333/endpoint"
description="General watchdog timer" />
</watchdogManagement>
The second Watchdog (MyWatchdog) has explicitly assigned its unique Id (911), only one-shot notification (autoreset="false") and error exception goes to the endpoint (exceptionurl="tcp://localhost:3333/endpoint"). To prevent the Watchdog to elapse its time (interval="20") and forcing the specified notification action, it has to be re-triggered any time during this interval using the string objWatchdogUrl = "tcp://localhost:3344/wd";
IWatchdogControl wdc = (IWatchdogControl)Activator.GetObject(
typeof(IWatchdogControl), objWatchdogUrl);
wdc.Restart("911", "Checkpoint 1");
The Watchdog endpoint url address can be obtained explicitly from the config file (appSettings) or dynamically from the watchdog state (channelurl property). Usage of the above watchdog is for instance; to control lifetime of the opened session, response timeout, cleanup specific resources, timeout of the business processing, etc. The following table shows the predefined Watchdog properties in the config file:
The above watchdog properties can be extended using the name/value pair pattern for application specific purposes, for instance: connection string, rules, etc. The Watchdog state can be updated dynamically based on the notification (action) result in the synchronously manner. For instance: the Ping Watchdog holds a state of the previously ping needed to calculate some business/system statistics. The following picture shows processing the Watchdog notifications.
During the remoting configuration process the WatchdogChannel constructor performs a process of creating Watchdogs based on the host config file. This process scans the watchdogManagement config section for the Watchdog configured for the notify action (property notifyurl). This configuration is used to setup the Watchdog state. That's the all for the first step - watchdog activation on the start-up. The second step is post-processing its state where notifications are called to the specified endpoints such as notify, exception or acknowledge remote objects. The non-null result from the notified remote object (NRO) is stored in the watchdog state for the application purposes.
Remoting WatchdogThe previously Watchdogs (based on the notifyurl property) required to use an interface contract to fire the notify endpoint when their time has been elapsed. That's fine for new or in-house development. What about already existed remote objects? Do we need to build a separate proxy to handle it? The answer is not necessary; here is the solution - the Remoting Watchdog design pattern. The concept of the Remoting Watchdog is based on storing a remoting Once we have a serialized image of the The following url address example shows how the Watchdog can take a control over the tcp channel using the chaining channels design pattern: string objectUrl = "wd://retry; tcp://localhost:3333/endpointA";
As you can see the remoting call between the endpointA and its consumer is divided into two processes indicated by a delimiter ';'
Based on the watchdog config template "retry" declared in the host process config file, the Remoting Watchdog can setup properly its state. When watchdog is requested to be controled, the watchdog Id can be assigned explicitly in the url address like the following syntax shows: string objectUrl = "wd://retry:12345; tcp://localhost:3333/endpointA"; The following flowchart shows flowing the
As you can see the The response
Usage requirementsUsing the Watchdog Management in your solution requires the following assemblies and config sections:
Also it's possible to declare common element such as configSections and channel into the machine.config file and use their references, which it will simplify your host process config files. MMC Snap-inFeatures:
Using the MMC WatchdogManagement snap-in is straightforward:
The watchdog host process node is switching between the configuration and runtime Watchdog access. In other words, the watchdog properties can be changed only when its host process is not running. Here are few examples to understand usage of the watchdogs in the Distributed patterns. Example 1.Let assume that your application framework need to scan the business state persisted in the database periodically each 200 seconds. The solution will need it:
From development point of the view, the above solution requires to implement only steps 1-3 using the standard remoting technique. The rest steps are issue of the deployment and they can be accomplished very easy using the MMC WatchdogManagement snap-in. The following screen snippet shows the AutoPing Watchdog state in runtime:
As you can see, the AutoPing watchdog (hosted by Watchdog_1 process) has been configured with the application specific properties such as ConnectionString and Condition to customize a generic stateless business object - endpointScanner via the WatchdogSettings argument (see That's been a simple example.
Example 2.Let's make a little bit more requirements for processing business logic such as initializing all business activity during the start-up process under the timeout control. The task can be again accomplished administratively using two watchdogs:
The both watchdogs are configured from the config file and activated during the remoting configuration process. The Boot watchdog has a small delay interval to notify a root object. This endpoint has a responsibility to re-trigger (restart) the Watchdog for each step of the business processing. Forgetting this duty, the Watchdog will be elapsed and its exception endpoint activated. Based on the Watchdog state is easy to figure out which step failed and perform a recovering process. It's recommend to register the Watchdog in the different host process for reliability issue. The following sequence diagram shows a situation when business logic during the step2 failed (for instance: database deadlock) which it forces the Watchdog Exception Notification. The
Example 3.This example shows how the standard remoting call can be turned on to deliver a remoting response asynchronously and distributable using the Watchdog mechanism. The Remoting Watchdog allows building an event driven architecture with a loosely coupled deployment pattern - known as the pushing architecture model. The client makes a remoting call in the Fire&Forget manner and Watchdog will distribute its response resp. exception to the configurable endpoints.
To change a standard remoting call to the Watchdog driven call is very simple just adding the watchdog channel into the url address: string objectUrl = "wd://notify; tcp://localhost:3333/endpointRemoteObject";
where "notify" is the name of the watchdog template from the config file: <watchdog mode="on"
name="Notify"
autoreset="false"
exceptionurl="tcp://localhost:3333/endpointException"
acknowledgeurl="tcp://localhost:3333/endpointAcknowledge"
description="Watchdog to distribute a response from the remote object" />
The following sequence diagram shows a message flow between the consumer and endpoints. The first channel is WatchdogChannel to initiate a Watchdog Timer with the properly state included also the
To retrieve a response value, the WatchdogSettings has implemented a method to de-serialize a byte array into the The following code snippet shows that: public void WatchdogAck(WatchdogSettings wds, DateTime signalTime)
{
IMethodCallMessage msgReq = wds.MethodCallMessage();
IMethodReturnMessage msgRsp = wds.MethodReturnMessage();
if(msgRsp != null)
{
object retVal = msgRsp.ReturnValue;
object outArg = msgRsp.Args[1];
//todo:
}
}
The other case, when a remoting call threw exception is simply too. The following code snippet shows how to obtain the name of the method where an exception occured. public bool WatchdogException(WatchdogSettings wds, Exception ex)
{
string strMethodName = "";
IMethodCallMessage mcm = wds.MethodCallMessage();
if(mcm != null)
{
strMethodName = mcm.MethodName;
//todo:
}
//destroy watchdog
return true;
}
Using the
ImplementationThe implementation of the Watchdog Management solution has been divided into the following projects:
The WatchdogChannel has a standard boilerplate implementation of the remoting Sender class extended for the remoting access. The class inherits the Here is its constructor: #region constructor
public Sender(IDictionary properties,
IClientChannelSinkProvider clientSinkProvider)
{
//administratively setup using the config values
if(properties.Contains("name"))
m_ChannelName = properties["name"].ToString().ToLower();;
if(properties.Contains("priority"))
m_ChannelPriority = Convert.ToInt32(properties["priority"]);
if(properties.Contains("maxtimers"))
m_NumOfMaxWatchdogTimers = Convert.ToInt32(properties["maxtimers"]);
//validation
if(m_NumOfMaxWatchdogTimers <= 0)
m_NumOfMaxWatchdogTimers = WatchdogManagement.NumOfMaxWatchdogTimers;
//collection of the timers
m_WatchdogTimers = Hashtable.Synchronized(new Hashtable());
//check config section
object objConfigSection = ConfigurationSettings.GetConfig(
WatchdogManagement.ConfigSection);
if(objConfigSection != null)
{
//setup "notified watchdogs" from the config file
WatchdogManagementSettings wdms = objConfigSection as
WatchdogManagementSettings;
foreach(WatchdogSettings wds in wdms.Watchdogs)
{
if(wds.NotifyObjectUrl != "")
{
WatchdogTimer wdt = new WatchdogTimer(wds);
wdt.Elapsed += new ElapsedEventHandler(OnWatchdogTimerEvent);
WatchdogTimers.Add(wds.Id, wdt);
wdt.Enabled = wds.TimerEnable;
}
}
}
//publish the Watchdog endpoint using the channel name.
m_thisObjRef = RemotingServices.Marshal(this, m_ChannelName);
//echo
Trace.WriteLine(strLogMsg);
}
#endregion
As you can see the channel name is used to publish the WatchdogChannel endpoint. To keep it this endpoint forever, the following override has been implemented: #region InitializeLifetimeService
public override object InitializeLifetimeService()
{
//infinite lifetime of the remoting access
return null;
}
#endregion
The Basically there are two kinds of action such as standard remoting notification configured explicitly using the url endpoint address and implicitly address from the The following code snippet shows the #region IMessageSink
public virtual IMessage SyncProcessMessage(IMessage msgReq)
{
IMessage msgRsp = null;
try
{
//check watchdogId in the collection
if(m_WDS.Id != "" && m_Sender.WatchdogTimers.Contains(m_WDS.Id))
throw new Exception(string.Format("The watchdog already exist,
name={0}, id={1}",
m_WDS.Name, m_WDS.Id));
//uri workaround
msgReq.Properties[WatchdogManagement.OBJECTURI] = m_ObjectUri;
// serialize IMessage
BinaryFormatter bf = new BinaryFormatter();
MemoryStream reqstream = new MemoryStream();
bf.Serialize(reqstream, msgReq);
reqstream.Position = 0;
//write stream to the buffer
byte[] buffer = new byte[reqstream.Length];
reqstream.Read(buffer, 0, buffer.Length);
reqstream.Close();
//store the stream into the WatchdogTimer
WatchdogTimer wdt = new WatchdogTimer(m_WDS);
wdt.Tag = buffer;
wdt.Id = m_WDS.Id == "" ? Guid.NewGuid().ToString() : m_WDS.Id;
wdt.NotifyObjectUrl = m_ObjectUri;
wdt.Elapsed += new ElapsedEventHandler(m_Sender.OnWatchdogTimerEvent);
m_Sender.WatchdogTimers.Add(wdt.Id, wdt);
wdt.Enabled = m_WDS.TimerEnable;
//this is a Fire&Forget call, so we have to simulate a return message
object retVal = null;
//generating a null return message
IMethodCallMessage mcm = msgReq as IMethodCallMessage;
MethodInfo mi = mcm.MethodBase as MethodInfo;
if(mi.ReturnType != Type.GetType("System.Void"))
retVal = mi.ReturnType.IsValueType ? Convert.ChangeType(0,
mi.ReturnType) : null;
//return message
msgRsp = (IMessage)new ReturnMessage(retVal, null, 0, null, mcm);
}
catch(Exception ex)
{
msgRsp = new ReturnMessage(ex, (IMethodCallMessage)msgReq);
}
return msgRsp;
}
//.
#endregion
The #region Forward IMessage to the properly channel
private byte[] IMessageDispatcher(byte[] bufferMsgReq, bool bException)
{
byte[] bufferMsgRsp = null;
#region deserialize IMessage
BinaryFormatter bf = new BinaryFormatter();
MemoryStream reqstream = new MemoryStream();
reqstream.Write(bufferMsgReq, 0, bufferMsgReq.Length);
reqstream.Position = 0;
IMessage iMsgReq = (IMessage)bf.Deserialize(reqstream);
reqstream.Close();
//endpoint
string strObjectUrl =
Convert.ToString(iMsgReq.Properties[WatchdogManagement.OBJECTURI]);
//workaround
iMsgReq.Properties["__Uri"] = strObjectUrl;
//chain channel
string strDummy = null;
IMessageSink sink = null;
#endregion
#region find the properly channel
foreach(IChannel ch in ChannelServices.RegisteredChannels)
{
if(ch is IChannelSender)
{
IChannelSender iChannelSender = (IChannelSender)ch;
sink = iChannelSender.CreateMessageSink(strObjectUrl, null,
out strDummy);
if(sink != null)
{
break;
}
}
}
#endregion
#region forward IMessage to the channel sink
if(sink == null)
{
//throw exception
string strError = string.Format("WatchdogChannel:" +
"A supported channel could not be found for {0}",
strObjectUrl);
Trace.WriteLine(strError);
throw new Exception(strError);
}
else
{
//response message
IMessage iMsgRsp = null;
//processing IMethodCallMessage based on the oneway attribute
IMethodCallMessage mcm = iMsgReq as IMethodCallMessage;
#region message processing
if(RemotingServices.IsOneWay(mcm.MethodBase) == true)
iMsgRsp = (IMessage)sink.AsyncProcessMessage(iMsgReq, null);
else
iMsgRsp = sink.SyncProcessMessage(iMsgReq);
#endregion
#region serializing a response
if(iMsgRsp != null && iMsgRsp is IMethodReturnMessage)
{
IMethodReturnMessage mrm = iMsgRsp as IMethodReturnMessage;
if(bException && mrm.Exception != null)
throw mrm.Exception;
//serialize IMessage response
MemoryStream rspstream = new MemoryStream();
bf.Serialize(rspstream, iMsgRsp);
rspstream.Position = 0;
//write stream to the buffer
bufferMsgRsp = new byte[rspstream.Length];
rspstream.Read(bufferMsgRsp, 0, bufferMsgRsp.Length);
rspstream.Close();
}
#endregion
}
#endregion
return bufferMsgRsp;
}
#endregion
InstallationThe solution contains a msi file to make its installation easy. Based on the selection, the source and test sample can be installed too. Using the installed WatchdogManagement desktop folder you have an access to the MMC snap-in and Sample Test (client and server program) The installation project is included in the source code, so if you want to modify or recompile the solution, don't forget to build a new msi file and uninstall old one. The other way of the Watchdog Management installation is using a manual process. Please follow the steps described by Usage requirements. TestThe WatchdogManagement solution can be tested by included Sample Test project. It's a simple remoting object hosted by console program and consumed by Windows Form:
The form has a UI to generate and manage Watchdogs in the client host process. Testing instructions:
Some tips:
Using the MMC snap-in in the test:
ConclusionIn this article I show you a different usage of the remoting infrastructure. Using the Watchdogs in the distributed design pattern allow to loosely coupled your business model with application services. The MMC WatchdogManagement snap-in give you a tool to administrate its deployment easy using the config files. I hope you will enjoy it. [1] http://www.codeproject.com/csharp/RemotingManagementConsole.asp
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||