Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / mscorlib / system / runtime / remoting / synchronizeddispatch.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 //
7 //   Synchronization Property for URT Contexts. Uses the ThreadPool API.
8 //   An instance of this property in a context enforces a synchronization
9 //   domain for the context (and all contexts that share the same instance).
10 //   This means that at any instant, at most 1 thread could be executing
11 //   in all contexts that share an instance of this property.
12 //
13 //   This is done by contributing sinks that intercept and serialize in-coming
14 //   calls for the respective contexts.
15 //
16 //   If the property is marked for re-entrancy, then call-outs are 
17 //   intercepted too. The call-out interception allows other waiting threads
18 //   to enter the synchronization domain for maximal throughput.
19 //   
20 namespace System.Runtime.Remoting.Contexts {
21     using System.Threading;
22     using System.Runtime.Remoting;
23     using System.Runtime.Remoting.Messaging;
24     using System.Runtime.Remoting.Activation;
25     using System.Security.Permissions;
26     using System;
27     using System.Diagnostics.Contracts;
28     using Queue = System.Collections.Queue;
29     using ArrayList = System.Collections.ArrayList;
30     [System.Security.SecurityCritical]  // auto-generated_required
31     [Serializable]
32     [AttributeUsage(AttributeTargets.Class)]
33     [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.Infrastructure)]    
34     [System.Runtime.InteropServices.ComVisible(true)]
35     public class SynchronizationAttribute
36         : ContextAttribute, IContributeServerContextSink, 
37                     IContributeClientContextSink
38     {
39         // The class should not be instantiated in a context that has Synchronization
40         public const int NOT_SUPPORTED  = 0x00000001;
41         
42         // The class does not care if the context has Synchronization or not
43         public const int SUPPORTED      = 0x00000002;
44     
45         // The class should be instantiated in a context that has Synchronization
46         public const int REQUIRED    = 0x00000004;
47     
48         // The class should be instantiated in a context with a new instance of 
49         // Synchronization property each time
50         public const int REQUIRES_NEW = 0x00000008;
51     
52         private const String PROPERTY_NAME = "Synchronization";
53     
54         private static readonly int _timeOut = -1;
55         // event that releases a thread-pool worker thread
56         [NonSerialized]
57         internal AutoResetEvent _asyncWorkEvent;
58         [NonSerialized]
59         private RegisteredWaitHandle _waitHandle;
60
61         // queue of work items.
62         [NonSerialized]
63         internal Queue _workItemQueue;
64         // flag for the domain lock (access always synchronized on the _workItemQueue)
65         [NonSerialized]
66         internal bool _locked;
67         // flag to indicate if the lock should be released during call-outs
68         internal bool _bReEntrant;
69         // flag for use as an attribute on types
70         internal int _flavor;
71
72         [NonSerialized]
73         private SynchronizationAttribute _cliCtxAttr;
74         // Logical call id (used only in non-reentrant case for deadlock avoidance)
75         [NonSerialized]
76         private String _syncLcid;
77         [NonSerialized]
78         private ArrayList _asyncLcidList;
79         
80     
81         public virtual bool Locked {get { return _locked;} set { _locked=value; } } 
82         public virtual bool IsReEntrant { get { return _bReEntrant;} }  
83
84         internal String SyncCallOutLCID
85         {
86             get 
87             { 
88                 Contract.Assert(
89                     !_bReEntrant, 
90                     "Should not use this for the reentrant case");
91                     
92                 return _syncLcid;
93             }
94
95             set
96             {
97                 Contract.Assert(
98                     !_bReEntrant, 
99                     "Should not use this for the reentrant case");
100
101                 Contract.Assert(
102                     _syncLcid==null 
103                         || (_syncLcid!=null && value==null) 
104                         || _syncLcid.Equals(value), 
105                     "context can be associated with one logical call at a time");
106                 
107                 _syncLcid = value;
108             }
109         }
110
111         internal ArrayList AsyncCallOutLCIDList
112         {
113             get { return _asyncLcidList; }
114         }
115
116         internal bool IsKnownLCID(IMessage reqMsg)
117         {
118             String msgLCID = 
119                 ((LogicalCallContext)reqMsg.Properties[Message.CallContextKey])
120                     .RemotingData.LogicalCallID;
121             return ( msgLCID.Equals(_syncLcid)
122                     || _asyncLcidList.Contains(msgLCID));
123             
124         }
125
126     
127         /*
128         *   Constructor for the synchronized dispatch property
129         */
130         public SynchronizationAttribute()
131         
132             : this(REQUIRED, false) {
133         }
134     
135         /*
136         *   Constructor. 
137         *   If reEntrant is true, we allow other calls to come in
138         *   if the currently running call leaves the domain for a call-out.
139         */
140         public SynchronizationAttribute(bool reEntrant)
141         
142             : this(REQUIRED, reEntrant) {
143         }
144     
145         public SynchronizationAttribute(int flag)
146         
147             : this(flag, false) {
148         }
149     
150         public SynchronizationAttribute(int flag, bool reEntrant)
151         
152             // Invoke ContextProperty ctor!
153             : base(PROPERTY_NAME) {
154             
155             _bReEntrant = reEntrant;
156     
157             switch (flag)
158             {
159             case NOT_SUPPORTED:
160             case SUPPORTED:
161             case REQUIRED:
162             case REQUIRES_NEW:
163                 _flavor = flag;
164                 break;
165             default:
166                 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "flag");
167             }
168         }
169     
170         // Dispose off the WaitHandle registered in Initialization
171         internal void Dispose()
172         {
173             //Unregister the RegisteredWaitHandle
174             if (_waitHandle != null)
175                 _waitHandle.Unregister(null);
176         }
177
178         // Override ContextAttribute's implementation of IContextAttribute::IsContextOK
179         [System.Security.SecurityCritical]
180         [System.Runtime.InteropServices.ComVisible(true)]
181         public override bool IsContextOK(Context ctx, IConstructionCallMessage msg)
182         {
183             if (ctx == null)
184                 throw new ArgumentNullException("ctx");
185             if (msg == null)
186                 throw new ArgumentNullException("msg");
187             Contract.EndContractBlock();
188
189             // <
190
191             bool isOK = true;
192             if (_flavor == REQUIRES_NEW)
193             {
194                 isOK = false;
195                 // Each activation request instantiates a new attribute class.
196                 // We are relying on that for the REQUIRES_NEW case!
197                 Contract.Assert(ctx.GetProperty(PROPERTY_NAME) != this,
198                     "ctx.GetProperty(PROPERTY_NAME) != this");
199             }
200             else
201             {
202                 SynchronizationAttribute syncProp = (SynchronizationAttribute) ctx.GetProperty(PROPERTY_NAME);
203                 if (   ( (_flavor == NOT_SUPPORTED)&&(syncProp != null) )
204                     || ( (_flavor == REQUIRED)&&(syncProp == null) )
205                     )
206                 {
207                     isOK = false;
208                 }
209
210                 if (_flavor == REQUIRED)
211                 {
212                     // pick up the property from the current context
213                     _cliCtxAttr = syncProp;
214                 }
215             }
216             return isOK;
217         }
218     
219         // Override ContextAttribute's impl. of IContextAttribute::GetPropForNewCtx
220         [System.Security.SecurityCritical]
221         [System.Runtime.InteropServices.ComVisible(true)]
222         public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
223         {
224             if ( (_flavor==NOT_SUPPORTED) || (_flavor==SUPPORTED) || (null == ctorMsg) )
225             {
226                 return ;
227             }
228
229             if (_cliCtxAttr != null)
230             {
231                 Contract.Assert(_flavor == REQUIRED,"Use cli-ctx property only for the REQUIRED flavor");
232                 ctorMsg.ContextProperties.Add((IContextProperty)_cliCtxAttr);
233                 _cliCtxAttr = null;
234             }
235             else
236             {
237                 ctorMsg.ContextProperties.Add((IContextProperty)this);
238             }
239         }
240     
241         // We need this to make the use of the property as an attribute 
242         // light-weight. This allows us to delay initialize everything we
243         // need to fully function as a ContextProperty.
244         internal virtual void InitIfNecessary()
245         {
246             lock(this) 
247             {
248                 if (_asyncWorkEvent == null)
249                 {
250                     // initialize thread pool event to non-signaled state.
251                     _asyncWorkEvent = new AutoResetEvent(false);
252         
253                     _workItemQueue = new Queue();
254                     _asyncLcidList = new ArrayList();
255                     
256                     WaitOrTimerCallback callBackDelegate = 
257                         new WaitOrTimerCallback(this.DispatcherCallBack);
258         
259                     // Register a callback to be executed by the thread-pool
260                     // each time the event is signaled.
261                     _waitHandle = ThreadPool.RegisterWaitForSingleObject(
262                                     _asyncWorkEvent, 
263                                     callBackDelegate, 
264                                     null, // state info
265                                     _timeOut, 
266                                     false); // bExecuteOnlyOnce
267                 }
268             }
269         }
270     
271         /* 
272         * Call back function -- executed for each work item that 
273         * was enqueued. This is invoked by a thread-pool thread for
274         * async work items and the caller thread for sync items.
275         */
276         private void DispatcherCallBack(Object stateIgnored, bool ignored)
277         {
278             // This function should be called by only one thread at a time. We will 
279             // ensure this by releasing exactly one waiting thread to go work on 
280             // a WorkItem
281
282             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] --- In DispatherCallBack ");
283
284             Contract.Assert(_locked==true,"_locked==true");
285             WorkItem work;     
286             // get the work item out of the queue.
287             lock (_workItemQueue)
288             {
289                 work = (WorkItem) _workItemQueue.Dequeue();
290                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] --- Dequeued Work for: " + work._thread.GetHashCode());
291             }
292             Contract.Assert(work!=null,"work!=null");
293             Contract.Assert(work.IsSignaled() && !(work.IsDummy()),"work.IsSignaled() && !(work.IsDummy())");
294             // execute the work item (WorkItem.Execute will switch to the proper context)
295             ExecuteWorkItem(work);
296             HandleWorkCompletion();
297             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] --- CallBack finished for: " + work._thread.GetHashCode());
298         }
299     
300         /*
301         *   This is used by the call-out (client context) sinks to notify 
302         *   the domain manager that the thread is leaving
303         */
304         internal virtual void HandleThreadExit()
305         {
306             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~~ Thread EXIT ~~~~");
307             // For now treat this as if the work was completed!
308             Contract.Assert(_locked==true,"_locked==true");
309             HandleWorkCompletion();    
310         }
311     
312         /* 
313         *   This is used by a returning call-out thread to request
314         *   that it be queued for re-entry into the domain.
315         */
316         internal virtual void HandleThreadReEntry()
317         {
318             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~~ Thread REQUEST REENTRY ~~~~");
319             // Treat this as if a new work item needs to be done
320             // <
321
322             WorkItem work = new WorkItem(null, null, null);
323             work.SetDummy();
324             HandleWorkRequest(work);
325         }
326     
327         /*
328         *   This gets called at the end of work.Execute and from 
329         *   HandleThreadExit() in the re-entrant scenario.
330         *   This is the point where we decide what to do next!
331         */
332         internal virtual void HandleWorkCompletion()
333         {
334             // We should still have the lock held for the workItem that just completed
335             Contract.Assert(_locked==true,"_locked==true");
336             // Now we check the queue to see if we need to release any one?
337             WorkItem nextWork = null;
338             bool bNotify = false;
339             lock (_workItemQueue)
340             {      
341                 if (_workItemQueue.Count >= 1)
342                 {
343                     nextWork = (WorkItem) _workItemQueue.Peek();
344                     bNotify = true;
345                     nextWork.SetSignaled();
346                 }
347                 else
348                 {
349                     // We set locked to false only in the case there is no
350                     // next work to be done.
351                     // NOTE: this is the only place _locked in ever set to false!
352                     //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] Domain UNLOCKED!");
353                     _locked = false;
354                 }
355             }
356             // See if we found a non-signaled work item at the head. 
357             if (bNotify)
358             {
359                 // In both sync and async cases we just hand off the _locked state to
360                 // the next thread which will execute.
361                 if (nextWork.IsAsync())
362                 {
363                     // Async-WorkItem: signal ThreadPool event to release one thread
364                     _asyncWorkEvent.Set();
365                     //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ### Signal " + nextWork._thread.GetHashCode() + (nextWork.IsDummy()?" DUMMY ":" REAL "));
366                 }
367                 else
368                 {
369                     // Sync-WorkItem: notify the waiting sync-thread.
370                     lock(nextWork)
371                     {
372                         Monitor.Pulse(nextWork);
373                         //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ Notify " + nextWork._thread.GetHashCode() + (nextWork.IsDummy()?" DUMMY ":" REAL ") );
374                     }
375                 }
376             }
377         }
378     
379         /*
380         *   This is called by any new incoming thread or from
381         *   HandleThreadReEntry() when a call-out thread wants to
382         *   re-enter the domain. 
383         *   In the latter case, the WorkItem is a dummy item, it
384         *   just serves the purpose of something to block on till
385         *   the thread is given a green signal to re-enter.
386         */
387         internal virtual void HandleWorkRequest(WorkItem work)
388         {
389             // <
390
391
392             bool bQueued;
393
394             // Check for nested call backs
395             if (!IsNestedCall(work._reqMsg))
396             {
397                 // See what type of work it is
398                 if (work.IsAsync()) 
399                 {
400                     // Async work is always queued.
401                     bQueued = true;
402                     // Enqueue the workItem
403                     lock (_workItemQueue)
404                     {
405                         //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ### Async Item EnQueue " + work._thread.GetHashCode());
406                         work.SetWaiting();
407                         _workItemQueue.Enqueue(work);
408                         // If this is the only work item in the queue we will
409                         // have to trigger the thread-pool event ourselves
410                         if ( (!_locked) && (_workItemQueue.Count == 1) )
411                         {
412                             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ### Async Signal Self: " + work._thread.GetHashCode());
413                             work.SetSignaled();
414                             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ### Domain Locked!");
415                             _locked = true;
416                             _asyncWorkEvent.Set();                    
417                         }
418                     }
419                 }
420                 else
421                 {        
422                     // Sync work is queued only if there are other items
423                     // already in the queue.
424                     lock(work)
425                     {
426                         // Enqueue if we need to
427                         lock(_workItemQueue)
428                         {
429                             if ((!_locked) && (_workItemQueue.Count == 0))
430                             {                    
431                                 _locked = true;
432                                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ### Domain Locked!");
433                                 bQueued = false;
434                             }
435                             else
436                             {
437                                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ ENQUEUE Sync!" + (work.IsDummy()?" DUMMY ":" REAL ") + work._thread);
438                                 bQueued = true;
439                                 work.SetWaiting();
440                                 _workItemQueue.Enqueue(work);
441                             }
442                         }
443                         
444                         if (bQueued == true)
445                         {
446                             // If we queued a work item we must wait for some
447                             // other thread to peek at it and Notify us.
448                             
449                             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ WORK::WAIT" + work._thread);
450                             Monitor.Wait(work);
451                             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ FINISH Work::WAIT" + work._thread);
452                             Contract.Assert(_locked==true,"_locked==true");
453                             // Our turn to complete the work! 
454                             // Execute the callBack only if this is real work
455                             if (!work.IsDummy())
456                             {
457                                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ Invoke DispatcherCallBack " + work._thread);
458                                 // We invoke the callback here that does exactly
459                                 // what we need to do ... dequeue work, execute, checkForMore
460                                 DispatcherCallBack(null, true);
461                             }
462                             else
463                             {
464                                 // DummyWork is just use to block/unblock a returning call.
465                                 // Throw away our dummy WorkItem. 
466                                 lock(_workItemQueue)
467                                 {
468                                     _workItemQueue.Dequeue();
469                                 }
470                                 // We don't check for more work here since we are already 
471                                 // in the midst of an executing WorkItem (at the end of which
472                                 // the check will be performed)
473                             }
474                         }
475                         else
476                         {
477                             // We did not queue the work item.
478                             if (!work.IsDummy())
479                             {
480                                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ Execute direct" + work._thread);
481                                 // Execute the work.
482                                 Contract.Assert(_locked==true,"_locked==true");
483                                 work.SetSignaled();
484                                 ExecuteWorkItem(work);
485                                 // Check for more work
486                                 HandleWorkCompletion();
487                             }
488                         }
489                     }
490                 }
491             }    
492             else
493             {
494                 // We allow the nested calls to execute directly                
495                 
496                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ Execute Nested Call direct" + work._thread);
497                 // Execute the work.
498                 Contract.Assert(_locked==true,"_locked==true");
499                 work.SetSignaled();
500                 work.Execute();
501                 // We are still inside the top level call ...
502                 // so after work.Execute finishes we don't check for more work
503                 // or unlock the domain as we do elsewhere.
504             }            
505         }
506
507         internal void ExecuteWorkItem(WorkItem work)
508         {
509             work.Execute();
510         }
511
512         internal bool IsNestedCall(IMessage reqMsg)
513         {
514             // This returns TRUE only if it is a non-reEntrant context
515             // AND 
516             // (the LCID of the reqMsg matches that of 
517             // the top level sync call lcid associated with the context.
518             //  OR
519             // it matches one of the async call out lcids)
520             
521             bool bNested = false;
522             if (!IsReEntrant)
523             {
524                 String lcid = SyncCallOutLCID;                
525                 if (lcid != null)
526                 {
527                     // This means we are inside a top level call
528                     LogicalCallContext callCtx = 
529                         (LogicalCallContext)reqMsg.Properties[Message.CallContextKey];
530                         
531                     if ( callCtx!=null && 
532                         lcid.Equals(callCtx.RemotingData.LogicalCallID))
533                     {
534                         // This is a nested call (we made a call out during
535                         // the top level call and eventually that has resulted 
536                         // in an incoming call with the same lcid)
537                         bNested = true;
538                     }                    
539                 }
540                 if (!bNested && AsyncCallOutLCIDList.Count>0)
541                 {
542                     // This means we are inside a top level call
543                     LogicalCallContext callCtx = 
544                         (LogicalCallContext)reqMsg.Properties[Message.CallContextKey];
545                     if (AsyncCallOutLCIDList.Contains(callCtx.RemotingData.LogicalCallID))
546                     {
547                         bNested = true;
548                     }
549                 }
550             }
551             return bNested;
552         }
553         
554         
555         /*
556         *   Implements IContributeServerContextSink::GetServerContextSink
557         *   Create a SynchronizedDispatch sink and return it.
558         */
559         [System.Security.SecurityCritical]
560         public virtual IMessageSink GetServerContextSink(IMessageSink nextSink)
561         {
562             InitIfNecessary();
563             
564             SynchronizedServerContextSink propertySink = 
565                 new SynchronizedServerContextSink(
566                             this,   
567                             nextSink);
568                             
569             return (IMessageSink) propertySink;
570         }
571     
572         /*
573         *   Implements IContributeClientContextSink::GetClientContextSink
574         *   Create a CallOut sink and return it.
575         */
576         [System.Security.SecurityCritical]
577         public virtual IMessageSink GetClientContextSink(IMessageSink nextSink)
578         {
579             InitIfNecessary();
580             
581             SynchronizedClientContextSink propertySink = 
582                 new SynchronizedClientContextSink(
583                             this,
584                             nextSink);
585                                                                         
586             return (IMessageSink) propertySink;
587         }
588         
589     }
590     
591     /*************************************** SERVER SINK ********************************/
592     /*
593     *   Implements the sink contributed by the Synch-Dispatch
594     *   Property. The sink holds a back pointer to the property.
595     *   The sink intercepts incoming calls to objects resident in
596     *   the Context and co-ordinates with the property to enforce
597     *   the domain policy.
598     */
599     internal class SynchronizedServerContextSink
600             : InternalSink, IMessageSink
601     {
602         internal IMessageSink   _nextSink;
603         [System.Security.SecurityCritical] // auto-generated
604         internal SynchronizationAttribute _property;
605     
606         [System.Security.SecurityCritical]  // auto-generated
607         internal SynchronizedServerContextSink(SynchronizationAttribute prop, IMessageSink nextSink)
608         {
609             _property = prop;
610             _nextSink = nextSink;
611         }
612         
613         [System.Security.SecuritySafeCritical]  // auto-generated
614         ~SynchronizedServerContextSink()
615         {
616             _property.Dispose();
617         }
618         
619         /*
620         * Implements IMessageSink::SyncProcessMessage
621         */
622         [System.Security.SecurityCritical]  // auto-generated
623         public virtual IMessage SyncProcessMessage(IMessage reqMsg)
624         {
625             // 1. Create a work item 
626             WorkItem work = new WorkItem(reqMsg,
627                                         _nextSink,
628                                         null /* replySink */);
629     
630             // 2. Notify the property to handle the WorkItem
631             // The work item may get put in a Queue or may execute directly
632             // if the domain is free.
633             _property.HandleWorkRequest(work);
634     
635             // 3. Pick up retMsg from the WorkItem and return
636             return work.ReplyMessage;
637         }
638     
639         /*
640         *   Implements IMessageSink::AsyncProcessMessage
641         */
642         [System.Security.SecurityCritical]  // auto-generated
643         public virtual IMessageCtrl AsyncProcessMessage(IMessage reqMsg, IMessageSink replySink) 
644         {
645             // 1. Create a work item 
646             WorkItem work = new WorkItem(reqMsg,
647                                         _nextSink,
648                                         replySink);
649             work.SetAsync();
650             // 2. We always queue the work item in async case
651             _property.HandleWorkRequest(work); 
652             // 3. Return an IMsgCtrl
653             return null;    
654         }
655     
656         /*  
657         * Implements IMessageSink::GetNextSink
658         */
659         public IMessageSink NextSink
660         {
661             [System.Security.SecurityCritical]  // auto-generated
662             get
663             {
664                 return _nextSink;
665             }
666         }
667     }
668     
669     //*************************************** WORK ITEM ********************************//
670     /*
671     *   A work item holds the info about a call to Sync or
672     *   Async-ProcessMessage.
673     */
674     internal class WorkItem
675     {
676         private const int FLG_WAITING  = 0x0001;
677         private const int FLG_SIGNALED = 0x0002;
678         private const int FLG_ASYNC      = 0x0004;
679         private const int FLG_DUMMY     = 0x0008;
680     
681         internal int _flags;
682         internal IMessage _reqMsg;
683         internal IMessageSink _nextSink;
684         // ReplySink will be null for an sync work item.
685         internal IMessageSink _replySink;
686         // ReplyMsg is set once the sync call is completed
687         internal IMessage _replyMsg;
688     
689         // Context in which the work should execute.
690         internal Context _ctx;
691
692         [System.Security.SecurityCritical] // auto-generated
693         internal LogicalCallContext _callCtx;
694         internal static InternalCrossContextDelegate _xctxDel = new InternalCrossContextDelegate(ExecuteCallback);
695     
696         //DBGDBG
697         //internal int _thread;   
698         
699         [System.Security.SecuritySafeCritical]  // auto-generated
700         static WorkItem()
701         {
702         }
703
704         [System.Security.SecurityCritical]  // auto-generated
705         internal WorkItem(IMessage reqMsg, IMessageSink nextSink, IMessageSink replySink)
706         {
707             _reqMsg = reqMsg;
708             _replyMsg = null;
709             _nextSink = nextSink;
710             _replySink = replySink;
711             _ctx = Thread.CurrentContext;
712             _callCtx = Thread.CurrentThread.GetMutableExecutionContext().LogicalCallContext;
713             //DBGDBG 
714             //_thread = Thread.CurrentThread.GetHashCode();
715         }
716     
717         // To mark a work item being enqueued
718         internal virtual void SetWaiting()
719         {
720             Contract.Assert(!IsWaiting(),"!IsWaiting()");
721             _flags |= FLG_WAITING;
722         }
723     
724         internal virtual bool IsWaiting()
725         {
726             return (_flags&FLG_WAITING) == FLG_WAITING;
727         }
728     
729         // To mark a work item that has been given the green light!
730         internal virtual void SetSignaled()
731         {
732             Contract.Assert(!IsSignaled(),"!IsSignaled()");
733             _flags |= FLG_SIGNALED;
734         }
735     
736         internal virtual bool IsSignaled()
737         {
738             return (_flags & FLG_SIGNALED) == FLG_SIGNALED;
739         }
740     
741         internal virtual void SetAsync()
742         {
743             _flags |= FLG_ASYNC;
744         }
745         
746         internal virtual bool IsAsync()
747         {
748             return (_flags & FLG_ASYNC) == FLG_ASYNC;
749         }
750     
751         internal virtual void SetDummy()
752         {
753             _flags |= FLG_DUMMY;
754         }
755         
756         internal virtual bool IsDummy()
757         {
758             return (_flags & FLG_DUMMY) == FLG_DUMMY;
759         }
760
761
762         [System.Security.SecurityCritical]  // auto-generated
763         internal static Object ExecuteCallback(Object[] args)
764         {
765             WorkItem This = (WorkItem) args[0];
766             
767             if (This.IsAsync())
768             {
769                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] AsyncWork.Execute");
770                 This._nextSink.AsyncProcessMessage(This._reqMsg, This._replySink);            
771             }
772             else if (This._nextSink != null)
773             {
774                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] SyncWork.Execute");               
775                 This._replyMsg = This._nextSink.SyncProcessMessage(This._reqMsg);
776             }          
777             return null;
778         }
779     
780         /*
781         *   Execute is called to complete a work item (sync or async).
782         *   Execute assumes that the context is set correctly and the lock
783         *   is taken (i.e. it makes no policy decisions)
784         * 
785         *   It is called from the following 3 points:
786         *       1. thread pool thread executing the callback for an async item
787         *       2. calling thread executing the callback for a queued sync item
788         *       3. calling thread directly calling Execute for a non-queued sync item
789         */
790         [System.Security.SecurityCritical]  // auto-generated
791         internal virtual void Execute()
792         {
793             // Execute should be called with the domain policy enforced
794             // i.e. a Synchronization domain should be locked etc ...
795             Contract.Assert(IsSignaled(),"IsSignaled()");
796
797             Thread.CurrentThread.InternalCrossContextCallback(_ctx, _xctxDel, new Object[] { this } );
798         }
799         internal virtual IMessage ReplyMessage { get {return _replyMsg;}}   
800     }
801     
802     //*************************************** CLIENT SINK ********************************//
803     
804     /*
805     *   Implements the client context sink contributed by the
806     *   Property. The sink holds a back pointer to the property.
807     *   The sink intercepts outgoing calls from objects the Context 
808     *   and co-ordinates with the property to enforce the domain policy.
809     */
810     internal class SynchronizedClientContextSink
811             : InternalSink, IMessageSink
812     {
813         internal IMessageSink   _nextSink;
814         [System.Security.SecurityCritical] // auto-generated
815         internal SynchronizationAttribute _property;
816     
817         [System.Security.SecurityCritical]  // auto-generated
818         internal SynchronizedClientContextSink(SynchronizationAttribute prop, IMessageSink nextSink)
819         {
820             _property = prop;
821             _nextSink = nextSink;
822         }
823         
824         [System.Security.SecuritySafeCritical]  // auto-generated
825         ~SynchronizedClientContextSink()
826         {
827             _property.Dispose();
828         }
829         
830         /*
831         *   Implements IMessageSink::SyncProcessMessage for the call-out sink
832         */
833         [System.Security.SecurityCritical]  // auto-generated
834         public virtual IMessage SyncProcessMessage(IMessage reqMsg)
835         {            
836             Contract.Assert(_property.Locked == true,"_property.Locked == true");
837             IMessage replyMsg;
838             if (_property.IsReEntrant)
839             {
840                 // In this case we are required to let anybody waiting for
841                 // the domain to enter and execute
842                 // Notify the property that we are leaving 
843                 _property.HandleThreadExit();
844
845                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] R: Sync call-out");
846                 replyMsg = _nextSink.SyncProcessMessage(reqMsg);
847     
848                 // We will just block till we are given permission to re-enter
849                 // Notify the property that we wish to re-enter the domain.
850                 // This will block the thread here if someone is in the domain.
851                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] R: Sync call-out returned, waiting for lock");                
852                 _property.HandleThreadReEntry(); 
853                 Contract.Assert(_property.Locked == true,"_property.Locked == true");
854             }
855             else
856             {
857                 // In the non-reentrant case we are just a pass-through sink
858                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] NR: Sync call-out (pass through)");                
859                 // We should mark the domain with our LCID so that call-backs are allowed to enter..
860                 LogicalCallContext cctx = 
861                     (LogicalCallContext) reqMsg.Properties[Message.CallContextKey];
862                                                          
863                 String lcid = cctx.RemotingData.LogicalCallID;
864                 bool bClear = false;
865                 if (lcid == null)
866                 {
867                     // We used to assign call-ids in RemotingProxy.cs at the
868                     // start of each Invoke. As an optimization we now do it 
869                     // here in a delayed fashion... since currently only 
870                     // Synchronization needs it
871                     // Note that for Sync-calls we would just inherit an LCID
872                     // if the call has one, if not we create one. However for
873                     // async calls we always generate a new LCID.
874                     lcid = Identity.GetNewLogicalCallID();
875                     cctx.RemotingData.LogicalCallID = lcid;
876                     bClear = true;
877
878                     Contract.Assert(
879                         _property.SyncCallOutLCID == null,
880                         "Synchronization domain is already in a callOut state");
881                 }
882
883                 bool bTopLevel=false;
884                 if (_property.SyncCallOutLCID==null)
885                 {
886                     _property.SyncCallOutLCID = lcid;
887                     bTopLevel = true;
888                 }
889                     
890                 Contract.Assert(lcid.Equals(_property.SyncCallOutLCID), "Bad synchronization domain state!");                    
891                 
892                 replyMsg = _nextSink.SyncProcessMessage(reqMsg);
893
894                 // if a top level call out returned we clear the callId in the domain
895                 if (bTopLevel)
896                 {
897                     _property.SyncCallOutLCID = null;
898
899                     // The sync callOut is done, we do not need the lcid
900                     // that was associated with the call any more.
901                     // (clear it only if we added one to the reqMsg)
902                     if (bClear)
903                     {
904                         // Note that we make changes to the callCtx in 
905                         // the reply message ... since this is the one that
906                         // will get installed back on the thread that called
907                         // the proxy.
908                         LogicalCallContext cctxRet = 
909                             (LogicalCallContext) replyMsg.Properties[Message.CallContextKey];
910                         Contract.Assert(    
911                             cctxRet != null,
912                             "CallContext should be non-null");
913                         cctxRet.RemotingData.LogicalCallID = null;
914                     }
915                 }
916                 
917                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] NR: Sync call-out returned");
918             }
919             return replyMsg;
920         }
921     
922         /*
923         *   Implements IMessageSink::AsyncProcessMessage for the call-out sink
924         */
925         [System.Security.SecurityCritical]  // auto-generated
926         public virtual IMessageCtrl AsyncProcessMessage(IMessage reqMsg, IMessageSink replySink)
927         {
928             IMessageCtrl msgCtrl = null;
929             
930             Contract.Assert(_property.Locked == true,"_property.Locked == true");
931
932             if (!_property.IsReEntrant)
933             {
934                 // In this case new calls are not allowed to enter the domain
935                 // We need to track potentially more than one async-call-outs
936                 // and allow the completion notifications to come in for those
937
938                 LogicalCallContext cctx = 
939                     (LogicalCallContext) reqMsg.Properties[Message.CallContextKey];
940                 // We used to generate a new lcid automatically in RemotingProxy
941                 // Invoke at the start of each Async call.
942                 // However now we do it here as an optimization (since only
943                 // Synchronization needs it)
944                 // RemotingProxy invoke code does Clone() the callContext at 
945                 // the start of each Async call so we don't have to worry 
946                 // about stomping someone else's lcid here.
947
948                                                          
949                 String lcid =  Identity.GetNewLogicalCallID();
950                 cctx.RemotingData.LogicalCallID = lcid;
951                     
952
953                 Contract.Assert(
954                     _property.SyncCallOutLCID == null,
955                     "Cannot handle async call outs when already in a top-level sync call out");
956                 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] NR: Async CallOut: adding to lcidList: " + lcid);                                            
957                 _property.AsyncCallOutLCIDList.Add(lcid);
958             }
959             // We will call AsyncProcessMessage directly on this thread 
960             // since the thread should not block much. However, we will
961             // have to intercept the callback on the replySink chain for
962             // which we wrap the caller provided replySink into our sink.
963             AsyncReplySink mySink = new AsyncReplySink(replySink, _property);          
964             
965             // NOTE: we will need to yield the Synchronization Domain at
966             // some time or another to get our own callBack to complete.
967
968             // Note that for the Async call-outs we have to provide an interception 
969             // sink whether we are re-entrant or not since we want 
970             // the replySink.SyncProcessMessage call to be wait for the lock just like
971             // any other call-in.
972             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] Async call-out");
973             
974             msgCtrl = _nextSink.AsyncProcessMessage(reqMsg, (IMessageSink)mySink);
975             //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] Async call-out AsyncPM returned, reply to come separately");
976
977             return msgCtrl;
978         }
979     
980         /*
981         *   Implements IMessageSink::GetNextSink
982         */
983         public IMessageSink NextSink
984         {
985             [System.Security.SecurityCritical]  // auto-generated
986             get
987             {
988                 return _nextSink;
989             }
990
991         }
992     
993         /*
994         *   This class just implements the CallBack sink we provide to 
995         *   intercept the callback of an Async out-call. The CallBack sink
996         *   ensures that arbitrary threads do not enter our Synchronization
997         *   Domain without asking us if it is Ok!
998         */
999         internal class AsyncReplySink : IMessageSink
1000         {
1001             internal IMessageSink _nextSink;
1002             [System.Security.SecurityCritical] // auto-generated
1003             internal SynchronizationAttribute _property;
1004             [System.Security.SecurityCritical]  // auto-generated
1005             internal AsyncReplySink(IMessageSink nextSink, SynchronizationAttribute prop)
1006             {
1007                 _nextSink = nextSink;
1008                 _property = prop;
1009             }
1010     
1011             [System.Security.SecurityCritical]  // auto-generated
1012             public virtual IMessage SyncProcessMessage(IMessage reqMsg)
1013             {
1014                 
1015                 // We handle this as a regular new Sync workItem
1016                 // 1. Create a work item 
1017                 WorkItem work = new WorkItem(reqMsg,
1018                                             _nextSink,
1019                                             null /* replySink */);
1020     
1021                 // 2. Notify the property to handle the WorkItem
1022                 // The work item may get put in a Queue or may execute right away.
1023                 _property.HandleWorkRequest(work);
1024
1025                 if (!_property.IsReEntrant)
1026                 {
1027                     // Remove the async lcid we had added to the call out list.
1028                     //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] NR: InterceptionSink::SyncPM Removing async call-out lcid: " + ((LogicalCallContext)reqMsg.Properties[Message.CallContextKey]).RemotingData.LogicalCallID);                   
1029                     _property.AsyncCallOutLCIDList.Remove(
1030                         ((LogicalCallContext)reqMsg.Properties[Message.CallContextKey]).RemotingData.LogicalCallID);
1031                 }
1032     
1033                 // 3. Pick up retMsg from the WorkItem and return
1034                 return work.ReplyMessage;                    
1035             }
1036     
1037             [System.Security.SecurityCritical]  // auto-generated
1038             public virtual IMessageCtrl AsyncProcessMessage(IMessage reqMsg, IMessageSink replySink)
1039             {
1040                 throw new NotSupportedException();
1041             }
1042     
1043             /*
1044             * Implements IMessageSink::GetNextSink
1045             */
1046             public IMessageSink NextSink
1047             {
1048                 [System.Security.SecurityCritical]  // auto-generated
1049                 get
1050                 {
1051                     return _nextSink;
1052                 }
1053             }
1054         }   
1055     }
1056
1057 }