Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / mscorlib / system / runtime / remoting / synchronizeddispatch.cs
blob7ade238a04418cc3c51c7b85aee11b9d360d2c85
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.
13 // This is done by contributing sinks that intercept and serialize in-coming
14 // calls for the respective contexts.
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
39 // The class should not be instantiated in a context that has Synchronization
40 public const int NOT_SUPPORTED = 0x00000001;
42 // The class does not care if the context has Synchronization or not
43 public const int SUPPORTED = 0x00000002;
45 // The class should be instantiated in a context that has Synchronization
46 public const int REQUIRED = 0x00000004;
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;
52 private const String PROPERTY_NAME = "Synchronization";
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;
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;
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;
81 public virtual bool Locked {get { return _locked;} set { _locked=value; } }
82 public virtual bool IsReEntrant { get { return _bReEntrant;} }
84 internal String SyncCallOutLCID
86 get
88 Contract.Assert(
89 !_bReEntrant,
90 "Should not use this for the reentrant case");
92 return _syncLcid;
95 set
97 Contract.Assert(
98 !_bReEntrant,
99 "Should not use this for the reentrant case");
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");
107 _syncLcid = value;
111 internal ArrayList AsyncCallOutLCIDList
113 get { return _asyncLcidList; }
116 internal bool IsKnownLCID(IMessage reqMsg)
118 String msgLCID =
119 ((LogicalCallContext)reqMsg.Properties[Message.CallContextKey])
120 .RemotingData.LogicalCallID;
121 return ( msgLCID.Equals(_syncLcid)
122 || _asyncLcidList.Contains(msgLCID));
128 * Constructor for the synchronized dispatch property
130 public SynchronizationAttribute()
132 : this(REQUIRED, false) {
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.
140 public SynchronizationAttribute(bool reEntrant)
142 : this(REQUIRED, reEntrant) {
145 public SynchronizationAttribute(int flag)
147 : this(flag, false) {
150 public SynchronizationAttribute(int flag, bool reEntrant)
152 // Invoke ContextProperty ctor!
153 : base(PROPERTY_NAME) {
155 _bReEntrant = reEntrant;
157 switch (flag)
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");
170 // Dispose off the WaitHandle registered in Initialization
171 internal void Dispose()
173 //Unregister the RegisteredWaitHandle
174 if (_waitHandle != null)
175 _waitHandle.Unregister(null);
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)
183 if (ctx == null)
184 throw new ArgumentNullException("ctx");
185 if (msg == null)
186 throw new ArgumentNullException("msg");
187 Contract.EndContractBlock();
189 // <
191 bool isOK = true;
192 if (_flavor == REQUIRES_NEW)
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");
200 else
202 SynchronizationAttribute syncProp = (SynchronizationAttribute) ctx.GetProperty(PROPERTY_NAME);
203 if ( ( (_flavor == NOT_SUPPORTED)&&(syncProp != null) )
204 || ( (_flavor == REQUIRED)&&(syncProp == null) )
207 isOK = false;
210 if (_flavor == REQUIRED)
212 // pick up the property from the current context
213 _cliCtxAttr = syncProp;
216 return isOK;
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)
224 if ( (_flavor==NOT_SUPPORTED) || (_flavor==SUPPORTED) || (null == ctorMsg) )
226 return ;
229 if (_cliCtxAttr != null)
231 Contract.Assert(_flavor == REQUIRED,"Use cli-ctx property only for the REQUIRED flavor");
232 ctorMsg.ContextProperties.Add((IContextProperty)_cliCtxAttr);
233 _cliCtxAttr = null;
235 else
237 ctorMsg.ContextProperties.Add((IContextProperty)this);
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()
246 lock(this)
248 if (_asyncWorkEvent == null)
250 // initialize thread pool event to non-signaled state.
251 _asyncWorkEvent = new AutoResetEvent(false);
253 _workItemQueue = new Queue();
254 _asyncLcidList = new ArrayList();
256 WaitOrTimerCallback callBackDelegate =
257 new WaitOrTimerCallback(this.DispatcherCallBack);
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
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.
276 private void DispatcherCallBack(Object stateIgnored, bool ignored)
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
282 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] --- In DispatherCallBack ");
284 Contract.Assert(_locked==true,"_locked==true");
285 WorkItem work;
286 // get the work item out of the queue.
287 lock (_workItemQueue)
289 work = (WorkItem) _workItemQueue.Dequeue();
290 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] --- Dequeued Work for: " + work._thread.GetHashCode());
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());
301 * This is used by the call-out (client context) sinks to notify
302 * the domain manager that the thread is leaving
304 internal virtual void HandleThreadExit()
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();
313 * This is used by a returning call-out thread to request
314 * that it be queued for re-entry into the domain.
316 internal virtual void HandleThreadReEntry()
318 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~~ Thread REQUEST REENTRY ~~~~");
319 // Treat this as if a new work item needs to be done
320 // <
322 WorkItem work = new WorkItem(null, null, null);
323 work.SetDummy();
324 HandleWorkRequest(work);
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!
332 internal virtual void HandleWorkCompletion()
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)
341 if (_workItemQueue.Count >= 1)
343 nextWork = (WorkItem) _workItemQueue.Peek();
344 bNotify = true;
345 nextWork.SetSignaled();
347 else
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;
356 // See if we found a non-signaled work item at the head.
357 if (bNotify)
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())
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 "));
367 else
369 // Sync-WorkItem: notify the waiting sync-thread.
370 lock(nextWork)
372 Monitor.Pulse(nextWork);
373 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ Notify " + nextWork._thread.GetHashCode() + (nextWork.IsDummy()?" DUMMY ":" REAL ") );
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.
387 internal virtual void HandleWorkRequest(WorkItem work)
389 // <
392 bool bQueued;
394 // Check for nested call backs
395 if (!IsNestedCall(work._reqMsg))
397 // See what type of work it is
398 if (work.IsAsync())
400 // Async work is always queued.
401 bQueued = true;
402 // Enqueue the workItem
403 lock (_workItemQueue)
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) )
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();
420 else
422 // Sync work is queued only if there are other items
423 // already in the queue.
424 lock(work)
426 // Enqueue if we need to
427 lock(_workItemQueue)
429 if ((!_locked) && (_workItemQueue.Count == 0))
431 _locked = true;
432 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ### Domain Locked!");
433 bQueued = false;
435 else
437 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] ~~~ ENQUEUE Sync!" + (work.IsDummy()?" DUMMY ":" REAL ") + work._thread);
438 bQueued = true;
439 work.SetWaiting();
440 _workItemQueue.Enqueue(work);
444 if (bQueued == true)
446 // If we queued a work item we must wait for some
447 // other thread to peek at it and Notify us.
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())
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);
462 else
464 // DummyWork is just use to block/unblock a returning call.
465 // Throw away our dummy WorkItem.
466 lock(_workItemQueue)
468 _workItemQueue.Dequeue();
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)
475 else
477 // We did not queue the work item.
478 if (!work.IsDummy())
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();
492 else
494 // We allow the nested calls to execute directly
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.
507 internal void ExecuteWorkItem(WorkItem work)
509 work.Execute();
512 internal bool IsNestedCall(IMessage reqMsg)
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)
521 bool bNested = false;
522 if (!IsReEntrant)
524 String lcid = SyncCallOutLCID;
525 if (lcid != null)
527 // This means we are inside a top level call
528 LogicalCallContext callCtx =
529 (LogicalCallContext)reqMsg.Properties[Message.CallContextKey];
531 if ( callCtx!=null &&
532 lcid.Equals(callCtx.RemotingData.LogicalCallID))
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;
540 if (!bNested && AsyncCallOutLCIDList.Count>0)
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))
547 bNested = true;
551 return bNested;
556 * Implements IContributeServerContextSink::GetServerContextSink
557 * Create a SynchronizedDispatch sink and return it.
559 [System.Security.SecurityCritical]
560 public virtual IMessageSink GetServerContextSink(IMessageSink nextSink)
562 InitIfNecessary();
564 SynchronizedServerContextSink propertySink =
565 new SynchronizedServerContextSink(
566 this,
567 nextSink);
569 return (IMessageSink) propertySink;
573 * Implements IContributeClientContextSink::GetClientContextSink
574 * Create a CallOut sink and return it.
576 [System.Security.SecurityCritical]
577 public virtual IMessageSink GetClientContextSink(IMessageSink nextSink)
579 InitIfNecessary();
581 SynchronizedClientContextSink propertySink =
582 new SynchronizedClientContextSink(
583 this,
584 nextSink);
586 return (IMessageSink) propertySink;
591 /*************************************** SERVER SINK ********************************/
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.
599 internal class SynchronizedServerContextSink
600 : InternalSink, IMessageSink
602 internal IMessageSink _nextSink;
603 [System.Security.SecurityCritical] // auto-generated
604 internal SynchronizationAttribute _property;
606 [System.Security.SecurityCritical] // auto-generated
607 internal SynchronizedServerContextSink(SynchronizationAttribute prop, IMessageSink nextSink)
609 _property = prop;
610 _nextSink = nextSink;
613 [System.Security.SecuritySafeCritical] // auto-generated
614 ~SynchronizedServerContextSink()
616 _property.Dispose();
620 * Implements IMessageSink::SyncProcessMessage
622 [System.Security.SecurityCritical] // auto-generated
623 public virtual IMessage SyncProcessMessage(IMessage reqMsg)
625 // 1. Create a work item
626 WorkItem work = new WorkItem(reqMsg,
627 _nextSink,
628 null /* replySink */);
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);
635 // 3. Pick up retMsg from the WorkItem and return
636 return work.ReplyMessage;
640 * Implements IMessageSink::AsyncProcessMessage
642 [System.Security.SecurityCritical] // auto-generated
643 public virtual IMessageCtrl AsyncProcessMessage(IMessage reqMsg, IMessageSink replySink)
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;
657 * Implements IMessageSink::GetNextSink
659 public IMessageSink NextSink
661 [System.Security.SecurityCritical] // auto-generated
664 return _nextSink;
669 //*************************************** WORK ITEM ********************************//
671 * A work item holds the info about a call to Sync or
672 * Async-ProcessMessage.
674 internal class WorkItem
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;
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;
689 // Context in which the work should execute.
690 internal Context _ctx;
692 [System.Security.SecurityCritical] // auto-generated
693 internal LogicalCallContext _callCtx;
694 internal static InternalCrossContextDelegate _xctxDel = new InternalCrossContextDelegate(ExecuteCallback);
696 //DBGDBG
697 //internal int _thread;
699 [System.Security.SecuritySafeCritical] // auto-generated
700 static WorkItem()
704 [System.Security.SecurityCritical] // auto-generated
705 internal WorkItem(IMessage reqMsg, IMessageSink nextSink, IMessageSink replySink)
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();
717 // To mark a work item being enqueued
718 internal virtual void SetWaiting()
720 Contract.Assert(!IsWaiting(),"!IsWaiting()");
721 _flags |= FLG_WAITING;
724 internal virtual bool IsWaiting()
726 return (_flags&FLG_WAITING) == FLG_WAITING;
729 // To mark a work item that has been given the green light!
730 internal virtual void SetSignaled()
732 Contract.Assert(!IsSignaled(),"!IsSignaled()");
733 _flags |= FLG_SIGNALED;
736 internal virtual bool IsSignaled()
738 return (_flags & FLG_SIGNALED) == FLG_SIGNALED;
741 internal virtual void SetAsync()
743 _flags |= FLG_ASYNC;
746 internal virtual bool IsAsync()
748 return (_flags & FLG_ASYNC) == FLG_ASYNC;
751 internal virtual void SetDummy()
753 _flags |= FLG_DUMMY;
756 internal virtual bool IsDummy()
758 return (_flags & FLG_DUMMY) == FLG_DUMMY;
762 [System.Security.SecurityCritical] // auto-generated
763 internal static Object ExecuteCallback(Object[] args)
765 WorkItem This = (WorkItem) args[0];
767 if (This.IsAsync())
769 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] AsyncWork.Execute");
770 This._nextSink.AsyncProcessMessage(This._reqMsg, This._replySink);
772 else if (This._nextSink != null)
774 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] SyncWork.Execute");
775 This._replyMsg = This._nextSink.SyncProcessMessage(This._reqMsg);
777 return null;
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)
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
790 [System.Security.SecurityCritical] // auto-generated
791 internal virtual void Execute()
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()");
797 Thread.CurrentThread.InternalCrossContextCallback(_ctx, _xctxDel, new Object[] { this } );
799 internal virtual IMessage ReplyMessage { get {return _replyMsg;}}
802 //*************************************** CLIENT SINK ********************************//
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.
810 internal class SynchronizedClientContextSink
811 : InternalSink, IMessageSink
813 internal IMessageSink _nextSink;
814 [System.Security.SecurityCritical] // auto-generated
815 internal SynchronizationAttribute _property;
817 [System.Security.SecurityCritical] // auto-generated
818 internal SynchronizedClientContextSink(SynchronizationAttribute prop, IMessageSink nextSink)
820 _property = prop;
821 _nextSink = nextSink;
824 [System.Security.SecuritySafeCritical] // auto-generated
825 ~SynchronizedClientContextSink()
827 _property.Dispose();
831 * Implements IMessageSink::SyncProcessMessage for the call-out sink
833 [System.Security.SecurityCritical] // auto-generated
834 public virtual IMessage SyncProcessMessage(IMessage reqMsg)
836 Contract.Assert(_property.Locked == true,"_property.Locked == true");
837 IMessage replyMsg;
838 if (_property.IsReEntrant)
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();
845 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] R: Sync call-out");
846 replyMsg = _nextSink.SyncProcessMessage(reqMsg);
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");
855 else
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];
863 String lcid = cctx.RemotingData.LogicalCallID;
864 bool bClear = false;
865 if (lcid == null)
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;
878 Contract.Assert(
879 _property.SyncCallOutLCID == null,
880 "Synchronization domain is already in a callOut state");
883 bool bTopLevel=false;
884 if (_property.SyncCallOutLCID==null)
886 _property.SyncCallOutLCID = lcid;
887 bTopLevel = true;
890 Contract.Assert(lcid.Equals(_property.SyncCallOutLCID), "Bad synchronization domain state!");
892 replyMsg = _nextSink.SyncProcessMessage(reqMsg);
894 // if a top level call out returned we clear the callId in the domain
895 if (bTopLevel)
897 _property.SyncCallOutLCID = null;
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)
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;
917 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] NR: Sync call-out returned");
919 return replyMsg;
923 * Implements IMessageSink::AsyncProcessMessage for the call-out sink
925 [System.Security.SecurityCritical] // auto-generated
926 public virtual IMessageCtrl AsyncProcessMessage(IMessage reqMsg, IMessageSink replySink)
928 IMessageCtrl msgCtrl = null;
930 Contract.Assert(_property.Locked == true,"_property.Locked == true");
932 if (!_property.IsReEntrant)
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
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.
949 String lcid = Identity.GetNewLogicalCallID();
950 cctx.RemotingData.LogicalCallID = lcid;
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);
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);
965 // NOTE: we will need to yield the Synchronization Domain at
966 // some time or another to get our own callBack to complete.
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");
974 msgCtrl = _nextSink.AsyncProcessMessage(reqMsg, (IMessageSink)mySink);
975 //DBGConsole.WriteLine(Thread.CurrentThread.GetHashCode()+"] Async call-out AsyncPM returned, reply to come separately");
977 return msgCtrl;
981 * Implements IMessageSink::GetNextSink
983 public IMessageSink NextSink
985 [System.Security.SecurityCritical] // auto-generated
988 return _nextSink;
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!
999 internal class AsyncReplySink : IMessageSink
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)
1007 _nextSink = nextSink;
1008 _property = prop;
1011 [System.Security.SecurityCritical] // auto-generated
1012 public virtual IMessage SyncProcessMessage(IMessage reqMsg)
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 */);
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);
1025 if (!_property.IsReEntrant)
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);
1033 // 3. Pick up retMsg from the WorkItem and return
1034 return work.ReplyMessage;
1037 [System.Security.SecurityCritical] // auto-generated
1038 public virtual IMessageCtrl AsyncProcessMessage(IMessage reqMsg, IMessageSink replySink)
1040 throw new NotSupportedException();
1044 * Implements IMessageSink::GetNextSink
1046 public IMessageSink NextSink
1048 [System.Security.SecurityCritical] // auto-generated
1051 return _nextSink;