I have a windows TCP service, which has many devices connecting to it, and a client can have one or more devices.
Requirement:
Separate Folder per client with separate log file for each device.
so something like this:
/MyService/25-04-2016/
Client 1/
Device1.txt
Device2.txt
Device3.txt
Client 2/
Device1.txt
Device2.txt
Device3.txt
Now I have not used a 3rd Party library like log4net or NLog, I have a class which handles this.
public class xPTLogger : IDisposable
{
private static object fileLocker = new object();
private readonly string _logFileName;
private readonly string _logFilesLocation;
private readonly int _clientId;
public xPTLogger() : this("General") { }
public xPTLogger(string logFileName)
{
_clientId = -1;
_logFileName = logFileName;
_logFilesLocation = SharedConstants.LogFilesLocation; // D:/LogFiles/
}
public xPTLogger(string logFileName, int companyId)
{
_clientId = companyId;
_logFileName = logFileName;
_logFilesLocation = SharedConstants.LogFilesLocation;
}
public void LogMessage(MessageType messageType, string message)
{
LogMessage(messageType, message, _logFileName);
}
public void LogExceptionMessage(string message, Exception innerException, string stackTrace)
{
var exceptionMessage = innerException != null
? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", message, innerException.Message, stackTrace)
: string.Format("Exception: [{0}], Stack Trace: [{1}]", message, stackTrace);
LogMessage(MessageType.Error, exceptionMessage, "Exceptions");
}
public void LogMessage(MessageType messageType, string message, string logFileName)
{
var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy");
var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, dateTime);
if (_clientId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\", _logFilesLocation, dateTime, _clientId); }
var fullLogFile = string.IsNullOrEmpty(logFileName) ? "GeneralLog.txt" : string.Format("{0}.txt", logFileName);
var msg = string.Format("{0} | {1} | {2}\r\n", DateTime.UtcNow.ToString("dd-MMM-yyyy HH:mm:ss"), messageType, message);
fullLogFile = GenerateLogFilePath(logFilesLocation, fullLogFile);
LogToFile(fullLogFile, msg);
}
private string GenerateLogFilePath(string objectLogDirectory, string objectLogFileName)
{
if (string.IsNullOrEmpty(objectLogDirectory))
throw new ArgumentNullException(string.Format("{0} location cannot be null or empty", "objectLogDirectory"));
if (string.IsNullOrEmpty(objectLogFileName))
throw new ArgumentNullException(string.Format("{0} cannot be null or empty", "objectLogFileName"));
if (!Directory.Exists(objectLogDirectory))
Directory.CreateDirectory(objectLogDirectory);
string logFilePath = string.Format("{0}\\{1}", objectLogDirectory, objectLogFileName);
return logFilePath;
}
private void LogToFile(string logFilePath, string message)
{
if (!File.Exists(logFilePath))
{
File.WriteAllText(logFilePath, message);
}
else
{
lock (fileLocker)
{
File.AppendAllText(logFilePath, message);
}
}
}
public void Dispose()
{
fileLocker = new object();
}
}
And then I can use it like this:
var _logger = new xPTLogger("DeviceId", 12);
_logger.LogMessage(MessageType.Info, string.Format("Information Message = [{0}]", 1));
The problem with the above class is that, because the service is multi-threaded, some threads try to access the same log file at the same time causing an Exception to Throw.
25-Apr-2016 13:07:00 | Error | Exception: The process cannot access the file 'D:\LogFiles\25-Apr-2016\0\LogFile.txt' because it is being used by another process.
Which sometimes causes my service to crash.
How do I make my Logger class to work in multi-threaded services?
EDIT
Changes to the Logger Class
public class xPTLogger : IDisposable
{
private object fileLocker = new object();
private readonly string _logFileName;
private readonly string _logFilesLocation;
private readonly int _companyId;
public xPTLogger() : this("General") { }
public xPTLogger(string logFileName)
{
_companyId = -1;
_logFileName = logFileName;
_logFilesLocation = SharedConstants.LogFilesLocation; // "D:\\MyLogs";
}
public xPTLogger(string logFileName, int companyId)
{
_companyId = companyId;
_logFileName = logFileName;
_logFilesLocation = SharedConstants.LogFilesLocation;
}
public void LogMessage(MessageType messageType, string message)
{
LogMessage(messageType, message, _logFileName);
}
public void LogExceptionMessage(string message, Exception innerException, string stackTrace)
{
var exceptionMessage = innerException != null
? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", message, innerException.Message, stackTrace)
: string.Format("Exception: [{0}], Stack Trace: [{1}]", message, stackTrace);
LogMessage(MessageType.Error, exceptionMessage, "Exceptions");
}
public void LogMessage(MessageType messageType, string message, string logFileName)
{
if (messageType == MessageType.Debug)
{
if (!SharedConstants.EnableDebugLog)
return;
}
var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy");
var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, dateTime);
if (_companyId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\", _logFilesLocation, dateTime, _companyId); }
var fullLogFile = string.IsNullOrEmpty(logFileName) ? "GeneralLog.txt" : string.Format("{0}.txt", logFileName);
var msg = string.Format("{0} | {1} | {2}\r\n", DateTime.UtcNow.ToString("dd-MMM-yyyy HH:mm:ss"), messageType, message);
fullLogFile = GenerateLogFilePath(logFilesLocation, fullLogFile);
LogToFile(fullLogFile, msg);
}
private string GenerateLogFilePath(string objectLogDirectory, string objectLogFileName)
{
if (string.IsNullOrEmpty(objectLogDirectory))
throw new ArgumentNullException(string.Format("{0} location cannot be null or empty", "objectLogDirectory"));
if (string.IsNullOrEmpty(objectLogFileName))
throw new ArgumentNullException(string.Format("{0} cannot be null or empty", "objectLogFileName"));
if (!Directory.Exists(objectLogDirectory))
Directory.CreateDirectory(objectLogDirectory);
string logFilePath = string.Format("{0}\\{1}", objectLogDirectory, objectLogFileName);
return logFilePath;
}
private void LogToFile(string logFilePath, string message)
{
lock (fileLocker)
{
try
{
if (!File.Exists(logFilePath))
{
File.WriteAllText(logFilePath, message);
}
else
{
File.AppendAllText(logFilePath, message);
}
}
catch (Exception ex)
{
var exceptionMessage = ex.InnerException != null
? string.Format("Exception: [{0}], Inner: [{1}], Stack Trace: [{2}]", ex.Message, ex.InnerException.Message, ex.StackTrace)
: string.Format("Exception: [{0}], Stack Trace: [{1}]", ex.Message, ex.StackTrace);
var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, DateTime.UtcNow.ToString("dd-MMM-yyyy"));
var logFile = GenerateLogFilePath(logFilesLocation, "FileAccessExceptions.txt");
try
{
if (!File.Exists(logFile))
{
File.WriteAllText(logFile, exceptionMessage);
}
else
{
File.AppendAllText(logFile, exceptionMessage);
}
}
catch (Exception) { }
}
}
}
public void Dispose()
{
//fileLocker = new object();
//_logFileName = null;
//_logFilesLocation = null;
//_companyId = null;
}
}
If you don't want to use existing solutions, the reasonable approach to handle multithreaded writes in your logger is to use queue. Here is a sketch:
public class LogQueue : IDisposable {
private static readonly Lazy<LogQueue> _isntance = new Lazy<LogQueue>(CreateInstance, true);
private Thread _thread;
private readonly BlockingCollection<LogItem> _queue = new BlockingCollection<LogItem>(new ConcurrentQueue<LogItem>());
private static LogQueue CreateInstance() {
var queue = new LogQueue();
queue.Start();
return queue;
}
public static LogQueue Instance => _isntance.Value;
public void QueueItem(LogItem item) {
_queue.Add(item);
}
public void Dispose() {
_queue.CompleteAdding();
// wait here until all pending messages are written
_thread.Join();
}
private void Start() {
_thread = new Thread(ConsumeQueue) {
IsBackground = true
};
_thread.Start();
}
private void ConsumeQueue() {
foreach (var item in _queue.GetConsumingEnumerable()) {
try {
// append to your item.TargetFile here
}
catch (Exception ex) {
// do something or ignore
}
}
}
}
public class LogItem {
public string TargetFile { get; set; }
public string Message { get; set; }
public MessageType MessageType { get; set; }
}
Then in your logger class:
private void LogToFile(string logFilePath, string message) {
LogQueue.Instance.QueueItem(new LogItem() {
TargetFile = logFilePath,
Message = message
});
}
Here we delegate actual logging to separate class which writes log messages one by one, so cannot have any multithreading issues. Additional benefit of such approach is that logging happens asynchronously and as such does not slow down the real work.
Drawback is you can lose some messages in case of process crash (don't think that is really a problem but still mention it) and you consume separate thread to log asynchronously. When there is one thread it's not a problem but if you create one thread per device, that might be (though there is no need to - just use single queue, unless you really write A LOT of messages per second).
While it probably isn't the most elegant solution, you could have retry logic built in. For example:
int retries = 0;
while(retries <= 3){
try{
var _logger = new xPTLogger("DeviceId", 12);
_logger.LogMessage(MessageType.Info, string.Format("Information Message = [{0}]", 1));
break;
}
catch (Exception ex){
//Console.WriteLine(ex.Message);
retries++;
}
}
Also, I wrote that code just now without actually testing it so if there's some stupid error in it forgive me. But quite simply it'll try to write to the log as many times as you set in the "while" line. You can even add a sleep statement in the catch block if you think it'd be worth it.
I have no experience with Log4Net or NLog so no comment there. Maybe there's a sweet solution via one of those packages. Good luck!
Related
My use case:
In a single threaded application, I need to serialize arbitrary classes for logging purposes.
The arbitrary classes are predominantly translated in an automated way from a massive VB6 application into .NET.
If serialized without a timeout, the serialization method will loop until it runs out of memory.
This is what I have currently:
internal class Serializer
{
private readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public volatile string result = null;
public volatile Func<string> toExecute = null;
public Thread thread;
public ManualResetEventSlim messageToSender = new ManualResetEventSlim(false);
public ManualResetEventSlim messageToReceiver = new ManualResetEventSlim(false);
public Serializer()
{
thread = new Thread(new ThreadStart(run));
thread.Start();
}
~Serializer()
{
try
{
if (messageToSender != null) messageToSender.Dispose();
}
catch { };
try
{
if (messageToReceiver != null) messageToReceiver.Dispose();
}
catch { };
}
public volatile bool ending = false;
public void run()
{
while (!ending)
{
try
{
if (toExecute != null)
{
result = toExecute();
}
messageToReceiver.Reset();
messageToSender.Set();
messageToReceiver.Wait();
}
catch (ThreadInterruptedException)
{
log.Warn("Serialization interrupted");
break;
}
catch (ThreadAbortException)
{
Thread.ResetAbort();
result = null;
}
catch (Exception ex)
{
log.Error("Error in Serialization", ex);
Console.WriteLine(ex);
break;
}
}
}
}
public class LocalStructuredLogging
{
private static volatile Serializer _serializer;
private static Serializer serializer
{
get
{
if (_serializer == null)
{
_serializer = new Serializer();
}
return _serializer;
}
}
public void LogStucturedEnd()
{
try
{
if (serializer != null)
{
serializer.ending = true;
serializer.thread.Interrupt();
}
}
catch { }
}
internal ConcurrentDictionary<long, bool> disallowedToSerialize = new ConcurrentDictionary<long, bool>();
public string TrySerialize<T>(T payload, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
bool dummy;
unchecked
{
if (disallowedToSerialize.TryGetValue(hashEl, out dummy))
{
return "°,°";
}
}
serializer.toExecute = () =>
{
try
{
return Newtonsoft.Json.JsonConvert.SerializeObject(payload, new Newtonsoft.Json.JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore });
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°°°";
}
};
try
{
serializer.messageToSender.Reset();
serializer.messageToReceiver.Set();
if (serializer.messageToSender.Wait(6000))
{
return Interlocked.Exchange(ref serializer.result, null);
}
serializer.toExecute = null;
serializer.thread.Abort();
serializer.messageToSender.Wait(2000);
disallowedToSerialize.TryAdd(hashEl, false);
return "°§°";
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°-°";
}
}
}
The code is called as in the following (test is an arbitrary class instance):
var logger = new LocalStructuredLogging();
var rr5 = logger.TrySerialize(test);
Although it seems to do the job, there are some issues with it:
it has a dependency on Thread.Abort
it is time dependent, so it will thus produce varied results on a loaded system
every class instance is treated like every other class instance - no tweaking
...
So, are there any better solutions available ?
Based upon dbc's excellent answer, I managed to create a better timed serializer.
It resolves all 3 issues mentioned above:
public class TimedJsonTextWriter : JsonTextWriter
{
public int? MaxDepth { get; set; }
public TimeSpan? MaxTimeUsed { get; set; }
public int MaxObservedDepth { get; private set; }
private DateTime start = DateTime.Now;
public TimedJsonTextWriter(TextWriter writer, JsonSerializerSettings settings, TimeSpan? maxTimeUsed)
: base(writer)
{
this.MaxDepth = (settings == null ? null : settings.MaxDepth);
this.MaxObservedDepth = 0;
this.MaxTimeUsed = maxTimeUsed;
}
public TimedJsonTextWriter(TextWriter writer, TimeSpan? maxTimeUsed, int? maxDepth = null)
: base(writer)
{
this.MaxDepth = maxDepth;
this.MaxTimeUsed = maxTimeUsed;
}
public override void WriteStartArray()
{
base.WriteStartArray();
CheckDepth();
}
public override void WriteStartConstructor(string name)
{
base.WriteStartConstructor(name);
CheckDepth();
}
public override void WriteStartObject()
{
base.WriteStartObject();
CheckDepth();
}
uint checkDepthCounter = 0;
private void CheckDepth()
{
MaxObservedDepth = Math.Max(MaxObservedDepth, Top);
if (Top > MaxDepth)
throw new JsonSerializationException($"Depth {Top} Exceeds MaxDepth {MaxDepth} at path \"{Path}\"");
unchecked
{
if ((++checkDepthCounter & 0x3ff) == 0 && DateTime.Now - start > MaxTimeUsed)
throw new JsonSerializationException($"Time Usage Exceeded at path \"{Path}\"");
}
}
}
public class LocalStructuredLogging
{
public void LogStucturedEnd()
{
}
internal HashSet<long> disallowedToSerialize = new HashSet<long>();
public string TrySerialize<T>(T payload, int maxDepth = 100, int secondsToTimeout = 2, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
if (disallowedToSerialize.Contains(hashEl))
{
return "°,°";
}
try
{
var settings = new JsonSerializerSettings { MaxDepth = maxDepth, ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore };
using (var writer = new StringWriter())
{
using (var jsonWriter = new TimedJsonTextWriter(writer, settings, new TimeSpan(0, 0, secondsToTimeout)))
{
JsonSerializer.Create(settings).Serialize(jsonWriter, payload);
// Log the MaxObservedDepth here, if you want to.
}
return writer.ToString();
}
}
catch (Exception)
{
disallowedToSerialize.Add(hashEl);
return "°-°";
}
}
}
The only issue remaining are the Hash collisions, which are easy to solve (e.g. by using the source file name as well or use another type of Collection).
The correct way to run an action timed would be to do something like the following. I would recommend taking a second look at how serialization should work as well :).
/// <summary>
/// Run an action timed.
/// </summary>
/// <param name="action">Action to execute timed.</param>
/// <param name="secondsTimout">Seconds before Task should cancel.</param>
/// <returns></returns>
public static async Task RunTimeout(Action action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
await Task.Run(action, tokenSource.Token);
}
You may also want to return a variable upon the completion of your timed task. That can be done like so...
public static async Task<T> RunTimeout<T>(Func<T> action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
var result = await Task.Run(action, tokenSource.Token);
return result;
}
I am have a windows service which will subscribe to a queue and process messages continuously. Every thing is working except except for the reconnect logic. I am using IBM.XMS.dll version 8 in my code.
I am having issues in re-establishing the subscription when IBM Websphere MQ server fail-over happens. During fail-over, I get the following exception:
IBM.XMS.IllegalStateException: XMSCC0026
XMSCC0026.explanation
XMSCC0026.useraction
at IBM.XMS.Client.Impl.State.CheckNotClosed(String message)
at IBM.XMS.Client.Impl.XmsMessageProducerImpl.Send_(Boolean inIdentifiedContext, XmsDestinationImpl dest, IMessage message, DeliveryMode deliveryMode, Int32 priority, Int64 timeToLive, Boolean explicitDlvModePriorityAndTimeToLive)
at IBM.XMS.Client.Impl.XmsMessageProducerImpl.Send(IMessage message)
Stack Trace: at IBM.XMS.Client.Impl.State.CheckNotClosed(String message)
at IBM.XMS.Client.Impl.XmsMessageProducerImpl.Send_(Boolean inIdentifiedContext, XmsDestinationImpl dest, IMessage message, DeliveryMode deliveryMode, Int32 priority, Int64 timeToLive, Boolean explicitDlvModePriorityAndTimeToLive)
at IBM.XMS.Client.Impl.XmsMessageProducerImpl.Send(IMessage message)
I need some help in re-establishing the subscription in onException event handler:
private void ReconnectToXMS_MQ()
{
if (!_cancellationToken.IsCancellationRequested)
{
LoggingService.LogDebug($"[XMSQueue] Reconnecting for queue: {ConfigQueueName} and Address: {Address}");
if(MsgQueueType == QueueType.Publisher)
{
Dispose(true);
InitializeXMSMQ();
InitialiseOutbound(ConfigQueueName, Pattern, false);
}
else if(MsgQueueType == QueueType.Subscriber)
{
//Need help here
}
else if(MsgQueueType == QueueType.Receiver)
{
Dispose(true);
InitializeXMSMQ();
InitialiseInbound(ConfigQueueName, Pattern, false);
}
}
}
Following is the full code snippet of my XMS implementation.
internal class XMSQueue : MessageQueueBase
{
#region " Local Methods/Properties "
private IConnectionFactory _connectionfactory;
private IConnection _connection;
private IDestination _destination;
private ISession _session;
private IMessageConsumer _consumer;
private IMessageProducer _producer;
private CancellationToken _cancellationTOke;
private string ConfigQueueName { get; set; }
private XMSProperties xmsConfiguration { get; set; }
private MessageFormat MsgFormat { get; set; }
private QueueType MsgQueueType { get; set; }
#endregion
#region " MessageQueueBase OVerrides "
public override string Name { get; set; }
#region " Receive/Subscribe "
public override void InitialiseInbound(string name, MessagePattern pattern, bool isTemporary)
{
try
{
ConfigQueueName = name;
Initialise(Direction.Inbound, name, pattern, isTemporary);
InitializeXMSMQ();
//Set Destination
_destination = CreateDestinationInbound();
//Create Consumer
_consumer = _session.CreateConsumer(_destination);
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
throw xmsError;
}
catch (Exception ex)
{
throw ex;
}
}
public override void Subscribe<TReceiveMessageType>(Action<TReceiveMessageType> onMessageReceived, CancellationToken cancellationToken, MessageFormat messageFormat = MessageFormat.XMS_IMessage)
{
try
{
MsgQueueType = QueueType.Subscriber;
_cancellationTOke = cancellationToken;
cancellationToken.Register(() =>
{
_consumer.MessageListener = null;
});
MsgFormat = messageFormat;
// Create and register the listener
MessageListener messageListener = new MessageListener((msg) =>
{
TReceiveMessageType message;
message = ProcessInboundMessage<TReceiveMessageType>(messageFormat, msg);
onMessageReceived(message);
});
_consumer.MessageListener = messageListener;
// Start the connection
_connection.Start();
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
ReconnectToXMS_MQ();
}
catch (Exception ex)
{
ReconnectToXMS_MQ();
}
}
public override void Receive<TReceiveMessageType>(Action<TReceiveMessageType> onMessageReceived, bool processAsync, int maximumWaitMilliseconds = 0, MessageFormat messageFormat = MessageFormat.XMS_IMessage)
{
try
{
MsgQueueType = QueueType.Receiver;
MsgFormat = messageFormat;
// Start the connection
_connection.Start();
IMessage inbound = null;
TReceiveMessageType message;
// Receive the message
inbound = _consumer.Receive();
if (inbound != null)
{
message = ProcessInboundMessage<TReceiveMessageType>(messageFormat, inbound);
if (processAsync)
{
Task.Factory.StartNew(() => onMessageReceived(message));
}
else
{
onMessageReceived(message);
}
}
else
{
throw new Exception("Message received was null.");
}
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
throw xmsError;
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
#region " Send/Publish "
public override void InitialiseOutbound(string name, MessagePattern pattern, bool isTemporary)
{
try
{
ConfigQueueName = name; //Save the config queue name for later use in reconnection logic
Initialise(Direction.Outbound, name, pattern, isTemporary);
InitializeXMSMQ();
//Set Destination
_destination = CreateDestinationOutbound();
//Create Producer
_producer = _session.CreateProducer(_destination);
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
throw xmsError;
}
catch (Exception ex)
{
throw ex;
}
}
public override bool Send<T>(T message, MessageFormat messageFormat)
{
try
{
MsgQueueType = QueueType.Publisher;
MsgFormat = messageFormat;
//Start the connection
_connection.Start();
//Create Message
IMessage outbound = null;
if (messageFormat == MessageFormat.String)
{
outbound = _session.CreateTextMessage(message.ToString());
}
else if (messageFormat == MessageFormat.ByteArray)
{
outbound = _session.CreateObjectMessage();
byte[] byteText = message as byte[];
((IObjectMessage)outbound).SetObject(byteText);
}
else if (messageFormat == MessageFormat.XMS_IMessage)
{
outbound = message as IMessage;
}
else
{
throw new NotSupportedException("UnRecognized/UnSUpported message format. Please use String, ByteArray or XMS_IMessage message formats");
}
_producer.Send(outbound);
return true;
}
catch (XMSException xmsError)
{
LogXMSExceptionDetails(xmsError);
return false;
}
catch (Exception ex)
{
return false;
}
}
#endregion
protected override void Dispose(bool disposing)
{
try
{
if (_consumer != null)
{
_consumer.Close();
_consumer.Dispose();
}
//Reset Producer
if (_producer != null)
{
_producer.Close();
_producer.Dispose();
}
//Reset Destination
if (_destination != null)
_destination.Dispose();
//Reset Session
if (_session != null)
{
_session.Close();
_session.Dispose();
}
//Reset Connection
if (_connection != null)
{
_connection.Close();
_consumer.Dispose();
}
}
catch (Exception)
{
//ignore any exceptions at this point
}
}
#endregion
#region " Local Methods "
#region " Initialize and Reconnect "
private void InitializeXMSMQ()
{
xmsConfiguration = new XMSProperties(Properties);
xmsConfiguration.ConnectionType = RequirePropertyValue(ConfigurationKeys.ConnectionType);
//SetConnectionFactory Connection Factory
SetConnectionFactory();
//Set Connection
_connection = _connectionfactory.CreateConnection(null, null); //We do not use UserID and Password to connect.
_connection.ExceptionListener = new ExceptionListener(OnConnectionException);
//Set Session
_session = _connection.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
}
private void ReconnectToXMS_MQ()
{
if (!_cancellationTOke.IsCancellationRequested)
{
if(MsgQueueType == QueueType.Publisher)
{
Dispose(true);
InitializeXMSMQ();
InitialiseOutbound(ConfigQueueName, Pattern, false);
}
else if(MsgQueueType == QueueType.Subscriber)
{
//Need help here
}
else if(MsgQueueType == QueueType.Receiver)
{
Dispose(true);
InitializeXMSMQ();
InitialiseInbound(ConfigQueueName, Pattern, false);
}
}
}
private void OnConnectionException(Exception ex)
{
ReconnectToXMS_MQ();
}
private static void LogXMSExceptionDetails(XMSException xmsError)
{
if (xmsError.LinkedException != null)
{
LogError($"[XMSQueue] Linked Exception: {xmsError.LinkedException.Message} ");
if (xmsError.LinkedException.InnerException != null)
LogError($"[XMSQueue] Linked Inner Exception: {xmsError.LinkedException.InnerException} ");
}
}
#endregion
#region " XMS Connection Factory "
private void SetConnectionFactory()
{
_connectionfactory = CreateConnectionFactory();
}
private IConnectionFactory CreateConnectionFactory()
{
IConnectionFactory iConnFact;
switch (xmsConfiguration.ConnectionType.ToUpper())
{
// WPM connection factory
case "WPM":
iConnFact = CreateConnectionFactoryWPM();
break;
// RTT connection factory
case "RTT":
iConnFact = CreateConnectionFactoryRTT();
break;
// WMQ connection factory
case "WMQ":
iConnFact = CreateConnectionFactoryWMQ();
break;
default:
iConnFact = null;
break;
}
return iConnFact;
}
/// <summary>
/// Create a WPM connection factory and set relevant properties.
/// </summary>
/// <returns>A connection factory</returns>
private static IConnectionFactory CreateConnectionFactoryWPM()
{
// Create the connection factories factory
XMSFactoryFactory factoryFactory = XMSFactoryFactory.GetInstance(XMSC.CT_WPM);
// Use the connection factories factory to create a connection factory
IConnectionFactory cf = factoryFactory.CreateConnectionFactory();
// WPM multicast is currently disabled
// cf.SetIntProperty(XMSC.WPM_MULTICAST, Options.MulticastModeWPM.ValueAsNumber);
return (cf);
}
/// <summary>
/// Create a RTT connection factory and set relevant properties.
/// </summary>
/// <returns>A connection factory</returns>
private static IConnectionFactory CreateConnectionFactoryRTT()
{
// Create the connection factories factory
XMSFactoryFactory factoryFactory = XMSFactoryFactory.GetInstance(XMSC.CT_RTT);
// Use the connection factories factory to create a connection factory
IConnectionFactory cf = factoryFactory.CreateConnectionFactory();
return (cf);
}
/// <summary>
/// Create a WMQ connection factory and set relevant properties.
/// </summary>
/// <returns>A connection factory</returns>
private IConnectionFactory CreateConnectionFactoryWMQ()
{
// Create the connection factories factory
XMSFactoryFactory factoryFactory = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
// Use the connection factories factory to create a connection factory
IConnectionFactory cf = factoryFactory.CreateConnectionFactory();
// Set the properties
cf.SetStringProperty(XMSC.WMQ_HOST_NAME, xmsConfiguration.WMQProperties.Hostname);
cf.SetIntProperty(XMSC.WMQ_PORT, xmsConfiguration.WMQProperties.Port);
cf.SetStringProperty(XMSC.WMQ_CHANNEL, xmsConfiguration.WMQProperties.Channel);
cf.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, xmsConfiguration.WMQProperties.ConnectionMode);
if (string.IsNullOrEmpty(xmsConfiguration.WMQProperties.QueueManager))
{
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, "");
}
else
{
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, xmsConfiguration.WMQProperties.QueueManager);
}
if (xmsConfiguration.WMQProperties.BrokerVersion != -1) //-1 => Not set in configuration
cf.SetIntProperty(XMSC.WMQ_BROKER_VERSION, xmsConfiguration.WMQProperties.BrokerVersion);
cf.SetStringProperty(XMSC.WMQ_CONNECTION_NAME_LIST, xmsConfiguration.WMQProperties.Hostname);
if (!string.IsNullOrWhiteSpace(xmsConfiguration.WMQProperties.ConnectionList))
{
cf.SetStringProperty(XMSC.WMQ_CONNECTION_NAME_LIST, xmsConfiguration.WMQProperties.ConnectionList);
cf.SetIntProperty(XMSC.WMQ_CLIENT_RECONNECT_TIMEOUT, xmsConfiguration.WMQProperties.ReconnectTimeout);
cf.SetIntProperty(XMSC.WMQ_CLIENT_RECONNECT_OPTIONS, xmsConfiguration.WMQProperties.ReconnectOptions);
}
if (!string.IsNullOrWhiteSpace(xmsConfiguration.WMQProperties.SSLCertRepository))
{
cf.SetStringProperty(XMSC.WMQ_SSL_KEY_REPOSITORY, xmsConfiguration.WMQProperties.SSLCertRepository);
cf.SetStringProperty(XMSC.WMQ_SSL_CIPHER_SPEC, xmsConfiguration.WMQProperties.SSLCipherSpec);
}
cf.SetStringProperty(XMSC.WMQ_PROVIDER_VERSION, XMSC.WMQ_PROVIDER_VERSION_DEFAULT);
cf.SetBooleanProperty(XMSC.WMQ_SYNCPOINT_ALL_GETS, true);
return (cf);
}
#endregion
#region " Create IDestination "
protected IDestination CreateDestinationOutbound()
{
IDestination iDest;
switch (xmsConfiguration.ConnectionType.ToUpper())
{
// WPM destination
case Literals.WPM:
case Literals.WMQ:
if (Pattern == MessagePattern.FireAndForget)
{
iDest = (Address.StartsWith("queue://")) ?
_session.CreateQueue(Address) : // Create a Queue
_session.CreateTopic(Address); // FireAndForget is defaulted to Topic for Outbound unless Address is defined with queue
}
else if (Pattern == MessagePattern.PublishSubscribe)
{
iDest = (Address.StartsWith("queue://")) ?
_session.CreateQueue(Address) : // Create a Queue
_session.CreateTopic(Address); // PublishSubscribe is defaulted to Topic for Outbound unless Address is defined with queue
}
else
{
iDest = (Address.StartsWith("queue://")) ?
_session.CreateQueue(Address) : // Create a queue
_session.CreateTopic(Address); // Otherwise, default to creating a topic
}
iDest.SetIntProperty(XMSC.DELIVERY_MODE, xmsConfiguration.WMQProperties.DeliveryMode);
iDest.SetIntProperty(XMSC.WMQ_TARGET_CLIENT, xmsConfiguration.WMQProperties.TargetClient);
break;
// RTT destination
case Literals.RTT:
iDest = _session.CreateTopic(Address); // Create a topic
break;
default:
iDest = null;
break;
}
return (iDest);
}
protected IDestination CreateDestinationInbound()
{
IDestination iDest;
switch (xmsConfiguration.ConnectionType.ToUpper())
{
// WPM destination
case Literals.WPM:
case Literals.WMQ:
if (Pattern == MessagePattern.FireAndForget)
{
iDest = (Address.StartsWith("topic://")) ?
_session.CreateTopic(Address) : // Create a Topic
_session.CreateQueue(Address); // FireAndForget is defaulted to Queues for Inbound unless Address is defined with topic
}
else if (Pattern == MessagePattern.PublishSubscribe)
{
iDest = (Address.StartsWith("topic://")) ?
_session.CreateTopic(Address) : // Create a Topic
_session.CreateQueue(Address); // PublishSubscribe is defaulted to Queue for Inbound unless Address is defined with topic
}
else
{
iDest = (Address.StartsWith("topic://")) ?
_session.CreateTopic(Address) : // Create a Topic
_session.CreateQueue(Address); // Otherwise, default to creating a Queue
}
iDest.SetIntProperty(XMSC.DELIVERY_MODE, xmsConfiguration.WMQProperties.DeliveryMode);
iDest.SetIntProperty(XMSC.WMQ_TARGET_CLIENT, xmsConfiguration.WMQProperties.TargetClient);
break;
// RTT destination
case Literals.RTT:
iDest = _session.CreateQueue(Address); // Create a Queue
break;
default:
iDest = null;
break;
}
return (iDest);
}
#endregion
private static TReceiveMessageType ProcessInboundMessage<TReceiveMessageType>(MessageFormat messageFormat, IMessage inbound)
{
TReceiveMessageType message;
if (messageFormat == MessageFormat.String)
{
ITextMessage txtMessage = (ITextMessage)inbound;
message = (TReceiveMessageType)(object)txtMessage.Text;
}
else if (messageFormat == MessageFormat.ByteArray)
{
IObjectMessage inboundBytes = (IObjectMessage)inbound;
byte[] body = inboundBytes.GetObject();
message = (TReceiveMessageType)(object)body;
}
else if (messageFormat == MessageFormat.WMQ_MQMessage)
{
throw new NotSupportedException("MessageFormat.WMQ_MQMessage is not supported in IBM.XMS implementation. Please use String, ByteArray, Stream, Object (byte[]) or XMS_IMessage message formats");
}
else if (messageFormat == MessageFormat.XMS_IMessage)
{
message = (TReceiveMessageType)inbound;
}
else if (messageFormat == MessageFormat.Object)
{
IObjectMessage inboundObject = (IObjectMessage)inbound;
byte[] body = inboundObject.GetObject();
message = (TReceiveMessageType)(object)body;
}
else if (messageFormat == MessageFormat.Stream)
{
IObjectMessage inboundObject = (IObjectMessage)inbound;
byte[] body = inboundObject.GetObject();
MemoryStream streamBody = new MemoryStream(body);
message = (TReceiveMessageType)(object)streamBody;
}
else
{
throw new NotSupportedException("UnRecognized message format. Please use String, ByteArray, Stream, Object (byte[]) or XMS_IMessage message formats");
}
return message;
}
#endregion
}
After reading about SOLID code in a book and in an online article, I wanted to refactor an existing class so that it can be 'SOLID'-compatible.
But I think I got lost, especially with the dependency injection: when I wanted to instantiate an object of the class, I needed to 'inject' the all the dependencies, but the dependencies themselves have dependencies.. this is where I started getting lost.
The Idea is like this: I want to create a class (In my case, a simple Amazon S3 wrapper class) to di simple upload & get URL actions.
How can I correctly use interfaces and dependency injection? what went wrong?
How should the class look like?
here is my code:
public interface IConfigurationProvider
{
string GetConfigurationValue(String configurationKey);
}
public interface ILogger
{
void WriteLog(String message);
}
public interface IAWSClientProvider
{
AmazonS3Client GetAmazonS3Client();
}
public interface IAWSBucketManager
{
string GetDefaultBucketName();
}
public class AWSBucketManager : IAWSBucketManager
{
ILogger logger;
IConfigurationProvider configurationProvider;
public AWSBucketManager(ILogger Logger, IConfigurationProvider ConfigurationProvider)
{
logger = Logger;
configurationProvider = ConfigurationProvider;
}
public string GetDefaultBucketName()
{
string bucketName = string.Empty;
try
{
bucketName = configurationProvider.GetConfigurationValue("Amazon_S3_ExportAds_BucketName");
}
catch (Exception ex)
{
logger.WriteLog(String.Format("getBucketName : Unable to get bucket name from configuration.\r\n{0}", ex));
}
return bucketName;
}
}
public class AWSClientProvider : IAWSClientProvider
{
IConfigurationProvider configurationProvider;
IAWSBucketManager awsBucketManager;
ILogger logger;
private string awsS3BucketName;
private Dictionary<string, RegionEndpoint> regionEndpoints;
public AWSClientProvider(IConfigurationProvider ConfigurationProvider, IAWSBucketManager BucketManager, ILogger Logger)
{
logger = Logger;
configurationProvider = ConfigurationProvider;
awsBucketManager = BucketManager;
}
private RegionEndpoint getAWSRegion()
{
RegionEndpoint regionEndpoint = null;
// Init endpoints dictionary
try
{
IEnumerable<RegionEndpoint> regions = RegionEndpoint.EnumerableAllRegions;
regionEndpoints = regions.ToDictionary(r => r.SystemName, r => r);
}
catch (Exception Ex)
{
logger.WriteLog(String.Format("getAWSRegion() - Failed to get region list from AWS.\r\n{0}", Ex));
throw;
}
// Get configuration value
try
{
string Config = configurationProvider.GetConfigurationValue("Amazon_S3_Region");
if (String.IsNullOrEmpty(Config))
{
throw new Exception("getAWSRegion() : Amazon_S3_Region must not be null or empty string.");
}
regionEndpoint = regionEndpoints[Config];
}
catch (Exception Ex)
{
logger.WriteLog(String.Format("getAWSRegion() : Unable to get region settings from configuration.\r\n{0}", Ex));
throw Ex;
}
return regionEndpoint;
}
private AWSCredentials getAWSCredentials()
{
string accessKey, secretKey;
BasicAWSCredentials awsCredentials;
try
{
accessKey = configurationProvider.GetConfigurationValue("Amazon_S3_AccessKey");
secretKey = configurationProvider.GetConfigurationValue("Amazon_S3_SecretKey");
}
catch (Exception Ex)
{
logger.WriteLog(String.Format("getAWSCredentials() - Unable to get access key and secrey key values from configuration.\r\n", Ex.Message));
throw;
}
try
{
awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
}
catch (Exception Ex)
{
logger.WriteLog(String.Format("getAWSCredentials() - Unable to create basic AWS credentials object.\r\n{0}", Ex.Message));
awsCredentials = null;
throw;
}
return awsCredentials;
}
public AmazonS3Client GetAmazonS3Client()
{
AmazonS3Client client = null;
RegionEndpoint region = getAWSRegion();
AWSCredentials credentials = getAWSCredentials();
awsS3BucketName = awsBucketManager.GetDefaultBucketName();
if (credentials != null)
{
client = new AmazonS3Client(credentials, region);
}
return client;
}
}
public class AWSS3Actions
{
IConfigurationProvider configurationProvider; // decoupling getting configuration
ILogger logger; // decoupling logger
IAWSClientProvider awsClientProvider;
private const int defaultExpirationDays = 14;
public AWSS3Actions(IConfigurationProvider ConfigurationProvider, ILogger Logger, IAWSClientProvider ClientProvider)
{
configurationProvider = ConfigurationProvider;
logger = Logger;
awsClientProvider = ClientProvider;
}
#region Private Mmethods
private string getFileUrl(string fileName, int expirationDaysPeriod, string awsS3BucketName)
{
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest();
string URL = "";
DateTime dtBase = new DateTime();
dtBase = DateTime.Now;
dtBase = dtBase.AddDays(expirationDaysPeriod);
request.BucketName = awsS3BucketName;
request.Key = fileName;
request.Expires = dtBase;
try
{
URL = awsClientProvider.GetAmazonS3Client().GetPreSignedURL(request);
}
catch (AmazonS3Exception ex)
{
// log
logger.WriteLog(String.Format("getFileUrl() : Could not get presigned URL for the provided request.\r\n{0}", ex));
throw ex;
}
return URL;
}
private int getDefaultURLExpiration()
{
int expirationDays = 0;
try
{
// set the time span in days
int.TryParse(configurationProvider.GetConfigurationValue("getDefaultURLExpiration() : Amazon_S3_ExportAds_ExpirationDaysOfURL"), out expirationDays); // get from configuration util
}
catch
{
// in case of exception, set the min 14 days time space exiration
expirationDays = defaultExpirationDays;
}
return expirationDays;
}
private void validateUpload(string fileName, Stream fileStream)
{
if (fileName == null || fileName.Equals(string.Empty) || fileStream.Length < 1)
{
throw new Exception("fileName : File name must not be an empty string.");
}
if (fileStream == null)
{
throw new Exception("fileStream : Input memory stream (file stream) must not be null.");
}
}
#endregion
#region Public methods
public bool IsFileExists(string fileName, string awsS3BucketName)
{
bool fileExists = false;
try
{
S3FileInfo fileInfo = new S3FileInfo(awsClientProvider.GetAmazonS3Client(), awsS3BucketName, fileName);
fileExists = fileInfo.Exists;
}
catch (AmazonS3Exception Ex)
{
// log
logger.WriteLog(String.Format("isFileExists() : Could not determine if file (key) exists in S3 Bucket.\r\n", Ex.Message));
throw;
}
return fileExists;
}
public bool UploadObject(string fileName, Stream fileStream, string awsS3BucketName)
{
bool uploadResult = true;
// Validate input parameters
validateUpload(fileName, fileStream);
if (awsClientProvider.GetAmazonS3Client() != null)
{
try
{
PutObjectRequest request = new PutObjectRequest
{
BucketName = awsS3BucketName,
Key = fileName,
InputStream = fileStream
};
PutObjectResponse response = awsClientProvider.GetAmazonS3Client().PutObject(request);
if (response != null)
{
if (response.HttpStatusCode != System.Net.HttpStatusCode.OK)
{
var meta = response.ResponseMetadata.Metadata.Keys.
Select(k => k.ToString() + " : " + response.ResponseMetadata.Metadata[k]).
ToList().Aggregate((current, next) => current + "\r\n" + next);
// log error
logger.WriteLog(String.Format("Status Code: {0}\r\nETag : {1}\r\nResponse metadata : {1}",
(int)response.HttpStatusCode, response.ETag, meta));
// set the return value
uploadResult = false;
}
}
}
catch (AmazonS3Exception ex)
{
uploadResult = false;
if (ex.ErrorCode != null && (ex.ErrorCode.Equals("InvalidAccessKeyId") || ex.ErrorCode.Equals("InvalidSecurity")))
{
// LOG
logger.WriteLog(String.Format("UploadObject() : invalied credentials"));
throw ex;
}
else
{
// LOG
logger.WriteLog(String.Format("UploadObject() : Error occurred. Message:'{0}' when writing an object", ex.Message));
throw ex;
}
}
}
else
{
throw new Exception("UploadObject() : Could not start object upload because Amazon client is null.");
}
return uploadResult;
}
public bool UploadObject(string subFolderInBucket, string FileName, Stream fileStream, string awsS3BucketName)
{
return UploadObject(subFolderInBucket + #"/" + FileName, fileStream, awsS3BucketName);
}
public string GetURL(string fileName, string bucket)
{
string url = string.Empty;
try
{
if (IsFileExists(fileName, bucket))
{
url = getFileUrl(fileName, getDefaultURLExpiration(), bucket);
}
}
catch (Exception Ex)
{
// log
logger.WriteLog(String.Format("getURL : Failed in isFileExists() method. \r\n{0}", Ex.Message));
}
return url;
}
#endregion
}
With your current class structure, the Composition Root might look like this:
var logger = new FileLogger("c:\\temp\\log.txt");
var configurationProvider = new ConfigurationProvider();
var actions = new AWSS3Actions(
configurationProvider,
logger,
new AWSClientProvider(
configurationProvider,
new AWSBucketManagerlogger(
logger,
configurationProvider),
logger));
The above example shows a hand-wired object graph (a.k.a. Pure DI). My advice is to start off by applying Pure DI and switch to a DI library (such as Simple Injector, Autofac or StructureMap) when building up object graphs by hand becomes cumbersome and maintenance heavy.
From perspective of Dependency Injection, what you're doing seems sane, although your code smells. Here are some references to look at:
Logging and the SOLID principles
How to design a logger abstraction
Side note: In general it's better to load configuration values up front (at application start-up), instead of reading it at runtime. Reading those values at runtime, causes these values to be read in delayed fashion, which prevents the application from failing fast, and it spreads the use of the configuration abstraction throughout the application. If possible, inject those primitive configuration values directly into the constructor of the type that requires that value.
I got an issue with Unity interception when throw an Exception in my method. Please reference from my sample application as below:
class Program
{
static void Main(string[] args)
{
var container = new UnityContainer();
container.RegisterType<IInterface1, Implementation1>();
container.RegisterType<ICallHandler, AuditLoggingCallHandler>();
container.RegisterInstance(typeof(IUnityContainer), container);
container.AddNewExtension<Interception>();
container.Configure<Interception>()
.SetInterceptorFor<IInterface1>(new InterfaceInterceptor());
var service = container.Resolve<IInterface1>();
service.Method1("xyz");
Console.ReadLine();
}
}
The AuditLogging behavior implement following code below:
public class AuditLoggingAttribute : HandlerAttribute
{
public int RequestId { get; set; }
public override ICallHandler CreateHandler(IUnityContainer container)
{
return container.Resolve<ICallHandler>();
}
}
public class AuditLoggingCallHandler : ICallHandler
{
public IMethodReturn Invoke(IMethodInvocation input,
GetNextHandlerDelegate getNext)
{
LogEntry logEntry = new LogEntry();
logEntry.Message = "[BEGIN] " + input.MethodBase.Name + "()";
Logger.Write(logEntry);
IMethodReturn result = getNext()(input, getNext);
if (result.Exception != null)
{
logEntry = new LogEntry();
logEntry.Message = " [EXCEPTION] " + input.MethodBase.Name
+ "." + input.MethodBase.Name
+ "() ";
logEntry.ExtendedProperties
.Add("Exception Message", result.Exception.Message);
logEntry.ExtendedProperties
.Add("Exception StackTrace", result.Exception.StackTrace);
Logger.Write(logEntry);
}
logEntry = new LogEntry();
logEntry.Message = " [FINISH] " + input.MethodBase.Name + "()";
if (result.ReturnValue != null)
{
logEntry
.ExtendedProperties
.Add("Return value", result.ReturnValue.ToString());
}
Logger.Write(logEntry);
return result;
}
public int Order { get; set; }
}
And the Interface1.cs and Implementation1.cs are below:
public interface IInterface1
{
[AuditLogging]
IEnumerable<string> Method1(string name);
}
public class Implementation1 : IInterface1
{
public IEnumerable<string> Method1(string name)
{
if (name.Equals("xyz"))
{
throw new Exception("xyz are not allow");
}
yield return "ABC";
}
}
The logging file point out the Exception does not write down.There is some thing I am missing or the Unity interception cannot working as expected.
Here the log file below, it should have [EXCEPTION] but there is nothing like that:
----------------------------------------
Timestamp: 5/30/2013 9:29:07 AM
Message: [BEGIN] Method1()
Category: General
Priority: -1
EventId: 0
Severity: Information
Title:
Machine: XXXY
App Domain: ConsoleApplication10.vshost.exe
ProcessId: 8228
Thread Name:
Win32 ThreadId:1280
Extended Properties: Parameter values - [(String) name = 'xyz']
----------------------------------------
BTW, I using the Enterprise Library version 5 and Unity version 2.0.
The issue at here is yield return "ABC". When we apply yield return the method will be executed under lazy mode so the Exception still not raised. The exception only raise after the MethodReturn return by interceptor. You change the code as below will see [EXCEPTION] will be logged into the file:
public IEnumerable<string> Method1(string name)
{
if (name.Equals("xyz"))
{
throw new Exception("xyz are not allow");
}
var list = new List<string>();
list.Add("ABC");
return list;
//yield return "ABC";
}
Hope this help.
Update: with help from Henk, determined that public Dispose() is being called, which in turn calls private Dispose(true). This is my first implementation of IDisposable interface, so not sure if it is correct. I don't call Dispose explicitly anywhere. It seems that WCF architecture is calling it on exit from each OperationContract member.
Took unmanaged cleanup code out of Dispose for now, and multiple calls are able to access the static data. It seems that Dispose() is called on all locally allocated objects on return from calls, even if there is a reference to object in static storage. Not sure in .net world how to get around this so that IDisposable interface will get callled correctly. I'm guessing that these objects will get garbage collected at some point also.
Here is call stack on return from 1st call when Dispose is being called:
BossISeriesCwbxService.dll!BossISeriesCwbxServices.DataContracts.ISeriesSystem.Dispose(bool
bDisposing = true) Line 119 C#
BossISeriesCwbxService.dll!BossISeriesCwbxServices.DataContracts.ISeriesSystem.Dispose()
Line 107 + 0xd bytes C#
System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.DisposeParametersCore()
+ 0x56 bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.DisposeParameters()
+ 0xf bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessageCleanup(ref
System.ServiceModel.Dispatcher.MessageRpc
rpc =
{System.ServiceModel.Dispatcher.MessageRpc})
+ 0x135 bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(ref
System.ServiceModel.Dispatcher.MessageRpc
rpc =
{System.ServiceModel.Dispatcher.MessageRpc})
+ 0x1bf bytes System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(ref
System.ServiceModel.Dispatcher.MessageRpc
rpc) + 0x80 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(ref
System.ServiceModel.Dispatcher.MessageRpc
rpc) + 0x36 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(ref
System.ServiceModel.Dispatcher.MessageRpc
rpc) + 0x43 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(ref
System.ServiceModel.Dispatcher.MessageRpc
rpc) + 0xd7 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.Process(bool
isOperationContextSet = false) + 0x9b
bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.Dispatch(ref
System.ServiceModel.Dispatcher.MessageRpc
rpc, bool isOperationContextSet) +
0x2d bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(System.ServiceModel.Channels.RequestContext
request =
{System.ServiceModel.Security.SecuritySessionServerSettings.SecuritySessionRequestContext},
bool cleanThread,
System.ServiceModel.OperationContext
currentOperationContext) + 0x20c
bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(System.ServiceModel.Channels.RequestContext
request,
System.ServiceModel.OperationContext
currentOperationContext) + 0xdf bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(System.IAsyncResult
result) + 0x43 bytes
System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.OnContinueAsyncReceive(object
state) + 0x45 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke2()
+ 0x46 bytes System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.OnSecurityContextCallback(object
o) + 0x28 bytes
mscorlib.dll!System.Security.SecurityContext.Run(System.Security.SecurityContext
securityContext,
System.Threading.ContextCallback
callback, object state) + 0x55 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke()
+ 0x4d bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ProcessCallbacks()
+ 0x180 bytes System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.CompletionCallback(object
state) + 0x7a bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ScheduledOverlapped.IOCallback(uint
errorCode, uint numBytes,
System.Threading.NativeOverlapped*
nativeOverlapped) + 0xf bytes
SMDiagnostics.dll!System.ServiceModel.Diagnostics.Utility.IOCompletionThunk.UnhandledExceptionFrame(uint
error, uint bytesRead,
System.Threading.NativeOverlapped*
nativeOverlapped) + 0x3d bytes
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint
errorCode, uint numBytes,
System.Threading.NativeOverlapped*
pOVERLAP) + 0x54 bytes
I read some posts on caching static data in a WCF service implementation class, and was having a problem with the GC calling dispose on the objects in a static Dictionary. I am referencing some activex objects from IBM iSeries Access, so I implemented IDisposable interface to clean up connection to iSeries. My problem is the GC is Disposing of objects in the Static members of the Service class. Not sure all the code was required, but here it is anyway. The problem is that on return from each OperationContract method, the GC is calling Dispose on the ISeriesSystem or Queue object that was added to the associated Dictionary, but the ISeriesSystem Dictionary is static, so I thought that it held a reference to the object, so GC wouldn't be done until it is removed from Dictionary.
Service Interface:
[ServiceContract(Namespace="BossISeriesCwbxServices")]
public interface IDataQueueService
{
[OperationContract]
ISeriesSystem SystemInitialize(string sISeriesName);
[OperationContract(Name="FinalizeSystemByName")]
void SystemFinalize(string sISeriesName);
[OperationContract]
void SystemFinalize(ISeriesSystem oISeriesSystem);
[OperationContract]
Queue QueueInitialize(string sQueueName, string sLibrary, string sISeriesName);
[OperationContract(Name="FinalizeQueueByName")]
void QueueFinalize(string sQueueName, string sLibrary, string sISeriesName);
[OperationContract]
void QueueFinalize(Queue oDataQueue);
[OperationContract (Name="QueueWriteByName")]
void QueueWrite(string sQueueName, string sLibrary, string sISeriesName, string sMessage);
[OperationContract]
void QueueWrite(Queue oDataQueue, string sMessage);
[OperationContract (Name="QueueReadByName")]
string QueueRead(string sQueueName, string sLibrary, string sISeriesName);
[OperationContract]
string QueueRead(Queue oDataQueue);
}
Service Implementation:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
public class DataQueueService : IDataQueueService
{
private static Dictionary<string, ISeriesSystem> mdictISeriesSystems = new Dictionary<string, ISeriesSystem>();
public static IDictionary<string, ISeriesSystem> ISeriesDict
{
get { return mdictISeriesSystems; }
}
public ISeriesSystem SystemInitialize(string sISeriesName)
{
ISeriesSystem oISeriesSystem = AddSystem(sISeriesName);
return oISeriesSystem;
}
public void SystemFinalize(string sISeriesName)
{
}
public void SystemFinalize(ISeriesSystem oISeriesSystem)
{
SystemFinalize(oISeriesSystem.Name);
}
public Queue QueueInitialize(string sQueueName, string sLibrary, string sISeriesName)
{
ISeriesSystem oISeriesSystem = null;
Queue oDataQueue = null;
try
{
oISeriesSystem = AddSystem(sISeriesName);
oDataQueue = oISeriesSystem.AddQueue(sQueueName, sLibrary);
}
catch (Exception ex)
{
// ToDo: Log ex to WCF service log and remove from Console.
Console.WriteLine(ex.ToString());
oDataQueue = null;
}
return oDataQueue;
}
public Queue QueueInitialize(string sQueueName, string sLibrary, ISeriesSystem oISeriesSystem)
{
return QueueInitialize(sQueueName, sLibrary, oISeriesSystem.Name);
}
public void QueueFinalize(string sQueueName, string sLibrary, string sISeriesName)
{
string sISeriesKey = sISeriesName.Trim();
string sDataQueueKey = sLibrary.Trim() + sQueueName.Trim();
ISeriesSystem oISeriesSystem = null;
Queue oDataQueue = null;
if (DataQueueService.ISeriesDict.TryGetValue(sISeriesKey, out oISeriesSystem))
{
if (oISeriesSystem.DataQueueDict.TryGetValue(sDataQueueKey, out oDataQueue))
{
oDataQueue.Dispose();
oDataQueue = null;
oISeriesSystem.DataQueueDict.Remove(sDataQueueKey);
}
if (oISeriesSystem.DataQueueDict.Count == 0)
{
oISeriesSystem.Dispose();
oISeriesSystem = null;
}
}
}
public void QueueFinalize(Queue oDataQueue)
{
QueueFinalize(oDataQueue.Name, oDataQueue.Library, oDataQueue.ISeriesName);
}
public void QueueWrite(string sQueueName, string sLibrary, string sISeriesName, string sMessage)
{
string sISeriesKey = sISeriesName.Trim();
string sDataQueueKey = sLibrary.Trim() + sQueueName.Trim();
ISeriesSystem oISeriesSystem = null;
Queue oDataQueue = null;
if (DataQueueService.ISeriesDict.TryGetValue(sISeriesKey, out oISeriesSystem))
{
if (oISeriesSystem.DataQueueDict.TryGetValue(sDataQueueKey, out oDataQueue))
{
oDataQueue.Write(sMessage);
}
}
}
public void QueueWrite(Queue oDataQueue, string sMessage)
{
QueueWrite(oDataQueue.Name, oDataQueue.Library, oDataQueue.ISeriesName, sMessage);
}
public string QueueRead(string sQueueName, string sLibrary, string sISeriesName)
{
string sISeriesKey = sISeriesName.Trim();
string sDataQueueKey = sLibrary.Trim() + sQueueName.Trim();
ISeriesSystem oISeriesSystem = null;
Queue oDataQueue = null;
if (DataQueueService.ISeriesDict.TryGetValue(sISeriesKey, out oISeriesSystem))
{
if (oISeriesSystem.DataQueueDict.TryGetValue(sDataQueueKey, out oDataQueue))
{
return oDataQueue.Read();
}
}
return "";
}
public string QueueRead(Queue oDataQueue)
{
return QueueRead(oDataQueue.Name, oDataQueue.Library, oDataQueue.ISeriesName);
}
ISeriesSystem AddSystem(string sISeriesName)
{
ISeriesSystem oISeriesSystem = null;
string sISeriesKey = sISeriesName.Trim();
if (!DataQueueService.ISeriesDict.TryGetValue(sISeriesKey, out oISeriesSystem))
{
oISeriesSystem = new ISeriesSystem(sISeriesName);
DataQueueService.ISeriesDict[sISeriesKey] = oISeriesSystem;
}
return oISeriesSystem;
}
ISeriesSystem DataContract:
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
using cwbx;
namespace BossISeriesCwbxServices.DataContracts
{
public class ISeriesSystem : IDisposable
{
private string msName;
[DataMember]
public string Name
{
get { return msName; }
set { msName = value; }
}
private Dictionary<string, Queue> mdictDataQueues = new Dictionary<string, Queue>();
public IDictionary<string, Queue> DataQueueDict
{
get { return mdictDataQueues; }
}
private cwbx.AS400System mcwbxISeriesSystem = new AS400System();
private cwbx.AS400System CwbxISeriesSystem
{
get { return mcwbxISeriesSystem; }
set { mcwbxISeriesSystem = value; }
}
private bool bDisposed = false;
public ISeriesSystem()
{
}
public ISeriesSystem(string sISeriesName)
{
try
{
// Set DataContract properties.
this.Name = sISeriesName;
// Connect to iSeries, Logon and connect to iSeries services that may be used.
this.CwbxISeriesSystem.Define(sISeriesName);
this.CwbxISeriesSystem.UserID = "APP1DAK";
this.CwbxISeriesSystem.Password = "DONNA99";
this.CwbxISeriesSystem.Signon();
this.CwbxISeriesSystem.Connect(cwbcoServiceEnum.cwbcoServiceDataQueues);
this.CwbxISeriesSystem.Connect(cwbcoServiceEnum.cwbcoServiceSecurity);
this.CwbxISeriesSystem.Connect(cwbcoServiceEnum.cwbcoServiceRemoteCmd);
}
catch (Exception ex)
{
// ToDo: Log ex to WCF service log and remove from Console.
Console.WriteLine(ex.ToString());
foreach (cwbx.Error cwbxError in this.CwbxISeriesSystem.Errors)
{
Console.WriteLine(cwbxError.Text);
Console.WriteLine(cwbxError.ToString());
}
}
}
~ISeriesSystem()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool bDisposing)
{
// Only Dispose of the object 1 time.
if (!this.bDisposed)
{
// If disposing equals true, Dispose() was called by GC, so dispose all managed resources.
if (bDisposing)
{
// Dispose managed resources, calling object Dispose method for objects
// that implement IDisposable interface.
}
try
{
if (this.CwbxISeriesSystem.IsConnected(cwbcoServiceEnum.cwbcoServiceAny) == 1)
{
this.CwbxISeriesSystem.Disconnect(cwbcoServiceEnum.cwbcoServiceAll);
}
}
catch (Exception ex)
{
// ToDo: Log ex to WCF service log and remove from Console.
Console.WriteLine(ex.ToString());
foreach (cwbx.Error cwbxError in this.CwbxISeriesSystem.Errors)
{
Console.WriteLine(cwbxError.Text);
Console.WriteLine(cwbxError.ToString());
}
}
// Mark disposing as being done.
bDisposed = true;
}
}
public Queue AddQueue(string sQueueName, string sLibrary)
{
Queue oDataQueue = null;
string sDataQueueKey = sLibrary.Trim() + sQueueName.Trim();
if (!this.DataQueueDict.TryGetValue(sDataQueueKey, out oDataQueue))
{
oDataQueue = new Queue(sQueueName, sLibrary, this.CwbxISeriesSystem);
this.DataQueueDict[sDataQueueKey] = oDataQueue;
}
return oDataQueue;
}
}
}
Queue DataContract:
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
using cwbx;
namespace BossISeriesCwbxServices.DataContracts
{
[DataContract]
public class Queue : IDisposable
{
private string msName;
[DataMember]
public string Name
{
get { return msName; }
set { msName = value; }
}
private string msLibrary;
[DataMember]
public string Library
{
get { return msLibrary; }
set { msLibrary = value; }
}
private string msISeriesName;
[DataMember]
public string ISeriesName
{
get { return msISeriesName; }
set { msISeriesName = value; }
}
private short miWaitTime = 10;
[DataMember]
public short WaitTime
{
get { return miWaitTime; }
set { miWaitTime = value; }
}
private short miNumberOfAttempts = 1;
[DataMember]
public short NumberOfAttempts
{
get { return miNumberOfAttempts; }
set { miNumberOfAttempts = value; }
}
private short miMaxQueueIndex = 1;
public short MaxQueueIndex
{
get { return miMaxQueueIndex; }
set { miMaxQueueIndex = value; }
}
private short miCurrentQueueIndex = 1;
public short CurrentQueueIndex
{
get { return miCurrentQueueIndex; }
set { miCurrentQueueIndex = value; }
}
private cwbx.DataQueue mcwbxDataQueue = new cwbx.DataQueue();
private cwbx.DataQueue CwbxDataQueue
{
get { return mcwbxDataQueue; }
set { mcwbxDataQueue = value; }
}
private bool bDisposed = false;
public Queue()
{
}
public Queue(string sQueueName, string sLibrary, cwbx.AS400System cwbxISeriesSystem)
{
this.Name = sQueueName;
this.Library = sLibrary;
this.ISeriesName = cwbxISeriesSystem.SystemName;
this.CwbxDataQueue.QueueName = sQueueName;
this.CwbxDataQueue.LibraryName = sLibrary;
this.CwbxDataQueue.system = cwbxISeriesSystem;
}
~Queue()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool bDisposing)
{
// Only Dispose of the object 1 time.
if (!this.bDisposed)
{
// If disposing equals true, Dispose() was called by GC, so dispose all managed resources.
if (bDisposing)
{
// Dispose managed resources, calling object Dispose method for objects
// that implement IDisposable interface.
}
// Call the appropriate methods to clean up unmanaged resources here.
try
{
this.CwbxDataQueue = null;
}
catch (Exception ex)
{
// ToDo: Log ex to WCF service log and remove from Console.
Console.WriteLine(ex.ToString());
foreach (cwbx.Error cwbxError in this.CwbxDataQueue.Errors)
{
Console.WriteLine(cwbxError.Text);
Console.WriteLine(cwbxError.ToString());
}
}
// Mark disposing as being done.
bDisposed = true;
}
}
public void Write(string sMessage)
{
try
{
cwbx.StringConverter cwbxStringConverter = new cwbx.StringConverter();
Object oBytes = cwbxStringConverter.ToBytes(sMessage);
this.CwbxDataQueue.Write(oBytes, false);
}
catch (Exception ex)
{
// ToDo: Log ex to WCF service log and remove from Console.
Console.WriteLine(ex.ToString());
foreach (cwbx.Error cwbxError in this.CwbxDataQueue.Errors)
{
Console.WriteLine(cwbxError.Text);
Console.WriteLine(cwbxError.ToString());
}
}
}
public string Read()
{
try
{
Object oObject = null;
return (new cwbx.StringConverter()).FromBytes(this.CwbxDataQueue.Read(this.WaitTime * this.NumberOfAttempts, out oObject));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
foreach (cwbx.Error cwbxError in this.CwbxDataQueue.Errors)
{
Console.WriteLine(cwbxError.Text);
Console.WriteLine(cwbxError.ToString());
}
return "";
}
}
}
}
Client Code:
ISeriesSystem oISeriesSystem = null;
Queue oDataQueue = null;
oISeriesSystem = DQService.SystemInitialize("A2029D2.AS400.US.UPS.COM");
oDataQueue = DQService.QueueInitialize("SMTLST020", "IB5EXE", oISeriesSystem.Name);
oISeriesSystem.DataQueueDict.Add(oDataQueue.Library + oDataQueue.Name, oDataQueue);
ISeriesSystemDict.Add(oISeriesSystem.Name, oISeriesSystem);
DQService.QueueWrite(oDataQueue, "Testing cwbx.DataQueue WCF service");
string sMessage = DQService.QueueRead(oDataQueue);
Exe Hosted Service:
Uri baseAddress = new Uri("http://localhost:8080/BossISeriesCwbxServices");
//Instantiate new ServiceHost
moServiceHost = new ServiceHost(typeof(BossISeriesCwbxServices.DataQueueService), baseAddress);
// Add Endpoint
moServiceHost.AddServiceEndpoint(typeof(BossISeriesCwbxServices.IDataQueueService), new WSHttpBinding(), "IDataQueueService");
// Enable metadata exchange.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
moServiceHost.Description.Behaviors.Add(smb);
//Open moServiceHost
moServiceHost.Open();
Console.WriteLine("The IDataQueueService is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Does it happen regularly or every now and then? Does it happen under development or only (after some time) on the production server?
When you host this under IIS, the server might decide to 'recycle' your app. Basic advice: don't use static in server apps. It's not reliable and not scalable.
Edit
OK, I've read a little more (but not all).
The problem is that on return from
each OperationContract method, the GC
is calling Dispose on the
ISeriesSystem or Queue object
You should verify that in great detail. Is it really the GC that calls your Finalizer(s) (aka destructors)? You should use logging or debugging to verify that the overload Dispose(false) is being called. If Dispose(true) is called (and I see a lot of code involved in doing that) you should stacktrace to the actual cause.
Can you put the following code :
[ServiceContract(SessionMode = SessionMode.Required)]
on your service contract just to make sure the transport session is being created? If it isn't this should throw an error.
WSHttpBinding does not create a transport session without security and reliable session.
EDIT :
Other than that, since you are creating a Single Instance service, why would you need static members? There is only going to be one instance alive, so, any instance members of that instance will live with it and will be kind of static. Have you thought about that or there is a specific reason for using static members?
I know it's a bit late but have you tried adding the [OperationBehavior(AutoDisposeParameters = false)] attribute?
Link to MSDN Forum