From 96a69f2157cbad021833d1dde338dfc386420d99 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Mon, 3 Jul 2017 18:54:57 +0200 Subject: [PATCH] Fix deadlock in FarmMultiple() Our Forget() tasks are in fact getting forgotten when given async method stumbles upon await call. We were executing async Farm() via Forget(), but it was entirely possible that we won't get opportunity to await anything if we follow FarmHours() path, making FarmingSemaphore signaled until we're done farming hours. --- ArchiSteamFarm.sln.DotSettings | 1 + ArchiSteamFarm/Bot.cs | 2 +- ArchiSteamFarm/CardsFarmer.cs | 10 ++++++++-- ArchiSteamFarm/IPC.cs | 1 + ArchiSteamFarm/Utilities.cs | 36 ++++++++++++++++++++++++++-------- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings index 6f45e6afc..8cec00722 100644 --- a/ArchiSteamFarm.sln.DotSettings +++ b/ArchiSteamFarm.sln.DotSettings @@ -1,4 +1,5 @@  + False True SUGGESTION SUGGESTION diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 2e2cbe025..e8d62d405 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -3603,7 +3603,7 @@ namespace ArchiSteamFarm { private async Task Start() { if (!KeepRunning) { KeepRunning = true; - Task.Factory.StartNew(HandleCallbacks, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning).Forget(); + Utilities.StartBackgroundAction(HandleCallbacks); ArchiLogger.LogGenericInfo(Strings.Starting); } diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index dff14d4c2..5734699a6 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -93,7 +93,13 @@ namespace ArchiSteamFarm { IdleFarmingTimer?.Dispose(); } - internal void OnDisconnected() => StopFarming().Forget(); + internal void OnDisconnected() { + if (!NowFarming) { + return; + } + + StopFarming().Forget(); + } internal async Task OnNewGameAdded() { // We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue @@ -222,7 +228,7 @@ namespace ArchiSteamFarm { } KeepFarming = NowFarming = true; - Farm().Forget(); // Farm() will end when we're done farming, so don't wait for it + Utilities.StartBackgroundFunction(Farm); } finally { FarmingSemaphore.Release(); } diff --git a/ArchiSteamFarm/IPC.cs b/ArchiSteamFarm/IPC.cs index 9b6fb1708..0ef7c2455 100644 --- a/ArchiSteamFarm/IPC.cs +++ b/ArchiSteamFarm/IPC.cs @@ -64,6 +64,7 @@ namespace ArchiSteamFarm { } KeepRunning = true; + Utilities.StartBackgroundFunction(Run); Task.Factory.StartNew(Run, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning).Forget(); ASF.ArchiLogger.LogGenericInfo(Strings.IPCReady); diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index 7ba952fbc..27760fd89 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -29,6 +29,7 @@ using System.Globalization; using System.Linq; using System.Net; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Humanizer; namespace ArchiSteamFarm { @@ -40,17 +41,18 @@ namespace ArchiSteamFarm { internal static void Forget(this object obj) { } internal static string GetArgsAsString(this string[] args, byte argsToSkip = 1) { - if (args.Length >= argsToSkip) { - return string.Join(" ", args.GetArgs(argsToSkip)); + if ((args == null) || (args.Length < argsToSkip)) { + ASF.ArchiLogger.LogNullError(nameof(args)); + return null; } - ASF.ArchiLogger.LogNullError(nameof(args)); - return null; + string result = string.Join(" ", args.GetArgs(argsToSkip)); + return result; } internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) { - if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) { - ASF.ArchiLogger.LogNullError(nameof(url) + " || " + nameof(name)); + if ((cookieContainer == null) || string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) { + ASF.ArchiLogger.LogNullError(nameof(cookieContainer) + " || " + nameof(url) + " || " + nameof(name)); return null; } @@ -93,14 +95,32 @@ namespace ArchiSteamFarm { } } - internal static IEnumerable ToEnumerable(this T item) { + internal static void StartBackgroundAction(Action action) { + if (action == null) { + ASF.ArchiLogger.LogNullError(nameof(action)); + return; + } + + Task.Factory.StartNew(action, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning).Forget(); + } + + internal static void StartBackgroundFunction(Func function) { + if (function == null) { + ASF.ArchiLogger.LogNullError(nameof(function)); + return; + } + + Task.Factory.StartNew(function, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning).Forget(); + } + + internal static IEnumerable ToEnumerable(this T item) where T : struct { yield return item; } internal static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3); private static string[] GetArgs(this string[] args, byte argsToSkip = 1) { - if (args.Length < argsToSkip) { + if ((args == null) || (args.Length < argsToSkip)) { ASF.ArchiLogger.LogNullError(nameof(args)); return null; }