//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
namespace System.ServiceModel.Channels
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Runtime;
using System.Runtime.Diagnostics;
using System.ServiceModel.Diagnostics;
using System.Threading;
using System.Xml;
internal abstract class UdpOutputChannel : OutputChannel, IOutputChannel
{
private bool cleanedUp;
private volatile AsyncWaitHandle retransmissionDoneWaitHandle;
private UdpRetransmissionSettings retransmitSettings;
private volatile Dictionary retransmitList;
private SynchronizedRandom randomNumberGenerator;
private Uri via;
public UdpOutputChannel(
ChannelManagerBase factory,
MessageEncoder encoder,
BufferManager bufferManager,
UdpSocket[] sendSockets,
UdpRetransmissionSettings retransmissionSettings,
Uri via,
bool isMulticast)
: base(factory)
{
Fx.Assert(encoder != null, "encoder shouldn't be null");
Fx.Assert(bufferManager != null, "buffer manager shouldn't be null");
Fx.Assert(sendSockets != null, "sendSockets can't be null");
Fx.Assert(sendSockets.Length > 0, "sendSockets can't be empty");
Fx.Assert(retransmissionSettings != null, "retransmissionSettings can't be null");
Fx.Assert(via != null, "via can't be null");
this.BufferManager = bufferManager;
this.IsMulticast = isMulticast;
this.Encoder = encoder;
this.retransmitSettings = retransmissionSettings;
this.SendSockets = sendSockets;
this.via = via;
if (this.retransmitSettings.Enabled)
{
this.retransmitList = new Dictionary();
this.randomNumberGenerator = new SynchronizedRandom(AppDomain.CurrentDomain.GetHashCode() | Environment.TickCount);
}
}
private interface IUdpRetransmitter
{
bool IsMulticast { get; }
void CancelRetransmission();
}
public override EndpointAddress RemoteAddress
{
get { return null; }
}
public override Uri Via
{
get { return this.via; }
}
internal bool IsMulticast
{
get;
private set;
}
internal TimeSpan InternalSendTimeout
{
get { return this.DefaultSendTimeout; }
}
protected BufferManager BufferManager
{
get;
private set;
}
protected MessageEncoder Encoder
{
get;
private set;
}
protected UdpSocket[] SendSockets
{
get;
private set;
}
[SuppressMessage("Microsoft.StyleCop.CSharp.ReadabilityRules", "SA1100:DoNotPrefixCallsWithBaseUnlessLocalImplementationExists", Justification = "StyleCop 4.5 does not validate this rule properly.")]
public override T GetProperty()
{
if (typeof(T) == typeof(IOutputChannel))
{
return (T)(object)this;
}
T messageEncoderProperty = this.Encoder.GetProperty();
if (messageEncoderProperty != null)
{
return messageEncoderProperty;
}
return base.GetProperty();
}
internal void CancelRetransmission(UniqueId messageId)
{
if (messageId != null && this.retransmitList != null)
{
lock (this.ThisLock)
{
if (this.retransmitList != null)
{
IUdpRetransmitter retransmitter;
if (this.retransmitList.TryGetValue(messageId, out retransmitter))
{
this.retransmitList.Remove(messageId);
retransmitter.CancelRetransmission();
}
}
}
}
}
protected static void LogMessage(ref Message message, ArraySegment messageData)
{
using (XmlDictionaryReader xmlDictionaryReader = XmlDictionaryReader.CreateTextReader(messageData.Array, messageData.Offset, messageData.Count, null, XmlDictionaryReaderQuotas.Max, null))
{
MessageLogger.LogMessage(ref message, xmlDictionaryReader, MessageLoggingSource.TransportSend);
}
}
protected override void AddHeadersTo(Message message)
{
Fx.Assert(message != null, "Message can't be null");
if (message is NullMessage)
{
return;
}
if (message.Version.Addressing != AddressingVersion.None)
{
if (message.Headers.MessageId == null)
{
message.Headers.MessageId = new UniqueId();
}
}
else
{
if (this.retransmitSettings.Enabled == true)
{
// we should only get here if some channel above us starts producing messages that don't match the encoder's message version.
throw FxTrace.Exception.AsError(new ProtocolException(SR.RetransmissionRequiresAddressingOnMessage(message.Version.Addressing.ToString())));
}
}
}
protected override IAsyncResult OnBeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
{
if (message is NullMessage)
{
return new CompletedAsyncResult(callback, state);
}
return new SendAsyncResult(this, message, timeout, callback, state);
}
protected override void OnEndSend(IAsyncResult result)
{
if (result is CompletedAsyncResult)
{
CompletedAsyncResult.End(result);
}
else
{
SendAsyncResult.End(result);
}
}
protected override void OnSend(Message message, TimeSpan timeout)
{
if (message is NullMessage)
{
return;
}
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
IPEndPoint remoteEndPoint;
UdpSocket[] sendSockets;
Exception exceptionToBeThrown;
sendSockets = this.GetSendSockets(message, out remoteEndPoint, out exceptionToBeThrown);
if (exceptionToBeThrown != null)
{
throw FxTrace.Exception.AsError(exceptionToBeThrown);
}
if (timeoutHelper.RemainingTime() <= TimeSpan.Zero)
{
throw FxTrace.Exception.AsError(new TimeoutException(SR.SendTimedOut(remoteEndPoint, timeout)));
}
bool returnBuffer = false;
ArraySegment messageData = default(ArraySegment);
bool sendingMulticast = UdpUtility.IsMulticastAddress(remoteEndPoint.Address);
SynchronousRetransmissionHelper retransmitHelper = null;
RetransmitIterator retransmitIterator = null;
bool shouldRetransmit = this.ShouldRetransmitMessage(sendingMulticast);
try
{
if (shouldRetransmit)
{
retransmitIterator = this.CreateRetransmitIterator(sendingMulticast);
retransmitHelper = new SynchronousRetransmissionHelper(sendingMulticast);
this.RetransmitStarting(message.Headers.MessageId, retransmitHelper);
}
messageData = this.EncodeMessage(message);
returnBuffer = true;
this.TransmitMessage(messageData, sendSockets, remoteEndPoint, timeoutHelper);
if (shouldRetransmit)
{
while (retransmitIterator.MoveNext())
{
// wait for currentDelay time, then retransmit
if (retransmitIterator.CurrentDelay > 0)
{
retransmitHelper.Wait(retransmitIterator.CurrentDelay);
}
if (retransmitHelper.IsCanceled)
{
ThrowIfAborted();
return;
}
// since we only invoke the encoder once just before the initial send of the message
// we need to handle logging the message in the retransmission case
if (MessageLogger.LogMessagesAtTransportLevel)
{
UdpOutputChannel.LogMessage(ref message, messageData);
}
this.TransmitMessage(messageData, sendSockets, remoteEndPoint, timeoutHelper);
}
}
}
finally
{
if (returnBuffer)
{
this.BufferManager.ReturnBuffer(messageData.Array);
}
if (shouldRetransmit)
{
this.RetransmitStopping(message.Headers.MessageId);
if (retransmitHelper != null)
{
retransmitHelper.Dispose();
}
}
}
}
protected abstract UdpSocket[] GetSendSockets(Message message, out IPEndPoint remoteEndPoint, out Exception exceptionToBeThrown);
protected override void OnAbort()
{
this.Cleanup(true, TimeSpan.Zero);
}
[SuppressMessage("Microsoft.StyleCop.CSharp.ReadabilityRules", "SA1100:DoNotPrefixCallsWithBaseUnlessLocalImplementationExists", Justification = "If BeginClose is overridden we still pass base.BeginClose here")]
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
{
return new CloseAsyncResult(
this,
timeout,
callback,
state);
}
protected override void OnClose(TimeSpan timeout)
{
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
this.Cleanup(false, timeoutHelper.RemainingTime());
}
protected override void OnEndClose(IAsyncResult result)
{
CloseAsyncResult.End(result);
}
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
{
this.OnOpen(timeout);
return new CompletedAsyncResult(callback, state);
}
protected override void OnEndOpen(IAsyncResult result)
{
CompletedAsyncResult.End(result);
}
protected override void OnOpen(TimeSpan timeout)
{
for (int i = 0; i < this.SendSockets.Length; i++)
{
this.SendSockets[i].Open();
}
}
protected ArraySegment EncodeMessage(Message message)
{
return this.Encoder.WriteMessage(message, int.MaxValue, this.BufferManager);
}
protected ObjectDisposedException CreateObjectDisposedException()
{
return new ObjectDisposedException(null, SR.ObjectDisposed(this.GetType().Name));
}
private RetransmitIterator CreateRetransmitIterator(bool sendingMulticast)
{
Fx.Assert(this.retransmitSettings.Enabled, "CreateRetransmitCalculator called when no retransmission set to happen");
int lowerBound = this.retransmitSettings.GetDelayLowerBound();
int upperBound = this.retransmitSettings.GetDelayUpperBound();
int currentDelay = this.randomNumberGenerator.Next(lowerBound, upperBound);
int maxDelay = this.retransmitSettings.GetMaxDelayPerRetransmission();
int maxRetransmitCount = sendingMulticast ? this.retransmitSettings.MaxMulticastRetransmitCount : this.retransmitSettings.MaxUnicastRetransmitCount;
return new RetransmitIterator(currentDelay, maxDelay, maxRetransmitCount);
}
private void RetransmitStarting(UniqueId messageId, IUdpRetransmitter retransmitter)
{
Fx.Assert(this.retransmitSettings.Enabled, "RetransmitStarting called when retransmission is disabled");
lock (this.ThisLock)
{
ThrowIfDisposed();
if (this.retransmitList.ContainsKey(messageId))
{
// someone is sending a message with the same MessageId
// while a retransmission is still in progress for that ID.
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.RecycledMessageIdDuringRetransmission(messageId)));
}
else
{
this.retransmitList[messageId] = retransmitter;
}
}
}
private void RetransmitStopping(UniqueId messageId)
{
Fx.Assert(this.retransmitSettings.Enabled, "RetransmitStopping called when retransmission is disabled");
lock (this.ThisLock)
{
// Cleanup sets retransmitList to null, so check before using...
if (this.retransmitList != null)
{
this.retransmitList.Remove(messageId);
// if we are closing down, then we need to unblock the Cleanup code
// this.retransmissionDoneEvent only != null if on cleaning up; abort case means that it == null.
if (this.retransmitList.Count == 0 && this.retransmissionDoneWaitHandle != null)
{
this.retransmissionDoneWaitHandle.Set();
}
}
}
}
private bool ShouldRetransmitMessage(bool sendingMulticast)
{
if (sendingMulticast)
{
return this.retransmitSettings.MaxMulticastRetransmitCount > 0;
}
else
{
return this.retransmitSettings.MaxUnicastRetransmitCount > 0;
}
}
private void TransmitMessage(ArraySegment messageBytes, UdpSocket[] sockets, IPEndPoint remoteEndpoint, TimeoutHelper timeoutHelper)
{
Fx.Assert(messageBytes.Array != null, "message data array can't be null");
Fx.Assert(sockets != null, "sockets can't be null");
Fx.Assert(sockets.Length > 0, "sockets must contain at least one item");
Fx.Assert(remoteEndpoint != null, "remoteEndPoint can't be null");
for (int i = 0; i < sockets.Length; i++)
{
if (timeoutHelper.RemainingTime() <= TimeSpan.Zero)
{
throw FxTrace.Exception.AsError(new TimeoutException(SR.SendTimedOut(remoteEndpoint, timeoutHelper.OriginalTimeout)));
}
sockets[i].SendTo(messageBytes.Array, messageBytes.Offset, messageBytes.Count, remoteEndpoint);
}
}
// we're guaranteed by CommunicationObject that at most ONE of Close or BeginClose will be called once.
private void Cleanup(bool aborting, TimeSpan timeout)
{
bool needToWait = false;
if (this.cleanedUp)
{
return;
}
lock (this.ThisLock)
{
if (this.cleanedUp)
{
return;
}
if (!aborting && this.retransmitList != null && this.retransmitList.Count > 0)
{
needToWait = true;
this.retransmissionDoneWaitHandle = new AsyncWaitHandle(EventResetMode.ManualReset);
}
else
{
// copied this call here in order to avoid releasing then retaking lock
this.CleanupAfterWait(aborting);
}
}
if (needToWait)
{
if (!this.retransmissionDoneWaitHandle.Wait(timeout))
{
throw FxTrace.Exception.AsError(new TimeoutException(SR.TimeoutOnOperation(timeout)));
}
lock (this.ThisLock)
{
this.retransmissionDoneWaitHandle = null;
// another thread could have called Abort while Close() was waiting for retransmission to complete.
if (this.cleanedUp)
{
return;
}
this.CleanupAfterWait(aborting);
}
}
}
// must be called from within this.ThisLock
private void CleanupAfterWait(bool aborting)
{
Fx.Assert(!this.cleanedUp, "We should only clean up once");
if (this.retransmitList != null)
{
foreach (IUdpRetransmitter retransmitter in this.retransmitList.Values)
{
retransmitter.CancelRetransmission();
}
if (aborting && this.retransmissionDoneWaitHandle != null)
{
// If another thread has called close and is waiting for retransmission to complete,
// we need to make sure that thread gets unblocked.
this.retransmissionDoneWaitHandle.Set();
}
this.retransmitList = null;
}
for (int i = 0; i < this.SendSockets.Length; i++)
{
this.SendSockets[i].Close();
}
this.cleanedUp = true;
}
private class RetransmitIterator
{
private int maxDelay;
private int retransmitCount;
private int initialDelay;
internal RetransmitIterator(int initialDelay, int maxDelay, int retransmitCount)
{
Fx.Assert(initialDelay >= 0, "initialDelay cannot be negative");
Fx.Assert(maxDelay >= initialDelay, "maxDelay must be >= initialDelay");
Fx.Assert(retransmitCount > 0, "retransmitCount must be > 0");
this.CurrentDelay = -1;
this.initialDelay = initialDelay;
this.maxDelay = maxDelay;
this.retransmitCount = retransmitCount;
}
public int CurrentDelay
{
get;
private set;
}
// should be called before each retransmission to determine if
// another one is needed.
public bool MoveNext()
{
if (this.CurrentDelay < 0)
{
this.CurrentDelay = this.initialDelay;
return true;
}
bool shouldContinue = --this.retransmitCount > 0;
if (shouldContinue && this.CurrentDelay < this.maxDelay)
{
this.CurrentDelay = Math.Min(this.CurrentDelay * 2, this.maxDelay);
}
return shouldContinue;
}
}
private sealed class SynchronousRetransmissionHelper : IUdpRetransmitter, IDisposable
{
private ManualResetEvent cancelEvent;
private object thisLock;
private bool cleanedUp;
public SynchronousRetransmissionHelper(bool isMulticast)
{
this.thisLock = new object();
this.IsMulticast = isMulticast;
this.cancelEvent = new ManualResetEvent(false);
}
public bool IsMulticast
{
get;
private set;
}
public bool IsCanceled
{
get;
private set;
}
public void Wait(int millisecondsTimeout)
{
if (this.ResetEvent())
{
// Dispose should only be called by the same thread that
// is calling this function, making it so that we don't need a lock here...
this.cancelEvent.WaitOne(millisecondsTimeout);
}
}
public void CancelRetransmission()
{
lock (this.thisLock)
{
this.IsCanceled = true;
if (!this.cleanedUp)
{
this.cancelEvent.Set();
}
}
}
public void Dispose()
{
lock (this.thisLock)
{
if (!this.cleanedUp)
{
this.cleanedUp = true;
this.cancelEvent.Dispose();
}
}
}
private bool ResetEvent()
{
lock (this.thisLock)
{
if (!this.IsCanceled && !this.cleanedUp)
{
this.cancelEvent.Reset();
return true;
}
}
return false;
}
}
private class SendAsyncResult : AsyncResult, IUdpRetransmitter
{
private static AsyncCallback onSocketSendComplete = Fx.ThunkCallback(new AsyncCallback(OnSocketSendComplete));
private static Action