Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / cdf / src / WF / RunTime / Tracking.cs / 1305376 / Tracking.cs
using System; using System.Text; using System.Reflection; using System.ComponentModel; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Xml; using System.Xml.XPath; using System.Xml.Schema; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; using System.Timers; using System.Security.Permissions; using System.Security.Cryptography; using System.Workflow.Runtime; using System.Workflow.ComponentModel; using System.Workflow.Runtime.Hosting; using System.Workflow.Runtime.Tracking; namespace System.Workflow.Runtime { ////// Creates TrackingListener instances /// internal class TrackingListenerFactory { private List_services = null; private bool _initialized = false; private Dictionary _listeners = new Dictionary (); private volatile object _listenerLock = new object(); private System.Timers.Timer _timer = null; private double _interval = 60000; private TrackingProfileManager _profileManager = new TrackingProfileManager(); internal TrackingListenerFactory() { } internal TrackingProfileManager TrackingProfileManager { get { return _profileManager; } } /// /// Must be called /// /// internal void Initialize( WorkflowRuntime runtime ) { lock ( this ) { _services = runtime.TrackingServices; _profileManager.Initialize(runtime); runtime.WorkflowExecutorInitializing += WorkflowExecutorInitializing; _timer = new System.Timers.Timer(); _timer.Interval = _interval; _timer.AutoReset = false; // ensure that only one timer thread at a time _timer.Elapsed += new ElapsedEventHandler( Cleanup ); _timer.Start(); _initialized = true; } } ////// Clean up static state created in Initialize /// internal void Uninitialize( WorkflowRuntime runtime ) { lock ( this ) { _profileManager.Uninitialize(); runtime.WorkflowExecutorInitializing -= WorkflowExecutorInitializing; _timer.Elapsed -= new ElapsedEventHandler(Cleanup); _timer.Stop(); _services = null; _initialized = false; _timer.Dispose(); _timer = null; } } ////// Callback for associating tracking listeners to in memory instances. Fires for new and loading instances. /// /// WorkflowExecutor /// void WorkflowExecutorInitializing( object sender, WorkflowRuntime.WorkflowExecutorInitializingEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( null == e ) throw new ArgumentNullException( "e" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; // // Add an event to clean up the WeakRef entry exec.WorkflowExecutionEvent += new EventHandler( WorkflowExecutionEvent ); TrackingCallingState trackingCallingState = exec.TrackingCallingState; TrackingListenerBroker listenerBroker = (TrackingListenerBroker)exec.RootActivity.GetValue(WorkflowExecutor.TrackingListenerBrokerProperty); if (listenerBroker != null) { listenerBroker.ReplaceServices(exec.WorkflowRuntime.TrackingServiceReplacement); } TrackingListener listener = null; // // Check if we still have a weakref to the listener for this instance WeakReference weakref = null; if ( e.Loading ) { bool found = false; lock ( _listenerLock ) { found = _listeners.TryGetValue( exec.InstanceId, out weakref ); } if ( found ) { try { // // Instead of checking IsAlive take a ref to the Target // so that it isn't GC'd underneath us. listener = weakref.Target as TrackingListener; } catch ( InvalidOperationException ) { // // This seems weird but according to msdn // accessing Target can throw ??? // Ignore because it's the same as a null target. } } // // If listener is null because we didn't find the wr in the cache // or because the Target has been GC'd create a new listener if (null != listener) { listener.Broker = listenerBroker; } else { Debug.Assert(null != listenerBroker, "TrackingListenerBroker should not be null during loading"); listener = GetTrackingListener(exec.WorkflowDefinition, exec, listenerBroker); if (null != listener) { if (null != weakref) weakref.Target = listener; else { lock (_listenerLock) { _listeners.Add(exec.ID, new WeakReference(listener)); } } } } } else { // // New instance is being created listener = GetTrackingListener( exec.WorkflowDefinition, exec ); if ( null != listener ) { exec.RootActivity.SetValue(WorkflowExecutor.TrackingListenerBrokerProperty, listener.Broker); lock ( _listenerLock ) { _listeners.Add( exec.ID, new WeakReference( listener ) ); } } else exec.RootActivity.SetValue(WorkflowExecutor.TrackingListenerBrokerProperty, new TrackingListenerBroker()); } if ( null != listener ) { exec.WorkflowExecutionEvent += new EventHandler ( listener.WorkflowExecutionEvent ); } } void WorkflowExecutionEvent( object sender, WorkflowExecutor.WorkflowExecutionEventArgs e ) { switch ( e.EventType ) { case WorkflowEventInternal.Aborted: case WorkflowEventInternal.Completed: case WorkflowEventInternal.Terminated: // // The instance is done - remove // the WeakRef from our list WorkflowExecutor exec = ( WorkflowExecutor ) sender; lock ( _listenerLock ) { _listeners.Remove( exec.ID ); } break; default: return; } } void Cleanup( object sender, ElapsedEventArgs e ) { List _toRemove = new List (); if ( ( null != _listeners ) || ( _listeners.Count > 0 ) ) { lock ( _listenerLock ) { foreach ( KeyValuePair kvp in _listeners ) { if ( null == kvp.Value.Target ) _toRemove.Add( kvp.Key ); } if ( _toRemove.Count > 0 ) { foreach( Guid g in _toRemove ) _listeners.Remove( g ); } } } lock (this) { if (_timer != null) { _timer.Start(); } } } /// /// Return a tracking listener for a new instance /// /// SequentialWorkflow for which the tracking listener will be associated /// ScheduleExecutor for the schedule instance ///New TrackingListener instance internal TrackingListener GetTrackingListener(Activity sked, WorkflowExecutor skedExec) { if ( !_initialized ) Initialize(skedExec.WorkflowRuntime); return GetListener(sked, skedExec, null ); } ////// Return a tracking listener for an existing instance (normally used during loading) /// /// SequentialWorkflow for which the tracking listener will be associated /// ScheduleExecutor for the schedule instance /// TrackingListenerBroker ///New TrackingListener instance internal TrackingListener GetTrackingListener(Activity sked, WorkflowExecutor skedExec, TrackingListenerBroker broker) { if ( !_initialized ) Initialize(skedExec.WorkflowRuntime); if ( null == broker ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NullTrackingBroker ); return null; } return GetListener(sked, skedExec, broker); } private TrackingListener GetListenerFromWRCache( Guid instanceId ) { WeakReference wr = null; TrackingListener listener = null; lock ( _listenerLock ) { if (!_listeners.TryGetValue(instanceId, out wr)) throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.InvariantCulture, ExecutionStringManager.ListenerNotInCache, instanceId)); listener = wr.Target as TrackingListener; if ( null == listener ) throw new ObjectDisposedException(string.Format(System.Globalization.CultureInfo.InvariantCulture, ExecutionStringManager.ListenerNotInCacheDisposed, instanceId)); } return listener; } internal void ReloadProfiles(WorkflowExecutor exec) { // Keep control events from other threads out using ( new ServiceEnvironment( exec.RootActivity ) ) { using ( exec.ExecutorLock.Enter() ) { // check if this is a valid in-memory instance if ( !exec.IsInstanceValid ) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); // suspend the instance bool localSuspend = exec.Suspend(ExecutionStringManager.TrackingProfileUpdate); try { // // Get new profiles TrackingListener listener = GetListenerFromWRCache( exec.InstanceId ); listener.ReloadProfiles(exec, exec.InstanceId); } finally { if ( localSuspend ) { // @undone: for now this will not return till the instance is done // Once Kumar has fixed 4335, we can enable this. exec.Resume(); } } } } } internal void ReloadProfiles( WorkflowExecutor exec, Guid instanceId, ref TrackingListenerBroker broker, ref Listchannels ) { Type workflowType = exec.WorkflowDefinition.GetType(); // // Ask every tracking service if they want to reload // even if they originally returned null for a profile foreach ( TrackingService service in _services ) { TrackingProfile profile = null; TrackingChannelWrapper w = null; // // Check if the service wants to reload a profile if ( service.TryReloadProfile( workflowType, instanceId, out profile ) ) { bool found = false; int i; for ( i = 0; i < channels.Count; i++ ) { if ( service.GetType() == channels[i].TrackingServiceType ) { w = channels[i]; found = true; break; } } // // If we don't have a profile, remove what we had for this service type (if anything) if ( null == profile ) { if ( found ) { broker.RemoveService( w.TrackingServiceType ); channels.RemoveAt( i ); } continue; } // // Parse the new profile - instance only, the cache is not involved RTTrackingProfile rtp = new RTTrackingProfile( profile, exec.WorkflowDefinition, workflowType ); rtp.IsPrivate = true; if ( !found ) { // // This is a new profile, create new channel, channelwrapper and broker item List activityCallPath = null; Guid callerInstanceId = Guid.Empty; TrackingCallingState trackingCallingState = exec.TrackingCallingState; Debug.Assert((null != trackingCallingState), "WorkflowState is null"); IList path = null; Guid context = GetContext(exec.RootActivity), callerContext = Guid.Empty, callerParentContext = Guid.Empty; // // Use CallerActivityPathProxy to determine if this is an invoked instance if(trackingCallingState != null) { path = trackingCallingState.CallerActivityPathProxy; if ((null != path) && (path.Count > 0)) { activityCallPath = new List (path); Debug.Assert(Guid.Empty != trackingCallingState.CallerWorkflowInstanceId, "Instance has an ActivityCallPath but CallerInstanceId is empty"); callerInstanceId = trackingCallingState.CallerWorkflowInstanceId; callerContext = trackingCallingState.CallerContextGuid; callerParentContext = trackingCallingState.CallerParentContextGuid; } } TrackingParameters tp = new TrackingParameters( instanceId, workflowType, exec.WorkflowDefinition, activityCallPath, callerInstanceId, context, callerContext, callerParentContext ); TrackingChannel channel = service.GetTrackingChannel( tp ); TrackingChannelWrapper wrapper = new TrackingChannelWrapper( channel, service.GetType(), workflowType, rtp ); channels.Add( wrapper ); Type t = service.GetType(); broker.AddService( t, rtp.Version ); broker.MakeProfileInstance( t ); } else { // // Don't need to call MakeProfilePrivate on the wrapper // because we've already marked it as private and we already // have a private copy of it. //w.MakeProfilePrivate( exec ); w.SetTrackingProfile( rtp ); broker.MakeProfileInstance( w.TrackingServiceType ); } } } } internal Guid GetContext(Activity activity) { return ((ActivityExecutionContextInfo)ContextActivityUtils.ContextActivity(activity).GetValue(Activity.ActivityExecutionContextInfoProperty)).ContextGuid; } private TrackingListener GetListener(Activity sked, WorkflowExecutor skedExec, TrackingListenerBroker broker) { if ( ( null == sked ) || ( null == skedExec ) ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NullParameters ); return null; } if ( ( null == _services ) || ( _services.Count <= 0 ) ) return null; bool load = ( null != broker ); List channels = GetChannels(sked, skedExec, skedExec.InstanceId, sked.GetType(), ref broker); if ( ( null == channels ) || ( 0 == channels.Count ) ) return null; return new TrackingListener( this, sked, skedExec, channels, broker, load ); } private List GetChannels(Activity schedule, WorkflowExecutor exec, Guid instanceID, Type workflowType, ref TrackingListenerBroker broker) { if ( null == _services ) return null; bool initBroker = false; if ( null == broker ) { broker = new TrackingListenerBroker(); initBroker = true; } List channels = new List (); List activityCallPath = null; Guid callerInstanceId = Guid.Empty; Guid context = GetContext(exec.RootActivity), callerContext = Guid.Empty, callerParentContext = Guid.Empty; Debug.Assert( exec is WorkflowExecutor, "Executor is not WorkflowExecutor" ); TrackingCallingState trackingCallingState = exec.TrackingCallingState; TrackingListenerBroker trackingListenerBroker = (TrackingListenerBroker)exec.RootActivity.GetValue(WorkflowExecutor.TrackingListenerBrokerProperty); IList path = trackingCallingState != null? trackingCallingState.CallerActivityPathProxy : null; // // Use CallerActivityPathProxy to determine if this is an invoked instance if ( ( null != path ) && ( path.Count > 0 ) ) { activityCallPath = new List (path); Debug.Assert(Guid.Empty != trackingCallingState.CallerWorkflowInstanceId, "Instance has an ActivityCallPath but CallerInstanceId is empty"); callerInstanceId = trackingCallingState.CallerWorkflowInstanceId; callerContext = trackingCallingState.CallerContextGuid; callerParentContext = trackingCallingState.CallerParentContextGuid; } TrackingParameters parameters = new TrackingParameters( instanceID, workflowType, exec.WorkflowDefinition, activityCallPath, callerInstanceId, context, callerContext, callerParentContext ); for( int i=0; i<_services.Count; i++ ) { TrackingChannel channel = null; Type serviceType = _services[i].GetType(); // // See if the service has a profile for this schedule type // If not we don't do any tracking for the service // RTTrackingProfile profile = null; // // If we've created the broker get the current version of the profile if ( initBroker ) { profile = _profileManager.GetProfile( _services[i], schedule ); if ( null == profile ) continue; broker.AddService( serviceType, profile.Version ); } else { // // Only reload the services that are in the broker // If services that weren't originally associated to an instance // wish to join that instance they should call ReloadTrackingProfiles if ( !broker.ContainsService( serviceType ) ) continue; if ( broker.IsProfileInstance( serviceType ) ) { profile = _profileManager.GetProfile( _services[i], schedule, instanceID ); if ( null == profile ) throw new InvalidOperationException( ExecutionStringManager.MissingProfileForService + serviceType.ToString() ); profile.IsPrivate = true; } else { Version versionId; if ( broker.TryGetProfileVersionId( serviceType, out versionId ) ) { profile = _profileManager.GetProfile( _services[i], schedule, versionId ); if ( null == profile ) throw new InvalidOperationException(ExecutionStringManager.MissingProfileForService + serviceType.ToString() + ExecutionStringManager.MissingProfileForVersion + versionId.ToString()); // // If the profile is marked as private clone the instance we got from the cache // The cloned instance is marked as private during the cloning if ( broker.IsProfilePrivate( serviceType ) ) { profile = profile.Clone(); profile.IsPrivate = true; } } else continue; } } // // If profile is not null get a channel channel = _services[i].GetTrackingChannel( parameters ); if ( null == channel ) throw new InvalidOperationException( ExecutionStringManager.NullChannel ); channels.Add( new TrackingChannelWrapper( channel, _services[i].GetType(), workflowType, profile ) ); } return channels; } } /// /// Handles subscribing to status change events and receiving event notifications. /// internal class TrackingListener { private List_channels = null; private TrackingListenerBroker _broker = null; private TrackingListenerFactory _factory = null; protected TrackingListener() { } internal TrackingListener(TrackingListenerFactory factory, Activity sked, WorkflowExecutor exec, List channels, TrackingListenerBroker broker, bool load) { if ( ( null == sked ) || ( null == broker ) ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NullParameters ); return; } _factory = factory; _channels = channels; // // Keep a reference to the broker so that we can hand it out when adding subscriptions _broker = broker; // // Give the broker our reference so that it can call us back on behalf of subscriptions _broker.TrackingListener = this; } internal TrackingListenerBroker Broker { get { return _broker; } set { _broker = value; } } internal void ReloadProfiles( WorkflowExecutor exec, Guid instanceId ) { // // Ask the factory to redo the channels and broker _factory.ReloadProfiles( exec, instanceId, ref _broker, ref _channels ); } #region Event Handlers internal void ActivityStatusChange( object sender, WorkflowExecutor.ActivityStatusChangeEventArgs e ) { WorkflowTrace.Tracking.TraceInformation("TrackingListener::ActivityStatusChange - Received Activity Status Change Event for activity {0}", e.Activity.QualifiedName); if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); if ( null == e ) throw new ArgumentNullException( "e" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; if ( ( null == _channels ) || ( _channels.Count <= 0 ) ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NoChannels ); return; } Activity activity = e.Activity; if ( !SubscriptionRequired( activity, exec ) ) return; // // Get the shared data that is the same for each tracking channel that gets a record Guid parentContextGuid = Guid.Empty, contextGuid = Guid.Empty; GetContext(activity, exec, out contextGuid, out parentContextGuid); DateTime dt = DateTime.UtcNow; int eventOrderId = _broker.GetNextEventOrderId(); foreach ( TrackingChannelWrapper wrapper in _channels ) { // // Create a record for each tracking channel // Each channel gets a distinct record because extract data will almost always be different. ActivityTrackingRecord record = new ActivityTrackingRecord( activity.GetType(), activity.QualifiedName, contextGuid, parentContextGuid, activity.ExecutionStatus, dt, eventOrderId, null ); bool extracted = wrapper.GetTrackingProfile(exec).TryTrackActivityEvent(activity, activity.ExecutionStatus, exec, record); // // Only send the record to the channel if the profile indicates that it is interested // This doesn't mean that the Body will always have data in it, // it may be an empty extraction (just header info) if (extracted) wrapper.TrackingChannel.Send(record); } } internal void UserTrackPoint( object sender, WorkflowExecutor.UserTrackPointEventArgs e ) { if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender is not WorkflowExecutor" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; Activity activity = e.Activity; DateTime dt = DateTime.UtcNow; int eventOrderId = _broker.GetNextEventOrderId(); Guid parentContextGuid, contextGuid; GetContext(activity, exec, out contextGuid, out parentContextGuid); foreach ( TrackingChannelWrapper wrapper in _channels ) { UserTrackingRecord record = new UserTrackingRecord( activity.GetType(), activity.QualifiedName, contextGuid, parentContextGuid, dt, eventOrderId, e.Key, e.Args ); if (wrapper.GetTrackingProfile(exec).TryTrackUserEvent(activity, e.Key, e.Args, exec, record)) wrapper.TrackingChannel.Send( record ); } } internal void WorkflowExecutionEvent( object sender, WorkflowExecutor.WorkflowExecutionEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); WorkflowExecutor exec = sender as WorkflowExecutor; if ( null == exec ) throw new ArgumentException( ExecutionStringManager.InvalidSenderWorkflowExecutor ); // // Many events are mapped "forward" and sent to tracking services // (Persisting->Persisted, SchedulerEmpty->Idle) // This is so that a batch is always available when a tracking service gets an event. // Without this tracking data could be inconsistent with the state of the instance. switch ( e.EventType ) { case WorkflowEventInternal.Creating: NotifyChannels( TrackingWorkflowEvent.Created, e, exec ); return; case WorkflowEventInternal.Starting: NotifyChannels(TrackingWorkflowEvent.Started, e, exec); return; case WorkflowEventInternal.Suspending: NotifyChannels(TrackingWorkflowEvent.Suspended, e, exec); return; case WorkflowEventInternal.Resuming: NotifyChannels( TrackingWorkflowEvent.Resumed, e, exec ); return; case WorkflowEventInternal.Persisting: NotifyChannels( TrackingWorkflowEvent.Persisted, e, exec ); return; case WorkflowEventInternal.Unloading: NotifyChannels( TrackingWorkflowEvent.Unloaded, e, exec ); return; case WorkflowEventInternal.Loading: NotifyChannels( TrackingWorkflowEvent.Loaded, e, exec ); return; case WorkflowEventInternal.Completing: NotifyChannels(TrackingWorkflowEvent.Completed, e, exec); NotifyChannelsOfCompletionOrTermination(); return; case WorkflowEventInternal.Aborting: NotifyChannels( TrackingWorkflowEvent.Aborted, e, exec ); return; case WorkflowEventInternal.Terminating: NotifyChannels( TrackingWorkflowEvent.Terminated, e, exec ); NotifyChannelsOfCompletionOrTermination(); return; case WorkflowEventInternal.Exception: NotifyChannels( TrackingWorkflowEvent.Exception, e, exec ); return; case WorkflowEventInternal.SchedulerEmpty: NotifyChannels( TrackingWorkflowEvent.Idle, e, exec ); return; case WorkflowEventInternal.UserTrackPoint: UserTrackPoint( exec, ( WorkflowExecutor.UserTrackPointEventArgs ) e ); return; case WorkflowEventInternal.ActivityStatusChange: ActivityStatusChange( exec, ( WorkflowExecutor.ActivityStatusChangeEventArgs ) e ); return; case WorkflowEventInternal.DynamicChangeBegin: DynamicUpdateBegin( exec, ( WorkflowExecutor.DynamicUpdateEventArgs ) e ); return; case WorkflowEventInternal.DynamicChangeRollback: DynamicUpdateRollback( exec, ( WorkflowExecutor.DynamicUpdateEventArgs ) e ); return; case WorkflowEventInternal.DynamicChangeCommit: DynamicUpdateCommit( exec, ( WorkflowExecutor.DynamicUpdateEventArgs ) e ); return; default: return; } } internal void DynamicUpdateBegin( object sender, WorkflowExecutor.DynamicUpdateEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; // // WorkflowChangeEventArgs may be null or the WorkflowChanges may be null or empty // If so there's no work to do here if ( null == e.ChangeActions ) return; // // Clone the profiles to create instance specific copies (if they aren't already) MakeProfilesPrivate( exec ); // // Give the profiles the changes. At this point we are in a volatile state. // Profiles must act as if the changes will succeed but roll back any internal changes if they do not. foreach ( TrackingChannelWrapper channel in _channels ) { channel.GetTrackingProfile( exec ).WorkflowChangeBegin( e.ChangeActions ); } } internal void DynamicUpdateRollback( object sender, WorkflowExecutor.DynamicUpdateEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; foreach ( TrackingChannelWrapper channel in _channels ) { channel.GetTrackingProfile( exec ).WorkflowChangeRollback(); } } internal void DynamicUpdateCommit( object sender, WorkflowExecutor.DynamicUpdateEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; DateTime dt = DateTime.UtcNow; foreach ( TrackingChannelWrapper channel in _channels ) { channel.GetTrackingProfile( exec ).WorkflowChangeCommit(); } // // Notify tracking channels of changes int eventOrderId = _broker.GetNextEventOrderId(); foreach ( TrackingChannelWrapper wrapper in _channels ) { WorkflowTrackingRecord rec = new WorkflowTrackingRecord( TrackingWorkflowEvent.Changed, dt, eventOrderId, new TrackingWorkflowChangedEventArgs( e.ChangeActions, exec.WorkflowDefinition ) ); if ( wrapper.GetTrackingProfile( exec ).TryTrackInstanceEvent( TrackingWorkflowEvent.Changed, rec ) ) wrapper.TrackingChannel.Send( rec ); } } #endregion Event Handlers #region Private Methods private void NotifyChannels(TrackingWorkflowEvent evt, WorkflowExecutor.WorkflowExecutionEventArgs e, WorkflowExecutor exec) { DateTime dt = DateTime.UtcNow; int eventOrderId = _broker.GetNextEventOrderId(); foreach ( TrackingChannelWrapper wrapper in _channels ) { EventArgs args = null; switch (evt) { case TrackingWorkflowEvent.Suspended: args = new TrackingWorkflowSuspendedEventArgs(((WorkflowExecutor.WorkflowExecutionSuspendingEventArgs)e).Error); break; case TrackingWorkflowEvent.Terminated: WorkflowExecutor.WorkflowExecutionTerminatingEventArgs wtea = ( WorkflowExecutor.WorkflowExecutionTerminatingEventArgs ) e; if ( null != wtea.Exception ) args = new TrackingWorkflowTerminatedEventArgs( wtea.Exception ); else args = new TrackingWorkflowTerminatedEventArgs( wtea.Error ); break; case TrackingWorkflowEvent.Exception: WorkflowExecutor.WorkflowExecutionExceptionEventArgs weea = ( WorkflowExecutor.WorkflowExecutionExceptionEventArgs ) e; args = new TrackingWorkflowExceptionEventArgs( weea.Exception, weea.CurrentPath, weea.OriginalPath, weea.ContextGuid, weea.ParentContextGuid ); break; } WorkflowTrackingRecord rec = new WorkflowTrackingRecord( evt, dt, eventOrderId, args ); if ( wrapper.GetTrackingProfile( exec ).TryTrackInstanceEvent( evt, rec ) ) wrapper.TrackingChannel.Send( rec ); } } private void NotifyChannelsOfCompletionOrTermination() { foreach ( TrackingChannelWrapper wrapper in _channels ) wrapper.TrackingChannel.InstanceCompletedOrTerminated(); } private void GetContext( Activity activity, WorkflowExecutor exec, out Guid contextGuid, out Guid parentContextGuid ) { contextGuid = _factory.GetContext(activity); if (null != activity.Parent) parentContextGuid = _factory.GetContext(activity.Parent); else parentContextGuid = contextGuid; Debug.Assert( contextGuid != Guid.Empty, "TrackingContext is empty" ); Debug.Assert( parentContextGuid != Guid.Empty, "Parent TrackingContext is empty" ); } /// /// Clone all profiles to create private versions in order to hold subscriptions for dynamic changes /// private void MakeProfilesPrivate( WorkflowExecutor exec ) { foreach ( TrackingChannelWrapper channel in _channels ) { channel.MakeProfilePrivate( exec ); _broker.MakeProfilePrivate( channel.TrackingServiceType ); } } ////// Determine if subscriptions are needed /// /// Activity for which to check subscription needs ///private bool SubscriptionRequired(Activity activity, WorkflowExecutor exec) { // // Give each channel a chance to prep itself bool needed = false; foreach ( TrackingChannelWrapper channel in _channels ) { if ( ( channel.GetTrackingProfile( exec ).ActivitySubscriptionNeeded( activity ) ) && ( !needed ) ) needed = true; } return needed; } #endregion } /// /// This is a lightweight class that is serialized so that the TrackingListener doesn't have to be. /// Every subscription that the listener adds holds a reference to this class. /// When an instance is loaded the broker is given to the listener factory and the listener factory /// gives the broker the new listener. This saves us from having to persist the listener itself which /// means that while we do need to persist a list of service types and their profile version we don't /// have to persist the channels themselves (and we can't control how heavy channels get as they are host defined). /// [Serializable] internal class TrackingListenerBroker : System.Runtime.Serialization.ISerializable { [NonSerialized] private TrackingListener _listener = null; private int _eventOrderId = 0; private Dictionary_services = new Dictionary (); internal TrackingListenerBroker() { } internal TrackingListenerBroker(TrackingListener listener) { _listener = listener; } internal TrackingListener TrackingListener { // // FxCops minbar complains because this isn't used. // The Setter is required; seems weird not to have a getter. //get { return _listener; } set { _listener = value; } } internal bool ContainsService( Type trackingServiceType ) { return _services.ContainsKey( HashHelper.HashServiceType( trackingServiceType ) ); } internal void AddService( Type trackingServiceType, Version profileVersionId ) { _services.Add( HashHelper.HashServiceType( trackingServiceType ), new ServiceProfileContainer( profileVersionId ) ); } internal void ReplaceServices(Dictionary replacements) { if (replacements != null && replacements.Count > 0) { ServiceProfileContainer item; foreach (KeyValuePair replacement in replacements) { Guid previous = HashHelper.HashServiceType(replacement.Key); if (_services.TryGetValue(previous, out item)) { _services.Remove(previous); Guid current = HashHelper.HashServiceType(replacement.Value); if (!_services.ContainsKey(current)) { _services.Add(current, item); } } } } } internal void RemoveService(Type trackingServiceType) { _services.Remove( HashHelper.HashServiceType( trackingServiceType ) ); } internal bool TryGetProfileVersionId( Type trackingServiceType, out Version profileVersionId ) { profileVersionId = new Version( 0, 0 ); ServiceProfileContainer service = null; if ( _services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service ) ) { profileVersionId = service.ProfileVersionId; return true; } return false; } internal void MakeProfilePrivate( Type trackingServiceType ) { ServiceProfileContainer service = null; if ( !_services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service)) throw new ArgumentException( ExecutionStringManager.InvalidTrackingService ); service.IsPrivate = true; } internal bool IsProfilePrivate( Type trackingServiceType ) { ServiceProfileContainer service = null; if ( !_services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service ) ) throw new ArgumentException( ExecutionStringManager.InvalidTrackingService ); return service.IsPrivate; } internal void MakeProfileInstance( Type trackingServiceType ) { ServiceProfileContainer service = null; if ( !_services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service ) ) throw new ArgumentException( ExecutionStringManager.InvalidTrackingService ); // // Can't be instance without being private service.IsPrivate = true; service.IsInstance = true; } internal bool IsProfileInstance( Type trackingServiceType ) { ServiceProfileContainer service = null; if ( !_services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service ) ) throw new ArgumentException( ExecutionStringManager.InvalidTrackingService ); return service.IsInstance; } internal int GetNextEventOrderId() { checked { return ++_eventOrderId; } } [Serializable] internal class ServiceProfileContainer { Version _profileVersionId = new Version( 0, 0 ); bool _isPrivate = false; bool _isInstance = false; protected ServiceProfileContainer() { } internal ServiceProfileContainer( Version profileVersionId ) { _profileVersionId = profileVersionId; } internal Version ProfileVersionId { get { return _profileVersionId; } } internal bool IsPrivate { get { return _isPrivate; } set { _isPrivate = value; } } internal bool IsInstance { get { return _isInstance; } set { _isInstance = value; } } } #region ISerializable Members [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { info.AddValue("eventOrderId", this._eventOrderId); info.AddValue("services", this._services.Count == 0 ? null : this._services); } private TrackingListenerBroker(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { this._eventOrderId = info.GetInt32("eventOrderId"); this._services = (Dictionary )info.GetValue("services", typeof(Dictionary )); if(this._services == null) this._services = new Dictionary (); } #endregion } /// /// Manages profile requests, caching profiles and creating RTTrackingProfile instances. /// internal class TrackingProfileManager { // // This is a dictionary keyed by tracking service type // that returns a dictionary that is key by schedule type // that returns a Set of profile versions for that schedule type // The set is constrained by VersionId private Dictionary> _cacheLookup; // // Protects _cacheLookup private object _cacheLock = new object(); // // Values assigned in Initialize private bool _init = false; private List _services = null; private WorkflowRuntime _runtime = null; internal TrackingProfileManager() { } /// /// Clears all entries from the cache by reinitializing the member /// public static void ClearCache() { WorkflowRuntime.ClearTrackingProfileCache(); } internal void ClearCacheImpl() { lock (_cacheLock) { _cacheLookup = new Dictionary>(); } } /// /// Create static state /// /// internal void Initialize(WorkflowRuntime runtime) { lock ( _cacheLock ) { if (null == runtime) throw new ArgumentException( ExecutionStringManager.NullEngine ); _runtime = runtime; // // Initialize the cache // Do this every time the runtime starts/stops to make life easier // for IProfileNotification tracking services that might have updated // profiles while we were stopped - we'll go get new versions since nothing is cached // without them having to fire updated events. _cacheLookup = new Dictionary>(); if (null != runtime.TrackingServices) { _services = runtime.TrackingServices; foreach ( TrackingService service in _services ) { if ( service is IProfileNotification ) { ( ( IProfileNotification ) service ).ProfileUpdated += new EventHandler ( ProfileUpdated ); ( ( IProfileNotification ) service ).ProfileRemoved += new EventHandler ( ProfileRemoved ); } } } _init = true; } } /// /// Clean up static state /// internal void Uninitialize() { lock ( _cacheLock ) { if (null != _runtime) { foreach ( TrackingService service in _services ) { if ( service is IProfileNotification ) { ( ( IProfileNotification ) service ).ProfileUpdated -= new EventHandler( ProfileUpdated ); ( ( IProfileNotification ) service ).ProfileRemoved -= new EventHandler ( ProfileRemoved ); } } } _runtime = null; _services = null; _init = false; } } /// /// Retrieves the current version of a profile from the specified service /// internal RTTrackingProfile GetProfile(TrackingService service, Activity schedule) { if ( !_init ) throw new ApplicationException( ExecutionStringManager.TrackingProfileManagerNotInitialized ); if ( ( null == service ) || ( null == schedule ) ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NullParameters ); return null; } Type workflowType = schedule.GetType(); RTTrackingProfile tp = null; if ( service is IProfileNotification ) { // // If we found the profile in the cache return it, it may be null, this is OK // (no profile for this service type/schedule type combination) if ( TryGetFromCache( service.GetType(), workflowType, out tp ) ) return tp; } // // Either we don't have anything in the cache for this schedule/service combination // or this is a base TrackingService that doesn't notify of profile updates // Get the profile from the service TrackingProfile profile = null; if ( !service.TryGetProfile( workflowType, out profile ) ) { // // No profile for this schedule from this service // RemoveProfile will just mark this service/schedule as not currently having a profile in the cache RemoveProfile( workflowType, service.GetType() ); return null; } // // Check the cache to see if we already have this version // For TrackingService types this is necessary. // For IProfileNotification types this is a bit redundant // but another threadcould have inserted the profile into the cache // so check again before acquiring the writer lock if ( TryGetFromCache( service.GetType(), workflowType, profile.Version, out tp ) ) return tp; // // No profile, create it string xaml = schedule.GetValue(Activity.WorkflowXamlMarkupProperty) as string; if (null != xaml && xaml.Length > 0) { // // Never add xaml only workflows to the cache // Each one must be handled distinctly return CreateProfile(profile, schedule, service.GetType()); } else { tp = CreateProfile(profile, workflowType, service.GetType()); } lock (_cacheLock) { // // Recheck the cache with exclusive access RTTrackingProfile tmp = null; if (TryGetFromCache(service.GetType(), workflowType, profile.Version, out tmp)) return tmp; // // Add it to the cache if (!AddToCache(tp, service.GetType())) throw new ApplicationException(ExecutionStringManager.ProfileCacheInsertFailure); return tp; } } ////// Retrieves the specified version of a profile from the specified service /// internal RTTrackingProfile GetProfile(TrackingService service, Activity workflow, Version versionId) { if ( null == service ) throw new ArgumentNullException( "service" ); if ( null == workflow ) throw new ArgumentNullException( "workflow" ); if ( !_init ) throw new InvalidOperationException( ExecutionStringManager.TrackingProfileManagerNotInitialized ); Type workflowType = workflow.GetType(); RTTrackingProfile tp = null; // // Looking for a specific version, see if it is in the cache if ( TryGetFromCache( service.GetType(), workflowType, versionId, out tp ) ) return tp; TrackingProfile profile = service.GetProfile(workflowType, versionId); // // No profile, create it string xaml = workflow.GetValue(Activity.WorkflowXamlMarkupProperty) as string; if (null != xaml && xaml.Length > 0) { // // Never add xaml only workflows to the cache // Each one must be handled distinctly return CreateProfile(profile, workflow, service.GetType()); } else { tp = CreateProfile(profile, workflowType, service.GetType()); } lock (_cacheLock) { // // Recheck the cache with exclusive access RTTrackingProfile tmp = null; if (TryGetFromCache(service.GetType(), workflowType, versionId, out tmp)) return tmp; // // Add it to the cache if (!AddToCache(tp, service.GetType())) throw new ApplicationException(ExecutionStringManager.ProfileCacheInsertFailure); return tp; } } internal RTTrackingProfile GetProfile(TrackingService service, Activity workflow, Guid instanceId) { // // An instance based profile will never be in the cache TrackingProfile profile = service.GetProfile( instanceId ); if ( null == profile ) return null; return new RTTrackingProfile( profile, workflow, service.GetType() ); } #region Private Methods private RTTrackingProfile CreateProfile( TrackingProfile profile, Type workflowType, Type serviceType ) { // // Can't use the activity definition that we have here, it may have been updated // Get the base definition and use it to create the profile. Activity tmpSchedule = _runtime.GetWorkflowDefinition(workflowType); return new RTTrackingProfile( profile, tmpSchedule, serviceType ); } private RTTrackingProfile CreateProfile(TrackingProfile profile, Activity schedule, Type serviceType) { // // This is called for Xaml only workflows return new RTTrackingProfile(profile, schedule, serviceType); } ////// Add a profile to the cache but do not reset the NoProfiles flag for the schedule type /// /// RTTrackingProfile to add /// TrackingService type ///True if the profile was successfully added; false if not private bool AddToCache( RTTrackingProfile profile, Type serviceType ) { return AddToCache( profile, serviceType, false ); } ////// Adds a profile to the cache and optionally resets the NoProfiles flag for the schedule type /// /// RTTrackingProfile to add /// TrackingService type /// true will reset NoProfiles (to false); false will leave NoProfiles as is ///True if the profile was successfully added; false if not private bool AddToCache( RTTrackingProfile profile, Type serviceType, bool resetNoProfiles ) { // // Profile may be null, serviceType may not if ( null == serviceType ) return false; lock(_cacheLock) { Dictionaryschedules = null; // // Get the dictionary for the service type, // create it if it doesn't exist if ( !_cacheLookup.TryGetValue( serviceType, out schedules ) ) { schedules = new Dictionary (); _cacheLookup.Add( serviceType, schedules ); } // // The the ProfileList for the schedule type, // create it if it doesn't exist ProfileList profiles = null; if ( !schedules.TryGetValue( profile.WorkflowType, out profiles ) ) { profiles = new ProfileList(); schedules.Add( profile.WorkflowType, profiles ); } if ( resetNoProfiles ) profiles.NoProfile = false; return profiles.Profiles.TryAdd( new CacheItem( profile ) ); } } /// /// Gets a profile from the cache /// private bool TryGetFromCache( Type serviceType, Type workflowType, out RTTrackingProfile profile ) { return TryGetFromCache( serviceType, workflowType, new Version( 0, 0 ), out profile ); // 0 is an internal signal to get the most current } ////// Gets a profile from the cache /// private bool TryGetFromCache( Type serviceType, Type workflowType, Version versionId, out RTTrackingProfile profile ) { profile = null; CacheItem item = null; lock(_cacheLock) { Dictionaryschedules = null; if ( !_cacheLookup.TryGetValue( serviceType, out schedules ) ) return false; ProfileList profiles = null; if ( !schedules.TryGetValue( workflowType, out profiles ) ) return false; // // 0 means get the current version if ( 0 == versionId.Major ) { // // Currently the schedule type doesn't have a profile associated to it if ( profiles.NoProfile ) return true; if ( ( null == profiles.Profiles ) || ( 0 == profiles.Profiles.Count ) ) return false; // // Current version is highest versionId // which means it is at the end of the Set int endPos = profiles.Profiles.Count-1; if ( null == profiles.Profiles[endPos] ) return false; profile = profiles.Profiles[endPos].TrackingProfile; return true; } else { if ( ( null == profiles.Profiles ) || ( 0 == profiles.Profiles.Count ) ) return false; if ( profiles.Profiles.TryGetValue( new CacheItem( workflowType, versionId ), out item ) ) { profile = item.TrackingProfile; return true; } else return false; } } } #endregion #region Event Handlers /// /// Listens on ProfileUpdated events from IProfileNotification services /// /// Type of the tracking service sending the update /// ProfileUpdatedEventArgs containing the new profile and the schedule type private void ProfileUpdated( object sender, ProfileUpdatedEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); Type t = sender.GetType(); if ( null == e.WorkflowType ) throw new ArgumentNullException( "e" ); if ( null == e.TrackingProfile ) { RemoveProfile( e.WorkflowType, t ); return; } RTTrackingProfile profile = CreateProfile( e.TrackingProfile, e.WorkflowType, t ); // // If AddToCache fails this version is already in the cache and we don't care AddToCache( profile, t, true ); } private void ProfileRemoved( object sender, ProfileRemovedEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( null == e.WorkflowType ) throw new ArgumentNullException( "e" ); RemoveProfile( e.WorkflowType, sender.GetType() ); } private void RemoveProfile( Type workflowType, Type serviceType ) { lock(_cacheLock) { Dictionaryschedules = null; if ( !_cacheLookup.TryGetValue( serviceType, out schedules ) ) { schedules = new Dictionary (); _cacheLookup.Add( serviceType, schedules ); } ProfileList profiles = null; if ( !schedules.TryGetValue( workflowType, out profiles ) ) { profiles = new ProfileList(); schedules.Add( workflowType, profiles ); } // // Finally indicate that there isn't a profile for this schedule type // Calling UpdateProfile for this type will result in resetting this field // regardless of whether the version of the profile passed is in the cache or not profiles.NoProfile = true; } } #endregion #region Private Classes private class ProfileList { internal bool NoProfile = false; internal Set Profiles = new Set ( 5 ); } private class CacheItem : IComparable { internal RTTrackingProfile TrackingProfile = null; internal DateTime LastAccess = DateTime.UtcNow; // // VersionId and ScheduleType are stored separately from the profile so that they // can be used to identify the profile if it has been pushed from the cache. internal Version VersionId = new Version( 0, 0 ); internal Type ScheduleType = null; internal CacheItem() { } internal CacheItem( RTTrackingProfile profile ) { if ( null == profile ) throw new ArgumentNullException( "profile" ); ScheduleType = profile.WorkflowType; this.TrackingProfile = profile; VersionId = profile.Version; } internal CacheItem( Type workflowType, Version versionId ) { VersionId = versionId; ScheduleType = workflowType; } #region IComparable Members public int CompareTo( object obj ) { if ( !( obj is CacheItem ) ) throw new ArgumentException( ExecutionStringManager.InvalidCacheItem ); CacheItem item = ( CacheItem ) obj; if ( ( VersionId == item.VersionId ) && ( ScheduleType == item.ScheduleType ) ) return 0; else return ( VersionId > item.VersionId ) ? 1 : -1; } #endregion } #endregion } /// /// Represents a wrapper around a channel and its artifacts, such as its tracking service type and profile /// internal class TrackingChannelWrapper { private Type _serviceType = null, _scheduleType = null; private TrackingChannel _channel = null; [NonSerialized] private RTTrackingProfile _profile = null; private Version _profileVersionId; private TrackingChannelWrapper(){} public TrackingChannelWrapper( TrackingChannel channel, Type serviceType, Type workflowType, RTTrackingProfile profile ) { _serviceType = serviceType; _scheduleType = workflowType; _channel = channel; _profile = profile; _profileVersionId = profile.Version; } internal Type TrackingServiceType { get{ return _serviceType; } } internal TrackingChannel TrackingChannel { get{ return _channel; } } ////// Get the tracking profile for the channel /// /// BaseExecutor ///RTTrackingProfile internal RTTrackingProfile GetTrackingProfile(WorkflowExecutor skedExec) { if ( null != _profile ) return _profile; else throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, ExecutionStringManager.NullProfileForChannel, this._scheduleType.AssemblyQualifiedName)); } internal void SetTrackingProfile( RTTrackingProfile profile ) { _profile = profile; } ////// Clone the tracking profile stored in the cache and /// /// internal void MakeProfilePrivate( WorkflowExecutor exec ) { if ( null != _profile ) { // // If the profile is not already a private copy make it so if ( !_profile.IsPrivate ) { _profile = _profile.Clone(); _profile.IsPrivate = true; } } else { // // We're not holding a reference to a profile // so get it from the cache and clone it into a private copy RTTrackingProfile tmp = GetTrackingProfile( exec ); _profile = tmp.Clone(); _profile.IsPrivate = true; } } } internal class Set: IEnumerable where T : IComparable { List list = null; public Set() { list = new List (); } public Set( int capacity ) { list = new List ( capacity ); } public int Count { get { return list.Count; } } public void Add( T item ) { int pos = -1; if ( !Search( item, out pos ) ) list.Insert( pos, item ); else throw new ArgumentException( ExecutionStringManager.ItemAlreadyExist ); } public bool TryAdd( T item ) { int pos = -1; if ( !Search( item, out pos ) ) { list.Insert( pos, item ); return true; } else return false; } public bool Contains( T item ) { int pos = -1; return Search( item, out pos ); } public IEnumerator GetEnumerator() { return list.GetEnumerator(); } System.Collections.IEnumerator IEnumerable.GetEnumerator() { return list.GetEnumerator(); } public bool TryGetValue( T item, out T value ) { int pos = -1; if ( Search( item, out pos ) ) { value = list[pos]; return true; } else { value = default( T ); return false; } } public T this[ int index ] { get { return list[index]; } } private bool Search( T item, out int insertPos ) { insertPos = -1; int pos = 0 , high = list.Count , low = -1 , diff = 0; while ( high - low > 1 ) { pos = ( high + low ) / 2; diff = list[pos].CompareTo( item ); if ( 0 == diff ) { insertPos = pos; return true; } else if ( diff > 0 ) high = pos; else low = pos; } if ( low == -1 ) { insertPos = 0; return false; } if ( 0 != diff ) { insertPos = ( diff < 0 ) ? pos + 1 : pos; return false; } return true; } } /// /// Persisted tracking State pertaining to workflow invoking for an individual schedule. There could be multiple called schedules under /// an instance. /// [Serializable] internal class TrackingCallingState { #region data members private IListcallerActivityPathProxy; private Guid callerInstanceId; private Guid callerContextGuid; private Guid callerParentContextGuid; #endregion data members #region Property accessors /// /// Activity proxy of the caller/execer activity, if any /// //@@Undone for Ashishmi: Hold on to ActivityPath Proxy in one of your class impl /// ///internal IList CallerActivityPathProxy { get { return callerActivityPathProxy; } set { callerActivityPathProxy = value; } } /// /// Instance ID of the caller/exec'er schedule, if any /// ///public Guid CallerWorkflowInstanceId { get { return callerInstanceId; } set { callerInstanceId = value; } } /// /// Context of the caller's invoke activity /// ///int public Guid CallerContextGuid { get { return callerContextGuid; } set { callerContextGuid = value; } } ////// ParentContext of the caller's invoke activity /// ///int public Guid CallerParentContextGuid { get { return callerParentContextGuid; } set { callerParentContextGuid = value; } } #endregion Property accessors } internal static class HashHelper { internal static Guid HashServiceType(Type serviceType) { return HashServiceType(serviceType.AssemblyQualifiedName); } internal static Guid HashServiceType(String serviceFullTypeName) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] data; byte[] result; UnicodeEncoding ue = new UnicodeEncoding(); data = ue.GetBytes(serviceFullTypeName); result = md5.ComputeHash(data); return new Guid(result); } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. using System; using System.Text; using System.Reflection; using System.ComponentModel; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Xml; using System.Xml.XPath; using System.Xml.Schema; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; using System.Timers; using System.Security.Permissions; using System.Security.Cryptography; using System.Workflow.Runtime; using System.Workflow.ComponentModel; using System.Workflow.Runtime.Hosting; using System.Workflow.Runtime.Tracking; namespace System.Workflow.Runtime { ////// Creates TrackingListener instances /// internal class TrackingListenerFactory { private List_services = null; private bool _initialized = false; private Dictionary _listeners = new Dictionary (); private volatile object _listenerLock = new object(); private System.Timers.Timer _timer = null; private double _interval = 60000; private TrackingProfileManager _profileManager = new TrackingProfileManager(); internal TrackingListenerFactory() { } internal TrackingProfileManager TrackingProfileManager { get { return _profileManager; } } /// /// Must be called /// /// internal void Initialize( WorkflowRuntime runtime ) { lock ( this ) { _services = runtime.TrackingServices; _profileManager.Initialize(runtime); runtime.WorkflowExecutorInitializing += WorkflowExecutorInitializing; _timer = new System.Timers.Timer(); _timer.Interval = _interval; _timer.AutoReset = false; // ensure that only one timer thread at a time _timer.Elapsed += new ElapsedEventHandler( Cleanup ); _timer.Start(); _initialized = true; } } ////// Clean up static state created in Initialize /// internal void Uninitialize( WorkflowRuntime runtime ) { lock ( this ) { _profileManager.Uninitialize(); runtime.WorkflowExecutorInitializing -= WorkflowExecutorInitializing; _timer.Elapsed -= new ElapsedEventHandler(Cleanup); _timer.Stop(); _services = null; _initialized = false; _timer.Dispose(); _timer = null; } } ////// Callback for associating tracking listeners to in memory instances. Fires for new and loading instances. /// /// WorkflowExecutor /// void WorkflowExecutorInitializing( object sender, WorkflowRuntime.WorkflowExecutorInitializingEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( null == e ) throw new ArgumentNullException( "e" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; // // Add an event to clean up the WeakRef entry exec.WorkflowExecutionEvent += new EventHandler( WorkflowExecutionEvent ); TrackingCallingState trackingCallingState = exec.TrackingCallingState; TrackingListenerBroker listenerBroker = (TrackingListenerBroker)exec.RootActivity.GetValue(WorkflowExecutor.TrackingListenerBrokerProperty); if (listenerBroker != null) { listenerBroker.ReplaceServices(exec.WorkflowRuntime.TrackingServiceReplacement); } TrackingListener listener = null; // // Check if we still have a weakref to the listener for this instance WeakReference weakref = null; if ( e.Loading ) { bool found = false; lock ( _listenerLock ) { found = _listeners.TryGetValue( exec.InstanceId, out weakref ); } if ( found ) { try { // // Instead of checking IsAlive take a ref to the Target // so that it isn't GC'd underneath us. listener = weakref.Target as TrackingListener; } catch ( InvalidOperationException ) { // // This seems weird but according to msdn // accessing Target can throw ??? // Ignore because it's the same as a null target. } } // // If listener is null because we didn't find the wr in the cache // or because the Target has been GC'd create a new listener if (null != listener) { listener.Broker = listenerBroker; } else { Debug.Assert(null != listenerBroker, "TrackingListenerBroker should not be null during loading"); listener = GetTrackingListener(exec.WorkflowDefinition, exec, listenerBroker); if (null != listener) { if (null != weakref) weakref.Target = listener; else { lock (_listenerLock) { _listeners.Add(exec.ID, new WeakReference(listener)); } } } } } else { // // New instance is being created listener = GetTrackingListener( exec.WorkflowDefinition, exec ); if ( null != listener ) { exec.RootActivity.SetValue(WorkflowExecutor.TrackingListenerBrokerProperty, listener.Broker); lock ( _listenerLock ) { _listeners.Add( exec.ID, new WeakReference( listener ) ); } } else exec.RootActivity.SetValue(WorkflowExecutor.TrackingListenerBrokerProperty, new TrackingListenerBroker()); } if ( null != listener ) { exec.WorkflowExecutionEvent += new EventHandler ( listener.WorkflowExecutionEvent ); } } void WorkflowExecutionEvent( object sender, WorkflowExecutor.WorkflowExecutionEventArgs e ) { switch ( e.EventType ) { case WorkflowEventInternal.Aborted: case WorkflowEventInternal.Completed: case WorkflowEventInternal.Terminated: // // The instance is done - remove // the WeakRef from our list WorkflowExecutor exec = ( WorkflowExecutor ) sender; lock ( _listenerLock ) { _listeners.Remove( exec.ID ); } break; default: return; } } void Cleanup( object sender, ElapsedEventArgs e ) { List _toRemove = new List (); if ( ( null != _listeners ) || ( _listeners.Count > 0 ) ) { lock ( _listenerLock ) { foreach ( KeyValuePair kvp in _listeners ) { if ( null == kvp.Value.Target ) _toRemove.Add( kvp.Key ); } if ( _toRemove.Count > 0 ) { foreach( Guid g in _toRemove ) _listeners.Remove( g ); } } } lock (this) { if (_timer != null) { _timer.Start(); } } } /// /// Return a tracking listener for a new instance /// /// SequentialWorkflow for which the tracking listener will be associated /// ScheduleExecutor for the schedule instance ///New TrackingListener instance internal TrackingListener GetTrackingListener(Activity sked, WorkflowExecutor skedExec) { if ( !_initialized ) Initialize(skedExec.WorkflowRuntime); return GetListener(sked, skedExec, null ); } ////// Return a tracking listener for an existing instance (normally used during loading) /// /// SequentialWorkflow for which the tracking listener will be associated /// ScheduleExecutor for the schedule instance /// TrackingListenerBroker ///New TrackingListener instance internal TrackingListener GetTrackingListener(Activity sked, WorkflowExecutor skedExec, TrackingListenerBroker broker) { if ( !_initialized ) Initialize(skedExec.WorkflowRuntime); if ( null == broker ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NullTrackingBroker ); return null; } return GetListener(sked, skedExec, broker); } private TrackingListener GetListenerFromWRCache( Guid instanceId ) { WeakReference wr = null; TrackingListener listener = null; lock ( _listenerLock ) { if (!_listeners.TryGetValue(instanceId, out wr)) throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.InvariantCulture, ExecutionStringManager.ListenerNotInCache, instanceId)); listener = wr.Target as TrackingListener; if ( null == listener ) throw new ObjectDisposedException(string.Format(System.Globalization.CultureInfo.InvariantCulture, ExecutionStringManager.ListenerNotInCacheDisposed, instanceId)); } return listener; } internal void ReloadProfiles(WorkflowExecutor exec) { // Keep control events from other threads out using ( new ServiceEnvironment( exec.RootActivity ) ) { using ( exec.ExecutorLock.Enter() ) { // check if this is a valid in-memory instance if ( !exec.IsInstanceValid ) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); // suspend the instance bool localSuspend = exec.Suspend(ExecutionStringManager.TrackingProfileUpdate); try { // // Get new profiles TrackingListener listener = GetListenerFromWRCache( exec.InstanceId ); listener.ReloadProfiles(exec, exec.InstanceId); } finally { if ( localSuspend ) { // @undone: for now this will not return till the instance is done // Once Kumar has fixed 4335, we can enable this. exec.Resume(); } } } } } internal void ReloadProfiles( WorkflowExecutor exec, Guid instanceId, ref TrackingListenerBroker broker, ref Listchannels ) { Type workflowType = exec.WorkflowDefinition.GetType(); // // Ask every tracking service if they want to reload // even if they originally returned null for a profile foreach ( TrackingService service in _services ) { TrackingProfile profile = null; TrackingChannelWrapper w = null; // // Check if the service wants to reload a profile if ( service.TryReloadProfile( workflowType, instanceId, out profile ) ) { bool found = false; int i; for ( i = 0; i < channels.Count; i++ ) { if ( service.GetType() == channels[i].TrackingServiceType ) { w = channels[i]; found = true; break; } } // // If we don't have a profile, remove what we had for this service type (if anything) if ( null == profile ) { if ( found ) { broker.RemoveService( w.TrackingServiceType ); channels.RemoveAt( i ); } continue; } // // Parse the new profile - instance only, the cache is not involved RTTrackingProfile rtp = new RTTrackingProfile( profile, exec.WorkflowDefinition, workflowType ); rtp.IsPrivate = true; if ( !found ) { // // This is a new profile, create new channel, channelwrapper and broker item List activityCallPath = null; Guid callerInstanceId = Guid.Empty; TrackingCallingState trackingCallingState = exec.TrackingCallingState; Debug.Assert((null != trackingCallingState), "WorkflowState is null"); IList path = null; Guid context = GetContext(exec.RootActivity), callerContext = Guid.Empty, callerParentContext = Guid.Empty; // // Use CallerActivityPathProxy to determine if this is an invoked instance if(trackingCallingState != null) { path = trackingCallingState.CallerActivityPathProxy; if ((null != path) && (path.Count > 0)) { activityCallPath = new List (path); Debug.Assert(Guid.Empty != trackingCallingState.CallerWorkflowInstanceId, "Instance has an ActivityCallPath but CallerInstanceId is empty"); callerInstanceId = trackingCallingState.CallerWorkflowInstanceId; callerContext = trackingCallingState.CallerContextGuid; callerParentContext = trackingCallingState.CallerParentContextGuid; } } TrackingParameters tp = new TrackingParameters( instanceId, workflowType, exec.WorkflowDefinition, activityCallPath, callerInstanceId, context, callerContext, callerParentContext ); TrackingChannel channel = service.GetTrackingChannel( tp ); TrackingChannelWrapper wrapper = new TrackingChannelWrapper( channel, service.GetType(), workflowType, rtp ); channels.Add( wrapper ); Type t = service.GetType(); broker.AddService( t, rtp.Version ); broker.MakeProfileInstance( t ); } else { // // Don't need to call MakeProfilePrivate on the wrapper // because we've already marked it as private and we already // have a private copy of it. //w.MakeProfilePrivate( exec ); w.SetTrackingProfile( rtp ); broker.MakeProfileInstance( w.TrackingServiceType ); } } } } internal Guid GetContext(Activity activity) { return ((ActivityExecutionContextInfo)ContextActivityUtils.ContextActivity(activity).GetValue(Activity.ActivityExecutionContextInfoProperty)).ContextGuid; } private TrackingListener GetListener(Activity sked, WorkflowExecutor skedExec, TrackingListenerBroker broker) { if ( ( null == sked ) || ( null == skedExec ) ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NullParameters ); return null; } if ( ( null == _services ) || ( _services.Count <= 0 ) ) return null; bool load = ( null != broker ); List channels = GetChannels(sked, skedExec, skedExec.InstanceId, sked.GetType(), ref broker); if ( ( null == channels ) || ( 0 == channels.Count ) ) return null; return new TrackingListener( this, sked, skedExec, channels, broker, load ); } private List GetChannels(Activity schedule, WorkflowExecutor exec, Guid instanceID, Type workflowType, ref TrackingListenerBroker broker) { if ( null == _services ) return null; bool initBroker = false; if ( null == broker ) { broker = new TrackingListenerBroker(); initBroker = true; } List channels = new List (); List activityCallPath = null; Guid callerInstanceId = Guid.Empty; Guid context = GetContext(exec.RootActivity), callerContext = Guid.Empty, callerParentContext = Guid.Empty; Debug.Assert( exec is WorkflowExecutor, "Executor is not WorkflowExecutor" ); TrackingCallingState trackingCallingState = exec.TrackingCallingState; TrackingListenerBroker trackingListenerBroker = (TrackingListenerBroker)exec.RootActivity.GetValue(WorkflowExecutor.TrackingListenerBrokerProperty); IList path = trackingCallingState != null? trackingCallingState.CallerActivityPathProxy : null; // // Use CallerActivityPathProxy to determine if this is an invoked instance if ( ( null != path ) && ( path.Count > 0 ) ) { activityCallPath = new List (path); Debug.Assert(Guid.Empty != trackingCallingState.CallerWorkflowInstanceId, "Instance has an ActivityCallPath but CallerInstanceId is empty"); callerInstanceId = trackingCallingState.CallerWorkflowInstanceId; callerContext = trackingCallingState.CallerContextGuid; callerParentContext = trackingCallingState.CallerParentContextGuid; } TrackingParameters parameters = new TrackingParameters( instanceID, workflowType, exec.WorkflowDefinition, activityCallPath, callerInstanceId, context, callerContext, callerParentContext ); for( int i=0; i<_services.Count; i++ ) { TrackingChannel channel = null; Type serviceType = _services[i].GetType(); // // See if the service has a profile for this schedule type // If not we don't do any tracking for the service // RTTrackingProfile profile = null; // // If we've created the broker get the current version of the profile if ( initBroker ) { profile = _profileManager.GetProfile( _services[i], schedule ); if ( null == profile ) continue; broker.AddService( serviceType, profile.Version ); } else { // // Only reload the services that are in the broker // If services that weren't originally associated to an instance // wish to join that instance they should call ReloadTrackingProfiles if ( !broker.ContainsService( serviceType ) ) continue; if ( broker.IsProfileInstance( serviceType ) ) { profile = _profileManager.GetProfile( _services[i], schedule, instanceID ); if ( null == profile ) throw new InvalidOperationException( ExecutionStringManager.MissingProfileForService + serviceType.ToString() ); profile.IsPrivate = true; } else { Version versionId; if ( broker.TryGetProfileVersionId( serviceType, out versionId ) ) { profile = _profileManager.GetProfile( _services[i], schedule, versionId ); if ( null == profile ) throw new InvalidOperationException(ExecutionStringManager.MissingProfileForService + serviceType.ToString() + ExecutionStringManager.MissingProfileForVersion + versionId.ToString()); // // If the profile is marked as private clone the instance we got from the cache // The cloned instance is marked as private during the cloning if ( broker.IsProfilePrivate( serviceType ) ) { profile = profile.Clone(); profile.IsPrivate = true; } } else continue; } } // // If profile is not null get a channel channel = _services[i].GetTrackingChannel( parameters ); if ( null == channel ) throw new InvalidOperationException( ExecutionStringManager.NullChannel ); channels.Add( new TrackingChannelWrapper( channel, _services[i].GetType(), workflowType, profile ) ); } return channels; } } /// /// Handles subscribing to status change events and receiving event notifications. /// internal class TrackingListener { private List_channels = null; private TrackingListenerBroker _broker = null; private TrackingListenerFactory _factory = null; protected TrackingListener() { } internal TrackingListener(TrackingListenerFactory factory, Activity sked, WorkflowExecutor exec, List channels, TrackingListenerBroker broker, bool load) { if ( ( null == sked ) || ( null == broker ) ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NullParameters ); return; } _factory = factory; _channels = channels; // // Keep a reference to the broker so that we can hand it out when adding subscriptions _broker = broker; // // Give the broker our reference so that it can call us back on behalf of subscriptions _broker.TrackingListener = this; } internal TrackingListenerBroker Broker { get { return _broker; } set { _broker = value; } } internal void ReloadProfiles( WorkflowExecutor exec, Guid instanceId ) { // // Ask the factory to redo the channels and broker _factory.ReloadProfiles( exec, instanceId, ref _broker, ref _channels ); } #region Event Handlers internal void ActivityStatusChange( object sender, WorkflowExecutor.ActivityStatusChangeEventArgs e ) { WorkflowTrace.Tracking.TraceInformation("TrackingListener::ActivityStatusChange - Received Activity Status Change Event for activity {0}", e.Activity.QualifiedName); if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); if ( null == e ) throw new ArgumentNullException( "e" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; if ( ( null == _channels ) || ( _channels.Count <= 0 ) ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NoChannels ); return; } Activity activity = e.Activity; if ( !SubscriptionRequired( activity, exec ) ) return; // // Get the shared data that is the same for each tracking channel that gets a record Guid parentContextGuid = Guid.Empty, contextGuid = Guid.Empty; GetContext(activity, exec, out contextGuid, out parentContextGuid); DateTime dt = DateTime.UtcNow; int eventOrderId = _broker.GetNextEventOrderId(); foreach ( TrackingChannelWrapper wrapper in _channels ) { // // Create a record for each tracking channel // Each channel gets a distinct record because extract data will almost always be different. ActivityTrackingRecord record = new ActivityTrackingRecord( activity.GetType(), activity.QualifiedName, contextGuid, parentContextGuid, activity.ExecutionStatus, dt, eventOrderId, null ); bool extracted = wrapper.GetTrackingProfile(exec).TryTrackActivityEvent(activity, activity.ExecutionStatus, exec, record); // // Only send the record to the channel if the profile indicates that it is interested // This doesn't mean that the Body will always have data in it, // it may be an empty extraction (just header info) if (extracted) wrapper.TrackingChannel.Send(record); } } internal void UserTrackPoint( object sender, WorkflowExecutor.UserTrackPointEventArgs e ) { if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender is not WorkflowExecutor" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; Activity activity = e.Activity; DateTime dt = DateTime.UtcNow; int eventOrderId = _broker.GetNextEventOrderId(); Guid parentContextGuid, contextGuid; GetContext(activity, exec, out contextGuid, out parentContextGuid); foreach ( TrackingChannelWrapper wrapper in _channels ) { UserTrackingRecord record = new UserTrackingRecord( activity.GetType(), activity.QualifiedName, contextGuid, parentContextGuid, dt, eventOrderId, e.Key, e.Args ); if (wrapper.GetTrackingProfile(exec).TryTrackUserEvent(activity, e.Key, e.Args, exec, record)) wrapper.TrackingChannel.Send( record ); } } internal void WorkflowExecutionEvent( object sender, WorkflowExecutor.WorkflowExecutionEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); WorkflowExecutor exec = sender as WorkflowExecutor; if ( null == exec ) throw new ArgumentException( ExecutionStringManager.InvalidSenderWorkflowExecutor ); // // Many events are mapped "forward" and sent to tracking services // (Persisting->Persisted, SchedulerEmpty->Idle) // This is so that a batch is always available when a tracking service gets an event. // Without this tracking data could be inconsistent with the state of the instance. switch ( e.EventType ) { case WorkflowEventInternal.Creating: NotifyChannels( TrackingWorkflowEvent.Created, e, exec ); return; case WorkflowEventInternal.Starting: NotifyChannels(TrackingWorkflowEvent.Started, e, exec); return; case WorkflowEventInternal.Suspending: NotifyChannels(TrackingWorkflowEvent.Suspended, e, exec); return; case WorkflowEventInternal.Resuming: NotifyChannels( TrackingWorkflowEvent.Resumed, e, exec ); return; case WorkflowEventInternal.Persisting: NotifyChannels( TrackingWorkflowEvent.Persisted, e, exec ); return; case WorkflowEventInternal.Unloading: NotifyChannels( TrackingWorkflowEvent.Unloaded, e, exec ); return; case WorkflowEventInternal.Loading: NotifyChannels( TrackingWorkflowEvent.Loaded, e, exec ); return; case WorkflowEventInternal.Completing: NotifyChannels(TrackingWorkflowEvent.Completed, e, exec); NotifyChannelsOfCompletionOrTermination(); return; case WorkflowEventInternal.Aborting: NotifyChannels( TrackingWorkflowEvent.Aborted, e, exec ); return; case WorkflowEventInternal.Terminating: NotifyChannels( TrackingWorkflowEvent.Terminated, e, exec ); NotifyChannelsOfCompletionOrTermination(); return; case WorkflowEventInternal.Exception: NotifyChannels( TrackingWorkflowEvent.Exception, e, exec ); return; case WorkflowEventInternal.SchedulerEmpty: NotifyChannels( TrackingWorkflowEvent.Idle, e, exec ); return; case WorkflowEventInternal.UserTrackPoint: UserTrackPoint( exec, ( WorkflowExecutor.UserTrackPointEventArgs ) e ); return; case WorkflowEventInternal.ActivityStatusChange: ActivityStatusChange( exec, ( WorkflowExecutor.ActivityStatusChangeEventArgs ) e ); return; case WorkflowEventInternal.DynamicChangeBegin: DynamicUpdateBegin( exec, ( WorkflowExecutor.DynamicUpdateEventArgs ) e ); return; case WorkflowEventInternal.DynamicChangeRollback: DynamicUpdateRollback( exec, ( WorkflowExecutor.DynamicUpdateEventArgs ) e ); return; case WorkflowEventInternal.DynamicChangeCommit: DynamicUpdateCommit( exec, ( WorkflowExecutor.DynamicUpdateEventArgs ) e ); return; default: return; } } internal void DynamicUpdateBegin( object sender, WorkflowExecutor.DynamicUpdateEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; // // WorkflowChangeEventArgs may be null or the WorkflowChanges may be null or empty // If so there's no work to do here if ( null == e.ChangeActions ) return; // // Clone the profiles to create instance specific copies (if they aren't already) MakeProfilesPrivate( exec ); // // Give the profiles the changes. At this point we are in a volatile state. // Profiles must act as if the changes will succeed but roll back any internal changes if they do not. foreach ( TrackingChannelWrapper channel in _channels ) { channel.GetTrackingProfile( exec ).WorkflowChangeBegin( e.ChangeActions ); } } internal void DynamicUpdateRollback( object sender, WorkflowExecutor.DynamicUpdateEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; foreach ( TrackingChannelWrapper channel in _channels ) { channel.GetTrackingProfile( exec ).WorkflowChangeRollback(); } } internal void DynamicUpdateCommit( object sender, WorkflowExecutor.DynamicUpdateEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( !typeof( WorkflowExecutor ).IsInstanceOfType( sender ) ) throw new ArgumentException( "sender" ); WorkflowExecutor exec = ( WorkflowExecutor ) sender; DateTime dt = DateTime.UtcNow; foreach ( TrackingChannelWrapper channel in _channels ) { channel.GetTrackingProfile( exec ).WorkflowChangeCommit(); } // // Notify tracking channels of changes int eventOrderId = _broker.GetNextEventOrderId(); foreach ( TrackingChannelWrapper wrapper in _channels ) { WorkflowTrackingRecord rec = new WorkflowTrackingRecord( TrackingWorkflowEvent.Changed, dt, eventOrderId, new TrackingWorkflowChangedEventArgs( e.ChangeActions, exec.WorkflowDefinition ) ); if ( wrapper.GetTrackingProfile( exec ).TryTrackInstanceEvent( TrackingWorkflowEvent.Changed, rec ) ) wrapper.TrackingChannel.Send( rec ); } } #endregion Event Handlers #region Private Methods private void NotifyChannels(TrackingWorkflowEvent evt, WorkflowExecutor.WorkflowExecutionEventArgs e, WorkflowExecutor exec) { DateTime dt = DateTime.UtcNow; int eventOrderId = _broker.GetNextEventOrderId(); foreach ( TrackingChannelWrapper wrapper in _channels ) { EventArgs args = null; switch (evt) { case TrackingWorkflowEvent.Suspended: args = new TrackingWorkflowSuspendedEventArgs(((WorkflowExecutor.WorkflowExecutionSuspendingEventArgs)e).Error); break; case TrackingWorkflowEvent.Terminated: WorkflowExecutor.WorkflowExecutionTerminatingEventArgs wtea = ( WorkflowExecutor.WorkflowExecutionTerminatingEventArgs ) e; if ( null != wtea.Exception ) args = new TrackingWorkflowTerminatedEventArgs( wtea.Exception ); else args = new TrackingWorkflowTerminatedEventArgs( wtea.Error ); break; case TrackingWorkflowEvent.Exception: WorkflowExecutor.WorkflowExecutionExceptionEventArgs weea = ( WorkflowExecutor.WorkflowExecutionExceptionEventArgs ) e; args = new TrackingWorkflowExceptionEventArgs( weea.Exception, weea.CurrentPath, weea.OriginalPath, weea.ContextGuid, weea.ParentContextGuid ); break; } WorkflowTrackingRecord rec = new WorkflowTrackingRecord( evt, dt, eventOrderId, args ); if ( wrapper.GetTrackingProfile( exec ).TryTrackInstanceEvent( evt, rec ) ) wrapper.TrackingChannel.Send( rec ); } } private void NotifyChannelsOfCompletionOrTermination() { foreach ( TrackingChannelWrapper wrapper in _channels ) wrapper.TrackingChannel.InstanceCompletedOrTerminated(); } private void GetContext( Activity activity, WorkflowExecutor exec, out Guid contextGuid, out Guid parentContextGuid ) { contextGuid = _factory.GetContext(activity); if (null != activity.Parent) parentContextGuid = _factory.GetContext(activity.Parent); else parentContextGuid = contextGuid; Debug.Assert( contextGuid != Guid.Empty, "TrackingContext is empty" ); Debug.Assert( parentContextGuid != Guid.Empty, "Parent TrackingContext is empty" ); } /// /// Clone all profiles to create private versions in order to hold subscriptions for dynamic changes /// private void MakeProfilesPrivate( WorkflowExecutor exec ) { foreach ( TrackingChannelWrapper channel in _channels ) { channel.MakeProfilePrivate( exec ); _broker.MakeProfilePrivate( channel.TrackingServiceType ); } } ////// Determine if subscriptions are needed /// /// Activity for which to check subscription needs ///private bool SubscriptionRequired(Activity activity, WorkflowExecutor exec) { // // Give each channel a chance to prep itself bool needed = false; foreach ( TrackingChannelWrapper channel in _channels ) { if ( ( channel.GetTrackingProfile( exec ).ActivitySubscriptionNeeded( activity ) ) && ( !needed ) ) needed = true; } return needed; } #endregion } /// /// This is a lightweight class that is serialized so that the TrackingListener doesn't have to be. /// Every subscription that the listener adds holds a reference to this class. /// When an instance is loaded the broker is given to the listener factory and the listener factory /// gives the broker the new listener. This saves us from having to persist the listener itself which /// means that while we do need to persist a list of service types and their profile version we don't /// have to persist the channels themselves (and we can't control how heavy channels get as they are host defined). /// [Serializable] internal class TrackingListenerBroker : System.Runtime.Serialization.ISerializable { [NonSerialized] private TrackingListener _listener = null; private int _eventOrderId = 0; private Dictionary_services = new Dictionary (); internal TrackingListenerBroker() { } internal TrackingListenerBroker(TrackingListener listener) { _listener = listener; } internal TrackingListener TrackingListener { // // FxCops minbar complains because this isn't used. // The Setter is required; seems weird not to have a getter. //get { return _listener; } set { _listener = value; } } internal bool ContainsService( Type trackingServiceType ) { return _services.ContainsKey( HashHelper.HashServiceType( trackingServiceType ) ); } internal void AddService( Type trackingServiceType, Version profileVersionId ) { _services.Add( HashHelper.HashServiceType( trackingServiceType ), new ServiceProfileContainer( profileVersionId ) ); } internal void ReplaceServices(Dictionary replacements) { if (replacements != null && replacements.Count > 0) { ServiceProfileContainer item; foreach (KeyValuePair replacement in replacements) { Guid previous = HashHelper.HashServiceType(replacement.Key); if (_services.TryGetValue(previous, out item)) { _services.Remove(previous); Guid current = HashHelper.HashServiceType(replacement.Value); if (!_services.ContainsKey(current)) { _services.Add(current, item); } } } } } internal void RemoveService(Type trackingServiceType) { _services.Remove( HashHelper.HashServiceType( trackingServiceType ) ); } internal bool TryGetProfileVersionId( Type trackingServiceType, out Version profileVersionId ) { profileVersionId = new Version( 0, 0 ); ServiceProfileContainer service = null; if ( _services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service ) ) { profileVersionId = service.ProfileVersionId; return true; } return false; } internal void MakeProfilePrivate( Type trackingServiceType ) { ServiceProfileContainer service = null; if ( !_services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service)) throw new ArgumentException( ExecutionStringManager.InvalidTrackingService ); service.IsPrivate = true; } internal bool IsProfilePrivate( Type trackingServiceType ) { ServiceProfileContainer service = null; if ( !_services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service ) ) throw new ArgumentException( ExecutionStringManager.InvalidTrackingService ); return service.IsPrivate; } internal void MakeProfileInstance( Type trackingServiceType ) { ServiceProfileContainer service = null; if ( !_services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service ) ) throw new ArgumentException( ExecutionStringManager.InvalidTrackingService ); // // Can't be instance without being private service.IsPrivate = true; service.IsInstance = true; } internal bool IsProfileInstance( Type trackingServiceType ) { ServiceProfileContainer service = null; if ( !_services.TryGetValue( HashHelper.HashServiceType( trackingServiceType ), out service ) ) throw new ArgumentException( ExecutionStringManager.InvalidTrackingService ); return service.IsInstance; } internal int GetNextEventOrderId() { checked { return ++_eventOrderId; } } [Serializable] internal class ServiceProfileContainer { Version _profileVersionId = new Version( 0, 0 ); bool _isPrivate = false; bool _isInstance = false; protected ServiceProfileContainer() { } internal ServiceProfileContainer( Version profileVersionId ) { _profileVersionId = profileVersionId; } internal Version ProfileVersionId { get { return _profileVersionId; } } internal bool IsPrivate { get { return _isPrivate; } set { _isPrivate = value; } } internal bool IsInstance { get { return _isInstance; } set { _isInstance = value; } } } #region ISerializable Members [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { info.AddValue("eventOrderId", this._eventOrderId); info.AddValue("services", this._services.Count == 0 ? null : this._services); } private TrackingListenerBroker(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { this._eventOrderId = info.GetInt32("eventOrderId"); this._services = (Dictionary )info.GetValue("services", typeof(Dictionary )); if(this._services == null) this._services = new Dictionary (); } #endregion } /// /// Manages profile requests, caching profiles and creating RTTrackingProfile instances. /// internal class TrackingProfileManager { // // This is a dictionary keyed by tracking service type // that returns a dictionary that is key by schedule type // that returns a Set of profile versions for that schedule type // The set is constrained by VersionId private Dictionary> _cacheLookup; // // Protects _cacheLookup private object _cacheLock = new object(); // // Values assigned in Initialize private bool _init = false; private List _services = null; private WorkflowRuntime _runtime = null; internal TrackingProfileManager() { } /// /// Clears all entries from the cache by reinitializing the member /// public static void ClearCache() { WorkflowRuntime.ClearTrackingProfileCache(); } internal void ClearCacheImpl() { lock (_cacheLock) { _cacheLookup = new Dictionary>(); } } /// /// Create static state /// /// internal void Initialize(WorkflowRuntime runtime) { lock ( _cacheLock ) { if (null == runtime) throw new ArgumentException( ExecutionStringManager.NullEngine ); _runtime = runtime; // // Initialize the cache // Do this every time the runtime starts/stops to make life easier // for IProfileNotification tracking services that might have updated // profiles while we were stopped - we'll go get new versions since nothing is cached // without them having to fire updated events. _cacheLookup = new Dictionary>(); if (null != runtime.TrackingServices) { _services = runtime.TrackingServices; foreach ( TrackingService service in _services ) { if ( service is IProfileNotification ) { ( ( IProfileNotification ) service ).ProfileUpdated += new EventHandler ( ProfileUpdated ); ( ( IProfileNotification ) service ).ProfileRemoved += new EventHandler ( ProfileRemoved ); } } } _init = true; } } /// /// Clean up static state /// internal void Uninitialize() { lock ( _cacheLock ) { if (null != _runtime) { foreach ( TrackingService service in _services ) { if ( service is IProfileNotification ) { ( ( IProfileNotification ) service ).ProfileUpdated -= new EventHandler( ProfileUpdated ); ( ( IProfileNotification ) service ).ProfileRemoved -= new EventHandler ( ProfileRemoved ); } } } _runtime = null; _services = null; _init = false; } } /// /// Retrieves the current version of a profile from the specified service /// internal RTTrackingProfile GetProfile(TrackingService service, Activity schedule) { if ( !_init ) throw new ApplicationException( ExecutionStringManager.TrackingProfileManagerNotInitialized ); if ( ( null == service ) || ( null == schedule ) ) { WorkflowTrace.Tracking.TraceEvent( TraceEventType.Error, 0, ExecutionStringManager.NullParameters ); return null; } Type workflowType = schedule.GetType(); RTTrackingProfile tp = null; if ( service is IProfileNotification ) { // // If we found the profile in the cache return it, it may be null, this is OK // (no profile for this service type/schedule type combination) if ( TryGetFromCache( service.GetType(), workflowType, out tp ) ) return tp; } // // Either we don't have anything in the cache for this schedule/service combination // or this is a base TrackingService that doesn't notify of profile updates // Get the profile from the service TrackingProfile profile = null; if ( !service.TryGetProfile( workflowType, out profile ) ) { // // No profile for this schedule from this service // RemoveProfile will just mark this service/schedule as not currently having a profile in the cache RemoveProfile( workflowType, service.GetType() ); return null; } // // Check the cache to see if we already have this version // For TrackingService types this is necessary. // For IProfileNotification types this is a bit redundant // but another threadcould have inserted the profile into the cache // so check again before acquiring the writer lock if ( TryGetFromCache( service.GetType(), workflowType, profile.Version, out tp ) ) return tp; // // No profile, create it string xaml = schedule.GetValue(Activity.WorkflowXamlMarkupProperty) as string; if (null != xaml && xaml.Length > 0) { // // Never add xaml only workflows to the cache // Each one must be handled distinctly return CreateProfile(profile, schedule, service.GetType()); } else { tp = CreateProfile(profile, workflowType, service.GetType()); } lock (_cacheLock) { // // Recheck the cache with exclusive access RTTrackingProfile tmp = null; if (TryGetFromCache(service.GetType(), workflowType, profile.Version, out tmp)) return tmp; // // Add it to the cache if (!AddToCache(tp, service.GetType())) throw new ApplicationException(ExecutionStringManager.ProfileCacheInsertFailure); return tp; } } ////// Retrieves the specified version of a profile from the specified service /// internal RTTrackingProfile GetProfile(TrackingService service, Activity workflow, Version versionId) { if ( null == service ) throw new ArgumentNullException( "service" ); if ( null == workflow ) throw new ArgumentNullException( "workflow" ); if ( !_init ) throw new InvalidOperationException( ExecutionStringManager.TrackingProfileManagerNotInitialized ); Type workflowType = workflow.GetType(); RTTrackingProfile tp = null; // // Looking for a specific version, see if it is in the cache if ( TryGetFromCache( service.GetType(), workflowType, versionId, out tp ) ) return tp; TrackingProfile profile = service.GetProfile(workflowType, versionId); // // No profile, create it string xaml = workflow.GetValue(Activity.WorkflowXamlMarkupProperty) as string; if (null != xaml && xaml.Length > 0) { // // Never add xaml only workflows to the cache // Each one must be handled distinctly return CreateProfile(profile, workflow, service.GetType()); } else { tp = CreateProfile(profile, workflowType, service.GetType()); } lock (_cacheLock) { // // Recheck the cache with exclusive access RTTrackingProfile tmp = null; if (TryGetFromCache(service.GetType(), workflowType, versionId, out tmp)) return tmp; // // Add it to the cache if (!AddToCache(tp, service.GetType())) throw new ApplicationException(ExecutionStringManager.ProfileCacheInsertFailure); return tp; } } internal RTTrackingProfile GetProfile(TrackingService service, Activity workflow, Guid instanceId) { // // An instance based profile will never be in the cache TrackingProfile profile = service.GetProfile( instanceId ); if ( null == profile ) return null; return new RTTrackingProfile( profile, workflow, service.GetType() ); } #region Private Methods private RTTrackingProfile CreateProfile( TrackingProfile profile, Type workflowType, Type serviceType ) { // // Can't use the activity definition that we have here, it may have been updated // Get the base definition and use it to create the profile. Activity tmpSchedule = _runtime.GetWorkflowDefinition(workflowType); return new RTTrackingProfile( profile, tmpSchedule, serviceType ); } private RTTrackingProfile CreateProfile(TrackingProfile profile, Activity schedule, Type serviceType) { // // This is called for Xaml only workflows return new RTTrackingProfile(profile, schedule, serviceType); } ////// Add a profile to the cache but do not reset the NoProfiles flag for the schedule type /// /// RTTrackingProfile to add /// TrackingService type ///True if the profile was successfully added; false if not private bool AddToCache( RTTrackingProfile profile, Type serviceType ) { return AddToCache( profile, serviceType, false ); } ////// Adds a profile to the cache and optionally resets the NoProfiles flag for the schedule type /// /// RTTrackingProfile to add /// TrackingService type /// true will reset NoProfiles (to false); false will leave NoProfiles as is ///True if the profile was successfully added; false if not private bool AddToCache( RTTrackingProfile profile, Type serviceType, bool resetNoProfiles ) { // // Profile may be null, serviceType may not if ( null == serviceType ) return false; lock(_cacheLock) { Dictionaryschedules = null; // // Get the dictionary for the service type, // create it if it doesn't exist if ( !_cacheLookup.TryGetValue( serviceType, out schedules ) ) { schedules = new Dictionary (); _cacheLookup.Add( serviceType, schedules ); } // // The the ProfileList for the schedule type, // create it if it doesn't exist ProfileList profiles = null; if ( !schedules.TryGetValue( profile.WorkflowType, out profiles ) ) { profiles = new ProfileList(); schedules.Add( profile.WorkflowType, profiles ); } if ( resetNoProfiles ) profiles.NoProfile = false; return profiles.Profiles.TryAdd( new CacheItem( profile ) ); } } /// /// Gets a profile from the cache /// private bool TryGetFromCache( Type serviceType, Type workflowType, out RTTrackingProfile profile ) { return TryGetFromCache( serviceType, workflowType, new Version( 0, 0 ), out profile ); // 0 is an internal signal to get the most current } ////// Gets a profile from the cache /// private bool TryGetFromCache( Type serviceType, Type workflowType, Version versionId, out RTTrackingProfile profile ) { profile = null; CacheItem item = null; lock(_cacheLock) { Dictionaryschedules = null; if ( !_cacheLookup.TryGetValue( serviceType, out schedules ) ) return false; ProfileList profiles = null; if ( !schedules.TryGetValue( workflowType, out profiles ) ) return false; // // 0 means get the current version if ( 0 == versionId.Major ) { // // Currently the schedule type doesn't have a profile associated to it if ( profiles.NoProfile ) return true; if ( ( null == profiles.Profiles ) || ( 0 == profiles.Profiles.Count ) ) return false; // // Current version is highest versionId // which means it is at the end of the Set int endPos = profiles.Profiles.Count-1; if ( null == profiles.Profiles[endPos] ) return false; profile = profiles.Profiles[endPos].TrackingProfile; return true; } else { if ( ( null == profiles.Profiles ) || ( 0 == profiles.Profiles.Count ) ) return false; if ( profiles.Profiles.TryGetValue( new CacheItem( workflowType, versionId ), out item ) ) { profile = item.TrackingProfile; return true; } else return false; } } } #endregion #region Event Handlers /// /// Listens on ProfileUpdated events from IProfileNotification services /// /// Type of the tracking service sending the update /// ProfileUpdatedEventArgs containing the new profile and the schedule type private void ProfileUpdated( object sender, ProfileUpdatedEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); Type t = sender.GetType(); if ( null == e.WorkflowType ) throw new ArgumentNullException( "e" ); if ( null == e.TrackingProfile ) { RemoveProfile( e.WorkflowType, t ); return; } RTTrackingProfile profile = CreateProfile( e.TrackingProfile, e.WorkflowType, t ); // // If AddToCache fails this version is already in the cache and we don't care AddToCache( profile, t, true ); } private void ProfileRemoved( object sender, ProfileRemovedEventArgs e ) { if ( null == sender ) throw new ArgumentNullException( "sender" ); if ( null == e.WorkflowType ) throw new ArgumentNullException( "e" ); RemoveProfile( e.WorkflowType, sender.GetType() ); } private void RemoveProfile( Type workflowType, Type serviceType ) { lock(_cacheLock) { Dictionaryschedules = null; if ( !_cacheLookup.TryGetValue( serviceType, out schedules ) ) { schedules = new Dictionary (); _cacheLookup.Add( serviceType, schedules ); } ProfileList profiles = null; if ( !schedules.TryGetValue( workflowType, out profiles ) ) { profiles = new ProfileList(); schedules.Add( workflowType, profiles ); } // // Finally indicate that there isn't a profile for this schedule type // Calling UpdateProfile for this type will result in resetting this field // regardless of whether the version of the profile passed is in the cache or not profiles.NoProfile = true; } } #endregion #region Private Classes private class ProfileList { internal bool NoProfile = false; internal Set Profiles = new Set ( 5 ); } private class CacheItem : IComparable { internal RTTrackingProfile TrackingProfile = null; internal DateTime LastAccess = DateTime.UtcNow; // // VersionId and ScheduleType are stored separately from the profile so that they // can be used to identify the profile if it has been pushed from the cache. internal Version VersionId = new Version( 0, 0 ); internal Type ScheduleType = null; internal CacheItem() { } internal CacheItem( RTTrackingProfile profile ) { if ( null == profile ) throw new ArgumentNullException( "profile" ); ScheduleType = profile.WorkflowType; this.TrackingProfile = profile; VersionId = profile.Version; } internal CacheItem( Type workflowType, Version versionId ) { VersionId = versionId; ScheduleType = workflowType; } #region IComparable Members public int CompareTo( object obj ) { if ( !( obj is CacheItem ) ) throw new ArgumentException( ExecutionStringManager.InvalidCacheItem ); CacheItem item = ( CacheItem ) obj; if ( ( VersionId == item.VersionId ) && ( ScheduleType == item.ScheduleType ) ) return 0; else return ( VersionId > item.VersionId ) ? 1 : -1; } #endregion } #endregion } /// /// Represents a wrapper around a channel and its artifacts, such as its tracking service type and profile /// internal class TrackingChannelWrapper { private Type _serviceType = null, _scheduleType = null; private TrackingChannel _channel = null; [NonSerialized] private RTTrackingProfile _profile = null; private Version _profileVersionId; private TrackingChannelWrapper(){} public TrackingChannelWrapper( TrackingChannel channel, Type serviceType, Type workflowType, RTTrackingProfile profile ) { _serviceType = serviceType; _scheduleType = workflowType; _channel = channel; _profile = profile; _profileVersionId = profile.Version; } internal Type TrackingServiceType { get{ return _serviceType; } } internal TrackingChannel TrackingChannel { get{ return _channel; } } ////// Get the tracking profile for the channel /// /// BaseExecutor ///RTTrackingProfile internal RTTrackingProfile GetTrackingProfile(WorkflowExecutor skedExec) { if ( null != _profile ) return _profile; else throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, ExecutionStringManager.NullProfileForChannel, this._scheduleType.AssemblyQualifiedName)); } internal void SetTrackingProfile( RTTrackingProfile profile ) { _profile = profile; } ////// Clone the tracking profile stored in the cache and /// /// internal void MakeProfilePrivate( WorkflowExecutor exec ) { if ( null != _profile ) { // // If the profile is not already a private copy make it so if ( !_profile.IsPrivate ) { _profile = _profile.Clone(); _profile.IsPrivate = true; } } else { // // We're not holding a reference to a profile // so get it from the cache and clone it into a private copy RTTrackingProfile tmp = GetTrackingProfile( exec ); _profile = tmp.Clone(); _profile.IsPrivate = true; } } } internal class Set: IEnumerable where T : IComparable { List list = null; public Set() { list = new List (); } public Set( int capacity ) { list = new List ( capacity ); } public int Count { get { return list.Count; } } public void Add( T item ) { int pos = -1; if ( !Search( item, out pos ) ) list.Insert( pos, item ); else throw new ArgumentException( ExecutionStringManager.ItemAlreadyExist ); } public bool TryAdd( T item ) { int pos = -1; if ( !Search( item, out pos ) ) { list.Insert( pos, item ); return true; } else return false; } public bool Contains( T item ) { int pos = -1; return Search( item, out pos ); } public IEnumerator GetEnumerator() { return list.GetEnumerator(); } System.Collections.IEnumerator IEnumerable.GetEnumerator() { return list.GetEnumerator(); } public bool TryGetValue( T item, out T value ) { int pos = -1; if ( Search( item, out pos ) ) { value = list[pos]; return true; } else { value = default( T ); return false; } } public T this[ int index ] { get { return list[index]; } } private bool Search( T item, out int insertPos ) { insertPos = -1; int pos = 0 , high = list.Count , low = -1 , diff = 0; while ( high - low > 1 ) { pos = ( high + low ) / 2; diff = list[pos].CompareTo( item ); if ( 0 == diff ) { insertPos = pos; return true; } else if ( diff > 0 ) high = pos; else low = pos; } if ( low == -1 ) { insertPos = 0; return false; } if ( 0 != diff ) { insertPos = ( diff < 0 ) ? pos + 1 : pos; return false; } return true; } } /// /// Persisted tracking State pertaining to workflow invoking for an individual schedule. There could be multiple called schedules under /// an instance. /// [Serializable] internal class TrackingCallingState { #region data members private IListcallerActivityPathProxy; private Guid callerInstanceId; private Guid callerContextGuid; private Guid callerParentContextGuid; #endregion data members #region Property accessors /// /// Activity proxy of the caller/execer activity, if any /// //@@Undone for Ashishmi: Hold on to ActivityPath Proxy in one of your class impl /// ///internal IList CallerActivityPathProxy { get { return callerActivityPathProxy; } set { callerActivityPathProxy = value; } } /// /// Instance ID of the caller/exec'er schedule, if any /// ///public Guid CallerWorkflowInstanceId { get { return callerInstanceId; } set { callerInstanceId = value; } } /// /// Context of the caller's invoke activity /// ///int public Guid CallerContextGuid { get { return callerContextGuid; } set { callerContextGuid = value; } } ////// ParentContext of the caller's invoke activity /// ///int public Guid CallerParentContextGuid { get { return callerParentContextGuid; } set { callerParentContextGuid = value; } } #endregion Property accessors } internal static class HashHelper { internal static Guid HashServiceType(Type serviceType) { return HashServiceType(serviceType.AssemblyQualifiedName); } internal static Guid HashServiceType(String serviceFullTypeName) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] data; byte[] result; UnicodeEncoding ue = new UnicodeEncoding(); data = ue.GetBytes(serviceFullTypeName); result = md5.ComputeHash(data); return new Guid(result); } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- MessageBuilder.cs
- WebOperationContext.cs
- MembershipAdapter.cs
- UndirectedGraph.cs
- XmlSchemaAttributeGroup.cs
- CapiSymmetricAlgorithm.cs
- ColumnResizeUndoUnit.cs
- LassoSelectionBehavior.cs
- BulletChrome.cs
- SplineKeyFrames.cs
- ImageCodecInfoPrivate.cs
- GeometryHitTestParameters.cs
- Missing.cs
- HtmlInputCheckBox.cs
- BoolExpression.cs
- SQLDoubleStorage.cs
- DataSourceSelectArguments.cs
- EventRouteFactory.cs
- PointConverter.cs
- IdentityValidationException.cs
- ContainerFilterService.cs
- TextElementEditingBehaviorAttribute.cs
- BitConverter.cs
- GetPageCompletedEventArgs.cs
- SchemaConstraints.cs
- DataTableExtensions.cs
- DataGridViewRowHeaderCell.cs
- DesignerDataStoredProcedure.cs
- MessagePartSpecification.cs
- CommandLineParser.cs
- ViewManager.cs
- SQLSingleStorage.cs
- SkipStoryboardToFill.cs
- WebPartDescription.cs
- IUnknownConstantAttribute.cs
- FormsAuthenticationModule.cs
- InvokePattern.cs
- DataGridViewUtilities.cs
- SelectionRange.cs
- BamlLocalizerErrorNotifyEventArgs.cs
- StringOutput.cs
- BaseValidator.cs
- SettingsPropertyCollection.cs
- ImportContext.cs
- ArrayWithOffset.cs
- DataGridViewCellParsingEventArgs.cs
- BaseDataBoundControlDesigner.cs
- CodeTryCatchFinallyStatement.cs
- DataServiceRequestOfT.cs
- DataGridCellEditEndingEventArgs.cs
- VisualStyleElement.cs
- TemplateBamlRecordReader.cs
- ToolStripPanel.cs
- RequestSecurityTokenResponseCollection.cs
- HtmlTable.cs
- WmlCalendarAdapter.cs
- COM2IProvidePropertyBuilderHandler.cs
- ImageAnimator.cs
- WindowsAltTab.cs
- CodeTypeDeclarationCollection.cs
- DispatcherEventArgs.cs
- RangeExpression.cs
- WebPartHelpVerb.cs
- EnumerableRowCollectionExtensions.cs
- DataGridRowsPresenter.cs
- TagMapCollection.cs
- XhtmlBasicValidatorAdapter.cs
- ListBindingHelper.cs
- LineGeometry.cs
- EntityDataSourceContextDisposingEventArgs.cs
- HttpTransportSecurity.cs
- Stack.cs
- KerberosTicketHashIdentifierClause.cs
- UserUseLicenseDictionaryLoader.cs
- SQLByte.cs
- CompilationSection.cs
- XmlHierarchicalDataSourceView.cs
- SessionEndingCancelEventArgs.cs
- WebPartPersonalization.cs
- PageAsyncTaskManager.cs
- ColorTypeConverter.cs
- NetDataContractSerializer.cs
- MergeFailedEvent.cs
- ValidatedControlConverter.cs
- QilGeneratorEnv.cs
- CompiledIdentityConstraint.cs
- DataColumn.cs
- DrawingState.cs
- Util.cs
- RootDesignerSerializerAttribute.cs
- MetadataResolver.cs
- ItemDragEvent.cs
- InputLanguageCollection.cs
- StyleXamlTreeBuilder.cs
- SQLResource.cs
- GZipDecoder.cs
- DesignerForm.cs
- SqlDataSourceAdvancedOptionsForm.cs
- EmptyEnumerator.cs
- TransformerInfoCollection.cs