1. Stopping a different timer, on the same slot as the timer being executed during OnTick
* Adds a check for the timer wheel currently being executed and does not detach the timer on Stop().
1. Timers are added to the current slot if they need 4095 slots, before the chain is fully detached/executed
* Removes all chains that will be executed from the timer wheel before executing.
237 lines
8.9 KiB
C#
237 lines
8.9 KiB
C#
/*************************************************************************
|
|
* ModernUO *
|
|
* Copyright (C) 2019-2021 - ModernUO Development Team *
|
|
* Email: hi@modernuo.com *
|
|
* File: Timer.DelayCall.cs *
|
|
* *
|
|
* This program is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation, either version 3 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
*************************************************************************/
|
|
|
|
using System;
|
|
#if DEBUG_TIMERS
|
|
using System.Collections.Generic;
|
|
#endif
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Server
|
|
{
|
|
public partial class Timer
|
|
{
|
|
private static string FormatDelegate(Delegate callback) =>
|
|
callback == null ? "null" : $"{callback.Method.DeclaringType?.FullName ?? ""}.{callback.Method.Name}";
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static DelayCallTimer DelayCall(Action callback) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static DelayCallTimer DelayCall(TimeSpan delay, Action callback) => DelayCall(delay, TimeSpan.Zero, 1, callback);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static DelayCallTimer DelayCall(TimeSpan delay, TimeSpan interval, Action callback) =>
|
|
DelayCall(delay, interval, 0, callback);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static DelayCallTimer DelayCall(TimeSpan interval, int count, Action callback) =>
|
|
DelayCall(TimeSpan.Zero, interval, count, callback);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static DelayCallTimer DelayCall(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
|
{
|
|
DelayCallTimer t = new DelayCallTimer(delay, interval, count, callback);
|
|
t.Start();
|
|
#if DEBUG_TIMERS
|
|
t._allowFinalization = true;
|
|
#endif
|
|
|
|
return t;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(Action callback) => StartTimer(TimeSpan.Zero, TimeSpan.Zero, 1, callback);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(TimeSpan delay, Action callback) => StartTimer(delay, TimeSpan.Zero, 1, callback);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(TimeSpan delay, TimeSpan interval, Action callback) =>
|
|
StartTimer(delay, interval, 0, callback);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(TimeSpan interval, int count, Action callback) =>
|
|
StartTimer(TimeSpan.Zero, interval, count, callback);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
|
{
|
|
DelayCallTimer t = DelayCallTimer.GetTimer(delay, interval, count, callback);
|
|
t._selfReturn = true;
|
|
t.Start();
|
|
|
|
#if DEBUG_TIMERS
|
|
DelayCallTimer._stackTraces[t.GetHashCode()] = new StackTrace().ToString();
|
|
#endif
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(Action callback, out TimerExecutionToken token) =>
|
|
StartTimer(TimeSpan.Zero, TimeSpan.Zero, 1, callback, out token);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(TimeSpan delay, Action callback, out TimerExecutionToken token) =>
|
|
StartTimer(delay, TimeSpan.Zero, 1, callback, out token);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(TimeSpan delay, TimeSpan interval, Action callback, out TimerExecutionToken token) =>
|
|
StartTimer(delay, interval, 0, callback, out token);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(TimeSpan interval, int count, Action callback, out TimerExecutionToken token) =>
|
|
StartTimer(TimeSpan.Zero, interval, count, callback, out token);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback, out TimerExecutionToken token)
|
|
{
|
|
DelayCallTimer t = DelayCallTimer.GetTimer(delay, interval, count, callback);
|
|
t.Start();
|
|
|
|
#if DEBUG_TIMERS
|
|
DelayCallTimer._stackTraces[t.GetHashCode()] = new StackTrace().ToString();
|
|
#endif
|
|
token = new TimerExecutionToken(t);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static DelayCallTimer Pause(TimeSpan ms) => new(ms);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static DelayCallTimer Pause(int ms) => Pause(TimeSpan.FromMilliseconds(ms));
|
|
|
|
public sealed class DelayCallTimer : Timer, INotifyCompletion
|
|
{
|
|
internal bool _selfReturn;
|
|
#if DEBUG_TIMERS
|
|
internal bool _allowFinalization;
|
|
#endif
|
|
private Action _continuation;
|
|
private bool _complete;
|
|
|
|
internal DelayCallTimer(TimeSpan delay, TimeSpan interval, int count, Action callback) : base(
|
|
delay,
|
|
interval,
|
|
count
|
|
) =>
|
|
_continuation = callback;
|
|
|
|
internal DelayCallTimer(TimeSpan delay) : base(delay)
|
|
{
|
|
#if DEBUG_TIMERS
|
|
_allowFinalization = true;
|
|
#endif
|
|
Start();
|
|
}
|
|
|
|
protected override void OnTick()
|
|
{
|
|
_complete = true;
|
|
_continuation?.Invoke();
|
|
}
|
|
|
|
public override void Stop()
|
|
{
|
|
base.Stop();
|
|
|
|
if (_selfReturn)
|
|
{
|
|
Return();
|
|
}
|
|
}
|
|
|
|
internal void Return()
|
|
{
|
|
if (Running)
|
|
{
|
|
logger.Error($"Timer is returned while still running!\n{new StackTrace()}");
|
|
return;
|
|
}
|
|
|
|
Version++; // Increment the version so if this is called from OnTick() and another timer is started, we don't have a problem
|
|
|
|
#if DEBUG_TIMERS
|
|
_stackTraces.Remove(GetHashCode());
|
|
#endif
|
|
|
|
if (_poolCount >= _poolCapacity)
|
|
{
|
|
#if DEBUG_TIMERS
|
|
logger.Warning($"DelayCallTimer pool reached maximum of {_poolCapacity} timers");
|
|
_allowFinalization = true;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
_continuation = null;
|
|
ReturnToPool(1, this, this);
|
|
}
|
|
|
|
public static DelayCallTimer GetTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
|
{
|
|
if (_poolHead != null)
|
|
{
|
|
_poolCount--;
|
|
#if DEBUG_TIMERS
|
|
logger.Information($"Pool count: {_poolCount} / {_poolCapacity}");
|
|
#endif
|
|
|
|
var timer = GetFromPool();
|
|
|
|
timer.Init(delay, interval, count);
|
|
timer._continuation = callback;
|
|
timer._selfReturn = false;
|
|
#if DEBUG_TIMERS
|
|
timer._allowFinalization = false;
|
|
#endif
|
|
|
|
return timer;
|
|
}
|
|
|
|
_timerPoolDepletionAmount++;
|
|
|
|
#if DEBUG_TIMERS
|
|
logger.Warning($"Timer pool depleted and timer was allocated.\n{new StackTrace()}");
|
|
#endif
|
|
return new DelayCallTimer(delay, interval, count, callback);
|
|
}
|
|
|
|
public override string ToString() => $"DelayCallTimer[{FormatDelegate(_continuation)}]";
|
|
|
|
#if DEBUG_TIMERS
|
|
internal static Dictionary<int, string> _stackTraces = new();
|
|
|
|
~DelayCallTimer()
|
|
{
|
|
if (!_allowFinalization)
|
|
{
|
|
logger.Warning($"Pooled timer was not returned to the pool.\n{_stackTraces[GetHashCode()]}");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public DelayCallTimer GetAwaiter() => this;
|
|
|
|
public bool IsCompleted => _complete;
|
|
|
|
public void OnCompleted(Action continuation) => _continuation = continuation;
|
|
|
|
public void GetResult()
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|