| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859 |
- //-----------------------------------------------------------------------------
- // Copyright (c) Microsoft Corporation. All rights reserved.
- //-----------------------------------------------------------------------------
- namespace System.ServiceModel.Dispatcher
- {
- using System;
- using System.Collections.ObjectModel;
- using System.Diagnostics;
- using System.Runtime;
- using System.Runtime.CompilerServices;
- using System.Runtime.Diagnostics;
- using System.Security;
- using System.ServiceModel;
- using System.ServiceModel.Activation;
- using System.ServiceModel.Channels;
- using System.ServiceModel.Diagnostics;
- using System.Threading;
- using System.Xml;
- using System.Transactions;
- using System.ServiceModel.Diagnostics.Application;
- delegate void MessageRpcProcessor(ref MessageRpc rpc);
- struct MessageRpc
- {
- internal readonly ServiceChannel Channel;
- internal readonly ChannelHandler channelHandler;
- internal readonly object[] Correlation;
- internal readonly ServiceHostBase Host;
- internal readonly OperationContext OperationContext;
- internal ServiceModelActivity Activity;
- internal Guid ResponseActivityId;
- internal IAsyncResult AsyncResult;
- internal bool CanSendReply;
- internal bool SuccessfullySendReply;
- internal CorrelationCallbackMessageProperty CorrelationCallback;
- internal object[] InputParameters;
- internal object[] OutputParameters;
- internal object ReturnParameter;
- internal bool ParametersDisposed;
- internal bool DidDeserializeRequestBody;
- internal TransactionMessageProperty TransactionMessageProperty;
- internal TransactedBatchContext TransactedBatchContext;
- internal Exception Error;
- internal MessageRpcProcessor ErrorProcessor;
- internal ErrorHandlerFaultInfo FaultInfo;
- internal bool HasSecurityContext;
- internal object Instance;
- internal bool MessageRpcOwnsInstanceContextThrottle;
- internal MessageRpcProcessor NextProcessor;
- internal Collection<MessageHeaderInfo> NotUnderstoodHeaders;
- internal DispatchOperationRuntime Operation;
- internal Message Request;
- internal RequestContext RequestContext;
- internal bool RequestContextThrewOnReply;
- internal UniqueId RequestID;
- internal Message Reply;
- internal TimeoutHelper ReplyTimeoutHelper;
- internal RequestReplyCorrelator.ReplyToInfo ReplyToInfo;
- internal MessageVersion RequestVersion;
- internal ServiceSecurityContext SecurityContext;
- internal InstanceContext InstanceContext;
- internal bool SuccessfullyBoundInstance;
- internal bool SuccessfullyIncrementedActivity;
- internal bool SuccessfullyLockedInstance;
- internal ReceiveContextRPCFacet ReceiveContext;
- internal TransactionRpcFacet transaction;
- internal IAspNetMessageProperty HostingProperty;
- internal MessageRpcInvokeNotification InvokeNotification;
- internal EventTraceActivity EventTraceActivity;
- static AsyncCallback handleEndComplete = Fx.ThunkCallback(new AsyncCallback(HandleEndComplete));
- static AsyncCallback handleEndAbandon = Fx.ThunkCallback(new AsyncCallback(HandleEndAbandon));
- bool paused;
- bool switchedThreads;
- bool isInstanceContextSingleton;
- SignalGate<IAsyncResult> invokeContinueGate;
- internal MessageRpc(RequestContext requestContext, Message request, DispatchOperationRuntime operation,
- ServiceChannel channel, ServiceHostBase host, ChannelHandler channelHandler, bool cleanThread,
- OperationContext operationContext, InstanceContext instanceContext, EventTraceActivity eventTraceActivity)
- {
- Fx.Assert((operationContext != null), "System.ServiceModel.Dispatcher.MessageRpc.MessageRpc(), operationContext == null");
- Fx.Assert(channelHandler != null, "System.ServiceModel.Dispatcher.MessageRpc.MessageRpc(), channelHandler == null");
- this.Activity = null;
- this.EventTraceActivity = eventTraceActivity;
- this.AsyncResult = null;
- this.CanSendReply = true;
- this.Channel = channel;
- this.channelHandler = channelHandler;
- this.Correlation = EmptyArray.Allocate(operation.Parent.CorrelationCount);
- this.CorrelationCallback = null;
- this.DidDeserializeRequestBody = false;
- this.TransactionMessageProperty = null;
- this.TransactedBatchContext = null;
- this.Error = null;
- this.ErrorProcessor = null;
- this.FaultInfo = new ErrorHandlerFaultInfo(request.Version.Addressing.DefaultFaultAction);
- this.HasSecurityContext = false;
- this.Host = host;
- this.Instance = null;
- this.MessageRpcOwnsInstanceContextThrottle = false;
- this.NextProcessor = null;
- this.NotUnderstoodHeaders = null;
- this.Operation = operation;
- this.OperationContext = operationContext;
- this.paused = false;
- this.ParametersDisposed = false;
- this.ReceiveContext = null;
- this.Request = request;
- this.RequestContext = requestContext;
- this.RequestContextThrewOnReply = false;
- this.SuccessfullySendReply = false;
- this.RequestVersion = request.Version;
- this.Reply = null;
- this.ReplyTimeoutHelper = new TimeoutHelper();
- this.SecurityContext = null;
- this.InstanceContext = instanceContext;
- this.SuccessfullyBoundInstance = false;
- this.SuccessfullyIncrementedActivity = false;
- this.SuccessfullyLockedInstance = false;
- this.switchedThreads = !cleanThread;
- this.transaction = null;
- this.InputParameters = null;
- this.OutputParameters = null;
- this.ReturnParameter = null;
- this.isInstanceContextSingleton = InstanceContextProviderBase.IsProviderSingleton(this.Channel.DispatchRuntime.InstanceContextProvider);
- this.invokeContinueGate = null;
- if (!operation.IsOneWay && !operation.Parent.ManualAddressing)
- {
- this.RequestID = request.Headers.MessageId;
- this.ReplyToInfo = new RequestReplyCorrelator.ReplyToInfo(request);
- }
- else
- {
- this.RequestID = null;
- this.ReplyToInfo = new RequestReplyCorrelator.ReplyToInfo();
- }
- this.HostingProperty = AspNetEnvironment.Current.GetHostingProperty(request, true);
- if (DiagnosticUtility.ShouldUseActivity)
- {
- this.Activity = TraceUtility.ExtractActivity(this.Request);
- }
- if (DiagnosticUtility.ShouldUseActivity || TraceUtility.ShouldPropagateActivity)
- {
- this.ResponseActivityId = ActivityIdHeader.ExtractActivityId(this.Request);
- }
- else
- {
- this.ResponseActivityId = Guid.Empty;
- }
- this.InvokeNotification = new MessageRpcInvokeNotification(this.Activity, this.channelHandler);
- if (this.EventTraceActivity == null && FxTrace.Trace.IsEnd2EndActivityTracingEnabled)
- {
- if (this.Request != null)
- {
- this.EventTraceActivity = EventTraceActivityHelper.TryExtractActivity(this.Request, true);
- }
- }
- }
- internal bool FinalizeCorrelationImplicitly
- {
- get { return this.CorrelationCallback != null && this.CorrelationCallback.IsFullyDefined; }
- }
- internal bool IsPaused
- {
- get { return this.paused; }
- }
- internal bool SwitchedThreads
- {
- get { return this.switchedThreads; }
- }
- internal bool IsInstanceContextSingleton
- {
- set
- {
- this.isInstanceContextSingleton = value;
- }
- }
- internal TransactionRpcFacet Transaction
- {
- get
- {
- if (this.transaction == null)
- {
- this.transaction = new TransactionRpcFacet(ref this);
- }
- return this.transaction;
- }
- }
- internal void Abort()
- {
- this.AbortRequestContext();
- this.AbortChannel();
- this.AbortInstanceContext();
- }
- void AbortRequestContext(RequestContext requestContext)
- {
- try
- {
- requestContext.Abort();
- ReceiveContextRPCFacet receiveContext = this.ReceiveContext;
- if (receiveContext != null)
- {
- this.ReceiveContext = null;
- IAsyncResult result = receiveContext.BeginAbandon(
- TimeSpan.MaxValue,
- handleEndAbandon,
- new CallbackState
- {
- ReceiveContext = receiveContext,
- ChannelHandler = this.channelHandler
- });
- if (result.CompletedSynchronously)
- {
- receiveContext.EndAbandon(result);
- }
- }
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- this.channelHandler.HandleError(e);
- }
- }
- internal void AbortRequestContext()
- {
- if (this.OperationContext.RequestContext != null)
- {
- this.AbortRequestContext(this.OperationContext.RequestContext);
- }
- if ((this.RequestContext != null) && (this.RequestContext != this.OperationContext.RequestContext))
- {
- this.AbortRequestContext(this.RequestContext);
- }
- TraceCallDurationInDispatcherIfNecessary(false);
- }
- void TraceCallDurationInDispatcherIfNecessary(bool requestContextWasClosedSuccessfully)
- {
- // only need to trace once (either for the failure or success case)
- if (TD.DispatchFailedIsEnabled())
- {
- if (requestContextWasClosedSuccessfully)
- {
- TD.DispatchSuccessful(this.EventTraceActivity, this.Operation.Name);
- }
- else
- {
- TD.DispatchFailed(this.EventTraceActivity, this.Operation.Name);
- }
- }
- }
- internal void CloseRequestContext()
- {
- if (this.OperationContext.RequestContext != null)
- {
- this.DisposeRequestContext(this.OperationContext.RequestContext);
- }
- if ((this.RequestContext != null) && (this.RequestContext != this.OperationContext.RequestContext))
- {
- this.DisposeRequestContext(this.RequestContext);
- }
- TraceCallDurationInDispatcherIfNecessary(true);
- }
- void DisposeRequestContext(RequestContext context)
- {
- try
- {
- context.Close();
- ReceiveContextRPCFacet receiveContext = this.ReceiveContext;
- if (receiveContext != null)
- {
- this.ReceiveContext = null;
- IAsyncResult result = receiveContext.BeginComplete(
- TimeSpan.MaxValue,
- null,
- this.channelHandler,
- handleEndComplete,
- new CallbackState
- {
- ChannelHandler = this.channelHandler,
- ReceiveContext = receiveContext
- });
- if (result.CompletedSynchronously)
- {
- receiveContext.EndComplete(result);
- }
- }
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- this.AbortRequestContext(context);
- this.channelHandler.HandleError(e);
- }
- }
- static void HandleEndAbandon(IAsyncResult result)
- {
- if (result.CompletedSynchronously)
- {
- return;
- }
- CallbackState callbackState = (CallbackState)result.AsyncState;
- try
- {
- callbackState.ReceiveContext.EndAbandon(result);
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- callbackState.ChannelHandler.HandleError(e);
- }
- }
- static void HandleEndComplete(IAsyncResult result)
- {
- if (result.CompletedSynchronously)
- {
- return;
- }
- CallbackState callbackState = (CallbackState)result.AsyncState;
- try
- {
- callbackState.ReceiveContext.EndComplete(result);
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- callbackState.ChannelHandler.HandleError(e);
- }
- }
- internal void AbortChannel()
- {
- if ((this.Channel != null) && this.Channel.HasSession)
- {
- try
- {
- this.Channel.Abort();
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- this.channelHandler.HandleError(e);
- }
- }
- }
- internal void CloseChannel()
- {
- if ((this.Channel != null) && this.Channel.HasSession)
- {
- try
- {
- this.Channel.Close(ChannelHandler.CloseAfterFaultTimeout);
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- this.channelHandler.HandleError(e);
- }
- }
- }
- internal void AbortInstanceContext()
- {
- if (this.InstanceContext != null && !this.isInstanceContextSingleton)
- {
- try
- {
- this.InstanceContext.Abort();
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- this.channelHandler.HandleError(e);
- }
- }
- }
- internal void EnsureReceive()
- {
- using (ServiceModelActivity.BoundOperation(this.Activity))
- {
- ChannelHandler.Register(this.channelHandler);
- }
- }
- bool ProcessError(Exception e)
- {
- MessageRpcProcessor handler = this.ErrorProcessor;
- try
- {
- Type exceptionType = e.GetType();
- if (exceptionType.IsAssignableFrom(typeof(FaultException)))
- {
- DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
- }
- else
- {
- DiagnosticUtility.TraceHandledException(e, TraceEventType.Error);
- }
- if (TraceUtility.MessageFlowTracingOnly)
- {
- TraceUtility.SetActivityId(this.Request.Properties);
- if (Guid.Empty == DiagnosticTraceBase.ActivityId)
- {
- Guid receivedActivityId = TraceUtility.ExtractActivityId(this.Request);
- if (Guid.Empty != receivedActivityId)
- {
- DiagnosticTraceBase.ActivityId = receivedActivityId;
- }
- }
- }
- this.Error = e;
- if (this.ErrorProcessor != null)
- {
- this.ErrorProcessor(ref this);
- }
- return (this.Error == null);
- }
- #pragma warning suppress 56500 // covered by FxCOP
- catch (Exception e2)
- {
- if (Fx.IsFatal(e2))
- {
- throw;
- }
- return ((handler != this.ErrorProcessor) && this.ProcessError(e2));
- }
- }
- internal void DisposeParameters(bool excludeInput)
- {
- if (this.Operation.DisposeParameters)
- {
- this.DisposeParametersCore(excludeInput);
- }
- }
- internal void DisposeParametersCore(bool excludeInput)
- {
- if (!this.ParametersDisposed)
- {
- if (!excludeInput)
- {
- this.DisposeParameterList(this.InputParameters);
- }
- this.DisposeParameterList(this.OutputParameters);
- IDisposable disposableParameter = this.ReturnParameter as IDisposable;
- if (disposableParameter != null)
- {
- try
- {
- disposableParameter.Dispose();
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- this.channelHandler.HandleError(e);
- }
- }
- this.ParametersDisposed = true;
- }
- }
- void DisposeParameterList(object[] parameters)
- {
- IDisposable disposableParameter = null;
- if (parameters != null)
- {
- foreach (Object obj in parameters)
- {
- disposableParameter = obj as IDisposable;
- if (disposableParameter != null)
- {
- try
- {
- disposableParameter.Dispose();
- }
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- this.channelHandler.HandleError(e);
- }
- }
- }
- }
- }
- // See notes on UnPause and Resume (mutually exclusive)
- // Pausing will Increment the BusyCount for the hosting environment
- internal IResumeMessageRpc Pause()
- {
- Wrapper wrapper = new Wrapper(ref this);
- this.paused = true;
- return wrapper;
- }
- [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method ApplyHostingIntegrationContextNoInline. Caller must ensure that"
- + "function is called appropriately and result is guarded and Dispose()'d correctly.")]
- [SecurityCritical]
- IDisposable ApplyHostingIntegrationContext()
- {
- if (this.HostingProperty != null)
- {
- return this.ApplyHostingIntegrationContextNoInline();
- }
- else
- {
- return null;
- }
- }
- [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method HostingMessageProperty.ApplyIntegrationContext. Caller must ensure that"
- + "function is called appropriately and result is guarded and Dispose()'d correctly.")]
- [SecurityCritical]
- [MethodImpl(MethodImplOptions.NoInlining)]
- IDisposable ApplyHostingIntegrationContextNoInline()
- {
- return this.HostingProperty.ApplyIntegrationContext();
- }
- [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method ApplyHostingIntegrationContext.",
- Safe = "Does call properly and calls Dispose, doesn't leak control of the IDisposable out of the function.")]
- [SecuritySafeCritical]
- internal bool Process(bool isOperationContextSet)
- {
- using (ServiceModelActivity.BoundOperation(this.Activity))
- {
- bool completed = true;
- if (this.NextProcessor != null)
- {
- MessageRpcProcessor processor = this.NextProcessor;
- this.NextProcessor = null;
- OperationContext originalContext;
- OperationContext.Holder contextHolder;
- if (!isOperationContextSet)
- {
- contextHolder = OperationContext.CurrentHolder;
- originalContext = contextHolder.Context;
- }
- else
- {
- contextHolder = null;
- originalContext = null;
- }
- IncrementBusyCount();
- IDisposable hostedIntegrationContext = this.ApplyHostingIntegrationContext();
- try
- {
- if (!isOperationContextSet)
- {
- contextHolder.Context = this.OperationContext;
- }
- processor(ref this);
- if (!this.paused)
- {
- this.OperationContext.SetClientReply(null, false);
- }
- }
- #pragma warning suppress 56500 // covered by FxCOP
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- if (!this.ProcessError(e) && this.FaultInfo.Fault == null)
- {
- this.Abort();
- }
- }
- finally
- {
- try
- {
- DecrementBusyCount();
- if (hostedIntegrationContext != null)
- {
- hostedIntegrationContext.Dispose();
- }
- if (!isOperationContextSet)
- {
- contextHolder.Context = originalContext;
- }
- completed = !this.paused;
- if (completed)
- {
- this.channelHandler.DispatchDone();
- this.OperationContext.ClearClientReplyNoThrow();
- }
- }
- #pragma warning suppress 56500 // covered by FxCOP
- catch (Exception e)
- {
- if (Fx.IsFatal(e))
- {
- throw;
- }
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperFatal(e.Message, e);
- }
- }
- }
- return completed;
- }
- }
- // UnPause is called on the original MessageRpc to continue work on the current thread, and the copy is ignored.
- // Since the copy is ignored, Decrement the BusyCount
- internal void UnPause()
- {
- this.paused = false;
- DecrementBusyCount();
- }
- internal bool UnlockInvokeContinueGate(out IAsyncResult result)
- {
- return this.invokeContinueGate.Unlock(out result);
- }
- internal void PrepareInvokeContinueGate()
- {
- this.invokeContinueGate = new SignalGate<IAsyncResult>();
- }
- void IncrementBusyCount()
- {
- // Only increment the counter on the service side.
- if (this.Host != null)
- {
- this.Host.IncrementBusyCount();
- if (AspNetEnvironment.Current.TraceIncrementBusyCountIsEnabled())
- {
- AspNetEnvironment.Current.TraceIncrementBusyCount(SR.GetString(SR.ServiceBusyCountTrace, this.Operation.Action));
- }
- }
- }
- void DecrementBusyCount()
- {
- if (this.Host != null)
- {
- this.Host.DecrementBusyCount();
- if (AspNetEnvironment.Current.TraceDecrementBusyCountIsEnabled())
- {
- AspNetEnvironment.Current.TraceDecrementBusyCount(SR.GetString(SR.ServiceBusyCountTrace, this.Operation.Action));
- }
- }
- }
- class CallbackState
- {
- public ReceiveContextRPCFacet ReceiveContext
- {
- get;
- set;
- }
- public ChannelHandler ChannelHandler
- {
- get;
- set;
- }
- }
- class Wrapper : IResumeMessageRpc
- {
- MessageRpc rpc;
- bool alreadyResumed;
- internal Wrapper(ref MessageRpc rpc)
- {
- this.rpc = rpc;
- if (rpc.NextProcessor == null)
- {
- Fx.Assert("MessageRpc.Wrapper.Wrapper: (rpc.NextProcessor != null)");
- }
- this.rpc.IncrementBusyCount();
- }
- public InstanceContext GetMessageInstanceContext()
- {
- return this.rpc.InstanceContext;
- }
- // Resume is called on the copy on some completing thread, whereupon work continues on that thread.
- // BusyCount is Decremented as the copy is now complete
- public void Resume(out bool alreadyResumedNoLock)
- {
- try
- {
- alreadyResumedNoLock = this.alreadyResumed;
- this.alreadyResumed = true;
- this.rpc.switchedThreads = true;
- if (this.rpc.Process(false) && !rpc.InvokeNotification.DidInvokerEnsurePump)
- {
- this.rpc.EnsureReceive();
- }
- }
- finally
- {
- this.rpc.DecrementBusyCount();
- }
- }
- public void Resume(IAsyncResult result)
- {
- this.rpc.AsyncResult = result;
- this.Resume();
- }
- public void Resume(object instance)
- {
- this.rpc.Instance = instance;
- this.Resume();
- }
- public void Resume()
- {
- using (ServiceModelActivity.BoundOperation(this.rpc.Activity, true))
- {
- bool alreadyResumedNoLock;
- this.Resume(out alreadyResumedNoLock);
- if (alreadyResumedNoLock)
- {
- string text = SR.GetString(SR.SFxMultipleCallbackFromAsyncOperation, rpc.Operation.Name);
- Exception error = new InvalidOperationException(text);
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(error);
- }
- }
- }
- public void SignalConditionalResume(IAsyncResult result)
- {
- if (this.rpc.invokeContinueGate.Signal(result))
- {
- this.rpc.AsyncResult = result;
- Resume();
- }
- }
- }
- }
- class MessageRpcInvokeNotification : IInvokeReceivedNotification
- {
- ServiceModelActivity activity;
- ChannelHandler handler;
- public MessageRpcInvokeNotification(ServiceModelActivity activity, ChannelHandler handler)
- {
- this.activity = activity;
- this.handler = handler;
- }
- public bool DidInvokerEnsurePump { get; set; }
- public void NotifyInvokeReceived()
- {
- using (ServiceModelActivity.BoundOperation(this.activity))
- {
- ChannelHandler.Register(this.handler);
- }
- this.DidInvokerEnsurePump = true;
- }
- public void NotifyInvokeReceived(RequestContext request)
- {
- using (ServiceModelActivity.BoundOperation(this.activity))
- {
- ChannelHandler.Register(this.handler, request);
- }
- this.DidInvokerEnsurePump = true;
- }
- }
- }
|