e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1040 lines
36 KiB
C#
1040 lines
36 KiB
C#
// <copyright>
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
|
|
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<UniqueId, IUdpRetransmitter> 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<UniqueId, IUdpRetransmitter>();
|
|
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<T>()
|
|
{
|
|
if (typeof(T) == typeof(IOutputChannel))
|
|
{
|
|
return (T)(object)this;
|
|
}
|
|
|
|
T messageEncoderProperty = this.Encoder.GetProperty<T>();
|
|
if (messageEncoderProperty != null)
|
|
{
|
|
return messageEncoderProperty;
|
|
}
|
|
|
|
return base.GetProperty<T>();
|
|
}
|
|
|
|
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<byte> 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<byte> messageData = default(ArraySegment<byte>);
|
|
|
|
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<byte> 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<byte> 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<object> onRetransmitMessage = new Action<object>(OnRetransmitMessage);
|
|
|
|
private UdpOutputChannel channel;
|
|
private ArraySegment<byte> messageData;
|
|
private TimeoutHelper timeoutHelper;
|
|
private IPEndPoint remoteEndpoint;
|
|
private int currentSocket;
|
|
private UdpSocket[] sendSockets;
|
|
private IOThreadTimer retransmitTimer;
|
|
private RetransmitIterator retransmitIterator;
|
|
private Message message;
|
|
private bool retransmissionEnabled;
|
|
|
|
public SendAsyncResult(UdpOutputChannel channel, Message message, TimeSpan timeout, AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.timeoutHelper = new TimeoutHelper(timeout);
|
|
|
|
this.channel = channel;
|
|
this.message = message;
|
|
bool throwing = true;
|
|
bool completedSynchronously = false;
|
|
|
|
try
|
|
{
|
|
this.Initialize(message);
|
|
|
|
completedSynchronously = this.BeginTransmitMessage();
|
|
|
|
if (completedSynchronously && this.retransmissionEnabled)
|
|
{
|
|
// initial send completed [....], now we need to start the retransmission process...
|
|
completedSynchronously = this.BeginRetransmission();
|
|
}
|
|
|
|
throwing = false;
|
|
}
|
|
finally
|
|
{
|
|
if (throwing)
|
|
{
|
|
this.Cleanup();
|
|
}
|
|
}
|
|
|
|
if (completedSynchronously)
|
|
{
|
|
this.CompleteAndCleanup(true, null);
|
|
}
|
|
}
|
|
|
|
private enum RetransmitState
|
|
{
|
|
WaitCompleted,
|
|
TransmitCompleted,
|
|
}
|
|
|
|
public bool IsCanceled
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public bool IsMulticast
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public static void End(IAsyncResult result)
|
|
{
|
|
AsyncResult.End<SendAsyncResult>(result);
|
|
}
|
|
|
|
// tries to terminate retransmission early, but won't cancel async IO immediately
|
|
public void CancelRetransmission()
|
|
{
|
|
this.IsCanceled = true;
|
|
}
|
|
|
|
private static void OnSocketSendComplete(IAsyncResult result)
|
|
{
|
|
if (result.CompletedSynchronously)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SendAsyncResult thisPtr = (SendAsyncResult)result.AsyncState;
|
|
bool completeSelf = false;
|
|
Exception completionException = null;
|
|
|
|
try
|
|
{
|
|
completeSelf = thisPtr.ContinueTransmitting(result);
|
|
|
|
if (completeSelf && thisPtr.retransmissionEnabled)
|
|
{
|
|
completeSelf = thisPtr.BeginRetransmission();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
completionException = e;
|
|
completeSelf = true;
|
|
}
|
|
|
|
if (completeSelf)
|
|
{
|
|
thisPtr.CompleteAndCleanup(false, completionException);
|
|
}
|
|
}
|
|
|
|
private static void OnRetransmitMessage(object state)
|
|
{
|
|
SendAsyncResult thisPtr = (SendAsyncResult)state;
|
|
bool completeSelf = false;
|
|
Exception completionException = null;
|
|
|
|
try
|
|
{
|
|
completeSelf = thisPtr.ContinueRetransmission(RetransmitState.WaitCompleted);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
completionException = e;
|
|
completeSelf = true;
|
|
}
|
|
|
|
if (completeSelf)
|
|
{
|
|
thisPtr.CompleteAndCleanup(false, completionException);
|
|
}
|
|
}
|
|
|
|
private bool BeginTransmitMessage()
|
|
{
|
|
this.currentSocket = 0;
|
|
return this.ContinueTransmitting(null);
|
|
}
|
|
|
|
private bool ContinueTransmitting(IAsyncResult socketAsyncResult)
|
|
{
|
|
while (this.currentSocket < this.sendSockets.Length)
|
|
{
|
|
if (socketAsyncResult == null)
|
|
{
|
|
socketAsyncResult = this.sendSockets[this.currentSocket].BeginSendTo(
|
|
this.messageData.Array,
|
|
this.messageData.Offset,
|
|
this.messageData.Count,
|
|
this.remoteEndpoint,
|
|
onSocketSendComplete,
|
|
this);
|
|
|
|
if (!socketAsyncResult.CompletedSynchronously)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
this.sendSockets[this.currentSocket].EndSendTo(socketAsyncResult);
|
|
|
|
// check for timeout after calling socket.EndSendTo
|
|
// so that we don't leave the socket in a bad state/leak async results
|
|
this.ThrowIfTimedOut();
|
|
|
|
if (this.IsCanceled)
|
|
{
|
|
// don't send on the next socket and return true to cause Complete to be called.
|
|
return true;
|
|
}
|
|
|
|
this.currentSocket++;
|
|
socketAsyncResult = null;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool BeginRetransmission()
|
|
{
|
|
// BeginRetransmission should only be called in the case where transmission of the message
|
|
// completes synchronously.
|
|
return this.ContinueRetransmission(RetransmitState.TransmitCompleted);
|
|
}
|
|
|
|
private bool ContinueRetransmission(RetransmitState state)
|
|
{
|
|
this.ThrowIfTimedOut();
|
|
|
|
while (true)
|
|
{
|
|
switch (state)
|
|
{
|
|
case RetransmitState.TransmitCompleted:
|
|
if (!this.retransmitIterator.MoveNext())
|
|
{
|
|
// We are done retransmitting
|
|
return true;
|
|
}
|
|
|
|
if (this.retransmitIterator.CurrentDelay > 0)
|
|
{
|
|
this.retransmitTimer.Set(this.retransmitIterator.CurrentDelay);
|
|
return false;
|
|
}
|
|
|
|
state = RetransmitState.WaitCompleted;
|
|
break;
|
|
case RetransmitState.WaitCompleted:
|
|
if (this.IsCanceled)
|
|
{
|
|
this.channel.ThrowIfAborted();
|
|
return true;
|
|
}
|
|
|
|
// 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 this.message, this.messageData);
|
|
}
|
|
|
|
// !completedSync
|
|
if (!this.BeginTransmitMessage())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
state = RetransmitState.TransmitCompleted;
|
|
break;
|
|
|
|
default:
|
|
Fx.Assert("Unknown RetransmitState value encountered");
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Initialize(Message message)
|
|
{
|
|
Exception exceptionToThrow;
|
|
this.sendSockets = this.channel.GetSendSockets(message, out this.remoteEndpoint, out exceptionToThrow);
|
|
|
|
if (exceptionToThrow != null)
|
|
{
|
|
throw FxTrace.Exception.AsError(exceptionToThrow);
|
|
}
|
|
|
|
this.IsMulticast = UdpUtility.IsMulticastAddress(this.remoteEndpoint.Address);
|
|
|
|
if (this.channel.ShouldRetransmitMessage(this.IsMulticast))
|
|
{
|
|
this.retransmissionEnabled = true;
|
|
this.channel.RetransmitStarting(this.message.Headers.MessageId, this);
|
|
this.retransmitTimer = new IOThreadTimer(onRetransmitMessage, this, false);
|
|
this.retransmitIterator = this.channel.CreateRetransmitIterator(this.IsMulticast);
|
|
}
|
|
|
|
this.messageData = this.channel.EncodeMessage(message);
|
|
}
|
|
|
|
private void ThrowIfTimedOut()
|
|
{
|
|
if (this.timeoutHelper.RemainingTime() <= TimeSpan.Zero)
|
|
{
|
|
throw FxTrace.Exception.AsError(new TimeoutException(SR.TimeoutOnOperation(this.timeoutHelper.OriginalTimeout)));
|
|
}
|
|
}
|
|
|
|
private void Cleanup()
|
|
{
|
|
if (this.retransmissionEnabled)
|
|
{
|
|
this.channel.RetransmitStopping(this.message.Headers.MessageId);
|
|
this.retransmitTimer.Cancel();
|
|
}
|
|
|
|
if (this.messageData.Array != null)
|
|
{
|
|
this.channel.BufferManager.ReturnBuffer(this.messageData.Array);
|
|
this.messageData = default(ArraySegment<byte>);
|
|
}
|
|
}
|
|
|
|
private void CompleteAndCleanup(bool completedSynchronously, Exception completionException)
|
|
{
|
|
this.Cleanup();
|
|
this.Complete(completedSynchronously, completionException);
|
|
}
|
|
}
|
|
|
|
// Control flow for async path
|
|
// We use this mechanism to avoid initializing two async objects as logically cleanup+close is one operation.
|
|
// At any point in the Begin* methods, we may go async. The steps are:
|
|
// - Cleanup channel
|
|
// - Close channel
|
|
private class CloseAsyncResult : AsyncResult
|
|
{
|
|
private static Action<object, TimeoutException> completeCleanupCallback = new Action<object, TimeoutException>(CompleteCleanup);
|
|
|
|
private UdpOutputChannel channel;
|
|
private TimeoutHelper timeoutHelper;
|
|
|
|
public CloseAsyncResult(UdpOutputChannel channel, TimeSpan timeout, AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.channel = channel;
|
|
this.timeoutHelper = new TimeoutHelper(timeout);
|
|
|
|
if (this.BeginCleanup())
|
|
{
|
|
this.Complete(true);
|
|
}
|
|
}
|
|
|
|
internal static void End(IAsyncResult result)
|
|
{
|
|
AsyncResult.End<CloseAsyncResult>(result);
|
|
}
|
|
|
|
private static void CompleteCleanup(object state, TimeoutException exception)
|
|
{
|
|
CloseAsyncResult thisPtr = (CloseAsyncResult)state;
|
|
Exception completionException = null;
|
|
|
|
if (exception != null)
|
|
{
|
|
Fx.Assert(exception.GetType() == typeof(TimeoutException), "Exception on callback should always be TimeoutException");
|
|
throw FxTrace.Exception.AsError(new TimeoutException(SR.TimeoutOnOperation(thisPtr.timeoutHelper.OriginalTimeout)));
|
|
}
|
|
|
|
try
|
|
{
|
|
lock (thisPtr.channel.ThisLock)
|
|
{
|
|
thisPtr.channel.retransmissionDoneWaitHandle = null;
|
|
|
|
// another thread could have called Abort while Close() was waiting for retransmission to complete.
|
|
if (!thisPtr.channel.cleanedUp)
|
|
{
|
|
// never aborting here
|
|
thisPtr.channel.CleanupAfterWait(false);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
completionException = e;
|
|
}
|
|
|
|
thisPtr.Complete(false, completionException);
|
|
}
|
|
|
|
private bool BeginCleanup()
|
|
{
|
|
bool needToWait = false;
|
|
|
|
if (this.channel.cleanedUp)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
lock (this.channel.ThisLock)
|
|
{
|
|
if (this.channel.cleanedUp)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// we're never aborting in this case...
|
|
if (this.channel.retransmitList != null && this.channel.retransmitList.Count > 0)
|
|
{
|
|
needToWait = true;
|
|
this.channel.retransmissionDoneWaitHandle = new AsyncWaitHandle(EventResetMode.ManualReset);
|
|
}
|
|
else
|
|
{
|
|
this.channel.CleanupAfterWait(false);
|
|
}
|
|
|
|
// we're guaranteed by CommunicationObject that at most ONE of Close or BeginClose will be called once.
|
|
// we don't null out retransmissionDoneEvent in the abort case; should be safe to use here.
|
|
return !needToWait || this.channel.retransmissionDoneWaitHandle.WaitAsync(completeCleanupCallback, this, this.timeoutHelper.RemainingTime());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|