DurableTimerExtension.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / cdf / src / NetFx40 / System.Activities / System / Activities / Statements / DurableTimerExtension.cs / 1305376 / DurableTimerExtension.cs

                            //------------------------------------------------------------------------------ 
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------

namespace System.Activities.Statements 
{
    using System; 
    using System.Activities; 
    using System.Runtime;
    using System.ComponentModel; 
    using System.Activities.Persistence;
    using System.Collections.Generic;
    using System.Xml.Linq;
    using System.Activities.Hosting; 
    using System.Threading;
 
    [Fx.Tag.XamlVisible(false)] 
    public class DurableTimerExtension : TimerExtension, IWorkflowInstanceExtension, IDisposable, ICancelable
    { 
        WorkflowInstanceProxy instance;
        TimerTable registeredTimers;
        Action onTimerFiredCallback;
        TimerPersistenceParticipant timerPersistenceParticipant; 
        static AsyncCallback onResumeBookmarkComplete = Fx.ThunkCallback(new AsyncCallback(OnResumeBookmarkComplete));
 
        static readonly XName timerTableName = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties").GetName("RegisteredTimers"); 
        static readonly XName timerExpirationTimeName = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties").GetName("TimerExpirationTime");
        bool isDisposed; 

        [Fx.Tag.SynchronizationObject()]
        object thisLock;
 
        public DurableTimerExtension()
            : base() 
        { 
            this.onTimerFiredCallback = new Action(this.OnTimerFired);
            this.thisLock = new object(); 
            this.timerPersistenceParticipant = new TimerPersistenceParticipant(this);
            this.isDisposed = false;
        }
 
        object ThisLock
        { 
            get 
            {
                return this.thisLock; 
            }
        }

        internal Action OnTimerFiredCallback 
        {
            get 
            { 
                return this.onTimerFiredCallback;
            } 
        }

        internal TimerTable RegisteredTimers
        { 
            get
            { 
                if (this.registeredTimers == null) 
                {
                    this.registeredTimers = new TimerTable(this); 
                }
                return this.registeredTimers;
            }
        } 

        public virtual IEnumerable GetAdditionalExtensions() 
        { 
            yield return this.timerPersistenceParticipant;
        } 

        public virtual void SetInstance(WorkflowInstanceProxy instance)
        {
            if (this.instance != null && instance != null) 
            {
                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.TimerExtensionAlreadyAttached)); 
            } 

