mirror of
https://github.com/JustArchiNET/ArchiSteamFarm
synced 2024-11-10 07:04:27 +00:00
Rewrite callbacks handling loop to new mechanism
This commit is contained in:
parent
67d9486495
commit
0c3c4c08ea
3 changed files with 72 additions and 24 deletions
|
@ -172,7 +172,6 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
internal bool HasLoginCodeReady => !string.IsNullOrEmpty(TwoFactorCode) || !string.IsNullOrEmpty(AuthCode);
|
internal bool HasLoginCodeReady => !string.IsNullOrEmpty(TwoFactorCode) || !string.IsNullOrEmpty(AuthCode);
|
||||||
|
|
||||||
private readonly CallbackManager CallbackManager;
|
private readonly CallbackManager CallbackManager;
|
||||||
private readonly SemaphoreSlim CallbackSemaphore = new(1, 1);
|
|
||||||
private readonly SemaphoreSlim GamesRedeemerInBackgroundSemaphore = new(1, 1);
|
private readonly SemaphoreSlim GamesRedeemerInBackgroundSemaphore = new(1, 1);
|
||||||
private readonly Timer HeartBeatTimer;
|
private readonly Timer HeartBeatTimer;
|
||||||
private readonly SemaphoreSlim InitializationSemaphore = new(1, 1);
|
private readonly SemaphoreSlim InitializationSemaphore = new(1, 1);
|
||||||
|
@ -295,8 +294,8 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
|
|
||||||
private DateTime? AccessTokenValidUntil;
|
private DateTime? AccessTokenValidUntil;
|
||||||
private string? AuthCode;
|
private string? AuthCode;
|
||||||
|
|
||||||
private string? BackingAccessToken;
|
private string? BackingAccessToken;
|
||||||
|
private CancellationTokenSource? CallbacksAborted;
|
||||||
private Timer? ConnectionFailureTimer;
|
private Timer? ConnectionFailureTimer;
|
||||||
private bool FirstTradeSent;
|
private bool FirstTradeSent;
|
||||||
private Timer? GamesRedeemerInBackgroundTimer;
|
private Timer? GamesRedeemerInBackgroundTimer;
|
||||||
|
@ -410,7 +409,6 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
// Those are objects that are always being created if constructor doesn't throw exception
|
// Those are objects that are always being created if constructor doesn't throw exception
|
||||||
ArchiWebHandler.Dispose();
|
ArchiWebHandler.Dispose();
|
||||||
BotDatabase.Dispose();
|
BotDatabase.Dispose();
|
||||||
CallbackSemaphore.Dispose();
|
|
||||||
GamesRedeemerInBackgroundSemaphore.Dispose();
|
GamesRedeemerInBackgroundSemaphore.Dispose();
|
||||||
InitializationSemaphore.Dispose();
|
InitializationSemaphore.Dispose();
|
||||||
MessagingSemaphore.Dispose();
|
MessagingSemaphore.Dispose();
|
||||||
|
@ -423,6 +421,8 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
HeartBeatTimer.Dispose();
|
HeartBeatTimer.Dispose();
|
||||||
|
|
||||||
// Those are objects that might be null and the check should be in-place
|
// Those are objects that might be null and the check should be in-place
|
||||||
|
CallbacksAborted?.Cancel();
|
||||||
|
CallbacksAborted?.Dispose();
|
||||||
ConnectionFailureTimer?.Dispose();
|
ConnectionFailureTimer?.Dispose();
|
||||||
GamesRedeemerInBackgroundTimer?.Dispose();
|
GamesRedeemerInBackgroundTimer?.Dispose();
|
||||||
PlayingWasBlockedTimer?.Dispose();
|
PlayingWasBlockedTimer?.Dispose();
|
||||||
|
@ -436,7 +436,6 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
// Those are objects that are always being created if constructor doesn't throw exception
|
// Those are objects that are always being created if constructor doesn't throw exception
|
||||||
ArchiWebHandler.Dispose();
|
ArchiWebHandler.Dispose();
|
||||||
BotDatabase.Dispose();
|
BotDatabase.Dispose();
|
||||||
CallbackSemaphore.Dispose();
|
|
||||||
GamesRedeemerInBackgroundSemaphore.Dispose();
|
GamesRedeemerInBackgroundSemaphore.Dispose();
|
||||||
InitializationSemaphore.Dispose();
|
InitializationSemaphore.Dispose();
|
||||||
MessagingSemaphore.Dispose();
|
MessagingSemaphore.Dispose();
|
||||||
|
@ -449,6 +448,12 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
await HeartBeatTimer.DisposeAsync().ConfigureAwait(false);
|
await HeartBeatTimer.DisposeAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// Those are objects that might be null and the check should be in-place
|
// Those are objects that might be null and the check should be in-place
|
||||||
|
if (CallbacksAborted != null) {
|
||||||
|
await CallbacksAborted.CancelAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
CallbacksAborted.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
if (ConnectionFailureTimer != null) {
|
if (ConnectionFailureTimer != null) {
|
||||||
await ConnectionFailureTimer.DisposeAsync().ConfigureAwait(false);
|
await ConnectionFailureTimer.DisposeAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -1925,7 +1930,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeepRunning = true;
|
KeepRunning = true;
|
||||||
Utilities.InBackground(HandleCallbacks, true);
|
|
||||||
ArchiLogger.LogGenericInfo(Strings.Starting);
|
ArchiLogger.LogGenericInfo(Strings.Starting);
|
||||||
|
|
||||||
// Support and convert 2FA files
|
// Support and convert 2FA files
|
||||||
|
@ -1955,6 +1960,13 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
await ImportKeysToRedeem(keysToRedeemFilePath).ConfigureAwait(false);
|
await ImportKeysToRedeem(keysToRedeemFilePath).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If any previous callbacks handling loop is still going, we're going to abort it
|
||||||
|
await StopHandlingCallbacks().ConfigureAwait(false);
|
||||||
|
|
||||||
|
CallbacksAborted = new CancellationTokenSource();
|
||||||
|
|
||||||
|
Utilities.InBackground(() => HandleCallbacks(CallbacksAborted.Token), true);
|
||||||
|
|
||||||
await Connect().ConfigureAwait(false);
|
await Connect().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1964,6 +1976,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeepRunning = false;
|
KeepRunning = false;
|
||||||
|
|
||||||
ArchiLogger.LogGenericInfo(Strings.BotStopping);
|
ArchiLogger.LogGenericInfo(Strings.BotStopping);
|
||||||
|
|
||||||
if (SteamClient.IsConnected) {
|
if (SteamClient.IsConnected) {
|
||||||
|
@ -2037,6 +2050,23 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure the handling loop is stopped, but allow a few extra seconds for any lost callbacks to trigger
|
||||||
|
CancellationTokenSource? callbacksAborted = CallbacksAborted;
|
||||||
|
|
||||||
|
if (callbacksAborted is { IsCancellationRequested: false }) {
|
||||||
|
Utilities.InBackground(
|
||||||
|
async () => {
|
||||||
|
await Task.Delay(5000, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await callbacksAborted.CancelAsync().ConfigureAwait(false);
|
||||||
|
} catch {
|
||||||
|
// Ignored, object already disposed or similar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Bots.TryRemove(BotName, out _);
|
Bots.TryRemove(BotName, out _);
|
||||||
await PluginsCore.OnBotDestroy(this).ConfigureAwait(false);
|
await PluginsCore.OnBotDestroy(this).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -2140,25 +2170,14 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleCallbacks() {
|
private async Task HandleCallbacks(CancellationToken cancellationToken = default) {
|
||||||
if (!await CallbackSemaphore.WaitAsync(CallbackSleep).ConfigureAwait(false)) {
|
|
||||||
if (Debugging.IsUserDebugging) {
|
|
||||||
ArchiLogger.LogGenericDebug(Strings.FormatWarningFailedWithError(nameof(CallbackSemaphore)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
|
// Our objective here is to process the callbacks for as long as it's relevant
|
||||||
|
while (!cancellationToken.IsCancellationRequested) {
|
||||||
while (KeepRunning || SteamClient.IsConnected) {
|
await CallbackManager.RunWaitCallbackAsync(cancellationToken).ConfigureAwait(false);
|
||||||
CallbackManager.RunWaitAllCallbacks(timeSpan);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (OperationCanceledException) {
|
||||||
ArchiLogger.LogGenericException(e);
|
// Ignored, we were asked to stop processing
|
||||||
} finally {
|
|
||||||
CallbackSemaphore.Release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2826,12 +2845,16 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
|
|
||||||
// If we initiated disconnect, do not attempt to reconnect
|
// If we initiated disconnect, do not attempt to reconnect
|
||||||
if (callback.UserInitiated && !ReconnectOnUserInitiated) {
|
if (callback.UserInitiated && !ReconnectOnUserInitiated) {
|
||||||
|
await StopHandlingCallbacks().ConfigureAwait(false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (lastLogOnResult) {
|
switch (lastLogOnResult) {
|
||||||
case EResult.AccountDisabled:
|
case EResult.AccountDisabled:
|
||||||
// Do not attempt to reconnect, those failures are permanent
|
// Do not attempt to reconnect, those failures are permanent
|
||||||
|
await StopHandlingCallbacks().ConfigureAwait(false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case EResult.AccessDenied when !string.IsNullOrEmpty(RefreshToken):
|
case EResult.AccessDenied when !string.IsNullOrEmpty(RefreshToken):
|
||||||
case EResult.Expired when !string.IsNullOrEmpty(RefreshToken):
|
case EResult.Expired when !string.IsNullOrEmpty(RefreshToken):
|
||||||
|
@ -2864,7 +2887,13 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!KeepRunning || SteamClient.IsConnected) {
|
if (!KeepRunning) {
|
||||||
|
await StopHandlingCallbacks().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SteamClient.IsConnected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2872,7 +2901,13 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
while (RequiredInput != ASF.EUserInputType.None) {
|
while (RequiredInput != ASF.EUserInputType.None) {
|
||||||
await Task.Delay(1000).ConfigureAwait(false);
|
await Task.Delay(1000).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!KeepRunning || SteamClient.IsConnected) {
|
if (!KeepRunning) {
|
||||||
|
await StopHandlingCallbacks().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SteamClient.IsConnected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3847,6 +3882,17 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||||
ConnectionFailureTimer = null;
|
ConnectionFailureTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StopHandlingCallbacks() {
|
||||||
|
if (CallbacksAborted == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CallbacksAborted.CancelAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
CallbacksAborted.Dispose();
|
||||||
|
CallbacksAborted = null;
|
||||||
|
}
|
||||||
|
|
||||||
private void StopPlayingWasBlockedTimer() {
|
private void StopPlayingWasBlockedTimer() {
|
||||||
if (PlayingWasBlockedTimer == null) {
|
if (PlayingWasBlockedTimer == null) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -843,6 +843,7 @@ public sealed class ArchiHandler : ClientMsgHandler {
|
||||||
// If we have custom name to display, we must workaround the Steam network broken behaviour and send request on clean non-playing session
|
// If we have custom name to display, we must workaround the Steam network broken behaviour and send request on clean non-playing session
|
||||||
// This ensures that custom name will in fact display properly (if it's not omitted due to MaxGamesPlayedConcurrently, that is)
|
// This ensures that custom name will in fact display properly (if it's not omitted due to MaxGamesPlayedConcurrently, that is)
|
||||||
Client.Send(request);
|
Client.Send(request);
|
||||||
|
|
||||||
await Task.Delay(Bot.CallbackSleep).ConfigureAwait(false);
|
await Task.Delay(Bot.CallbackSleep).ConfigureAwait(false);
|
||||||
|
|
||||||
request.Body.games_played.Add(
|
request.Body.games_played.Add(
|
||||||
|
|
|
@ -269,6 +269,7 @@ public sealed class Actions : IAsyncDisposable, IDisposable {
|
||||||
// We add extra delay because OnFarmingStopped() also executes PlayGames()
|
// We add extra delay because OnFarmingStopped() also executes PlayGames()
|
||||||
// Despite of proper order on our end, Steam network might not respect it
|
// Despite of proper order on our end, Steam network might not respect it
|
||||||
await Task.Delay(Bot.CallbackSleep).ConfigureAwait(false);
|
await Task.Delay(Bot.CallbackSleep).ConfigureAwait(false);
|
||||||
|
|
||||||
await Bot.ArchiHandler.PlayGames([], Bot.BotConfig.CustomGamePlayedWhileIdle).ConfigureAwait(false);
|
await Bot.ArchiHandler.PlayGames([], Bot.BotConfig.CustomGamePlayedWhileIdle).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue