I have several objects in my solution that use the following pattern:
#region Repositories
private RepositoryAccount _account;
private RepositoryInquiry _inquiry;
private RepositoryLoan _loan;
public RepositoryAccount Account { get { return _account ?? (_account = new RepositoryAccount(this)); } }
public RepositoryInquiry Inquiry { get { return _inquiry ?? (_inquiry = new RepositoryInquiry(this)); } }
public RepositoryLoan Loan { get { return _loan ?? (_loan = new RepositoryLoan(this)); } }
#endregion
I created a Generic Class to try to make the Property handling a little cleaner but ran into a snag. The Property Class looks like:
public class Property<T> where T : class, new()
{
private T _value;
public T Value
{
get { return _value ?? (_value = new T()); }
set
{
// insert desired logic here
_value = value;
}
}
public static implicit operator T(Property<T> value)
{
return value.Value;
}
public static implicit operator Property<T>(T value)
{
return new Property<T> { Value = value };
}
}
I replace my property (with backing objects) with the new Generic Property like:
public Property<RepositoryAccount> Account { get; set; }
public Property<RepositoryInquiry> Inquiry { get; set; }
public Property<RepositoryLoan> Loan { get; set; }
However, if you noticed in the original public getter, I need to instantiate each object with another object: this is required and there is no paramaterless constructor for these repository objects.
Any suggestions?
The whole object looks like:
public class UnitOfWorkSymitar : IUnitOfWork
{
#region Properties
internal IPHostEntry IpHostInfo;
internal IPAddress IpAddress;
internal IPEndPoint RemotEndPoint;
internal System.Net.Sockets.Socket Sender;
internal byte[] Bytes = new byte[1024];
internal bool IsAllowedToRun = false;
internal string SymServerIp;
internal int SymPort;
public string State { get; set; }
#endregion
#region Constructor
public UnitOfWorkSymitar() { OpenSymitarConnection(); }
protected UnitOfWorkSymitar(string serverIp, int? port) { OpenSymitarConnection(serverIp, port); }
#endregion
#region Implement IUnitOfWork
public void Commit() { /* No commit on this socket connection */ }
public void Rollback() { /* No rollback on this socket connection */ }
#endregion
#region Implement IDisposable
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
CloseSymitarConnection();
}
#endregion
#region Private Helpers
/// <summary>
/// Connect to a remote device.
/// </summary>
/// <returns>Success/failure message</returns>
private string OpenSymitarConnection(string serverIp = null, int? port = null)
{
var config = ConfigInfoSymitar.Instance;
SymServerIp = serverIp ?? config.SymServerIp;
SymPort = port ?? config.SymPort;
try
{
// Establish the remote endpoint for the socket.
//IpHostInfo = Dns.GetHostEntry(SymServerIp);
//IpAddress = IpHostInfo.AddressList[0];
IpAddress = Dns.GetHostAddresses(SymServerIp)[0];
RemotEndPoint = new IPEndPoint(IpAddress, SymPort);
// Create a TCP/IP socket.
Sender = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Connect the socket to the remote endpoint. Catch any errors.
Sender.Connect(RemotEndPoint);
}
catch (Exception ex)
{
State = DetermineError(ex);
return State;
//throw;
}
IsAllowedToRun = true;
State = "Success";
return State;
}
/// <summary>
/// Setup and send the request
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public string SendMessage(string request)
{
try
{
// Encode the data string into a byte array and add the carriage return for SymConnect.
var msg = Encoding.ASCII.GetBytes(request);
if(!request.EndsWith("\n"))
msg = Encoding.ASCII.GetBytes(request + "\n");
// Send the data through the socket.
var bytesSent = Sender.Send(msg);
// Receive the response from the remote device.
var bytesRec = Sender.Receive(Bytes);
var response = Encoding.ASCII.GetString(Bytes, 0, bytesRec);
response = response.Replace("\n", "");
return response;
}
catch (Exception ex)
{
return DetermineError(ex);
}
}
/// <summary>
/// Close the connection to a remote device.
/// </summary>
/// <returns></returns>
private string CloseSymitarConnection()
{
try
{
// Release the socket.
Sender.Shutdown(SocketShutdown.Both);
Sender.Close();
}
catch (Exception ex)
{
return DetermineError(ex);
}
finally
{
IsAllowedToRun = false;
}
return "Success!";
}
/// <summary>
/// Determine if this is an Arguments, Socket or some other exception
/// </summary>
/// <param name="ex">The exception to be checked</param>
/// <returns>String message</returns>
private static string DetermineError(Exception ex)
{
if (ex.GetType() == typeof(ArgumentNullException))
return "Missing Arguments: " + Environment.NewLine + ex.GetFullMessage();
if (ex.GetType() == typeof(SocketException))
return "Socket Error: " + Environment.NewLine + ex.GetFullMessage();
return "Unexpected Error: " + Environment.NewLine + ex.GetFullMessage();
}
#endregion
#region Symitar Samples
private static string ExecSymConnectRequest(string symServerIP, int symPort, string request)
{
// Data buffer for incoming data.
var bytes = new byte[1024];
// Connect to a remote device.
try
{
// Establish the remote endpoint for the socket.
// This example uses port 11000 on the local computer.
var ipHostInfo = Dns.GetHostEntry(symServerIP);
var ipAddress = ipHostInfo.AddressList[0];
var remoteEP = new IPEndPoint(ipAddress, symPort);
// Create a TCP/IP socket.
var sender = new System.Net.Sockets.Socket(AddressFamily.InterNetwork
, SocketType.Stream
, ProtocolType.Tcp);
// Connect the socket to the remote endpoint. Catch any errors.
try
{
sender.Connect(remoteEP);
// Encode the data string into a byte array and add the carriage return for SymConnect.
var msg = Encoding.ASCII.GetBytes(request + "\n");
// Send the data through the socket.
var bytesSent = sender.Send(msg);
// Receive the response from the remote device.
var bytesRec = sender.Receive(bytes);
var response = Encoding.ASCII.GetString(bytes, 0, bytesRec);
// Release the socket.
sender.Shutdown(SocketShutdown.Both);
sender.Close();
response.Replace("\n", "");
return response;
}
catch (ArgumentNullException ane)
{
return "Missing Arguments: " + ane.Message;
}
catch (SocketException se)
{
return "Socket Error: " + se.Message;
}
catch (Exception e)
{
return "Unexpected Error: " + e.Message;
}
}
catch (Exception e)
{
return e.Message;
}
}
#endregion
#region Repositories
private RepositoryAccount _account;
private RepositoryInquiry _inquiry;
private RepositoryLoan _loan;
public RepositoryAccount Account { get { return _account ?? (_account = new RepositoryAccount(this)); } }
public RepositoryInquiry Inquiry { get { return _inquiry ?? (_inquiry = new RepositoryInquiry(this)); } }
public RepositoryLoan Loan { get { return _loan ?? (_loan = new RepositoryLoan(this)); } }
#endregion
}
Might try something like:
class Program {
Property<string> Foo = new Property<string>(() => "FOO!");
}
public class Property<T> where T : class {
private Lazy<T> instance;
public Property(Func<T> generator) {
instance = new Lazy<T>(generator);
}
public T Value {
get { return instance.Value; }
}
public static implicit operator T(Property<T> value) {
return value.Value;
}
public static implicit operator Property<T>(T value) {
return new Property<T>(() => value);
}
}
There is a solution using reflection although it might not be the cleanest OOP one. A method of the FormatterService class in the System.Runtime namespace FormatterServices.GetUninitializedObject() will create an instance without calling the constructor. There is a similar answer to this problem here.
In order to make it work with your solution you have to make some changes to your code. First of all remove the new() from your generic class Property declaration otherwise you will always get an error from the compiler if you try to use a type T with no parameter-less constructor. Second add this method to your Property class:
private T GetInstance()
{
return (T)FormatterServices.GetUninitializedObject(typeof(T)); //does not call ctor
}
It will return an unitialized object but it won't call the constructor.
Here is the full code:
public class Property<T> where T : class
{
private T _value;
public T Value
{
get
{
return _value ?? (_value = GetInstance());
}
set
{
// insert desired logic here
_value = value;
}
}
private T GetInstance()
{
return (T)FormatterServices.GetUninitializedObject(typeof(T)); //does not call ctor
}
public static implicit operator T(Property<T> value)
{
return value.Value;
}
public static implicit operator Property<T>(T value)
{
return new Property<T> { Value = value };
}
}
I created a gist here, you can try the code into LinqPad and see the result.
hope it helps.
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;
}
This topic resembles this thread and this thread. Also I have read this example
But, in any of this thread I can't find clear example how organize p2p connection across the internet.
All examples good working in local network.
I made a small program, you can see it below. It works good in local network.
Main question: After peer registration how to open Wcf host and wait calls from another peers? In this topic I have read that:
When a peer is resolved on the global cloud, it is typically resolved to an IPv6 address
So I need open my WCF on this IPv6 address? How I can receive it?
Please give me some coding tips.
My program:
namespace FileShare
{
internal class Program
{
private static void Main()
{
Console.WriteLine("Hello enter your username");
IPeerRegistrationRepository peerRegistrationRepository = new PeerRegistrationRepository();
IPeerNameResolverRepository peerNameResolverRepository = new PeerNameResolverRepository();
ServiceHost host = null;
try
{
string username = Console.ReadLine();
int port = PeerUtils.FindFreePort();
peerRegistrationRepository.StartRegistration("Max951Peer", port, PeerNameType.Secured);
string serviceUrl = $"net.tcp://{peerRegistrationRepository.PeerName.PeerHostName}:{port}/Max951P2PService";
IP2PService localService = new P2PService();
host = new ServiceHost(localService, new Uri(serviceUrl));
host.AddServiceEndpoint(typeof(IP2PService), new NetTcpBinding { Security = { Mode = SecurityMode.None } }, serviceUrl);
try
{
host.Open();
Console.WriteLine($"Host opened on { serviceUrl }");
}
catch (Exception e)
{
Console.WriteLine($"Error { e }");
return;
}
Console.WriteLine($"Registered peer. PeerHostName: { peerRegistrationRepository.PeerName.PeerHostName }, Classifier: { peerRegistrationRepository.PeerName.Classifier }");
PeerNameRecordCollection results = peerNameResolverRepository.ResolvePeerName(peerRegistrationRepository.PeerName, Cloud.Global);
Console.WriteLine("{0} Peers Found:", results.Count.ToString());
int i = 1;
foreach (PeerNameRecord peer in results)
{
Console.WriteLine("{0} Peer:{1}", i++, peer.PeerName);
foreach (IPEndPoint ip in peer.EndPointCollection)
{
Console.WriteLine("\t Endpoint: {0}", ip);
string endpointUrl = $"net.tcp://[{ip.Address}]:{ip.Port}/Max951P2PService";
NetTcpBinding binding = new NetTcpBinding { Security = { Mode = SecurityMode.None } };
try
{
IP2PService serviceProxy = ChannelFactory<IP2PService>.CreateChannel(binding, new EndpointAddress(endpointUrl));
serviceProxy.SendMessage("Hello!", username);
}
catch (Exception)
{
// ignored
}
}
}
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
peerRegistrationRepository.StopRegistration();
host?.Close();
}
//serviceModelClient.Stop();
}
}
[ServiceContract]
public interface IP2PService
{
[OperationContract(IsOneWay = true)]
void SendMessage(string message, string from);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class P2PService : IP2PService
{
/// <inheritdoc />
public void SendMessage(string message, string from)
{
Console.WriteLine($"{from} says: { message }");
}
}
}
namespace Utils.PeerToPeer
{
public class PeerNameResolverRepository : IPeerNameResolverRepository
{
private readonly PeerNameResolver peerNameResolver;
public PeerNameResolverRepository()
{
this.peerNameResolver = new PeerNameResolver();
}
/// <inheritdoc />
public PeerNameRecordCollection ResolvePeerName(string name, PeerNameType peerNameType, Cloud cloud)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
return this.peerNameResolver.Resolve(new PeerName(name, peerNameType), cloud);
}
/// <inheritdoc />
public PeerNameRecordCollection ResolvePeerName(PeerName peerName, Cloud cloud)
{
return this.peerNameResolver.Resolve(peerName, cloud);
}
}
}
namespace Utils.PeerToPeer
{
public class PeerRegistrationRepository : IPeerRegistrationRepository
{
private PeerNameRegistration peerNameRegistration;
/// <inheritdoc />
public bool IsRegistered => this.peerNameRegistration != null && this.peerNameRegistration.IsRegistered();
/// <inheritdoc />
public string PeerUri => GetPeerUri();
/// <inheritdoc />
public PeerName PeerName { get; set; }
/// <inheritdoc />
public void StartRegistration(string name, int port, PeerNameType peerNameType)
{
this.PeerName = new PeerName(name, peerNameType);
this.peerNameRegistration = new PeerNameRegistration(PeerName, port, Cloud.Global);
this.peerNameRegistration.Start();
}
/// <inheritdoc />
public void StopRegistration()
{
this.peerNameRegistration?.Stop();
this.peerNameRegistration = null;
}
private string GetPeerUri()
{
return this.peerNameRegistration?.PeerName.PeerHostName;
}
}
}
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
}
I'm trying to send an UDP Broadcast with Windows Phone 8.1. Therefore, I created a UDP Server (a console app) which runs on my laptop in the same Wifi Network as my WP8.1.
This is the test server:
static void Main(string[] args)
{
var client = new UdpClient(12345);
client.MulticastLoopback = false;
var isRunning = true;
var thread = new Thread(() =>
{
while (isRunning)
{
var endpoint = new IPEndPoint(IPAddress.Any, 0);
var data = client.Receive(ref endpoint);
Console.WriteLine("Received message from {0}:{1} with length={2}", endpoint.Address, endpoint.Port, data.Length);
client.Send(data, data.Length, endpoint);
}
});
thread.Start();
Console.ReadLine();
isRunning = false;
}
When I create another console app which sends some data as UDP Broadcast, I will get the same data as response from the server. So the server is working fine. With Windows Phone 8.1 I have to use DatagramSocket. This is my code:
var socket = new DatagramSocket();
socket.MessageReceived += HandleIncomingMessages;
using (var stream = await socket.GetOutputStreamAsync(new HostName("255.255.255.255"), "12345"))
{
await stream.WriteAsync(data.AsBuffer());
await stream.FlushAsync();
}
I also tried it with an additional await _socket.ConnectAsync(new HostName("255.255.255.255"), "12345"); after creating the socket object.
Unfortunately, I'll never get an answer.
This is my implementation for a UdpClient which supports broadcast.
public class UdpClient : IDisposable
{
private readonly DatagramSocket _socket;
private readonly BlockingCollection<Tuple<IpAddress, byte[]>> _incomingMessages;
private readonly IpAddress _ipAddress;
private readonly object _lock = new object();
private bool _isBound;
private bool _isBinding;
public UdpClient(IpAddress ipAddress)
{
_ipAddress = ipAddress;
_socket = new DatagramSocket();
_socket.Control.DontFragment = true;
_incomingMessages = new BlockingCollection<Tuple<IpAddress, byte[]>>();
_socket.MessageReceived += HandleIncomingMessages;
}
public async Task SendAsync(byte[] data)
{
try
{
await Connect();
using (var stream = await _socket.GetOutputStreamAsync(_ipAddress.Host, _ipAddress.ServiceName))
{
await stream.WriteAsync(data.AsBuffer(0, data.Length));
}
}
catch (Exception e)
{
Debug.WriteLine(e);
}
}
public bool TryGetIncomingMessage(out Tuple<IpAddress, byte[]> message)
{
return _incomingMessages.TryTake(out message, TimeSpan.FromMilliseconds(20));
}
public void Dispose()
{
_socket.Dispose();
}
private async Task Connect()
{
if (_isBound || _isBinding)
{
return;
}
lock (_lock)
{
if (_isBound || _isBinding)
{
return;
}
_isBinding = true;
}
var possibleConnectionProfiles = NetworkInformation.GetConnectionProfiles()
.Where(p => p.IsWlanConnectionProfile && p.GetNetworkConnectivityLevel() != NetworkConnectivityLevel.None)
.ToList();
var connectionProfile = possibleConnectionProfiles.FirstOrDefault();
if (connectionProfile != null)
{
await _socket.BindServiceNameAsync(_ipAddress.ServiceName, connectionProfile.NetworkAdapter);
}
_isBound = true;
_isBinding = false;
}
private void HandleIncomingMessages(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs e)
{
var address = e.RemoteAddress.ToString();
var port = int.Parse(e.RemotePort);
using (var reader = e.GetDataReader())
{
var data = reader.DetachBuffer().ToArray();
_incomingMessages.Add(Tuple.Create(new IpAddress(address, port), data));
}
}
}
public struct IpAddress
{
public static readonly IpAddress Broadcast = new IpAddress("255.255.255.255");
public readonly string Address;
public readonly int Port;
public readonly HostName Host;
public readonly string ServiceName;
public IpAddress(string address, int port)
{
Address = address;
Port = port;
Host = new HostName(address);
ServiceName = port.ToString(CultureInfo.InvariantCulture);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}:{1}", Address, Port);
}
public bool Equals(IpAddress other)
{
return string.Equals(Address, other.Address) && Port == other.Port;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is IpAddress && Equals((IpAddress)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((Address != null ? Address.GetHashCode() : 0) * 397) ^ Port;
}
}
}
I have read that it's not a good habit to broadcast messages to 255.255.255.255. Instead one should send to the local broadcast address like (i.e. 192.168.1.255)
For this I wrote this IpAddressProvider:
public static class IpProvider
{
private static readonly Lazy<string> _localAddress;
private static readonly Lazy<string> _broadcastAddress;
static IpProvider()
{
_localAddress = new Lazy<string>(GetLocalAddress);
_broadcastAddress = new Lazy<string>(GetBroadcastAddress);
}
public static string LocalAddress { get { return _localAddress.Value; } }
public static string BroadcastAddress { get { return _broadcastAddress.Value; } }
private static string GetLocalAddress()
{
var hostnames = NetworkInformation.GetHostNames();
foreach (var hn in hostnames)
{
//IanaInterfaceType == 71 => Wifi
//IanaInterfaceType == 6 => Ethernet (Emulator)
if (hn.IPInformation != null &&
(hn.IPInformation.NetworkAdapter.IanaInterfaceType == 71
|| hn.IPInformation.NetworkAdapter.IanaInterfaceType == 6))
{
return hn.DisplayName;
}
}
return IpAddress.Broadcast.Address;
}
private static string GetBroadcastAddress()
{
var parts = _localAddress.Value.Split(new[] { '.' }).Take(3);
return string.Join(".", parts) + ".255";
}
}
And now I use them together to send broadcast messages:
var gatewayAddress = new IpAddress(IpProvider.BroadcastAddress);
var udpClient = new UdpClient(gatewayAddress);
await udpClient.SendAsync(data);
Tuple<IpAddress, byte[]> message;
while (udpClient.TryGetIncomingMessage(out message))
{
if (message.Item1.Address == IpProvider.LocalAddress)
{
continue;
}
HandleIncomingPacket(message);
}
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