Remove semaphore in SelectiveAutoUserDownload

Also updated style for this file.
Additionally, reworded the corresponding error message to something friendlier.
This commit is contained in:
Noi 2021-10-13 21:36:00 -07:00
parent bef22a5548
commit 8cff530a7c
2 changed files with 45 additions and 70 deletions

View file

@ -1,48 +1,42 @@
using BirthdayBot.Data; using BirthdayBot.Data;
using Discord;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace BirthdayBot.BackgroundServices namespace BirthdayBot.BackgroundServices;
{
/// <summary>
/// A type of workaround to the issue of user information not being cached for guilds that
/// have user information existing in the bot's database. This service runs frequently and
/// determines guilds in which user data must be downloaded, and proceeds to request it.
/// </summary>
class SelectiveAutoUserDownload : BackgroundService
{
private static readonly SemaphoreSlim _updateLock = new(2);
/// <summary>
/// Rather than use up unnecessary resources by auto-downloading the user list in -every-
/// server we're in, this service checks if fetching the user list is warranted for each
/// guild before proceeding to request it.
/// </summary>
class SelectiveAutoUserDownload : BackgroundService {
private readonly HashSet<ulong> _fetchRequests = new(); private readonly HashSet<ulong> _fetchRequests = new();
public SelectiveAutoUserDownload(ShardInstance instance) : base(instance) { } public SelectiveAutoUserDownload(ShardInstance instance) : base(instance) { }
public override async Task OnTick(CancellationToken token) public override async Task OnTick(CancellationToken token) {
{
IEnumerable<ulong> requests; IEnumerable<ulong> requests;
lock (_fetchRequests) lock (_fetchRequests) {
{
requests = _fetchRequests.ToArray(); requests = _fetchRequests.ToArray();
_fetchRequests.Clear(); _fetchRequests.Clear();
} }
foreach (var guild in ShardInstance.DiscordClient.Guilds) foreach (var guild in ShardInstance.DiscordClient.Guilds) {
{ if (ShardInstance.DiscordClient.ConnectionState != ConnectionState.Connected) {
if (ShardInstance.DiscordClient.ConnectionState != Discord.ConnectionState.Connected)
{
Log("Client no longer connected. Stopping early."); Log("Client no longer connected. Stopping early.");
return; return;
} }
// Determine if there is action to be taken... // Determine if there is action to be taken...
if (guild.HasAllMembers) continue; if (guild.HasAllMembers) continue;
if (requests.Contains(guild.Id) || await GuildUserAnyAsync(guild.Id, token).ConfigureAwait(false)) if (requests.Contains(guild.Id) || await GuildUserAnyAsync(guild.Id).ConfigureAwait(false)) {
{
await guild.DownloadUsersAsync().ConfigureAwait(false); await guild.DownloadUsersAsync().ConfigureAwait(false);
await Task.Delay(500, CancellationToken.None).ConfigureAwait(false); // Must delay after a download request. Seems to hang indefinitely otherwise.
await Task.Delay(300, CancellationToken.None).ConfigureAwait(false);
} }
} }
} }
@ -51,36 +45,17 @@ namespace BirthdayBot.BackgroundServices
/// Determines if the user database contains any entries corresponding to this guild. /// Determines if the user database contains any entries corresponding to this guild.
/// </summary> /// </summary>
/// <returns>True if any entries exist.</returns> /// <returns>True if any entries exist.</returns>
private static async Task<bool> GuildUserAnyAsync(ulong guildId, CancellationToken token) private static async Task<bool> GuildUserAnyAsync(ulong guildId) {
{
try
{
await _updateLock.WaitAsync(token).ConfigureAwait(false);
}
catch (Exception ex) when (ex is OperationCanceledException or ObjectDisposedException)
{
// Calling thread does not expect the exception that SemaphoreSlim throws...
throw new TaskCanceledException();
}
try
{
using var db = await Database.OpenConnectionAsync().ConfigureAwait(false); using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
using var c = db.CreateCommand(); using var c = db.CreateCommand();
c.CommandText = $"select count(*) from {GuildUserConfiguration.BackingTable} where guild_id = @Gid"; c.CommandText = $"select true from {GuildUserConfiguration.BackingTable} where guild_id = @Gid limit 1";
c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guildId; c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guildId;
await c.PrepareAsync(CancellationToken.None).ConfigureAwait(false); await c.PrepareAsync(CancellationToken.None).ConfigureAwait(false);
var r = (long)await c.ExecuteScalarAsync(token).ConfigureAwait(false); using var r = await c.ExecuteReaderAsync(CancellationToken.None).ConfigureAwait(false);
return r != 0; return r.Read();
}
finally
{
_updateLock.Release();
}
} }
public void RequestDownload(ulong guildId) public void RequestDownload(ulong guildId) {
{
lock (_fetchRequests) _fetchRequests.Add(guildId); lock (_fetchRequests) _fetchRequests.Add(guildId);
} }
}
} }

View file

@ -22,7 +22,7 @@ namespace BirthdayBot.UserInterface
public const string ParameterError = ":x: Invalid usage. Refer to how to use the command and try again."; public const string ParameterError = ":x: Invalid usage. Refer to how to use the command and try again.";
public const string NoParameterError = ":x: This command does not accept any parameters."; public const string NoParameterError = ":x: This command does not accept any parameters.";
public const string InternalError = ":x: An internal bot error occurred. The bot maintainer has been notified of the issue."; public const string InternalError = ":x: An internal bot error occurred. The bot maintainer has been notified of the issue.";
public const string UsersNotDownloadedError = ":x: Currently unavailable. Please try again in a few minutes."; public const string UsersNotDownloadedError = ":eight_spoked_asterisk: Still catching up... Please try the command again in a few minutes.";
public delegate Task CommandHandler(ShardInstance instance, GuildConfiguration gconf, public delegate Task CommandHandler(ShardInstance instance, GuildConfiguration gconf,
string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser); string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser);