BirthdayBot/BackgroundServices/ShardBackgroundWorker.cs
Noi a0571ce3d7 Add some leeway regarding incomplete user cache
Implemented several workarounds to what appearto be a library bug in
which not all users will be downloaded onto the cache. User cache
checking and downloading is delegated to a new Common method and a
new BackgroundService, respectively.
This is hopefully temporary.
2021-02-01 22:48:20 -08:00

102 lines
3.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace BirthdayBot.BackgroundServices
{
/// <summary>
/// Handles the execution of periodic background tasks specific to each shard.
/// </summary>
class ShardBackgroundWorker : IDisposable
{
/// <summary>
/// The interval, in seconds, in which background tasks are attempted to be run within a shard.
/// </summary>
public const int Interval = 40;
private readonly Task _workerTask;
private readonly CancellationTokenSource _workerCanceller;
private readonly List<BackgroundService> _workers;
private ShardInstance Instance { get; }
public ConnectionStatus ConnStatus { get; }
public BirthdayRoleUpdate BirthdayUpdater { get; }
public SelectiveAutoUserDownload UserDownloader { get; }
public DateTimeOffset LastBackgroundRun { get; private set; }
public int ConnectionScore => ConnStatus.Score;
public ShardBackgroundWorker(ShardInstance instance)
{
Instance = instance;
_workerCanceller = new CancellationTokenSource();
ConnStatus = new ConnectionStatus(instance);
BirthdayUpdater = new BirthdayRoleUpdate(instance);
UserDownloader = new SelectiveAutoUserDownload(instance);
_workers = new List<BackgroundService>()
{
{UserDownloader},
{BirthdayUpdater},
{new DataRetention(instance)}
};
_workerTask = Task.Factory.StartNew(WorkerLoop, _workerCanceller.Token);
}
public void Dispose()
{
_workerCanceller.Cancel();
_workerTask.Wait(5000);
if (!_workerTask.IsCompleted)
Instance.Log("Dispose", "Warning: Background worker has not yet stopped. Forcing its disposal.");
_workerTask.Dispose();
_workerCanceller.Dispose();
}
/// <summary>
/// *The* background task for the shard.
/// Executes service tasks and handles errors.
/// </summary>
private async Task WorkerLoop()
{
LastBackgroundRun = DateTimeOffset.UtcNow;
try
{
while (!_workerCanceller.IsCancellationRequested)
{
await Task.Delay(Interval * 1000, _workerCanceller.Token).ConfigureAwait(false);
// ConnectionStatus will always run. Its result determines if remaining tasks also this time.
await ConnStatus.OnTick(_workerCanceller.Token).ConfigureAwait(false);
if (!ConnStatus.Stable) continue;
// Execute tasks sequentially
foreach (var service in _workers)
{
try { await service.OnTick(_workerCanceller.Token).ConfigureAwait(false); }
catch (Exception ex)
{
var svcname = service.GetType().Name;
if (ex is TaskCanceledException)
{
Instance.Log(nameof(WorkerLoop), $"{svcname} was interrupted by a cancellation request.");
throw;
}
else
{
// TODO webhook log
Instance.Log(nameof(WorkerLoop), $"{svcname} encountered an exception:\n" + ex.ToString());
}
}
}
LastBackgroundRun = DateTimeOffset.UtcNow;
}
}
catch (TaskCanceledException) { }
Instance.Log(nameof(WorkerLoop), "Background worker has concluded normally.");
}
}
}