e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
419 lines
14 KiB
C#
419 lines
14 KiB
C#
//-----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace System.Activities.Statements
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime;
|
|
using System.Runtime.Serialization;
|
|
|
|
// This class won't be thread safe, it relies on the callers to synchronize addTimer and removeTimer
|
|
[DataContract]
|
|
class TimerTable : IDisposable
|
|
{
|
|
SortedTimerList sortedTimerList;
|
|
|
|
bool isImmutable;
|
|
DurableTimerExtension timerExtension;
|
|
|
|
HybridCollection<Bookmark> pendingRemoveBookmark;
|
|
HybridCollection<Bookmark> pendingRetryBookmark;
|
|
|
|
public TimerTable(DurableTimerExtension timerExtension)
|
|
{
|
|
this.sortedTimerList = new SortedTimerList();
|
|
this.timerExtension = timerExtension;
|
|
}
|
|
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
return this.sortedTimerList.Count;
|
|
}
|
|
}
|
|
|
|
[DataMember(Name = "sortedTimerList")]
|
|
internal SortedTimerList SerializedSortedTimerList
|
|
{
|
|
get { return this.sortedTimerList; }
|
|
set { this.sortedTimerList = value; }
|
|
}
|
|
|
|
public void AddTimer(TimeSpan timeout, Bookmark bookmark)
|
|
{
|
|
// Add timer is only called on the workflow thread,
|
|
// It can't be racing with the persistence thread.
|
|
// So the table MUST be mutable when this method is called
|
|
Fx.Assert(!this.isImmutable, "Add timer is called when table is immutable");
|
|
DateTime dueTime = TimeoutHelper.Add(DateTime.UtcNow, timeout);
|
|
TimerData timerData = new TimerData(bookmark, dueTime);
|
|
timerData.IOThreadTimer = new IOThreadTimer(this.timerExtension.OnTimerFiredCallback, bookmark, false, 0);
|
|
timerData.IOThreadTimer.Set(timeout);
|
|
this.sortedTimerList.Add(timerData);
|
|
}
|
|
|
|
public void RemoveTimer(Bookmark bookmark)
|
|
{
|
|
// When IOThread Timer calls back, it will call remove timer
|
|
// In another thread, we may be in the middle of persistence.
|
|
// During persisting, we will mark the table as immutable
|
|
// After we are done writing to the database, we will buffer the remove request
|
|
// Meanwhile, since we are not scheduling any IOThreadTimers,
|
|
// we can only have at most one pending Remove request
|
|
// We don't want to remove
|
|
if (!this.isImmutable)
|
|
{
|
|
TimerData expirationTimeData;
|
|
if (this.sortedTimerList.TryGetValue(bookmark, out expirationTimeData))
|
|
{
|
|
this.sortedTimerList.Remove(bookmark);
|
|
expirationTimeData.IOThreadTimer.Cancel();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.pendingRemoveBookmark == null)
|
|
{
|
|
this.pendingRemoveBookmark = new HybridCollection<Bookmark>(bookmark);
|
|
}
|
|
else
|
|
{
|
|
this.pendingRemoveBookmark.Add(bookmark);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the timer from the table, and set expiration date to a new value.
|
|
public void RetryTimer(Bookmark bookmark)
|
|
{
|
|
// This value controls how many seconds do we retry
|
|
const int retryDuration = 10;
|
|
|
|
// When IOThread Timer calls back, it might call RetryTimer timer if ResumeBookmark returned notReady
|
|
// In another thread, we may be in the middle of persistence.
|
|
// During persisting, we will mark the table as immutable
|
|
// After we are done writing to the database, we will buffer the remove request
|
|
// Meanwhile, since we are not scheduling any IOThreadTimers,
|
|
// we can only have at most one pending Remove request
|
|
// We don't want to remove
|
|
if (!this.isImmutable)
|
|
{
|
|
// We only retry the timer IFF no one has removed it from the table
|
|
// Otherwise, we are just retrying a timer that doesn't exist
|
|
if (this.sortedTimerList.ContainsKey(bookmark))
|
|
{
|
|
this.RemoveTimer(bookmark);
|
|
|
|
// Update it to the retry time and put it back to the timer list
|
|
this.AddTimer(TimeSpan.FromSeconds(retryDuration), bookmark);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.pendingRetryBookmark == null)
|
|
{
|
|
this.pendingRetryBookmark = new HybridCollection<Bookmark>(bookmark);
|
|
}
|
|
else
|
|
{
|
|
this.pendingRetryBookmark.Add(bookmark);
|
|
}
|
|
}
|
|
}
|
|
|
|
public DateTime GetNextDueTime()
|
|
{
|
|
if (this.sortedTimerList.Count > 0)
|
|
{
|
|
return this.sortedTimerList.Timers[0].ExpirationTime;
|
|
}
|
|
else
|
|
{
|
|
return DateTime.MaxValue;
|
|
}
|
|
}
|
|
|
|
public void OnLoad(DurableTimerExtension timerExtension)
|
|
{
|
|
this.timerExtension = timerExtension;
|
|
this.sortedTimerList.OnLoad();
|
|
|
|
foreach (TimerData timerData in this.sortedTimerList.Timers)
|
|
{
|
|
timerData.IOThreadTimer = new IOThreadTimer(this.timerExtension.OnTimerFiredCallback, timerData.Bookmark, false, 0);
|
|
if (timerData.ExpirationTime <= DateTime.UtcNow)
|
|
{
|
|
// If the timer expired, we want to fire it immediately to win the ---- against UnloadOnIdle policy
|
|
timerExtension.OnTimerFiredCallback(timerData.Bookmark);
|
|
}
|
|
else
|
|
{
|
|
timerData.IOThreadTimer.Set(timerData.ExpirationTime - DateTime.UtcNow);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void MarkAsImmutable()
|
|
{
|
|
this.isImmutable = true;
|
|
}
|
|
|
|
public void MarkAsMutable()
|
|
{
|
|
if (this.isImmutable)
|
|
{
|
|
int index = 0;
|
|
this.isImmutable = false;
|
|
|
|
if (this.pendingRemoveBookmark != null)
|
|
{
|
|
for (index = 0; index < this.pendingRemoveBookmark.Count; index++)
|
|
{
|
|
this.RemoveTimer(this.pendingRemoveBookmark[index]);
|
|
}
|
|
this.pendingRemoveBookmark = null;
|
|
}
|
|
|
|
if (this.pendingRetryBookmark != null)
|
|
{
|
|
for (index = 0; index < this.pendingRemoveBookmark.Count; index++)
|
|
{
|
|
this.RetryTimer(this.pendingRetryBookmark[index]);
|
|
}
|
|
this.pendingRetryBookmark = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// Cancel the active timer so we stop retrying
|
|
foreach (TimerData timerData in this.sortedTimerList.Timers)
|
|
{
|
|
timerData.IOThreadTimer.Cancel();
|
|
}
|
|
|
|
// And we clear the table and other member variables that might cause the retry logic
|
|
this.sortedTimerList.Clear();
|
|
this.pendingRemoveBookmark = null;
|
|
this.pendingRetryBookmark = null;
|
|
}
|
|
|
|
[DataContract]
|
|
internal class TimerData
|
|
{
|
|
Bookmark bookmark;
|
|
DateTime expirationTime;
|
|
|
|
public TimerData(Bookmark timerBookmark, DateTime expirationTime)
|
|
{
|
|
this.Bookmark = timerBookmark;
|
|
this.ExpirationTime = expirationTime;
|
|
}
|
|
|
|
public Bookmark Bookmark
|
|
{
|
|
get
|
|
{
|
|
return this.bookmark;
|
|
}
|
|
private set
|
|
{
|
|
this.bookmark = value;
|
|
}
|
|
}
|
|
|
|
public DateTime ExpirationTime
|
|
{
|
|
get
|
|
{
|
|
return this.expirationTime;
|
|
}
|
|
private set
|
|
{
|
|
this.expirationTime = value;
|
|
}
|
|
}
|
|
|
|
public IOThreadTimer IOThreadTimer
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[DataMember(Name = "Bookmark")]
|
|
internal Bookmark SerializedBookmark
|
|
{
|
|
get { return this.Bookmark; }
|
|
set { this.Bookmark = value; }
|
|
}
|
|
|
|
[DataMember(Name = "ExpirationTime")]
|
|
internal DateTime SerializedExpirationTime
|
|
{
|
|
get { return this.ExpirationTime; }
|
|
set { this.ExpirationTime = value; }
|
|
}
|
|
}
|
|
|
|
// In Dev11 we don't need to keep the timers in sorted order, since they each have their own IOThreadTimer.
|
|
// However we still sort it for back-compat with Dev10.
|
|
[DataContract]
|
|
internal class SortedTimerList
|
|
{
|
|
List<TimerData> list;
|
|
|
|
Dictionary<Bookmark, TimerData> dictionary;
|
|
|
|
public SortedTimerList()
|
|
{
|
|
this.list = new List<TimerData>();
|
|
this.dictionary = new Dictionary<Bookmark, TimerData>();
|
|
}
|
|
|
|
public List<TimerData> Timers
|
|
{
|
|
get
|
|
{
|
|
return this.list;
|
|
}
|
|
}
|
|
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
return this.list.Count;
|
|
}
|
|
}
|
|
|
|
[DataMember(Name = "list")]
|
|
internal List<TimerData> SerializedList
|
|
{
|
|
get { return this.list; }
|
|
set { this.list = value; }
|
|
}
|
|
|
|
[DataMember(Name = "dictionary")]
|
|
internal Dictionary<Bookmark, TimerData> SerializedDictionary
|
|
{
|
|
get { return this.dictionary; }
|
|
set { this.dictionary = value; }
|
|
}
|
|
|
|
public void Add(TimerData timerData)
|
|
{
|
|
int index = this.list.BinarySearch(timerData, TimerComparer.Instance);
|
|
if (index < 0)
|
|
{
|
|
this.list.Insert(~index, timerData);
|
|
this.dictionary.Add(timerData.Bookmark, timerData);
|
|
}
|
|
}
|
|
|
|
public bool ContainsKey(Bookmark bookmark)
|
|
{
|
|
return this.dictionary.ContainsKey(bookmark);
|
|
}
|
|
|
|
public void OnLoad()
|
|
{
|
|
// If upgrading from Dev10, the dictionary will be empty, so we need to create it
|
|
if (this.dictionary == null)
|
|
{
|
|
this.dictionary = new Dictionary<Bookmark, TimerData>();
|
|
for (int i = 0; i < this.list.Count; i++)
|
|
{
|
|
this.dictionary.Add(this.list[i].Bookmark, this.list[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Remove(Bookmark bookmark)
|
|
{
|
|
TimerData timerData;
|
|
if (this.dictionary.TryGetValue(bookmark, out timerData))
|
|
{
|
|
int index = this.list.BinarySearch(timerData, TimerComparer.Instance);
|
|
this.list.RemoveAt(index);
|
|
this.dictionary.Remove(bookmark);
|
|
}
|
|
}
|
|
|
|
public bool TryGetValue(Bookmark bookmark, out TimerData timerData)
|
|
{
|
|
return this.dictionary.TryGetValue(bookmark, out timerData);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
this.list.Clear();
|
|
this.dictionary.Clear();
|
|
}
|
|
}
|
|
|
|
class TimerComparer : IComparer<TimerData>
|
|
{
|
|
internal static readonly TimerComparer Instance = new TimerComparer();
|
|
|
|
public int Compare(TimerData x, TimerData y)
|
|
{
|
|
if (object.ReferenceEquals(x, y))
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if (x == null)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
if (y == null)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if (x.ExpirationTime == y.ExpirationTime)
|
|
{
|
|
if (x.Bookmark.IsNamed)
|
|
{
|
|
if (y.Bookmark.IsNamed)
|
|
{
|
|
return string.Compare(x.Bookmark.Name, y.Bookmark.Name, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (y.Bookmark.IsNamed)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return x.Bookmark.Id.CompareTo(y.Bookmark.Id);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return x.ExpirationTime.CompareTo(y.ExpirationTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|