1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 namespace System.ServiceModel.Channels
5 {
6     using System;
7     using System.Diagnostics;
8     using System.Runtime;
9     using System.ServiceModel;
10     using System.ServiceModel.Diagnostics;
11     using System.ServiceModel.Diagnostics.Application;
12     using System.Transactions;
13     using System.Runtime.Diagnostics;
14 
15     public abstract class ReceiveContext
16     {
17         public readonly static string Name = "ReceiveContext";
18         ThreadNeutralSemaphore stateLock; // protects state that may be reverted
19         bool contextFaulted;
20         object thisLock;
21         EventTraceActivity eventTraceActivity;
22 
ReceiveContext()23         protected ReceiveContext()
24         {
25             this.thisLock = new object();
26             this.State = ReceiveContextState.Received;
27             this.stateLock = new ThreadNeutralSemaphore(1);
28         }
29 
30         public ReceiveContextState State
31         {
32             get;
33             protected set;
34         }
35 
36         protected object ThisLock
37         {
38             get { return thisLock; }
39         }
40 
41         public event EventHandler Faulted;
42 
TryGet(Message message, out ReceiveContext property)43         public static bool TryGet(Message message, out ReceiveContext property)
44         {
45             if (message == null)
46             {
47                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
48             }
49 
50             bool result = TryGet(message.Properties, out property);
51             if (result && FxTrace.Trace.IsEnd2EndActivityTracingEnabled && property.eventTraceActivity == null)
52             {
53                 property.eventTraceActivity = EventTraceActivityHelper.TryExtractActivity(message);
54             }
55 
56             return result;
57         }
58 
TryGet(MessageProperties properties, out ReceiveContext property)59         public static bool TryGet(MessageProperties properties, out ReceiveContext property)
60         {
61             if (properties == null)
62             {
63                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("properties");
64             }
65 
66             property = null;
67             object foundProperty;
68             if (properties.TryGetValue(Name, out foundProperty))
69             {
70                 property = (ReceiveContext)foundProperty;
71                 return true;
72             }
73             return false;
74         }
75 
Abandon(TimeSpan timeout)76         public virtual void Abandon(TimeSpan timeout)
77         {
78             Abandon(null, timeout);
79         }
80 
Abandon(Exception exception, TimeSpan timeout)81         public virtual void Abandon(Exception exception, TimeSpan timeout)
82         {
83             EnsureValidTimeout(timeout);
84             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
85             this.WaitForStateLock(timeoutHelper.RemainingTime());
86 
87             try
88             {
89                 if (PreAbandon())
90                 {
91                     return;
92                 }
93             }
94             finally
95             {
96                 // Abandon can never be reverted, release the state lock.
97                 this.ReleaseStateLock();
98             }
99 
100             bool success = false;
101             try
102             {
103                 if (exception == null)
104                 {
105                     OnAbandon(timeoutHelper.RemainingTime());
106                 }
107                 else
108                 {
109                     if (TD.ReceiveContextAbandonWithExceptionIsEnabled())
110                     {
111                         TD.ReceiveContextAbandonWithException(this.eventTraceActivity, this.GetType().ToString(), exception.GetType().ToString());
112                     }
113                     OnAbandon(exception, timeoutHelper.RemainingTime());
114                 }
115                 lock (ThisLock)
116                 {
117                     ThrowIfFaulted();
118                     ThrowIfNotAbandoning();
119                     this.State = ReceiveContextState.Abandoned;
120                 }
121                 success = true;
122             }
123             finally
124             {
125                 if (!success)
126                 {
127                     if (TD.ReceiveContextAbandonFailedIsEnabled())
128                     {
129                         TD.ReceiveContextAbandonFailed(this.eventTraceActivity, this.GetType().ToString());
130                     }
131                     Fault();
132                 }
133             }
134 
135         }
136 
BeginAbandon(TimeSpan timeout, AsyncCallback callback, object state)137         public virtual IAsyncResult BeginAbandon(TimeSpan timeout, AsyncCallback callback, object state)
138         {
139             return BeginAbandon(null, timeout, callback, state);
140         }
141 
BeginAbandon(Exception exception, TimeSpan timeout, AsyncCallback callback, object state)142         public virtual IAsyncResult BeginAbandon(Exception exception, TimeSpan timeout, AsyncCallback callback, object state)
143         {
144             EnsureValidTimeout(timeout);
145             return new AbandonAsyncResult(this, exception, timeout, callback, state);
146         }
147 
BeginComplete(TimeSpan timeout, AsyncCallback callback, object state)148         public virtual IAsyncResult BeginComplete(TimeSpan timeout, AsyncCallback callback, object state)
149         {
150             EnsureValidTimeout(timeout);
151             return new CompleteAsyncResult(this, timeout, callback, state);
152         }
153 
Complete(TimeSpan timeout)154         public virtual void Complete(TimeSpan timeout)
155         {
156             EnsureValidTimeout(timeout);
157             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
158             this.WaitForStateLock(timeoutHelper.RemainingTime());
159             bool success = false;
160 
161             try
162             {
163                 PreComplete();
164                 success = true;
165             }
166             finally
167             {
168                 // Case 1: State validation fails, release the lock.
169                 // Case 2: No trasaction, the state can never be reverted, release the lock.
170                 // Case 3: Transaction, keep the lock until we know the transaction outcome (OnTransactionStatusNotification).
171                 if (!success || Transaction.Current == null)
172                 {
173                     this.ReleaseStateLock();
174                 }
175             }
176 
177             success = false;
178             try
179             {
180                 OnComplete(timeoutHelper.RemainingTime());
181                 lock (ThisLock)
182                 {
183                     ThrowIfFaulted();
184                     ThrowIfNotCompleting();
185                     this.State = ReceiveContextState.Completed;
186                 }
187                 success = true;
188             }
189             finally
190             {
191                 if (!success)
192                 {
193                     if (TD.ReceiveContextCompleteFailedIsEnabled())
194                     {
195                         TD.ReceiveContextCompleteFailed(this.eventTraceActivity, this.GetType().ToString());
196                     }
197                     Fault();
198                 }
199             }
200         }
201 
EndAbandon(IAsyncResult result)202         public virtual void EndAbandon(IAsyncResult result)
203         {
204             AbandonAsyncResult.End(result);
205         }
206 
EndComplete(IAsyncResult result)207         public virtual void EndComplete(IAsyncResult result)
208         {
209             CompleteAsyncResult.End(result);
210         }
211 
EnsureValidTimeout(TimeSpan timeout)212         void EnsureValidTimeout(TimeSpan timeout)
213         {
214             if (timeout < TimeSpan.Zero)
215             {
216                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
217                     new ArgumentOutOfRangeException("timeout", SR.GetString(SR.SFxTimeoutOutOfRange0)));
218             }
219 
220             if (TimeoutHelper.IsTooLarge(timeout))
221             {
222                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
223                     new ArgumentOutOfRangeException("timeout", timeout, SR.GetString(SR.SFxTimeoutOutOfRangeTooBig)));
224             }
225         }
226 
Fault()227         protected internal virtual void Fault()
228         {
229             lock (ThisLock)
230             {
231                 if (this.State == ReceiveContextState.Completed || this.State == ReceiveContextState.Abandoned || this.State == ReceiveContextState.Faulted)
232                 {
233                     return;
234                 }
235                 this.State = ReceiveContextState.Faulted;
236             }
237             OnFaulted();
238         }
239 
OnAbandon(TimeSpan timeout)240         protected abstract void OnAbandon(TimeSpan timeout);
OnAbandon(Exception exception, TimeSpan timeout)241         protected virtual void OnAbandon(Exception exception, TimeSpan timeout)
242         {
243             // default implementation: delegate to non-exception overload, ignoring reason
244             OnAbandon(timeout);
245         }
246 
OnBeginAbandon(TimeSpan timeout, AsyncCallback callback, object state)247         protected abstract IAsyncResult OnBeginAbandon(TimeSpan timeout, AsyncCallback callback, object state);
OnBeginAbandon(Exception exception, TimeSpan timeout, AsyncCallback callback, object state)248         protected virtual IAsyncResult OnBeginAbandon(Exception exception, TimeSpan timeout, AsyncCallback callback, object state)
249         {
250             // default implementation: delegate to non-exception overload, ignoring reason
251             return OnBeginAbandon(timeout, callback, state);
252         }
253 
OnBeginComplete(TimeSpan timeout, AsyncCallback callback, object state)254         protected abstract IAsyncResult OnBeginComplete(TimeSpan timeout, AsyncCallback callback, object state);
255 
OnComplete(TimeSpan timeout)256         protected abstract void OnComplete(TimeSpan timeout);
OnEndAbandon(IAsyncResult result)257         protected abstract void OnEndAbandon(IAsyncResult result);
OnEndComplete(IAsyncResult result)258         protected abstract void OnEndComplete(IAsyncResult result);
259 
OnFaulted()260         protected virtual void OnFaulted()
261         {
262             lock (ThisLock)
263             {
264                 if (this.contextFaulted)
265                 {
266                     return;
267                 }
268                 this.contextFaulted = true;
269             }
270 
271             if (TD.ReceiveContextFaultedIsEnabled())
272             {
273                 TD.ReceiveContextFaulted(this.eventTraceActivity, this);
274             }
275 
276             EventHandler handler = this.Faulted;
277 
278             if (handler != null)
279             {
280                 try
281                 {
282                     handler(this, EventArgs.Empty);
283                 }
284                 catch (Exception exception)
285                 {
286                     if (Fx.IsFatal(exception))
287                     {
288                         throw;
289                     }
290 
291                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(exception);
292                 }
293             }
294         }
295 
OnTransactionStatusNotification(TransactionStatus status)296         void OnTransactionStatusNotification(TransactionStatus status)
297         {
298             lock (ThisLock)
299             {
300                 if (status == TransactionStatus.Aborted)
301                 {
302                     if (this.State == ReceiveContextState.Completing || this.State == ReceiveContextState.Completed)
303                     {
304                         this.State = ReceiveContextState.Received;
305                     }
306                 }
307             }
308 
309             if (status != TransactionStatus.Active)
310             {
311                 this.ReleaseStateLock();
312             }
313         }
314 
PreAbandon()315         bool PreAbandon()
316         {
317             bool alreadyAbandoned = false;
318             lock (ThisLock)
319             {
320                 if (this.State == ReceiveContextState.Abandoning || this.State == ReceiveContextState.Abandoned)
321                 {
322                     alreadyAbandoned = true;
323                 }
324                 else
325                 {
326                     ThrowIfFaulted();
327                     ThrowIfNotReceived();
328                     this.State = ReceiveContextState.Abandoning;
329                 }
330             }
331             return alreadyAbandoned;
332         }
333 
PreComplete()334         void PreComplete()
335         {
336             lock (ThisLock)
337             {
338                 ThrowIfFaulted();
339                 ThrowIfNotReceived();
340                 if (Transaction.Current != null)
341                 {
342                     Transaction.Current.EnlistVolatile(new EnlistmentNotifications(this), EnlistmentOptions.None);
343                 }
344                 this.State = ReceiveContextState.Completing;
345             }
346         }
347 
ReleaseStateLock()348         void ReleaseStateLock()
349         {
350             this.stateLock.Exit();
351         }
352 
ThrowIfFaulted()353         void ThrowIfFaulted()
354         {
355 
356             if (State == ReceiveContextState.Faulted)
357             {
358                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
359                     new CommunicationException(SR.GetString(SR.ReceiveContextFaulted, this.GetType().ToString())));
360             }
361         }
362 
ThrowIfNotAbandoning()363         void ThrowIfNotAbandoning()
364         {
365             if (State != ReceiveContextState.Abandoning)
366             {
367                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
368                     new InvalidOperationException(SR.GetString(SR.ReceiveContextInInvalidState, this.GetType().ToString(), this.State.ToString())));
369             }
370         }
371 
ThrowIfNotCompleting()372         void ThrowIfNotCompleting()
373         {
374             if (State != ReceiveContextState.Completing)
375             {
376                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
377                     new InvalidOperationException(SR.GetString(SR.ReceiveContextInInvalidState, this.GetType().ToString(), this.State.ToString())));
378             }
379         }
380 
ThrowIfNotReceived()381         void ThrowIfNotReceived()
382         {
383             if (State != ReceiveContextState.Received)
384             {
385                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
386                     new InvalidOperationException(SR.GetString(SR.ReceiveContextCannotBeUsed, this.GetType().ToString(), this.State.ToString())));
387             }
388         }
389 
WaitForStateLock(TimeSpan timeout)390         void WaitForStateLock(TimeSpan timeout)
391         {
392             try
393             {
394                 this.stateLock.Enter(timeout);
395             }
396             catch (TimeoutException exception)
397             {
398                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(WrapStateException(exception));
399             }
400         }
401 
WaitForStateLockAsync(TimeSpan timeout, FastAsyncCallback callback, object state)402         bool WaitForStateLockAsync(TimeSpan timeout, FastAsyncCallback callback, object state)
403         {
404             return this.stateLock.EnterAsync(timeout, callback, state);
405         }
406 
WrapStateException(Exception exception)407         Exception WrapStateException(Exception exception)
408         {
409             return new InvalidOperationException(SR.GetString(SR.ReceiveContextInInvalidState, this.GetType().ToString(), this.State.ToString()), exception);
410         }
411 
412         sealed class AbandonAsyncResult : WaitAndContinueOperationAsyncResult
413         {
414             Exception exception;
415             static AsyncCompletion handleOperationComplete = new AsyncCompletion(HandleOperationComplete);
416 
AbandonAsyncResult(ReceiveContext receiveContext, Exception exception, TimeSpan timeout, AsyncCallback callback, object state)417             public AbandonAsyncResult(ReceiveContext receiveContext, Exception exception, TimeSpan timeout, AsyncCallback callback, object state)
418                 : base(receiveContext, timeout, callback, state)
419             {
420                 this.exception = exception;
421                 base.Begin();
422             }
423 
424             // The main Abandon logic.
ContinueOperation()425             protected override bool ContinueOperation()
426             {
427                 try
428                 {
429                     if (this.ReceiveContext.PreAbandon())
430                     {
431                         return true;
432                     }
433                 }
434                 finally
435                 {
436                     // Abandon can never be reverted, release the state lock.
437                     this.ReceiveContext.ReleaseStateLock();
438                 }
439 
440                 bool success = false;
441                 IAsyncResult result;
442                 try
443                 {
444                     if (exception == null)
445                     {
446                         result = this.ReceiveContext.OnBeginAbandon(this.TimeoutHelper.RemainingTime(), PrepareAsyncCompletion(handleOperationComplete), this);
447                     }
448                     else
449                     {
450                         if (TD.ReceiveContextAbandonWithExceptionIsEnabled())
451                         {
452                             TD.ReceiveContextAbandonWithException(this.ReceiveContext.eventTraceActivity, this.GetType().ToString(), exception.GetType().ToString());
453                         }
454 
455                         result = this.ReceiveContext.OnBeginAbandon(exception, this.TimeoutHelper.RemainingTime(), PrepareAsyncCompletion(handleOperationComplete), this);
456                     }
457 
458                     success = true;
459                 }
460                 finally
461                 {
462                     if (!success)
463                     {
464                         if (TD.ReceiveContextAbandonFailedIsEnabled())
465                         {
466                             TD.ReceiveContextAbandonFailed((this.ReceiveContext != null) ? this.ReceiveContext.eventTraceActivity : null,
467                                                             this.GetType().ToString());
468                         }
469 
470                         this.ReceiveContext.Fault();
471                     }
472                 }
473 
474                 return SyncContinue(result);
475             }
476 
End(IAsyncResult result)477             public static void End(IAsyncResult result)
478             {
479                 AsyncResult.End<AbandonAsyncResult>(result);
480             }
481 
EndAbandon(IAsyncResult result)482             void EndAbandon(IAsyncResult result)
483             {
484                 this.ReceiveContext.OnEndAbandon(result);
485                 lock (this.ReceiveContext.ThisLock)
486                 {
487                     this.ReceiveContext.ThrowIfFaulted();
488                     this.ReceiveContext.ThrowIfNotAbandoning();
489                     this.ReceiveContext.State = ReceiveContextState.Abandoned;
490                 }
491             }
492 
HandleOperationComplete(IAsyncResult result)493             static bool HandleOperationComplete(IAsyncResult result)
494             {
495                 bool success = false;
496                 AbandonAsyncResult thisPtr = (AbandonAsyncResult)result.AsyncState;
497 
498                 try
499                 {
500                     thisPtr.EndAbandon(result);
501                     success = true;
502                     return true;
503                 }
504                 finally
505                 {
506                     if (!success)
507                     {
508                         if (TD.ReceiveContextAbandonFailedIsEnabled())
509                         {
510                             TD.ReceiveContextAbandonFailed(thisPtr.ReceiveContext.eventTraceActivity, thisPtr.GetType().ToString());
511                         }
512 
513                         thisPtr.ReceiveContext.Fault();
514                     }
515                 }
516             }
517         }
518 
519         sealed class CompleteAsyncResult : WaitAndContinueOperationAsyncResult
520         {
521             Transaction transaction;
522             static AsyncCompletion handleOperationComplete = new AsyncCompletion(HandleOperationComplete);
523 
CompleteAsyncResult(ReceiveContext receiveContext, TimeSpan timeout, AsyncCallback callback, object state)524             public CompleteAsyncResult(ReceiveContext receiveContext, TimeSpan timeout, AsyncCallback callback, object state)
525                 : base(receiveContext, timeout, callback, state)
526             {
527                 this.transaction = Transaction.Current;
528                 this.Begin();
529             }
530 
ContinueOperation()531             protected override bool ContinueOperation()
532             {
533                 IAsyncResult result;
534 
535                 using (PrepareTransactionalCall(this.transaction))
536                 {
537                     bool success = false;
538 
539                     try
540                     {
541                         this.ReceiveContext.PreComplete();
542                         success = true;
543                     }
544                     finally
545                     {
546                         // Case 1: State validation fails, release the lock.
547                         // Case 2: No trasaction, the state can never be reverted, release the lock.
548                         // Case 3: Transaction, keep the lock until we know the transaction outcome (OnTransactionStatusNotification).
549                         if (!success || this.transaction == null)
550                         {
551                             this.ReceiveContext.ReleaseStateLock();
552                         }
553                     }
554 
555                     success = false;
556 
557                     try
558                     {
559                         result = this.ReceiveContext.OnBeginComplete(this.TimeoutHelper.RemainingTime(), PrepareAsyncCompletion(handleOperationComplete), this);
560                         success = true;
561                     }
562                     finally
563                     {
564                         if (!success)
565                         {
566                             if (TD.ReceiveContextCompleteFailedIsEnabled())
567                             {
568                                 TD.ReceiveContextCompleteFailed(this.ReceiveContext.eventTraceActivity, this.GetType().ToString());
569                             }
570 
571                             this.ReceiveContext.Fault();
572                         }
573                     }
574                 }
575 
576                 return SyncContinue(result);
577             }
578 
End(IAsyncResult result)579             public static void End(IAsyncResult result)
580             {
581                 AsyncResult.End<CompleteAsyncResult>(result);
582             }
583 
EndComplete(IAsyncResult result)584             void EndComplete(IAsyncResult result)
585             {
586                 this.ReceiveContext.OnEndComplete(result);
587                 lock (this.ReceiveContext.ThisLock)
588                 {
589                     this.ReceiveContext.ThrowIfFaulted();
590                     this.ReceiveContext.ThrowIfNotCompleting();
591                     this.ReceiveContext.State = ReceiveContextState.Completed;
592                 }
593             }
594 
HandleOperationComplete(IAsyncResult result)595             static bool HandleOperationComplete(IAsyncResult result)
596             {
597                 CompleteAsyncResult thisPtr = (CompleteAsyncResult)result.AsyncState;
598                 bool success = false;
599 
600                 try
601                 {
602                     thisPtr.EndComplete(result);
603                     success = true;
604                     return true;
605                 }
606                 finally
607                 {
608                     if (!success)
609                     {
610                         if (TD.ReceiveContextCompleteFailedIsEnabled())
611                         {
612                             TD.ReceiveContextCompleteFailed(thisPtr.ReceiveContext.eventTraceActivity, thisPtr.GetType().ToString());
613                         }
614 
615                         thisPtr.ReceiveContext.Fault();
616                     }
617                 }
618             }
619         }
620 
621         class EnlistmentNotifications : IEnlistmentNotification
622         {
623             ReceiveContext context;
624 
EnlistmentNotifications(ReceiveContext context)625             public EnlistmentNotifications(ReceiveContext context)
626             {
627                 this.context = context;
628             }
629 
Commit(Enlistment enlistment)630             public void Commit(Enlistment enlistment)
631             {
632                 this.context.OnTransactionStatusNotification(TransactionStatus.Committed);
633                 enlistment.Done();
634             }
635 
InDoubt(Enlistment enlistment)636             public void InDoubt(Enlistment enlistment)
637             {
638                 this.context.OnTransactionStatusNotification(TransactionStatus.InDoubt);
639                 enlistment.Done();
640             }
641 
Prepare(PreparingEnlistment preparingEnlistment)642             public void Prepare(PreparingEnlistment preparingEnlistment)
643             {
644                 this.context.OnTransactionStatusNotification(TransactionStatus.Active);
645                 preparingEnlistment.Prepared();
646             }
647 
Rollback(Enlistment enlistment)648             public void Rollback(Enlistment enlistment)
649             {
650                 this.context.OnTransactionStatusNotification(TransactionStatus.Aborted);
651                 enlistment.Done();
652             }
653         }
654 
655         abstract class WaitAndContinueOperationAsyncResult : TransactedAsyncResult
656         {
657             static FastAsyncCallback onWaitForStateLockComplete = new FastAsyncCallback(OnWaitForStateLockComplete);
658 
WaitAndContinueOperationAsyncResult(ReceiveContext receiveContext, TimeSpan timeout, AsyncCallback callback, object state)659             public WaitAndContinueOperationAsyncResult(ReceiveContext receiveContext, TimeSpan timeout, AsyncCallback callback, object state)
660                 : base(callback, state)
661             {
662                 this.ReceiveContext = receiveContext;
663                 this.TimeoutHelper = new TimeoutHelper(timeout);
664             }
665 
666             protected ReceiveContext ReceiveContext
667             {
668                 get;
669                 private set;
670             }
671 
672             protected TimeoutHelper TimeoutHelper
673             {
674                 get;
675                 private set;
676             }
677 
Begin()678             protected void Begin()
679             {
680                 if (!this.ReceiveContext.WaitForStateLockAsync(this.TimeoutHelper.RemainingTime(), onWaitForStateLockComplete, this))
681                 {
682                     return;
683                 }
684 
685                 if (this.ContinueOperation())
686                 {
687                     this.Complete(true);
688                 }
689             }
690 
ContinueOperation()691             protected abstract bool ContinueOperation();
692 
OnWaitForStateLockComplete(object state, Exception asyncException)693             static void OnWaitForStateLockComplete(object state, Exception asyncException)
694             {
695                 WaitAndContinueOperationAsyncResult thisPtr = (WaitAndContinueOperationAsyncResult)state;
696                 bool completeAsyncResult = true;
697                 Exception completeException = null;
698 
699                 if (asyncException != null)
700                 {
701                     if (asyncException is TimeoutException)
702                     {
703                         asyncException = thisPtr.ReceiveContext.WrapStateException(asyncException);
704                     }
705 
706                     completeException = asyncException;
707                 }
708                 else
709                 {
710                     try
711                     {
712                         completeAsyncResult = thisPtr.ContinueOperation();
713                     }
714                     catch (Exception e)
715                     {
716                         if (Fx.IsFatal(e))
717                         {
718                             throw;
719                         }
720 
721                         completeException = e;
722                     }
723                 }
724 
725                 if (completeAsyncResult)
726                 {
727                     thisPtr.Complete(false, completeException);
728                 }
729             }
730         }
731     }
732 }
733