diff --git a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenRogueNativeErrorMessage.cs b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenRogueNativeErrorMessage.cs index a95ca49..2a2a85d 100644 --- a/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenRogueNativeErrorMessage.cs +++ b/Laerdal.McuMgr.Tests/FileDownloader/FileDownloaderTestbed.SingleFileDownloadAsync.ShouldThrowAllDownloadAttemptsFailedException_GivenRogueNativeErrorMessage.cs @@ -26,8 +26,8 @@ public async Task SingleFileDownloadAsync_ShouldThrowAllDownloadAttemptsFailedEx var mockedNativeFileDownloaderProxy = new MockedErroneousNativeFileDownloaderProxySpy13( mockedFileData: mockedFileData, - downloaderCallbacksProxy: new GenericNativeFileDownloaderCallbacksProxy_(), - rogueNativeErrorMessage: nativeRogueErrorMessage + rogueNativeErrorMessage: nativeRogueErrorMessage, + downloaderCallbacksProxy: new GenericNativeFileDownloaderCallbacksProxy_() ); var fileDownloader = new McuMgr.FileDownloader.FileDownloader(mockedNativeFileDownloaderProxy); @@ -44,8 +44,7 @@ public async Task SingleFileDownloadAsync_ShouldThrowAllDownloadAttemptsFailedEx )); // Assert - await work.Should() - .ThrowWithinAsync(3.Seconds()); + await work.Should().ThrowWithinAsync(3.Seconds()); mockedNativeFileDownloaderProxy.CancelCalled.Should().BeFalse(); mockedNativeFileDownloaderProxy.DisconnectCalled.Should().BeFalse(); //00 diff --git a/Laerdal.McuMgr.slnx.DotSettings.user b/Laerdal.McuMgr.slnx.DotSettings.user index 8e5f73b..b93224e 100644 --- a/Laerdal.McuMgr.slnx.DotSettings.user +++ b/Laerdal.McuMgr.slnx.DotSettings.user @@ -6,6 +6,10 @@ <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Solution /> </SessionState> + True + True + True + True diff --git a/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceExtensions.cs b/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceExtensions.cs index d245727..9fa8c0a 100644 --- a/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceExtensions.cs +++ b/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceExtensions.cs @@ -12,7 +12,7 @@ namespace Laerdal.McuMgr.Common.Helpers /// Task is not actually in a "scheduler" (since TCS tasks are Promise Tasks) /// never completing tasks, of any type, is generally considered a bug. /// - public static class TaskCompletionSourceExtensions + static internal class TaskCompletionSourceExtensions { /// /// Sets up a timeout-monitor on the given task. This is essentially a wrapper around @@ -35,7 +35,7 @@ public static class TaskCompletionSourceExtensions /// try /// { /// PropertyChanged += MyEventHandler_; - /// await tcs.WaitTaskWithOptionalTimeoutAsync(timeout); + /// await tcs.WaitAndFossilizeTaskWithOptionalTimeoutAsync(timeout); /// } /// finally /// { @@ -60,11 +60,11 @@ public static class TaskCompletionSourceExtensions /// } /// } /// - public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default) + public static Task WaitAndFossilizeTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default) { return timeoutInMilliseconds <= 0 ? tcs.Task - : tcs.WaitTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken); + : tcs.WaitAndFossilizeTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken); } /// @@ -88,7 +88,7 @@ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tc /// try /// { /// PropertyChanged += MyEventHandler_; - /// await tcs.WaitTaskWithOptionalTimeoutAsync(timeout); + /// await tcs.WaitAndFossilizeTaskWithOptionalTimeoutAsync(timeout); /// } /// finally /// { @@ -113,11 +113,11 @@ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tc /// } /// } /// - public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default) + public static Task WaitAndFossilizeTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default) { return timespan <= TimeSpan.Zero ? tcs.Task - : tcs.WaitTaskWithTimeoutAsync(timespan, cancellationToken); + : tcs.WaitAndFossilizeTaskWithTimeoutAsync(timespan, cancellationToken); } /// @@ -142,7 +142,7 @@ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tc /// try /// { /// PropertyChanged += MyEventHandler_; - /// await tcs.WaitTaskWithOptionalTimeoutAsync(timeout); + /// await tcs.WaitAndFossilizeTaskWithOptionalTimeoutAsync(timeout); /// } /// finally /// { @@ -167,9 +167,9 @@ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tc /// } /// } /// - public static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default) + public static Task WaitAndFossilizeTaskWithTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default) { - return tcs.WaitTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken); + return tcs.WaitAndFossilizeTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken); } /// @@ -194,7 +194,7 @@ public static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, int t /// try /// { /// PropertyChanged += MyEventHandler_; - /// await tcs.WaitTaskWithTimeoutAsync(timeout); + /// await tcs.WaitAndFossilizeTaskWithTimeoutAsync(timeout); /// } /// finally /// { @@ -219,7 +219,7 @@ public static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, int t /// } /// } /// - public async static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default) + public async static Task WaitAndFossilizeTaskWithTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default) { if (timespan < TimeSpan.Zero) //note that this deviates from the behaviour of .WaitAsync() which does accept -1 milliseconds which means "wait forever" { @@ -279,7 +279,7 @@ public async static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, /// try /// { /// PropertyChanged += MyEventHandler_; - /// await tcs.WaitTaskWithTimeoutAsync<int>(timeout); + /// await tcs.WaitAndFossilizeTaskWithTimeoutAsync<int>(timeout); /// } /// finally /// { @@ -304,11 +304,11 @@ public async static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, /// } /// } /// - public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default) + public static Task WaitAndFossilizeTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default) { return timeoutInMilliseconds <= 0 ? tcs.Task - : tcs.WaitTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken); + : tcs.WaitAndFossilizeTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken); } /// @@ -332,7 +332,7 @@ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSou /// try /// { /// PropertyChanged += MyEventHandler_; - /// await tcs.WaitTaskWithOptionalTimeoutAsync<int>(timeout); + /// await tcs.WaitAndFossilizeTaskWithOptionalTimeoutAsync<int>(timeout); /// } /// finally /// { @@ -357,11 +357,11 @@ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSou /// } /// } /// - public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default) + public static Task WaitAndFossilizeTaskWithOptionalTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default) { return timespan <= TimeSpan.Zero ? tcs.Task - : tcs.WaitTaskWithTimeoutAsync(timespan, cancellationToken); + : tcs.WaitAndFossilizeTaskWithTimeoutAsync(timespan, cancellationToken); } /// @@ -385,7 +385,7 @@ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSou /// try /// { /// PropertyChanged += MyEventHandler_; - /// await tcs.WaitTaskWithTimeoutAsync<int>(timeout); + /// await tcs.WaitAndFossilizeTaskWithTimeoutAsync<int>(timeout); /// } /// finally /// { @@ -410,9 +410,9 @@ public static Task WaitTaskWithOptionalTimeoutAsync(this TaskCompletionSou /// } /// } /// - public static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default) + public static Task WaitAndFossilizeTaskWithTimeoutAsync(this TaskCompletionSource tcs, int timeoutInMilliseconds, CancellationToken cancellationToken = default) { - return tcs.WaitTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken); + return tcs.WaitAndFossilizeTaskWithTimeoutAsync(TimeSpan.FromMilliseconds(timeoutInMilliseconds), cancellationToken); } /// @@ -436,7 +436,7 @@ public static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource t /// try /// { /// PropertyChanged += MyEventHandler_; - /// await tcs.WaitTaskWithTimeoutAsync<int>(timeout); + /// await tcs.WaitAndFossilizeTaskWithTimeoutAsync<int>(timeout); /// } /// finally /// { @@ -461,7 +461,7 @@ public static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource t /// } /// } /// - public async static Task WaitTaskWithTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default) + public async static Task WaitAndFossilizeTaskWithTimeoutAsync(this TaskCompletionSource tcs, TimeSpan timespan, CancellationToken cancellationToken = default) { if (timespan < TimeSpan.Zero) //note that this deviates from the behaviour of .WaitAsync() which does accept -1 milliseconds which means "wait forever" { diff --git a/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceRCA.cs b/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceRCA.cs new file mode 100644 index 0000000..d0d3ba6 --- /dev/null +++ b/Laerdal.McuMgr/Shared/Common/Helpers/TaskCompletionSourceRCA.cs @@ -0,0 +1,92 @@ +using System.Threading.Tasks; + +namespace Laerdal.McuMgr.Common.Helpers +{ + /// Like but with . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + internal sealed class TaskCompletionSourceRCA : TaskCompletionSource + { + /// Like but with the . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + public TaskCompletionSourceRCA() : base(TaskCreationOptions.RunContinuationsAsynchronously) + { + } + + /// Like but with . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + public TaskCompletionSourceRCA(object state) : base(state, TaskCreationOptions.RunContinuationsAsynchronously) + { + } + + /// Like but with . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + public TaskCompletionSourceRCA(object state, TaskCreationOptions creationOptions) : base(state, creationOptions | TaskCreationOptions.RunContinuationsAsynchronously) + { + } + + /// Like but with . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + public TaskCompletionSourceRCA(TaskCreationOptions creationOptions) : base(creationOptions | TaskCreationOptions.RunContinuationsAsynchronously) + { + } + } + + /// Like but with . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + internal sealed class TaskCompletionSourceRCA : TaskCompletionSource + { + /// Like but with the . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + public TaskCompletionSourceRCA() : base(TaskCreationOptions.RunContinuationsAsynchronously) + { + } + + /// Like but with . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + public TaskCompletionSourceRCA(object state) : base(state, TaskCreationOptions.RunContinuationsAsynchronously) + { + } + + /// Like but with . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + public TaskCompletionSourceRCA(object state, TaskCreationOptions creationOptions) : base(state, creationOptions | TaskCreationOptions.RunContinuationsAsynchronously) + { + } + + /// Like but with . turned on by default in all constructors. + /// + /// Inspired by Microsoft's recommendation on TCS which strongly + /// advises using . with to avoid exotic deadlocks in certain corner-cases. + /// + public TaskCompletionSourceRCA(TaskCreationOptions creationOptions) : base(creationOptions | TaskCreationOptions.RunContinuationsAsynchronously) + { + } + } +} diff --git a/Laerdal.McuMgr/Shared/DeviceResetter/DeviceResetter.cs b/Laerdal.McuMgr/Shared/DeviceResetter/DeviceResetter.cs index 78f98c0..97d77a6 100644 --- a/Laerdal.McuMgr/Shared/DeviceResetter/DeviceResetter.cs +++ b/Laerdal.McuMgr/Shared/DeviceResetter/DeviceResetter.cs @@ -98,7 +98,7 @@ public event EventHandler StateChanged public async Task ResetAsync(int timeoutInMs = -1) { - var taskCompletionSource = new TaskCompletionSource(state: false); + var taskCompletionSource = new TaskCompletionSourceRCA(state: false); try { @@ -109,7 +109,7 @@ public async Task ResetAsync(int timeoutInMs = -1) if (verdict != EDeviceResetterInitializationVerdict.Success) throw new ArgumentException(verdict.ToString()); - await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutInMs); + await taskCompletionSource.WaitAndFossilizeTaskWithOptionalTimeoutAsync(timeoutInMs); } catch (TimeoutException ex) { diff --git a/Laerdal.McuMgr/Shared/FileDownloader/FileDownloader.cs b/Laerdal.McuMgr/Shared/FileDownloader/FileDownloader.cs index 61df02e..43b8117 100644 --- a/Laerdal.McuMgr/Shared/FileDownloader/FileDownloader.cs +++ b/Laerdal.McuMgr/Shared/FileDownloader/FileDownloader.cs @@ -240,7 +240,7 @@ public async Task DownloadAsync( var didWarnOnceAboutUnstableConnection = false; for (var triesCount = 1; !isCancellationRequested;) { - var taskCompletionSource = new TaskCompletionSource(state: null); + var taskCompletionSource = new TaskCompletionSourceRCA(state: null); try { @@ -285,7 +285,7 @@ public async Task DownloadAsync( if (verdict != EFileDownloaderVerdict.Success) throw new ArgumentException(verdict.ToString()); - result = await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutForDownloadInMs); + result = await taskCompletionSource.WaitAndFossilizeTaskWithOptionalTimeoutAsync(timeoutForDownloadInMs); break; } catch (TimeoutException ex) @@ -344,6 +344,8 @@ ex is not ArgumentException //10 wops probably missing native lib symbols! } finally { + taskCompletionSource.TrySetCanceled(); //it is best to ensure that the task is fossilized in case of rogue exceptions + Cancelled -= FileDownloader_Cancelled_; StateChanged -= FileDownloader_StateChanged_; DownloadCompleted -= FileDownloader_DownloadCompleted_; diff --git a/Laerdal.McuMgr/Shared/FileUploader/FileUploader.cs b/Laerdal.McuMgr/Shared/FileUploader/FileUploader.cs index 7ab5240..2425092 100644 --- a/Laerdal.McuMgr/Shared/FileUploader/FileUploader.cs +++ b/Laerdal.McuMgr/Shared/FileUploader/FileUploader.cs @@ -299,7 +299,7 @@ public async Task UploadAsync( var didWarnOnceAboutUnstableConnection = false; for (var triesCount = 1; !isCancellationRequested;) { - var taskCompletionSource = new TaskCompletionSource(state: false); + var taskCompletionSource = new TaskCompletionSourceRCA(state: false); try { Cancelled += FileUploader_Cancelled_; @@ -352,7 +352,7 @@ public async Task UploadAsync( if (verdict != EFileUploaderVerdict.Success) throw new ArgumentException(verdict.ToString()); - await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutForUploadInMs); //order + await taskCompletionSource.WaitAndFossilizeTaskWithOptionalTimeoutAsync(timeoutForUploadInMs); //order break; } catch (TimeoutException ex) diff --git a/Laerdal.McuMgr/Shared/FirmwareEraser/FirmwareEraser.cs b/Laerdal.McuMgr/Shared/FirmwareEraser/FirmwareEraser.cs index ba9d5ba..4e66ef5 100644 --- a/Laerdal.McuMgr/Shared/FirmwareEraser/FirmwareEraser.cs +++ b/Laerdal.McuMgr/Shared/FirmwareEraser/FirmwareEraser.cs @@ -114,7 +114,7 @@ public event EventHandler FatalErrorOccurred public async Task EraseAsync(int imageIndex = 1, int timeoutInMs = -1) { - var taskCompletionSource = new TaskCompletionSource(state: false); + var taskCompletionSource = new TaskCompletionSourceRCA(state: false); try { @@ -125,7 +125,7 @@ public async Task EraseAsync(int imageIndex = 1, int timeoutInMs = -1) if (verdict != EFirmwareErasureInitializationVerdict.Success) throw new ArgumentException(verdict.ToString()); - await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutInMs); + await taskCompletionSource.WaitAndFossilizeTaskWithOptionalTimeoutAsync(timeoutInMs); } catch (TimeoutException ex) { diff --git a/Laerdal.McuMgr/Shared/FirmwareInstaller/FirmwareInstaller.cs b/Laerdal.McuMgr/Shared/FirmwareInstaller/FirmwareInstaller.cs index f4d558b..f76bea1 100644 --- a/Laerdal.McuMgr/Shared/FirmwareInstaller/FirmwareInstaller.cs +++ b/Laerdal.McuMgr/Shared/FirmwareInstaller/FirmwareInstaller.cs @@ -213,7 +213,7 @@ public async Task InstallAsync( var didWarnOnceAboutUnstableConnection = false; for (var triesCount = 1; !isCancellationRequested;) { - var taskCompletionSource = new TaskCompletionSource(state: false); + var taskCompletionSource = new TaskCompletionSourceRCA(state: false); try { Cancelled += FirmwareInstaller_Cancelled_; @@ -266,7 +266,7 @@ public async Task InstallAsync( if (verdict != EFirmwareInstallationVerdict.Success) throw new ArgumentException(verdict.ToString()); - await taskCompletionSource.WaitTaskWithOptionalTimeoutAsync(timeoutInMs); + await taskCompletionSource.WaitAndFossilizeTaskWithOptionalTimeoutAsync(timeoutInMs); } catch (TimeoutException ex) { diff --git a/Laerdal.Scripts/Laerdal.Builder.targets b/Laerdal.Scripts/Laerdal.Builder.targets index ada0606..0e18d8c 100644 --- a/Laerdal.Scripts/Laerdal.Builder.targets +++ b/Laerdal.Scripts/Laerdal.Builder.targets @@ -4,32 +4,27 @@ - - - - + + + + - - - - - - - - - - + + + + +