e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1966 lines
74 KiB
C#
1966 lines
74 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
|
|
namespace System.ServiceModel.Channels
|
|
{
|
|
using System.Runtime;
|
|
using System.ServiceModel;
|
|
using System.ServiceModel.Diagnostics;
|
|
using System.ServiceModel.Diagnostics.Application;
|
|
using System.Threading;
|
|
using System.Xml;
|
|
|
|
abstract class ReliableDuplexSessionChannel : DuplexChannel, IDuplexSessionChannel
|
|
{
|
|
bool acknowledgementScheduled = false;
|
|
IOThreadTimer acknowledgementTimer;
|
|
UInt64 ackVersion = 1;
|
|
bool advertisedZero = false;
|
|
IReliableChannelBinder binder;
|
|
InterruptibleWaitObject closeOutputWaitObject;
|
|
SendWaitReliableRequestor closeRequestor;
|
|
DeliveryStrategy<Message> deliveryStrategy;
|
|
Guard guard = new Guard(Int32.MaxValue);
|
|
ReliableInputConnection inputConnection;
|
|
Exception maxRetryCountException = null;
|
|
static AsyncCallback onReceiveCompleted = Fx.ThunkCallback(new AsyncCallback(OnReceiveCompletedStatic));
|
|
ReliableOutputConnection outputConnection;
|
|
int pendingAcknowledgements = 0;
|
|
ChannelReliableSession session;
|
|
IReliableFactorySettings settings;
|
|
SendWaitReliableRequestor terminateRequestor;
|
|
static Action<object> asyncReceiveComplete = new Action<object>(AsyncReceiveCompleteStatic);
|
|
|
|
protected ReliableDuplexSessionChannel(ChannelManagerBase manager, IReliableFactorySettings settings, IReliableChannelBinder binder)
|
|
: base(manager, binder.LocalAddress)
|
|
{
|
|
this.binder = binder;
|
|
this.settings = settings;
|
|
this.acknowledgementTimer = new IOThreadTimer(new Action<object>(this.OnAcknowledgementTimeoutElapsed), null, true);
|
|
this.binder.Faulted += OnBinderFaulted;
|
|
this.binder.OnException += OnBinderException;
|
|
}
|
|
|
|
public IReliableChannelBinder Binder
|
|
{
|
|
get { return this.binder; }
|
|
}
|
|
|
|
public override EndpointAddress LocalAddress
|
|
{
|
|
get { return this.binder.LocalAddress; }
|
|
}
|
|
|
|
protected ReliableOutputConnection OutputConnection
|
|
{
|
|
get { return this.outputConnection; }
|
|
}
|
|
|
|
protected UniqueId OutputID
|
|
{
|
|
get { return this.session.OutputID; }
|
|
}
|
|
|
|
protected ChannelReliableSession ReliableSession
|
|
{
|
|
get { return this.session; }
|
|
}
|
|
|
|
public override EndpointAddress RemoteAddress
|
|
{
|
|
get { return this.binder.RemoteAddress; }
|
|
}
|
|
|
|
protected IReliableFactorySettings Settings
|
|
{
|
|
get { return this.settings; }
|
|
}
|
|
|
|
public override Uri Via
|
|
{
|
|
get { return this.RemoteAddress.Uri; }
|
|
}
|
|
|
|
public IDuplexSession Session
|
|
{
|
|
get { return (IDuplexSession)this.session; }
|
|
}
|
|
|
|
void AddPendingAcknowledgements(Message message)
|
|
{
|
|
lock (this.ThisLock)
|
|
{
|
|
if (this.pendingAcknowledgements > 0)
|
|
{
|
|
this.acknowledgementTimer.Cancel();
|
|
this.acknowledgementScheduled = false;
|
|
this.pendingAcknowledgements = 0;
|
|
this.ackVersion++;
|
|
|
|
int bufferRemaining = this.GetBufferRemaining();
|
|
|
|
WsrmUtilities.AddAcknowledgementHeader(
|
|
this.settings.ReliableMessagingVersion,
|
|
message,
|
|
this.session.InputID,
|
|
this.inputConnection.Ranges,
|
|
this.inputConnection.IsLastKnown,
|
|
bufferRemaining);
|
|
}
|
|
}
|
|
}
|
|
|
|
IAsyncResult BeginCloseBinder(TimeSpan timeout, AsyncCallback callback,
|
|
object state)
|
|
{
|
|
return this.binder.BeginClose(timeout, MaskingMode.Handled, callback, state);
|
|
}
|
|
|
|
void CloseSequence(TimeSpan timeout)
|
|
{
|
|
this.CreateCloseRequestor();
|
|
this.closeRequestor.Request(timeout);
|
|
// reply came from receive loop, receive loop owns verified message so nothing more to do.
|
|
}
|
|
|
|
IAsyncResult BeginCloseSequence(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
this.CreateCloseRequestor();
|
|
return this.closeRequestor.BeginRequest(timeout, callback, state);
|
|
}
|
|
|
|
void EndCloseSequence(IAsyncResult result)
|
|
{
|
|
this.closeRequestor.EndRequest(result);
|
|
// reply came from receive loop, receive loop owns verified message so nothing more to do.
|
|
}
|
|
|
|
void ConfigureRequestor(ReliableRequestor requestor)
|
|
{
|
|
requestor.MessageVersion = this.settings.MessageVersion;
|
|
requestor.Binder = this.binder;
|
|
requestor.SetRequestResponsePattern();
|
|
}
|
|
|
|
Message CreateAcknowledgmentMessage()
|
|
{
|
|
lock (this.ThisLock)
|
|
this.ackVersion++;
|
|
|
|
int bufferRemaining = this.GetBufferRemaining();
|
|
|
|
Message message = WsrmUtilities.CreateAcknowledgmentMessage(this.Settings.MessageVersion,
|
|
this.Settings.ReliableMessagingVersion, this.session.InputID, this.inputConnection.Ranges,
|
|
this.inputConnection.IsLastKnown, bufferRemaining);
|
|
|
|
if (TD.SequenceAcknowledgementSentIsEnabled())
|
|
{
|
|
TD.SequenceAcknowledgementSent(this.session.Id);
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
void CreateCloseRequestor()
|
|
{
|
|
SendWaitReliableRequestor temp = new SendWaitReliableRequestor();
|
|
|
|
this.ConfigureRequestor(temp);
|
|
temp.TimeoutString1Index = SR.TimeoutOnClose;
|
|
temp.MessageAction = WsrmIndex.GetCloseSequenceActionHeader(
|
|
this.settings.MessageVersion.Addressing);
|
|
temp.MessageBody = new CloseSequence(this.session.OutputID, this.outputConnection.Last);
|
|
|
|
lock (this.ThisLock)
|
|
{
|
|
this.ThrowIfClosed();
|
|
this.closeRequestor = temp;
|
|
}
|
|
}
|
|
|
|
void CreateTerminateRequestor()
|
|
{
|
|
SendWaitReliableRequestor temp = new SendWaitReliableRequestor();
|
|
|
|
this.ConfigureRequestor(temp);
|
|
ReliableMessagingVersion reliableMessagingVersion = this.settings.ReliableMessagingVersion;
|
|
temp.MessageAction = WsrmIndex.GetTerminateSequenceActionHeader(
|
|
this.settings.MessageVersion.Addressing, reliableMessagingVersion);
|
|
temp.MessageBody = new TerminateSequence(reliableMessagingVersion, this.session.OutputID,
|
|
this.outputConnection.Last);
|
|
|
|
lock (this.ThisLock)
|
|
{
|
|
this.ThrowIfClosed();
|
|
this.terminateRequestor = temp;
|
|
|
|
if (this.inputConnection.IsLastKnown)
|
|
{
|
|
this.session.CloseSession();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EndCloseBinder(IAsyncResult result)
|
|
{
|
|
this.binder.EndClose(result);
|
|
}
|
|
|
|
int GetBufferRemaining()
|
|
{
|
|
int bufferRemaining = -1;
|
|
|
|
if (this.settings.FlowControlEnabled)
|
|
{
|
|
bufferRemaining = this.settings.MaxTransferWindowSize - this.deliveryStrategy.EnqueuedCount;
|
|
this.advertisedZero = (bufferRemaining == 0);
|
|
}
|
|
|
|
return bufferRemaining;
|
|
}
|
|
|
|
public override T GetProperty<T>()
|
|
{
|
|
if (typeof(T) == typeof(IDuplexSessionChannel))
|
|
{
|
|
return (T)(object)this;
|
|
}
|
|
|
|
T baseProperty = base.GetProperty<T>();
|
|
if (baseProperty != null)
|
|
{
|
|
return baseProperty;
|
|
}
|
|
|
|
T innerProperty = this.binder.Channel.GetProperty<T>();
|
|
if ((innerProperty == null) && (typeof(T) == typeof(FaultConverter)))
|
|
{
|
|
return (T)(object)FaultConverter.GetDefaultFaultConverter(this.settings.MessageVersion);
|
|
}
|
|
else
|
|
{
|
|
return innerProperty;
|
|
}
|
|
}
|
|
|
|
void InternalCloseOutputSession(TimeSpan timeout)
|
|
{
|
|
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
|
|
this.outputConnection.Close(timeoutHelper.RemainingTime());
|
|
|
|
if (this.settings.ReliableMessagingVersion == ReliableMessagingVersion.WSReliableMessaging11)
|
|
{
|
|
this.CloseSequence(timeoutHelper.RemainingTime());
|
|
}
|
|
|
|
this.TerminateSequence(timeoutHelper.RemainingTime());
|
|
}
|
|
|
|
IAsyncResult BeginInternalCloseOutputSession(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
bool wsrm11 = this.settings.ReliableMessagingVersion == ReliableMessagingVersion.WSReliableMessaging11;
|
|
|
|
OperationWithTimeoutBeginCallback[] beginOperations = new OperationWithTimeoutBeginCallback[] {
|
|
this.outputConnection.BeginClose,
|
|
wsrm11 ? this.BeginCloseSequence : default(OperationWithTimeoutBeginCallback),
|
|
this.BeginTerminateSequence };
|
|
|
|
OperationEndCallback[] endOperations = new OperationEndCallback[] {
|
|
this.outputConnection.EndClose,
|
|
wsrm11 ? this.EndCloseSequence : default(OperationEndCallback),
|
|
this.EndTerminateSequence };
|
|
|
|
return OperationWithTimeoutComposer.BeginComposeAsyncOperations(timeout,
|
|
beginOperations, endOperations, callback, state);
|
|
}
|
|
|
|
void EndInternalCloseOutputSession(IAsyncResult result)
|
|
{
|
|
OperationWithTimeoutComposer.EndComposeAsyncOperations(result);
|
|
}
|
|
|
|
protected virtual void OnRemoteActivity()
|
|
{
|
|
this.session.OnRemoteActivity(false);
|
|
}
|
|
|
|
WsrmFault ProcessCloseOrTerminateSequenceResponse(bool close, WsrmMessageInfo info)
|
|
{
|
|
SendWaitReliableRequestor requestor = close ? this.closeRequestor : this.terminateRequestor;
|
|
|
|
if (requestor != null)
|
|
{
|
|
WsrmFault fault = close
|
|
? WsrmUtilities.ValidateCloseSequenceResponse(this.session, this.closeRequestor.MessageId, info,
|
|
this.outputConnection.Last)
|
|
: WsrmUtilities.ValidateTerminateSequenceResponse(this.session, this.terminateRequestor.MessageId,
|
|
info, this.outputConnection.Last);
|
|
|
|
if (fault != null)
|
|
{
|
|
return fault;
|
|
}
|
|
|
|
requestor.SetInfo(info);
|
|
return null;
|
|
}
|
|
|
|
string request = close ? Wsrm11Strings.CloseSequence : WsrmFeb2005Strings.TerminateSequence;
|
|
string faultString = SR.GetString(SR.ReceivedResponseBeforeRequestFaultString, request);
|
|
string exceptionString = SR.GetString(SR.ReceivedResponseBeforeRequestExceptionString, request);
|
|
return SequenceTerminatedFault.CreateProtocolFault(this.session.OutputID, faultString, exceptionString);
|
|
}
|
|
|
|
protected void ProcessDuplexMessage(WsrmMessageInfo info)
|
|
{
|
|
bool closeMessage = true;
|
|
|
|
try
|
|
{
|
|
bool wsrmFeb2005 = this.settings.ReliableMessagingVersion == ReliableMessagingVersion.WSReliableMessagingFebruary2005;
|
|
bool wsrm11 = this.settings.ReliableMessagingVersion == ReliableMessagingVersion.WSReliableMessaging11;
|
|
bool final = false;
|
|
|
|
if (this.outputConnection != null && info.AcknowledgementInfo != null)
|
|
{
|
|
final = wsrm11 && info.AcknowledgementInfo.Final;
|
|
|
|
int bufferRemaining = -1;
|
|
|
|
if (this.settings.FlowControlEnabled)
|
|
bufferRemaining = info.AcknowledgementInfo.BufferRemaining;
|
|
|
|
this.outputConnection.ProcessTransferred(info.AcknowledgementInfo.Ranges, bufferRemaining);
|
|
}
|
|
|
|
this.OnRemoteActivity();
|
|
|
|
bool tryAckNow = (info.AckRequestedInfo != null);
|
|
bool forceAck = false;
|
|
bool terminate = false;
|
|
bool scheduleShutdown = false;
|
|
UInt64 oldAckVersion = 0;
|
|
WsrmFault fault = null;
|
|
Message message = null;
|
|
Exception remoteFaultException = null;
|
|
|
|
if (info.SequencedMessageInfo != null)
|
|
{
|
|
bool needDispatch = false;
|
|
|
|
lock (this.ThisLock)
|
|
{
|
|
if (this.Aborted || this.State == CommunicationState.Faulted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Int64 sequenceNumber = info.SequencedMessageInfo.SequenceNumber;
|
|
bool isLast = wsrmFeb2005 && info.SequencedMessageInfo.LastMessage;
|
|
|
|
if (!this.inputConnection.IsValid(sequenceNumber, isLast))
|
|
{
|
|
if (wsrmFeb2005)
|
|
{
|
|
fault = new LastMessageNumberExceededFault(this.ReliableSession.InputID);
|
|
}
|
|
else
|
|
{
|
|
message = new SequenceClosedFault(this.session.InputID).CreateMessage(
|
|
this.settings.MessageVersion, this.settings.ReliableMessagingVersion);
|
|
forceAck = true;
|
|
|
|
this.OnMessageDropped();
|
|
}
|
|
}
|
|
else if (this.inputConnection.Ranges.Contains(sequenceNumber))
|
|
{
|
|
this.OnMessageDropped();
|
|
tryAckNow = true;
|
|
}
|
|
else if (wsrmFeb2005 && info.Action == WsrmFeb2005Strings.LastMessageAction)
|
|
{
|
|
this.inputConnection.Merge(sequenceNumber, isLast);
|
|
|
|
if (this.inputConnection.AllAdded)
|
|
{
|
|
scheduleShutdown = true;
|
|
|
|
if (this.outputConnection.CheckForTermination())
|
|
{
|
|
this.session.CloseSession();
|
|
}
|
|
}
|
|
}
|
|
else if (this.State == CommunicationState.Closing)
|
|
{
|
|
if (wsrmFeb2005)
|
|
{
|
|
fault = SequenceTerminatedFault.CreateProtocolFault(this.session.InputID,
|
|
SR.GetString(SR.SequenceTerminatedSessionClosedBeforeDone),
|
|
SR.GetString(SR.SessionClosedBeforeDone));
|
|
}
|
|
else
|
|
{
|
|
message = new SequenceClosedFault(this.session.InputID).CreateMessage(
|
|
this.settings.MessageVersion, this.settings.ReliableMessagingVersion);
|
|
forceAck = true;
|
|
|
|
this.OnMessageDropped();
|
|
}
|
|
}
|
|
// In the unordered case we accept no more than MaxSequenceRanges ranges to limit the
|
|
// serialized ack size and the amount of memory taken by the ack ranges. In the
|
|
// ordered case, the delivery strategy MaxTransferWindowSize quota mitigates this
|
|
// threat.
|
|
else if (this.deliveryStrategy.CanEnqueue(sequenceNumber)
|
|
&& (this.Settings.Ordered || this.inputConnection.CanMerge(sequenceNumber)))
|
|
{
|
|
this.inputConnection.Merge(sequenceNumber, isLast);
|
|
needDispatch = this.deliveryStrategy.Enqueue(info.Message, sequenceNumber);
|
|
closeMessage = false;
|
|
oldAckVersion = this.ackVersion;
|
|
this.pendingAcknowledgements++;
|
|
|
|
if (this.inputConnection.AllAdded)
|
|
{
|
|
scheduleShutdown = true;
|
|
|
|
if (this.outputConnection.CheckForTermination())
|
|
{
|
|
this.session.CloseSession();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.OnMessageDropped();
|
|
}
|
|
|
|
// if (ack now && we enqueued && an ack has been sent since we enqueued (and thus
|
|
// carries the sequence number of the message we just processed)) then we don't
|
|
// need to ack again.
|
|
if (this.inputConnection.IsLastKnown || this.pendingAcknowledgements == this.settings.MaxTransferWindowSize)
|
|
tryAckNow = true;
|
|
|
|
bool startTimer = tryAckNow || (this.pendingAcknowledgements > 0 && fault == null);
|
|
if (startTimer && !this.acknowledgementScheduled)
|
|
{
|
|
this.acknowledgementScheduled = true;
|
|
this.acknowledgementTimer.Set(this.settings.AcknowledgementInterval);
|
|
}
|
|
}
|
|
|
|
if (needDispatch)
|
|
{
|
|
this.Dispatch();
|
|
}
|
|
}
|
|
else if (wsrmFeb2005 && info.TerminateSequenceInfo != null)
|
|
{
|
|
bool isTerminateEarly;
|
|
|
|
lock (this.ThisLock)
|
|
{
|
|
isTerminateEarly = !this.inputConnection.Terminate();
|
|
}
|
|
|
|
if (isTerminateEarly)
|
|
{
|
|
fault = SequenceTerminatedFault.CreateProtocolFault(this.session.InputID,
|
|
SR.GetString(SR.SequenceTerminatedEarlyTerminateSequence),
|
|
SR.GetString(SR.EarlyTerminateSequence));
|
|
}
|
|
}
|
|
else if (wsrm11)
|
|
{
|
|
if (((info.TerminateSequenceInfo != null) && (info.TerminateSequenceInfo.Identifier == this.session.InputID))
|
|
|| (info.CloseSequenceInfo != null))
|
|
{
|
|
bool isTerminate = info.TerminateSequenceInfo != null;
|
|
WsrmRequestInfo requestInfo = isTerminate
|
|
? (WsrmRequestInfo)info.TerminateSequenceInfo
|
|
: (WsrmRequestInfo)info.CloseSequenceInfo;
|
|
Int64 last = isTerminate ? info.TerminateSequenceInfo.LastMsgNumber : info.CloseSequenceInfo.LastMsgNumber;
|
|
|
|
if (!WsrmUtilities.ValidateWsrmRequest(this.session, requestInfo, this.binder, null))
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isLastLargeEnough = true;
|
|
bool isLastConsistent = true;
|
|
|
|
lock (this.ThisLock)
|
|
{
|
|
if (!this.inputConnection.IsLastKnown)
|
|
{
|
|
if (isTerminate)
|
|
{
|
|
if (this.inputConnection.SetTerminateSequenceLast(last, out isLastLargeEnough))
|
|
{
|
|
scheduleShutdown = true;
|
|
}
|
|
else if (isLastLargeEnough)
|
|
{
|
|
remoteFaultException = new ProtocolException(SR.GetString(SR.EarlyTerminateSequence));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
scheduleShutdown = this.inputConnection.SetCloseSequenceLast(last);
|
|
isLastLargeEnough = scheduleShutdown;
|
|
}
|
|
|
|
if (scheduleShutdown)
|
|
{
|
|
this.session.SetFinalAck(this.inputConnection.Ranges);
|
|
if (this.terminateRequestor != null)
|
|
{
|
|
this.session.CloseSession();
|
|
}
|
|
|
|
this.deliveryStrategy.Dispose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
isLastConsistent = (last == this.inputConnection.Last);
|
|
|
|
// Have seen CloseSequence already, TerminateSequence means cleanup.
|
|
if (isTerminate && isLastConsistent && this.inputConnection.IsSequenceClosed)
|
|
{
|
|
terminate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isLastLargeEnough)
|
|
{
|
|
string faultString = SR.GetString(SR.SequenceTerminatedSmallLastMsgNumber);
|
|
string exceptionString = SR.GetString(SR.SmallLastMsgNumberExceptionString);
|
|
fault = SequenceTerminatedFault.CreateProtocolFault(this.session.InputID, faultString, exceptionString);
|
|
}
|
|
else if (!isLastConsistent)
|
|
{
|
|
string faultString = SR.GetString(SR.SequenceTerminatedInconsistentLastMsgNumber);
|
|
string exceptionString = SR.GetString(SR.InconsistentLastMsgNumberExceptionString);
|
|
fault = SequenceTerminatedFault.CreateProtocolFault(this.session.InputID, faultString, exceptionString);
|
|
}
|
|
else
|
|
{
|
|
message = isTerminate
|
|
? WsrmUtilities.CreateTerminateResponseMessage(this.settings.MessageVersion,
|
|
requestInfo.MessageId, this.session.InputID)
|
|
: WsrmUtilities.CreateCloseSequenceResponse(this.settings.MessageVersion,
|
|
requestInfo.MessageId, this.session.InputID);
|
|
forceAck = true;
|
|
}
|
|
}
|
|
else if (info.TerminateSequenceInfo != null) // Identifier == OutputID
|
|
{
|
|
fault = SequenceTerminatedFault.CreateProtocolFault(this.session.InputID,
|
|
SR.GetString(SR.SequenceTerminatedUnsupportedTerminateSequence),
|
|
SR.GetString(SR.UnsupportedTerminateSequenceExceptionString));
|
|
}
|
|
else if (info.TerminateSequenceResponseInfo != null)
|
|
{
|
|
fault = this.ProcessCloseOrTerminateSequenceResponse(false, info);
|
|
}
|
|
else if (info.CloseSequenceResponseInfo != null)
|
|
{
|
|
fault = this.ProcessCloseOrTerminateSequenceResponse(true, info);
|
|
}
|
|
else if (final)
|
|
{
|
|
if (this.closeRequestor == null)
|
|
{
|
|
string exceptionString = SR.GetString(SR.UnsupportedCloseExceptionString);
|
|
string faultString = SR.GetString(SR.SequenceTerminatedUnsupportedClose);
|
|
|
|
fault = SequenceTerminatedFault.CreateProtocolFault(this.session.OutputID, faultString,
|
|
exceptionString);
|
|
}
|
|
else
|
|
{
|
|
fault = WsrmUtilities.ValidateFinalAck(this.session, info, this.outputConnection.Last);
|
|
|
|
if (fault == null)
|
|
{
|
|
this.closeRequestor.SetInfo(info);
|
|
}
|
|
}
|
|
}
|
|
else if (info.WsrmHeaderFault != null)
|
|
{
|
|
if (!(info.WsrmHeaderFault is UnknownSequenceFault))
|
|
{
|
|
throw Fx.AssertAndThrow("Fault must be UnknownSequence fault.");
|
|
}
|
|
|
|
if (this.terminateRequestor == null)
|
|
{
|
|
throw Fx.AssertAndThrow("In wsrm11, if we start getting UnknownSequence, terminateRequestor cannot be null.");
|
|
}
|
|
|
|
this.terminateRequestor.SetInfo(info);
|
|
}
|
|
}
|
|
|
|
if (fault != null)
|
|
{
|
|
this.session.OnLocalFault(fault.CreateException(), fault, null);
|
|
return;
|
|
}
|
|
|
|
if (scheduleShutdown)
|
|
{
|
|
ActionItem.Schedule(this.ShutdownCallback, null);
|
|
}
|
|
|
|
if (message != null)
|
|
{
|
|
if (forceAck)
|
|
{
|
|
WsrmUtilities.AddAcknowledgementHeader(this.settings.ReliableMessagingVersion, message,
|
|
this.session.InputID, this.inputConnection.Ranges, true, this.GetBufferRemaining());
|
|
}
|
|
else if (tryAckNow)
|
|
{
|
|
this.AddPendingAcknowledgements(message);
|
|
}
|
|
}
|
|
else if (tryAckNow)
|
|
{
|
|
lock (this.ThisLock)
|
|
{
|
|
if (oldAckVersion != 0 && oldAckVersion != this.ackVersion)
|
|
return;
|
|
|
|
if (this.acknowledgementScheduled)
|
|
{
|
|
this.acknowledgementTimer.Cancel();
|
|
this.acknowledgementScheduled = false;
|
|
}
|
|
this.pendingAcknowledgements = 0;
|
|
}
|
|
|
|
message = this.CreateAcknowledgmentMessage();
|
|
}
|
|
|
|
if (message != null)
|
|
{
|
|
using (message)
|
|
{
|
|
if (this.guard.Enter())
|
|
{
|
|
try
|
|
{
|
|
this.binder.Send(message, this.DefaultSendTimeout);
|
|
}
|
|
finally
|
|
{
|
|
this.guard.Exit();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (terminate)
|
|
{
|
|
lock (this.ThisLock)
|
|
{
|
|
this.inputConnection.Terminate();
|
|
}
|
|
}
|
|
|
|
if (remoteFaultException != null)
|
|
{
|
|
this.ReliableSession.OnRemoteFault(remoteFaultException);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (closeMessage)
|
|
{
|
|
info.Message.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected abstract void ProcessMessage(WsrmMessageInfo info);
|
|
|
|
protected override void OnAbort()
|
|
{
|
|
if (this.outputConnection != null)
|
|
this.outputConnection.Abort(this);
|
|
|
|
if (this.inputConnection != null)
|
|
this.inputConnection.Abort(this);
|
|
|
|
this.guard.Abort();
|
|
|
|
ReliableRequestor tempRequestor = this.closeRequestor;
|
|
if (tempRequestor != null)
|
|
{
|
|
tempRequestor.Abort(this);
|
|
}
|
|
|
|
tempRequestor = this.terminateRequestor;
|
|
if (tempRequestor != null)
|
|
{
|
|
tempRequestor.Abort(this);
|
|
}
|
|
|
|
this.session.Abort();
|
|
}
|
|
|
|
void OnAcknowledgementTimeoutElapsed(object state)
|
|
{
|
|
lock (this.ThisLock)
|
|
{
|
|
this.acknowledgementScheduled = false;
|
|
this.pendingAcknowledgements = 0;
|
|
|
|
if (this.State == CommunicationState.Closing
|
|
|| this.State == CommunicationState.Closed
|
|
|| this.State == CommunicationState.Faulted)
|
|
return;
|
|
}
|
|
|
|
if (this.guard.Enter())
|
|
{
|
|
try
|
|
{
|
|
using (Message message = CreateAcknowledgmentMessage())
|
|
{
|
|
this.binder.Send(message, this.DefaultSendTimeout);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.guard.Exit();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected IAsyncResult OnBeginCloseOutputSession(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
bool complete = false;
|
|
|
|
lock (this.ThisLock)
|
|
{
|
|
this.ThrowIfNotOpened();
|
|
this.ThrowIfFaulted();
|
|
|
|
if ((this.State != CommunicationState.Opened)
|
|
|| (this.closeOutputWaitObject != null))
|
|
{
|
|
complete = true;
|
|
}
|
|
else
|
|
{
|
|
this.closeOutputWaitObject = new InterruptibleWaitObject(false, true);
|
|
}
|
|
}
|
|
|
|
if (complete)
|
|
{
|
|
return new CompletedAsyncResult(callback, state);
|
|
}
|
|
else
|
|
{
|
|
bool throwing = true;
|
|
|
|
try
|
|
{
|
|
IAsyncResult result = this.BeginInternalCloseOutputSession(timeout, callback,
|
|
state);
|
|
|
|
throwing = false;
|
|
return result;
|
|
}
|
|
finally
|
|
{
|
|
if (throwing)
|
|
{
|
|
this.session.OnLocalFault(null, SequenceTerminatedFault.CreateCommunicationFault(this.session.OutputID, SR.GetString(SR.CloseOutputSessionErrorReason), null), null);
|
|
this.closeOutputWaitObject.Fault(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
this.ThrowIfCloseInvalid();
|
|
|
|
OperationWithTimeoutBeginCallback closeOutputConnectionBeginCallback;
|
|
OperationEndCallback closeOutputConnectionEndCallback;
|
|
|
|
if (this.outputConnection == null)
|
|
{
|
|
closeOutputConnectionBeginCallback = default(OperationWithTimeoutBeginCallback);
|
|
closeOutputConnectionEndCallback = default(OperationEndCallback);
|
|
}
|
|
else if (this.closeOutputWaitObject == null)
|
|
{
|
|
closeOutputConnectionBeginCallback = new OperationWithTimeoutBeginCallback(
|
|
this.BeginInternalCloseOutputSession);
|
|
closeOutputConnectionEndCallback = new OperationEndCallback(
|
|
this.EndInternalCloseOutputSession);
|
|
}
|
|
else
|
|
{
|
|
closeOutputConnectionBeginCallback = new OperationWithTimeoutBeginCallback(
|
|
this.closeOutputWaitObject.BeginWait);
|
|
closeOutputConnectionEndCallback = new OperationEndCallback(
|
|
this.closeOutputWaitObject.EndWait);
|
|
}
|
|
|
|
OperationWithTimeoutBeginCallback closeInputConnectionBeginCallback;
|
|
OperationEndCallback closeInputConnectionEndCallback;
|
|
|
|
if (this.inputConnection == null)
|
|
{
|
|
closeInputConnectionBeginCallback = default(OperationWithTimeoutBeginCallback);
|
|
closeInputConnectionEndCallback = default(OperationEndCallback);
|
|
}
|
|
else
|
|
{
|
|
closeInputConnectionBeginCallback = new OperationWithTimeoutBeginCallback(
|
|
this.inputConnection.BeginClose);
|
|
closeInputConnectionEndCallback = new OperationEndCallback(
|
|
this.inputConnection.EndClose);
|
|
}
|
|
|
|
OperationWithTimeoutBeginCallback[] beginOperations;
|
|
OperationEndCallback[] endOperations;
|
|
|
|
beginOperations = new OperationWithTimeoutBeginCallback[]
|
|
{
|
|
closeOutputConnectionBeginCallback,
|
|
closeInputConnectionBeginCallback,
|
|
this.guard.BeginClose,
|
|
this.session.BeginClose,
|
|
this.BeginCloseBinder,
|
|
base.OnBeginClose
|
|
};
|
|
|
|
endOperations = new OperationEndCallback[]
|
|
{
|
|
closeOutputConnectionEndCallback,
|
|
closeInputConnectionEndCallback,
|
|
this.guard.EndClose,
|
|
this.session.EndClose,
|
|
this.EndCloseBinder,
|
|
base.OnEndClose
|
|
};
|
|
|
|
return OperationWithTimeoutComposer.BeginComposeAsyncOperations(timeout,
|
|
beginOperations, endOperations, callback, state);
|
|
}
|
|
|
|
protected override IAsyncResult OnBeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return this.outputConnection.BeginAddMessage(message, timeout, null, callback, state);
|
|
}
|
|
|
|
IAsyncResult OnBeginSendHandler(MessageAttemptInfo attemptInfo, TimeSpan timeout, bool maskUnhandledException, AsyncCallback callback, object state)
|
|
{
|
|
if (attemptInfo.RetryCount > this.settings.MaxRetryCount)
|
|
{
|
|
this.session.OnLocalFault(new CommunicationException(SR.GetString(SR.MaximumRetryCountExceeded), this.maxRetryCountException),
|
|
SequenceTerminatedFault.CreateMaxRetryCountExceededFault(this.session.OutputID), null);
|
|
return new CompletedAsyncResult(callback, state);
|
|
}
|
|
else
|
|
{
|
|
this.session.OnLocalActivity();
|
|
this.AddPendingAcknowledgements(attemptInfo.Message);
|
|
|
|
ReliableBinderSendAsyncResult result = new ReliableBinderSendAsyncResult(callback, state);
|
|
result.Binder = this.binder;
|
|
result.MessageAttemptInfo = attemptInfo;
|
|
result.MaskingMode = maskUnhandledException ? MaskingMode.Unhandled : MaskingMode.None;
|
|
|
|
if (attemptInfo.RetryCount < this.settings.MaxRetryCount)
|
|
{
|
|
result.MaskingMode |= MaskingMode.Handled;
|
|
result.SaveHandledException = false;
|
|
}
|
|
else
|
|
{
|
|
result.SaveHandledException = true;
|
|
}
|
|
|
|
result.Begin(timeout);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
IAsyncResult OnBeginSendAckRequestedHandler(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
this.session.OnLocalActivity();
|
|
|
|
ReliableBinderSendAsyncResult sendResult = new ReliableBinderSendAsyncResult(callback, state);
|
|
sendResult.Binder = this.binder;
|
|
sendResult.MaskingMode = MaskingMode.Handled;
|
|
sendResult.Message = WsrmUtilities.CreateAckRequestedMessage(this.Settings.MessageVersion,
|
|
this.Settings.ReliableMessagingVersion, this.ReliableSession.OutputID);
|
|
sendResult.Begin(timeout);
|
|
|
|
return sendResult;
|
|
}
|
|
|
|
void OnBinderException(IReliableChannelBinder sender, Exception exception)
|
|
{
|
|
if (exception is QuotaExceededException)
|
|
{
|
|
if (this.State == CommunicationState.Opening ||
|
|
this.State == CommunicationState.Opened ||
|
|
this.State == CommunicationState.Closing)
|
|
{
|
|
this.session.OnLocalFault(exception, SequenceTerminatedFault.CreateQuotaExceededFault(this.session.OutputID), null);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.EnqueueAndDispatch(exception, null, false);
|
|
}
|
|
}
|
|
|
|
void OnBinderFaulted(IReliableChannelBinder sender, Exception exception)
|
|
{
|
|
this.binder.Abort();
|
|
|
|
if (this.State == CommunicationState.Opening ||
|
|
this.State == CommunicationState.Opened ||
|
|
this.State == CommunicationState.Closing)
|
|
{
|
|
exception = new CommunicationException(SR.GetString(SR.EarlySecurityFaulted), exception);
|
|
this.session.OnLocalFault(exception, (Message)null, null);
|
|
}
|
|
}
|
|
|
|
// CloseOutputSession && Close: CloseOutputSession only closes the ReliableOutputConnection
|
|
// from the Opened state, if it does, it must create the closeOutputWaitObject so that
|
|
// close may properly synchronize. If no closeOutputWaitObject is present, Close may close
|
|
// the ---- safely since it is in the Closing state.
|
|
protected override void OnClose(TimeSpan timeout)
|
|
{
|
|
this.ThrowIfCloseInvalid();
|
|
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
|
|
|
|
if (this.outputConnection != null)
|
|
{
|
|
if (this.closeOutputWaitObject != null)
|
|
{
|
|
this.closeOutputWaitObject.Wait(timeoutHelper.RemainingTime());
|
|
}
|
|
else
|
|
{
|
|
this.InternalCloseOutputSession(timeoutHelper.RemainingTime());
|
|
}
|
|
|
|
this.inputConnection.Close(timeoutHelper.RemainingTime());
|
|
}
|
|
|
|
this.guard.Close(timeoutHelper.RemainingTime());
|
|
this.session.Close(timeoutHelper.RemainingTime());
|
|
this.binder.Close(timeoutHelper.RemainingTime(), MaskingMode.Handled);
|
|
base.OnClose(timeoutHelper.RemainingTime());
|
|
}
|
|
|
|
protected void OnCloseOutputSession(TimeSpan timeout)
|
|
{
|
|
lock (this.ThisLock)
|
|
{
|
|
this.ThrowIfNotOpened();
|
|
this.ThrowIfFaulted();
|
|
|
|
if ((this.State != CommunicationState.Opened)
|
|
|| (this.closeOutputWaitObject != null))
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.closeOutputWaitObject = new InterruptibleWaitObject(false, true);
|
|
}
|
|
|
|
bool throwing = true;
|
|
|
|
try
|
|
{
|
|
this.InternalCloseOutputSession(timeout);
|
|
throwing = false;
|
|
}
|
|
finally
|
|
{
|
|
if (throwing)
|
|
{
|
|
this.session.OnLocalFault(null, SequenceTerminatedFault.CreateCommunicationFault(this.session.OutputID, SR.GetString(SR.CloseOutputSessionErrorReason), null), null);
|
|
this.closeOutputWaitObject.Fault(this);
|
|
}
|
|
else
|
|
{
|
|
this.closeOutputWaitObject.Set();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnClosed()
|
|
{
|
|
base.OnClosed();
|
|
|
|
this.binder.Faulted -= this.OnBinderFaulted;
|
|
if (this.deliveryStrategy != null)
|
|
this.deliveryStrategy.Dispose();
|
|
}
|
|
|
|
protected override void OnClosing()
|
|
{
|
|
base.OnClosing();
|
|
this.acknowledgementTimer.Cancel();
|
|
}
|
|
|
|
void OnComponentFaulted(Exception faultException, WsrmFault fault)
|
|
{
|
|
this.session.OnLocalFault(faultException, fault, null);
|
|
}
|
|
|
|
void OnComponentException(Exception exception)
|
|
{
|
|
this.ReliableSession.OnUnknownException(exception);
|
|
}
|
|
|
|
protected override void OnEndClose(IAsyncResult result)
|
|
{
|
|
OperationWithTimeoutComposer.EndComposeAsyncOperations(result);
|
|
}
|
|
|
|
protected void OnEndCloseOutputSession(IAsyncResult result)
|
|
{
|
|
if (result is CompletedAsyncResult)
|
|
{
|
|
CompletedAsyncResult.End(result);
|
|
}
|
|
else
|
|
{
|
|
bool throwing = true;
|
|
try
|
|
{
|
|
this.EndInternalCloseOutputSession(result);
|
|
throwing = false;
|
|
}
|
|
finally
|
|
{
|
|
if (throwing)
|
|
{
|
|
this.session.OnLocalFault(null, SequenceTerminatedFault.CreateCommunicationFault(this.session.OutputID, SR.GetString(SR.CloseOutputSessionErrorReason), null), null);
|
|
this.closeOutputWaitObject.Fault(this);
|
|
}
|
|
else
|
|
{
|
|
this.closeOutputWaitObject.Set();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnEndSend(IAsyncResult result)
|
|
{
|
|
if (!this.outputConnection.EndAddMessage(result))
|
|
this.ThrowInvalidAddException();
|
|
}
|
|
|
|
void OnEndSendHandler(IAsyncResult result)
|
|
{
|
|
if (result is CompletedAsyncResult)
|
|
{
|
|
CompletedAsyncResult.End(result);
|
|
}
|
|
else
|
|
{
|
|
Exception handledException;
|
|
|
|
ReliableBinderSendAsyncResult.End(result, out handledException);
|
|
ReliableBinderSendAsyncResult sendResult = (ReliableBinderSendAsyncResult)result;
|
|
if (sendResult.MessageAttemptInfo.RetryCount == this.settings.MaxRetryCount)
|
|
{
|
|
this.maxRetryCountException = handledException;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnEndSendAckRequestedHandler(IAsyncResult result)
|
|
{
|
|
ReliableBinderSendAsyncResult.End(result);
|
|
}
|
|
|
|
protected override void OnFaulted()
|
|
{
|
|
this.session.OnFaulted();
|
|
this.UnblockClose();
|
|
base.OnFaulted();
|
|
}
|
|
|
|
protected override void OnSend(Message message, TimeSpan timeout)
|
|
{
|
|
if (!this.outputConnection.AddMessage(message, timeout, null))
|
|
this.ThrowInvalidAddException();
|
|
}
|
|
|
|
void OnSendHandler(MessageAttemptInfo attemptInfo, TimeSpan timeout, bool maskUnhandledException)
|
|
{
|
|
using (attemptInfo.Message)
|
|
{
|
|
if (attemptInfo.RetryCount > this.settings.MaxRetryCount)
|
|
{
|
|
this.session.OnLocalFault(new CommunicationException(SR.GetString(SR.MaximumRetryCountExceeded), this.maxRetryCountException),
|
|
SequenceTerminatedFault.CreateMaxRetryCountExceededFault(this.session.OutputID), null);
|
|
}
|
|
else
|
|
{
|
|
this.session.OnLocalActivity();
|
|
this.AddPendingAcknowledgements(attemptInfo.Message);
|
|
|
|
MaskingMode maskingMode = maskUnhandledException ? MaskingMode.Unhandled : MaskingMode.None;
|
|
|
|
if (attemptInfo.RetryCount < this.settings.MaxRetryCount)
|
|
{
|
|
maskingMode |= MaskingMode.Handled;
|
|
this.binder.Send(attemptInfo.Message, timeout, maskingMode);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
this.binder.Send(attemptInfo.Message, timeout, maskingMode);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
if (this.binder.IsHandleable(e))
|
|
{
|
|
this.maxRetryCountException = e;
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnSendAckRequestedHandler(TimeSpan timeout)
|
|
{
|
|
this.session.OnLocalActivity();
|
|
using (Message message = WsrmUtilities.CreateAckRequestedMessage(this.Settings.MessageVersion,
|
|
this.Settings.ReliableMessagingVersion, this.ReliableSession.OutputID))
|
|
{
|
|
this.binder.Send(message, timeout, MaskingMode.Handled);
|
|
}
|
|
}
|
|
|
|
static void OnReceiveCompletedStatic(IAsyncResult result)
|
|
{
|
|
if (result.CompletedSynchronously)
|
|
return;
|
|
ReliableDuplexSessionChannel channel = (ReliableDuplexSessionChannel)(result.AsyncState);
|
|
|
|
try
|
|
{
|
|
if (channel.HandleReceiveComplete(result))
|
|
{
|
|
channel.StartReceiving(true);
|
|
}
|
|
}
|
|
#pragma warning suppress 56500 // covered by FxCOP
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
channel.ReliableSession.OnUnknownException(e);
|
|
}
|
|
}
|
|
|
|
static void AsyncReceiveCompleteStatic(object state)
|
|
{
|
|
IAsyncResult result = (IAsyncResult)state;
|
|
ReliableDuplexSessionChannel channel = (ReliableDuplexSessionChannel)(result.AsyncState);
|
|
try
|
|
{
|
|
if (channel.HandleReceiveComplete(result))
|
|
{
|
|
channel.StartReceiving(true);
|
|
}
|
|
}
|
|
#pragma warning suppress 56500 // covered by FxCOP
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
channel.ReliableSession.OnUnknownException(e);
|
|
}
|
|
}
|
|
|
|
bool HandleReceiveComplete(IAsyncResult result)
|
|
{
|
|
RequestContext context;
|
|
if (this.Binder.EndTryReceive(result, out context))
|
|
{
|
|
if (context == null)
|
|
{
|
|
bool terminated = false;
|
|
|
|
lock (this.ThisLock)
|
|
{
|
|
terminated = this.inputConnection.Terminate();
|
|
}
|
|
|
|
if (!terminated && (this.Binder.State == CommunicationState.Opened))
|
|
{
|
|
Exception e = new CommunicationException(SR.GetString(SR.EarlySecurityClose));
|
|
this.ReliableSession.OnLocalFault(e, (Message)null, null);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Message message = context.RequestMessage;
|
|
context.Close();
|
|
|
|
WsrmMessageInfo info = WsrmMessageInfo.Get(this.settings.MessageVersion,
|
|
this.settings.ReliableMessagingVersion, this.binder.Channel, this.binder.GetInnerSession(),
|
|
message);
|
|
|
|
this.StartReceiving(false);
|
|
this.ProcessMessage(info);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected override void OnOpened()
|
|
{
|
|
base.OnOpened();
|
|
}
|
|
|
|
protected virtual void OnMessageDropped()
|
|
{
|
|
}
|
|
|
|
protected void SetConnections()
|
|
{
|
|
this.outputConnection = new ReliableOutputConnection(this.session.OutputID,
|
|
this.settings.MaxTransferWindowSize, this.Settings.MessageVersion,
|
|
this.Settings.ReliableMessagingVersion, this.session.InitiationTime, true, this.DefaultSendTimeout);
|
|
this.outputConnection.Faulted += OnComponentFaulted;
|
|
this.outputConnection.OnException += OnComponentException;
|
|
this.outputConnection.BeginSendHandler = OnBeginSendHandler;
|
|
this.outputConnection.EndSendHandler = OnEndSendHandler;
|
|
this.outputConnection.SendHandler = OnSendHandler;
|
|
this.outputConnection.BeginSendAckRequestedHandler = OnBeginSendAckRequestedHandler;
|
|
this.outputConnection.EndSendAckRequestedHandler = OnEndSendAckRequestedHandler;
|
|
this.outputConnection.SendAckRequestedHandler = OnSendAckRequestedHandler;
|
|
|
|
this.inputConnection = new ReliableInputConnection();
|
|
this.inputConnection.ReliableMessagingVersion = this.Settings.ReliableMessagingVersion;
|
|
|
|
if (this.settings.Ordered)
|
|
this.deliveryStrategy = new OrderedDeliveryStrategy<Message>(this, this.settings.MaxTransferWindowSize, false);
|
|
else
|
|
this.deliveryStrategy = new UnorderedDeliveryStrategy<Message>(this, this.settings.MaxTransferWindowSize);
|
|
|
|
this.deliveryStrategy.DequeueCallback = this.OnDeliveryStrategyItemDequeued;
|
|
}
|
|
|
|
protected void SetSession(ChannelReliableSession session)
|
|
{
|
|
session.UnblockChannelCloseCallback = this.UnblockClose;
|
|
this.session = session;
|
|
}
|
|
|
|
void OnDeliveryStrategyItemDequeued()
|
|
{
|
|
if (this.advertisedZero)
|
|
this.OnAcknowledgementTimeoutElapsed(null);
|
|
}
|
|
|
|
protected void StartReceiving(bool canBlock)
|
|
{
|
|
while (true)
|
|
{
|
|
IAsyncResult result = this.binder.BeginTryReceive(TimeSpan.MaxValue, onReceiveCompleted, this);
|
|
if (!result.CompletedSynchronously)
|
|
{
|
|
return;
|
|
}
|
|
if (!canBlock)
|
|
{
|
|
ActionItem.Schedule(asyncReceiveComplete, result);
|
|
return;
|
|
}
|
|
|
|
if (!this.HandleReceiveComplete(result))
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ShutdownCallback(object state)
|
|
{
|
|
this.Shutdown();
|
|
}
|
|
|
|
void TerminateSequence(TimeSpan timeout)
|
|
{
|
|
ReliableMessagingVersion reliableMessagingVersion = this.settings.ReliableMessagingVersion;
|
|
|
|
if (reliableMessagingVersion == ReliableMessagingVersion.WSReliableMessagingFebruary2005)
|
|
{
|
|
if (this.outputConnection.CheckForTermination())
|
|
{
|
|
this.session.CloseSession();
|
|
}
|
|
|
|
Message message = WsrmUtilities.CreateTerminateMessage(this.settings.MessageVersion,
|
|
reliableMessagingVersion, this.session.OutputID);
|
|
this.binder.Send(message, timeout, MaskingMode.Handled);
|
|
}
|
|
else if (reliableMessagingVersion == ReliableMessagingVersion.WSReliableMessaging11)
|
|
{
|
|
this.CreateTerminateRequestor();
|
|
this.terminateRequestor.Request(timeout);
|
|
// reply came from receive loop, receive loop owns verified message so nothing more to do.
|
|
}
|
|
else
|
|
{
|
|
throw Fx.AssertAndThrow("Reliable messaging version not supported.");
|
|
}
|
|
}
|
|
|
|
IAsyncResult BeginTerminateSequence(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
ReliableMessagingVersion reliableMessagingVersion = this.settings.ReliableMessagingVersion;
|
|
|
|
if (reliableMessagingVersion == ReliableMessagingVersion.WSReliableMessagingFebruary2005)
|
|
{
|
|
if (this.outputConnection.CheckForTermination())
|
|
{
|
|
this.session.CloseSession();
|
|
}
|
|
|
|
Message message = WsrmUtilities.CreateTerminateMessage(this.settings.MessageVersion,
|
|
reliableMessagingVersion, this.session.OutputID);
|
|
return this.binder.BeginSend(message, timeout, MaskingMode.Handled, callback, state);
|
|
}
|
|
else if (reliableMessagingVersion == ReliableMessagingVersion.WSReliableMessaging11)
|
|
{
|
|
this.CreateTerminateRequestor();
|
|
return this.terminateRequestor.BeginRequest(timeout, callback, state);
|
|
}
|
|
else
|
|
{
|
|
throw Fx.AssertAndThrow("Reliable messaging version not supported.");
|
|
}
|
|
}
|
|
|
|
void EndTerminateSequence(IAsyncResult result)
|
|
{
|
|
ReliableMessagingVersion reliableMessagingVersion = this.settings.ReliableMessagingVersion;
|
|
|
|
if (reliableMessagingVersion == ReliableMessagingVersion.WSReliableMessagingFebruary2005)
|
|
{
|
|
this.binder.EndSend(result);
|
|
}
|
|
else if (reliableMessagingVersion == ReliableMessagingVersion.WSReliableMessaging11)
|
|
{
|
|
this.terminateRequestor.EndRequest(result);
|
|
// reply came from receive loop, receive loop owns verified message so nothing more to do.
|
|
}
|
|
else
|
|
{
|
|
throw Fx.AssertAndThrow("Reliable messaging version not supported.");
|
|
}
|
|
}
|
|
|
|
void ThrowIfCloseInvalid()
|
|
{
|
|
bool shouldFault = false;
|
|
|
|
if (this.settings.ReliableMessagingVersion == ReliableMessagingVersion.WSReliableMessagingFebruary2005)
|
|
{
|
|
if (this.deliveryStrategy.EnqueuedCount > 0 || this.inputConnection.Ranges.Count > 1)
|
|
{
|
|
shouldFault = true;
|
|
}
|
|
}
|
|
else if (this.settings.ReliableMessagingVersion == ReliableMessagingVersion.WSReliableMessaging11)
|
|
{
|
|
if (this.deliveryStrategy.EnqueuedCount > 0)
|
|
{
|
|
shouldFault = true;
|
|
}
|
|
}
|
|
|
|
if (shouldFault)
|
|
{
|
|
WsrmFault fault = SequenceTerminatedFault.CreateProtocolFault(this.session.InputID,
|
|
SR.GetString(SR.SequenceTerminatedSessionClosedBeforeDone), SR.GetString(SR.SessionClosedBeforeDone));
|
|
this.session.OnLocalFault(null, fault, null);
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(fault.CreateException());
|
|
}
|
|
}
|
|
|
|
void ThrowInvalidAddException()
|
|
{
|
|
if (this.State == CommunicationState.Opened)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SendCannotBeCalledAfterCloseOutputSession)));
|
|
else if (this.State == CommunicationState.Faulted)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(this.GetTerminalException());
|
|
else
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(this.CreateClosedException());
|
|
}
|
|
|
|
void UnblockClose()
|
|
{
|
|
if (this.outputConnection != null)
|
|
{
|
|
this.outputConnection.Fault(this);
|
|
}
|
|
|
|
if (this.inputConnection != null)
|
|
{
|
|
this.inputConnection.Fault(this);
|
|
}
|
|
|
|
ReliableRequestor tempRequestor = this.closeRequestor;
|
|
if (tempRequestor != null)
|
|
{
|
|
tempRequestor.Fault(this);
|
|
}
|
|
|
|
tempRequestor = this.terminateRequestor;
|
|
if (tempRequestor != null)
|
|
{
|
|
tempRequestor.Fault(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
class ClientReliableDuplexSessionChannel : ReliableDuplexSessionChannel
|
|
{
|
|
ChannelParameterCollection channelParameters;
|
|
DuplexClientReliableSession clientSession;
|
|
TimeoutHelper closeTimeoutHelper;
|
|
bool closing;
|
|
static AsyncCallback onReconnectComplete = Fx.ThunkCallback(new AsyncCallback(OnReconnectComplete));
|
|
static Action<object> onReconnectTimerElapsed = new Action<object>(OnReconnectTimerElapsed);
|
|
|
|
public ClientReliableDuplexSessionChannel(ChannelManagerBase factory, IReliableFactorySettings settings,
|
|
IReliableChannelBinder binder, FaultHelper faultHelper,
|
|
LateBoundChannelParameterCollection channelParameters, UniqueId inputID)
|
|
: base(factory, settings, binder)
|
|
{
|
|
this.clientSession = new DuplexClientReliableSession(this, settings, faultHelper, inputID);
|
|
this.clientSession.PollingCallback = this.PollingCallback;
|
|
this.SetSession(this.clientSession);
|
|
|
|
this.channelParameters = channelParameters;
|
|
channelParameters.SetChannel(this);
|
|
((IClientReliableChannelBinder)binder).ConnectionLost += this.OnConnectionLost;
|
|
}
|
|
|
|
public override T GetProperty<T>()
|
|
{
|
|
if (typeof(T) == typeof(ChannelParameterCollection))
|
|
{
|
|
return (T)(object)this.channelParameters;
|
|
}
|
|
|
|
return base.GetProperty<T>();
|
|
}
|
|
|
|
void HandleReconnectComplete(IAsyncResult result)
|
|
{
|
|
bool handleException = true;
|
|
|
|
try
|
|
{
|
|
this.Binder.EndSend(result);
|
|
handleException = false;
|
|
|
|
lock (this.ThisLock)
|
|
{
|
|
if (this.Binder.Connected)
|
|
this.clientSession.ResumePolling(this.OutputConnection.Strategy.QuotaRemaining == 0);
|
|
else
|
|
this.WaitForReconnect();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
if (handleException)
|
|
this.WaitForReconnect();
|
|
else
|
|
throw;
|
|
}
|
|
}
|
|
|
|
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
this.closeTimeoutHelper = new TimeoutHelper(timeout);
|
|
this.closing = true;
|
|
return base.OnBeginClose(timeout, callback, state);
|
|
}
|
|
|
|
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return new ReliableChannelOpenAsyncResult(this.Binder, this.ReliableSession,
|
|
timeout, callback, state);
|
|
}
|
|
|
|
protected override void OnClose(TimeSpan timeout)
|
|
{
|
|
this.closeTimeoutHelper = new TimeoutHelper(timeout);
|
|
this.closing = true;
|
|
base.OnClose(timeout);
|
|
}
|
|
|
|
void OnConnectionLost(object sender, EventArgs args)
|
|
{
|
|
lock (this.ThisLock)
|
|
{
|
|
if ((this.State == CommunicationState.Opened || this.State == CommunicationState.Closing) &&
|
|
!this.Binder.Connected && this.clientSession.StopPolling())
|
|
{
|
|
|
|
if (TD.ClientReliableSessionReconnectIsEnabled())
|
|
{
|
|
TD.ClientReliableSessionReconnect(this.clientSession.Id);
|
|
}
|
|
|
|
this.Reconnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnEndOpen(IAsyncResult result)
|
|
{
|
|
ReliableChannelOpenAsyncResult.End(result);
|
|
}
|
|
|
|
protected override void OnOpen(TimeSpan timeout)
|
|
{
|
|
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
|
|
bool throwing = true;
|
|
|
|
try
|
|
{
|
|
this.Binder.Open(timeoutHelper.RemainingTime());
|
|
this.ReliableSession.Open(timeoutHelper.RemainingTime());
|
|
throwing = false;
|
|
}
|
|
finally
|
|
{
|
|
if (throwing)
|
|
{
|
|
this.Binder.Close(timeoutHelper.RemainingTime());
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnOpened()
|
|
{
|
|
base.OnOpened();
|
|
this.SetConnections();
|
|
|
|
if (Thread.CurrentThread.IsThreadPoolThread)
|
|
{
|
|
try
|
|
{
|
|
this.StartReceiving(false);
|
|
}
|
|
#pragma warning suppress 56500 // covered by FxCOP
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
this.ReliableSession.OnUnknownException(e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ActionItem.Schedule(new Action<object>(StartReceivingStatic), this);
|
|
}
|
|
}
|
|
|
|
static void OnReconnectComplete(IAsyncResult result)
|
|
{
|
|
if (result.CompletedSynchronously)
|
|
return;
|
|
|
|
ClientReliableDuplexSessionChannel channel = (ClientReliableDuplexSessionChannel)result.AsyncState;
|
|
channel.HandleReconnectComplete(result);
|
|
}
|
|
|
|
static void OnReconnectTimerElapsed(object state)
|
|
{
|
|
ClientReliableDuplexSessionChannel channel = (ClientReliableDuplexSessionChannel)state;
|
|
|
|
lock (channel.ThisLock)
|
|
{
|
|
if ((channel.State == CommunicationState.Opened || channel.State == CommunicationState.Closing) &&
|
|
!channel.Binder.Connected)
|
|
{
|
|
channel.Reconnect();
|
|
}
|
|
else
|
|
{
|
|
channel.clientSession.ResumePolling(channel.OutputConnection.Strategy.QuotaRemaining == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnRemoteActivity()
|
|
{
|
|
this.ReliableSession.OnRemoteActivity(this.OutputConnection.Strategy.QuotaRemaining == 0);
|
|
}
|
|
|
|
void PollingCallback()
|
|
{
|
|
using (Message message = WsrmUtilities.CreateAckRequestedMessage(this.Settings.MessageVersion,
|
|
this.Settings.ReliableMessagingVersion, this.ReliableSession.OutputID))
|
|
{
|
|
this.Binder.Send(message, this.DefaultSendTimeout);
|
|
}
|
|
}
|
|
|
|
protected override void ProcessMessage(WsrmMessageInfo info)
|
|
{
|
|
if (!this.ReliableSession.ProcessInfo(info, null))
|
|
return;
|
|
|
|
if (!this.ReliableSession.VerifyDuplexProtocolElements(info, null))
|
|
return;
|
|
|
|
this.ProcessDuplexMessage(info);
|
|
}
|
|
|
|
static void StartReceivingStatic(object state)
|
|
{
|
|
ClientReliableDuplexSessionChannel channel = (ClientReliableDuplexSessionChannel)state;
|
|
|
|
try
|
|
{
|
|
channel.StartReceiving(true);
|
|
}
|
|
#pragma warning suppress 56500 // covered by FxCOP
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
channel.ReliableSession.OnUnknownException(e);
|
|
}
|
|
}
|
|
|
|
// It is safe to call this in a lock.
|
|
void Reconnect()
|
|
{
|
|
bool handleException = true;
|
|
|
|
try
|
|
{
|
|
Message message = WsrmUtilities.CreateAckRequestedMessage(this.Settings.MessageVersion,
|
|
this.Settings.ReliableMessagingVersion, this.ReliableSession.OutputID);
|
|
TimeSpan timeout = this.closing ? this.closeTimeoutHelper.RemainingTime() : this.DefaultCloseTimeout;
|
|
IAsyncResult result = this.Binder.BeginSend(message, timeout, onReconnectComplete, this);
|
|
|
|
handleException = false;
|
|
if (result.CompletedSynchronously)
|
|
this.HandleReconnectComplete(result);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
if (handleException)
|
|
this.WaitForReconnect();
|
|
else
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// If anything throws out of this method, we'll consider it fatal.
|
|
void WaitForReconnect()
|
|
{
|
|
TimeSpan timeout;
|
|
|
|
if (this.closing)
|
|
timeout = TimeoutHelper.Divide(this.closeTimeoutHelper.RemainingTime(), 2);
|
|
else
|
|
timeout = TimeoutHelper.Divide(this.DefaultSendTimeout, 2);
|
|
|
|
IOThreadTimer timer = new IOThreadTimer(onReconnectTimerElapsed, this, false);
|
|
timer.Set(timeout);
|
|
}
|
|
|
|
class DuplexClientReliableSession : ClientReliableSession, IDuplexSession
|
|
{
|
|
ClientReliableDuplexSessionChannel channel;
|
|
|
|
public DuplexClientReliableSession(ClientReliableDuplexSessionChannel channel,
|
|
IReliableFactorySettings settings, FaultHelper helper, UniqueId inputID)
|
|
: base(channel, settings, (IClientReliableChannelBinder)channel.Binder, helper, inputID)
|
|
{
|
|
this.channel = channel;
|
|
}
|
|
|
|
public IAsyncResult BeginCloseOutputSession(AsyncCallback callback, object state)
|
|
{
|
|
return this.BeginCloseOutputSession(this.channel.DefaultCloseTimeout, callback, state);
|
|
}
|
|
|
|
public IAsyncResult BeginCloseOutputSession(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return channel.OnBeginCloseOutputSession(timeout, callback, state);
|
|
}
|
|
|
|
public void EndCloseOutputSession(IAsyncResult result)
|
|
{
|
|
channel.OnEndCloseOutputSession(result);
|
|
}
|
|
|
|
public void CloseOutputSession()
|
|
{
|
|
this.CloseOutputSession(this.channel.DefaultCloseTimeout);
|
|
}
|
|
|
|
public void CloseOutputSession(TimeSpan timeout)
|
|
{
|
|
channel.OnCloseOutputSession(timeout);
|
|
}
|
|
}
|
|
}
|
|
|
|
sealed class ServerReliableDuplexSessionChannel : ReliableDuplexSessionChannel
|
|
{
|
|
ReliableChannelListenerBase<IDuplexSessionChannel> listener;
|
|
string perfCounterId;
|
|
|
|
public ServerReliableDuplexSessionChannel(
|
|
ReliableChannelListenerBase<IDuplexSessionChannel> listener,
|
|
IReliableChannelBinder binder, FaultHelper faultHelper,
|
|
UniqueId inputID,
|
|
UniqueId outputID)
|
|
: base(listener, listener, binder)
|
|
{
|
|
this.listener = listener;
|
|
DuplexServerReliableSession session = new DuplexServerReliableSession(this, listener, faultHelper, inputID, outputID);
|
|
this.SetSession(session);
|
|
session.Open(TimeSpan.Zero);
|
|
this.SetConnections();
|
|
|
|
if (PerformanceCounters.PerformanceCountersEnabled)
|
|
this.perfCounterId = this.listener.Uri.ToString().ToUpperInvariant();
|
|
|
|
if (binder.HasSession)
|
|
{
|
|
try
|
|
{
|
|
this.StartReceiving(false);
|
|
}
|
|
#pragma warning suppress 56500 // covered by FxCOP
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
this.ReliableSession.OnUnknownException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
IAsyncResult BeginUnregisterChannel(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return this.listener.OnReliableChannelBeginClose(this.ReliableSession.InputID,
|
|
this.ReliableSession.OutputID, timeout, callback, state);
|
|
}
|
|
|
|
void EndUnregisterChannel(IAsyncResult result)
|
|
{
|
|
this.listener.OnReliableChannelEndClose(result);
|
|
}
|
|
|
|
// Close/Abort: The base Close/Abort is called first because it is shutting down the
|
|
// channel. Shutting down the server state should be done after shutting down the channel.
|
|
protected override void OnAbort()
|
|
{
|
|
base.OnAbort();
|
|
this.listener.OnReliableChannelAbort(this.ReliableSession.InputID,
|
|
this.ReliableSession.OutputID);
|
|
}
|
|
|
|
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback,
|
|
object state)
|
|
{
|
|
OperationWithTimeoutBeginCallback[] beginOperations =
|
|
new OperationWithTimeoutBeginCallback[] {
|
|
new OperationWithTimeoutBeginCallback(base.OnBeginClose),
|
|
new OperationWithTimeoutBeginCallback(this.BeginUnregisterChannel) };
|
|
|
|
OperationEndCallback[] endOperations =
|
|
new OperationEndCallback[] {
|
|
new OperationEndCallback(base.OnEndClose),
|
|
new OperationEndCallback(this.EndUnregisterChannel) };
|
|
|
|
return OperationWithTimeoutComposer.BeginComposeAsyncOperations(timeout,
|
|
beginOperations, endOperations, callback, state);
|
|
}
|
|
|
|
protected override void OnClose(TimeSpan timeout)
|
|
{
|
|
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
|
|
base.OnClose(timeoutHelper.RemainingTime());
|
|
this.listener.OnReliableChannelClose(this.ReliableSession.InputID,
|
|
this.ReliableSession.OutputID, timeoutHelper.RemainingTime());
|
|
}
|
|
|
|
protected override void OnEndClose(IAsyncResult result)
|
|
{
|
|
OperationWithTimeoutComposer.EndComposeAsyncOperations(result);
|
|
}
|
|
|
|
protected override void OnOpen(TimeSpan timeout)
|
|
{
|
|
}
|
|
|
|
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return new CompletedAsyncResult(callback, state);
|
|
}
|
|
|
|
protected override void OnEndOpen(IAsyncResult result)
|
|
{
|
|
CompletedAsyncResult.End(result);
|
|
}
|
|
|
|
protected override void OnFaulted()
|
|
{
|
|
base.OnFaulted();
|
|
if (PerformanceCounters.PerformanceCountersEnabled)
|
|
PerformanceCounters.SessionFaulted(this.perfCounterId);
|
|
}
|
|
|
|
protected override void OnMessageDropped()
|
|
{
|
|
if (PerformanceCounters.PerformanceCountersEnabled)
|
|
PerformanceCounters.MessageDropped(this.perfCounterId);
|
|
}
|
|
|
|
public void ProcessDemuxedMessage(WsrmMessageInfo info)
|
|
{
|
|
try
|
|
{
|
|
this.ProcessMessage(info);
|
|
}
|
|
#pragma warning suppress 56500 // covered by FxCOP
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
throw;
|
|
|
|
this.ReliableSession.OnUnknownException(e);
|
|
}
|
|
}
|
|
|
|
protected override void ProcessMessage(WsrmMessageInfo info)
|
|
{
|
|
if (!this.ReliableSession.ProcessInfo(info, null))
|
|
return;
|
|
|
|
if (!this.ReliableSession.VerifyDuplexProtocolElements(info, null))
|
|
return;
|
|
|
|
if (info.CreateSequenceInfo != null)
|
|
{
|
|
EndpointAddress acksTo;
|
|
|
|
if (WsrmUtilities.ValidateCreateSequence<IDuplexSessionChannel>(info, this.listener, this.Binder.Channel, out acksTo))
|
|
{
|
|
Message response = WsrmUtilities.CreateCreateSequenceResponse(this.Settings.MessageVersion,
|
|
this.Settings.ReliableMessagingVersion, true, info.CreateSequenceInfo, this.Settings.Ordered,
|
|
this.ReliableSession.InputID, acksTo);
|
|
using (info.Message)
|
|
{
|
|
using (response)
|
|
{
|
|
if (((IServerReliableChannelBinder)this.Binder).AddressResponse(info.Message, response))
|
|
this.Binder.Send(response, this.DefaultSendTimeout);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.ReliableSession.OnLocalFault(info.FaultException, info.FaultReply, null);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
this.ProcessDuplexMessage(info);
|
|
}
|
|
|
|
class DuplexServerReliableSession : ServerReliableSession, IDuplexSession
|
|
{
|
|
ServerReliableDuplexSessionChannel channel;
|
|
|
|
public DuplexServerReliableSession(ServerReliableDuplexSessionChannel channel,
|
|
ReliableChannelListenerBase<IDuplexSessionChannel> listener, FaultHelper faultHelper, UniqueId inputID,
|
|
UniqueId outputID)
|
|
: base(channel, listener, (IServerReliableChannelBinder)channel.Binder, faultHelper, inputID, outputID)
|
|
{
|
|
this.channel = channel;
|
|
}
|
|
|
|
public IAsyncResult BeginCloseOutputSession(AsyncCallback callback, object state)
|
|
{
|
|
return this.BeginCloseOutputSession(this.channel.DefaultCloseTimeout, callback, state);
|
|
}
|
|
|
|
public IAsyncResult BeginCloseOutputSession(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return channel.OnBeginCloseOutputSession(timeout, callback, state);
|
|
}
|
|
|
|
public void EndCloseOutputSession(IAsyncResult result)
|
|
{
|
|
channel.OnEndCloseOutputSession(result);
|
|
}
|
|
|
|
public void CloseOutputSession()
|
|
{
|
|
this.CloseOutputSession(this.channel.DefaultCloseTimeout);
|
|
}
|
|
|
|
public void CloseOutputSession(TimeSpan timeout)
|
|
{
|
|
channel.OnCloseOutputSession(timeout);
|
|
}
|
|
}
|
|
}
|
|
}
|