|
|
|
|
using System;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
|
|
namespace ZeroLevel.Services.Shedulling
|
|
|
|
|
{
|
|
|
|
|
public class DateTimeSheduller
|
|
|
|
|
: IExpirationSheduller
|
|
|
|
|
{
|
|
|
|
|
private Timer _timer;
|
|
|
|
|
private ExpiredObject _head = null;
|
|
|
|
|
private readonly object _rw_lock = new object();
|
|
|
|
|
private volatile bool _stopped = false;
|
|
|
|
|
|
|
|
|
|
#region Ctor
|
|
|
|
|
|
|
|
|
|
public DateTimeSheduller()
|
|
|
|
|
{
|
|
|
|
|
_timer = new Timer(TimerCallbackHandler, null, Timeout.Infinite, Timeout.Infinite);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Ctor
|
|
|
|
|
|
|
|
|
|
private void TimerCallbackHandler(object state)
|
|
|
|
|
{
|
|
|
|
|
// POP
|
|
|
|
|
ExpiredObject result = null;
|
|
|
|
|
lock (_rw_lock)
|
|
|
|
|
{
|
|
|
|
|
if (null != _head)
|
|
|
|
|
{
|
|
|
|
|
if ((_head.ExpirationDate - DateTime.Now).Ticks > 0)
|
|
|
|
|
{
|
|
|
|
|
ResetTimer();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// SWAP
|
|
|
|
|
result = _head;
|
|
|
|
|
_head = _head.Next;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (result != null)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
result.Callback.Invoke(result.Key);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.SystemError(ex, $"Fault task '{result.Key}' on expiration date '{result.ExpirationDate.ToString("yyyy-MM-dd HH:mm:ss fff}")}'");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal long Push(ExpiredObject insert)
|
|
|
|
|
{
|
|
|
|
|
if (insert == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(insert));
|
|
|
|
|
lock (_rw_lock)
|
|
|
|
|
{
|
|
|
|
|
if (null == _head)
|
|
|
|
|
{
|
|
|
|
|
_head = insert;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var cursor = _head;
|
|
|
|
|
var reset = false;
|
|
|
|
|
if (cursor.Key == -1) // if system task for task with big interval (> 2^32 - 2 ms)
|
|
|
|
|
{
|
|
|
|
|
DisableTimer();
|
|
|
|
|
_head = _head.Next; // remove system task from head
|
|
|
|
|
cursor = _head;
|
|
|
|
|
reset = true;
|
|
|
|
|
}
|
|
|
|
|
ExpiredObject prev = null;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
if ((cursor.ExpirationDate - insert.ExpirationDate).Ticks > 0)
|
|
|
|
|
{
|
|
|
|
|
insert.Next = cursor;
|
|
|
|
|
if (null == prev) // insert to head
|
|
|
|
|
{
|
|
|
|
|
_head = insert;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
reset = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
prev.Next = insert;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
prev = cursor;
|
|
|
|
|
cursor = cursor.Next;
|
|
|
|
|
if (cursor == null)
|
|
|
|
|
{
|
|
|
|
|
prev.Next = insert;
|
|
|
|
|
}
|
|
|
|
|
} while (cursor != null);
|
|
|
|
|
if (reset)
|
|
|
|
|
{
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return insert.Key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Remove(long key)
|
|
|
|
|
{
|
|
|
|
|
lock (_rw_lock)
|
|
|
|
|
{
|
|
|
|
|
if (_head != null)
|
|
|
|
|
{
|
|
|
|
|
ExpiredObject previous, current;
|
|
|
|
|
FindTaskByKeyWithPreviousTask(key, out previous, out current);
|
|
|
|
|
if (current != null)
|
|
|
|
|
{
|
|
|
|
|
if (_head.Key == current.Key)
|
|
|
|
|
{
|
|
|
|
|
_head = _head.Next;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
previous.Next = current.Next;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region API
|
|
|
|
|
|
|
|
|
|
public long Push(TimeSpan timespan, Action<long> callback)
|
|
|
|
|
{
|
|
|
|
|
return Push(new ExpiredObject { Callback = callback, ExpirationDate = DateTime.Now.AddMilliseconds(timespan.TotalMilliseconds) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public long Push(DateTime date, Action<long> callback)
|
|
|
|
|
{
|
|
|
|
|
return Push(new ExpiredObject { Callback = callback, ExpirationDate = date });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DisableTimer()
|
|
|
|
|
{
|
|
|
|
|
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Pause()
|
|
|
|
|
{
|
|
|
|
|
lock (_rw_lock)
|
|
|
|
|
{
|
|
|
|
|
_stopped = true;
|
|
|
|
|
DisableTimer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Resume()
|
|
|
|
|
{
|
|
|
|
|
lock (_rw_lock)
|
|
|
|
|
{
|
|
|
|
|
_stopped = false;
|
|
|
|
|
ResetTimer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Clean()
|
|
|
|
|
{
|
|
|
|
|
lock (_rw_lock)
|
|
|
|
|
{
|
|
|
|
|
DisableTimer();
|
|
|
|
|
_head = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion API
|
|
|
|
|
|
|
|
|
|
#region Control
|
|
|
|
|
|
|
|
|
|
private void FindTaskByKeyWithPreviousTask(long key, out ExpiredObject previous, out ExpiredObject current)
|
|
|
|
|
{
|
|
|
|
|
if (_head.Key == key)
|
|
|
|
|
{
|
|
|
|
|
previous = null;
|
|
|
|
|
current = _head;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var cursor = _head.Next;
|
|
|
|
|
var prev = _head;
|
|
|
|
|
while (cursor != null)
|
|
|
|
|
{
|
|
|
|
|
if (cursor.Key == key)
|
|
|
|
|
{
|
|
|
|
|
previous = prev;
|
|
|
|
|
current = cursor;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
prev = cursor;
|
|
|
|
|
cursor = cursor.Next;
|
|
|
|
|
}
|
|
|
|
|
previous = null;
|
|
|
|
|
current = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private const uint _max_interval = 4294967294;
|
|
|
|
|
private static readonly TimeSpan _infinite = TimeSpan.FromMilliseconds(Timeout.Infinite);
|
|
|
|
|
|
|
|
|
|
private void ResetTimer()
|
|
|
|
|
{
|
|
|
|
|
if (_timer != null)
|
|
|
|
|
{
|
|
|
|
|
if (null != _head && _stopped == false)
|
|
|
|
|
{
|
|
|
|
|
var diff = (_head.ExpirationDate - DateTime.Now);
|
|
|
|
|
if (diff.TotalMilliseconds > _max_interval)
|
|
|
|
|
{
|
|
|
|
|
var _big_interval_waiting_obj = new ExpiredObject(true)
|
|
|
|
|
{
|
|
|
|
|
ExpirationDate = DateTime.Now.AddMilliseconds(_max_interval),
|
|
|
|
|
Callback = (key) =>
|
|
|
|
|
{
|
|
|
|
|
lock (_rw_lock)
|
|
|
|
|
{
|
|
|
|
|
ResetTimer();
|
|
|
|
|
_head = null;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Next = _head
|
|
|
|
|
};
|
|
|
|
|
_head = _big_interval_waiting_obj;
|
|
|
|
|
_timer.Change(_max_interval, Timeout.Infinite);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (diff.Ticks < 0)
|
|
|
|
|
{
|
|
|
|
|
diff = TimeSpan.Zero;
|
|
|
|
|
}
|
|
|
|
|
_timer.Change(diff, _infinite);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
DisableTimer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Control
|
|
|
|
|
|
|
|
|
|
#region IDisposable
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
|
|
|
|
if (_timer != null)
|
|
|
|
|
{
|
|
|
|
|
Clean();
|
|
|
|
|
if (null != _timer)
|
|
|
|
|
{
|
|
|
|
|
_timer.Dispose();
|
|
|
|
|
_timer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion IDisposable
|
|
|
|
|
}
|
|
|
|
|
}
|