            this.instance = instance; 
        }

        protected override void OnRegisterTimer(TimeSpan timeout, Bookmark bookmark)
        { 
            // This lock is to synchronize with the Timer callback
            if (timeout < TimeSpan.MaxValue) 
            { 
                lock (this.ThisLock)
                { 
                    this.RegisteredTimers.AddTimer(timeout, bookmark);
                }
            }
        } 

        protected override void OnCancelTimer(Bookmark bookmark) 
        { 
            // This lock is to synchronize with the Timer callback
            lock (this.ThisLock) 
            {
                this.RegisteredTimers.RemoveTimer(bookmark);
            }
        } 

        internal void OnSave(out IDictionary readWriteValues, out IDictionary writeOnlyValues) 
        { 
            readWriteValues = null;
            writeOnlyValues = null; 

            // Using a lock here to prevent the timer firing back without us being ready
            lock (this.ThisLock)
            { 
                this.RegisteredTimers.MarkAsImmutable();
                if (this.registeredTimers != null && this.registeredTimers.Count > 0) 
                { 
                    readWriteValues = new Dictionary(1);
                    writeOnlyValues = new Dictionary(1); 
                    readWriteValues.Add(timerTableName, this.registeredTimers);
                    writeOnlyValues.Add(timerExpirationTimeName, this.registeredTimers.GetNextDueTime());
                }
            } 
        }
 
        internal void PersistenceDone() 
        {
            lock (this.ThisLock) 
            {
                this.RegisteredTimers.MarkAsMutable();
            }
        } 

        internal void OnLoad(IDictionary readWriteValues) 
        { 
            lock (this.ThisLock)
            { 
                object timerTable;
                if (readWriteValues != null && readWriteValues.TryGetValue(timerTableName, out timerTable))
                {
                    this.registeredTimers = timerTable as TimerTable; 
                    Fx.Assert(this.RegisteredTimers != null, "Timer Table cannot be null");
                    this.RegisteredTimers.OnLoad(this); 
                } 
            }
        } 

        void OnTimerFired(object state)
        {
            Bookmark timerBookmark; 

            lock (this.ThisLock) 
            { 
                timerBookmark = this.RegisteredTimers.GetNextExpiredBookmark();
                // If the timer is canceled and there is no more in the queue. 
                if (timerBookmark == null)
                {
                    return;
                } 

                // If the timer is canceled We don't want to resume the next bookmark in the queue 
                // Verify the expiration time is really DateTime.UtcNow 
                if (this.RegisteredTimers.GetNextDueTime() > DateTime.UtcNow)
                { 
                    return;
                }
            }
 
            WorkflowInstanceProxy targetInstance = this.instance;
            // it's possible that we've been unloaded while the timer was in the process of firing, in 
            // which case targetInstance will be null 
            if (targetInstance != null)
            { 
                BookmarkResumptionResult resumptionResult;
                IAsyncResult result = null;
                bool completed = false;
 
                try
                { 
                    // CSDMain Bug 140368: Added timeout to avoid potential deadlock situation 
                    // We choose 2 seconds because we think if there are no deadlock, and workflow is Idle, it should
                    // take no more than 2 seconds to complete the resumebookmark call, this time we chose is just an estimation. 
                    result = targetInstance.BeginResumeBookmark(timerBookmark, null, TimeSpan.FromSeconds(2),
                        onResumeBookmarkComplete, new BookmarkResumptionState(timerBookmark, this, targetInstance));
                    completed = result.CompletedSynchronously;
                } 
                catch (TimeoutException)
                { 
                    ProcessBookmarkResumptionResult(timerBookmark, BookmarkResumptionResult.NotReady); 
                }
 
                if (completed && result != null)
                {
                    try
                    { 
                        resumptionResult = targetInstance.EndResumeBookmark(result);
                        ProcessBookmarkResumptionResult(timerBookmark, resumptionResult); 
                    } 
                    catch (TimeoutException)
                    { 
                        ProcessBookmarkResumptionResult(timerBookmark, BookmarkResumptionResult.NotReady);
                    }
                }
            } 
        }
 
        static void OnResumeBookmarkComplete(IAsyncResult result) 
        {
            if (result.CompletedSynchronously) 
            {
                return;
            }
 
            BookmarkResumptionState state = (BookmarkResumptionState)result.AsyncState;
 
            // CSDMain Bug 140368: Workflow hangs when a delay completes in parallel while TransactionScope activity is executing 
            // ResumeBookmark can potentially timeout due to the following conditions:
            // 1. Workflow is busy processing other work item. 
            // 2. Workflow is in an isolation scope
            // In these cases, we should treat the bookmark as NotReady and retry.
            try
            { 
                BookmarkResumptionResult resumptionResult = state.Instance.EndResumeBookmark(result);
                state.TimerExtension.ProcessBookmarkResumptionResult(state.TimerBookmark, resumptionResult); 
            } 
            catch (TimeoutException)
            { 
                state.TimerExtension.ProcessBookmarkResumptionResult(state.TimerBookmark, BookmarkResumptionResult.NotReady);
            }
        }
 

        void ProcessBookmarkResumptionResult(Bookmark timerBookmark, BookmarkResumptionResult result) 
        { 
            switch (result)
            { 
                case BookmarkResumptionResult.NotFound:
                case BookmarkResumptionResult.Success:
                    // The bookmark is removed maybe due to WF cancel, abort or the bookmark succeeds
                    // no need to keep the timer around 
                    lock (this.ThisLock)
                    { 
                        if (!this.isDisposed) 
                        {
                            this.RegisteredTimers.RemoveTimer(timerBookmark); 
                        }
                    }
                    break;
                case BookmarkResumptionResult.NotReady: 
                    // The workflow maybe in one of these states: Completed, Aborted, Abandoned, unloading, Suspended
                    // In the first 3 cases, we will let TimerExtension.CancelTimer take care of the cleanup. 
                    // In the 4th case, we want the timer to retry when it is loaded back, in all 4 cases we don't need to delete the timer 
                    // In the 5th case, we want the timer to retry until it succeeds.
                    // Retry: 
                    lock (this.ThisLock)
                    {
                        this.RegisteredTimers.RetryTimer(timerBookmark);
                    } 
                    break;
            } 
        } 

        public void Dispose() 
        {
            if (this.registeredTimers != null)
            {
                lock (this.ThisLock) 
                {
                    this.isDisposed = true; 
                    if (this.registeredTimers != null) 
                    {
                        this.registeredTimers.Dispose(); 
                    }
                }
            }
            GC.SuppressFinalize(this); 
        }
 
        void ICancelable.Cancel() 
        {
            Dispose(); 
        }

        class BookmarkResumptionState
        { 
            public BookmarkResumptionState(Bookmark timerBookmark, DurableTimerExtension timerExtension, WorkflowInstanceProxy instance)
            { 
                this.TimerBookmark = timerBookmark; 
                this.TimerExtension = timerExtension;
                this.Instance = instance; 
            }

            public Bookmark TimerBookmark
            { 
                get;
                private set; 
            } 

            public DurableTimerExtension TimerExtension 
            {
                get;
                private set;
            } 

            public WorkflowInstanceProxy Instance 
            { 
                get;
                private set; 
            }
        }

        class TimerPersistenceParticipant : PersistenceIOParticipant 
        {
            DurableTimerExtension defaultTimerExtension; 
 
            public TimerPersistenceParticipant(DurableTimerExtension timerExtension)
                : base(false, false) 
            {
                this.defaultTimerExtension = timerExtension;
            }
 
            protected override void CollectValues(out IDictionary readWriteValues, out IDictionary writeOnlyValues)
            { 
                this.defaultTimerExtension.OnSave(out readWriteValues, out writeOnlyValues); 
            }
 
            protected override void PublishValues(IDictionary readWriteValues)
            {
                this.defaultTimerExtension.OnLoad(readWriteValues);
            } 

            protected override IAsyncResult BeginOnSave(IDictionary readWriteValues, IDictionary writeOnlyValues, TimeSpan timeout, AsyncCallback callback, object state) 
            { 
                this.defaultTimerExtension.PersistenceDone();
                return base.BeginOnSave(readWriteValues, writeOnlyValues, timeout, callback, state); 
            }

            protected override void Abort()
            { 
                this.defaultTimerExtension.PersistenceDone();
            } 
        } 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK