diff --git a/TableDependency.SqlClient.Test/Inheritance/SqlTableDependencyTest.cs b/TableDependency.SqlClient.Test/Inheritance/SqlTableDependencyTest.cs index db5dfb6..c531012 100644 --- a/TableDependency.SqlClient.Test/Inheritance/SqlTableDependencyTest.cs +++ b/TableDependency.SqlClient.Test/Inheritance/SqlTableDependencyTest.cs @@ -283,7 +283,7 @@ protected override async Task WaitForNotifications( sqlCommand.CommandTimeout = 0; this.WriteTraceMessage(TraceLevel.Verbose, "Executing WAITFOR command."); - using (var sqlDataReader = await sqlCommand.ExecuteReaderAsync(cancellationToken).WithCancellation(cancellationToken)) + using (var sqlDataReader = await sqlCommand.ExecuteReaderAsync(cancellationToken)) { if (_throwExceptionInWaitForNotificationsPoint3) throw new Exception(); diff --git a/TableDependency.SqlClient.Test/Issue253Test.cs b/TableDependency.SqlClient.Test/Issue253Test.cs new file mode 100644 index 0000000..ae5f332 --- /dev/null +++ b/TableDependency.SqlClient.Test/Issue253Test.cs @@ -0,0 +1,99 @@ +using System; +using System.Data.SqlClient; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace TableDependency.SqlClient.Test +{ + [TestClass] + public class Issue253Test : Base.SqlTableDependencyBaseTest + { + private class Issue253Model + { + public string Id { get; set; } + } + + private static readonly string TableName = nameof(Issue253Model); + + private Exception _unobservedTaskException; + + [ClassInitialize] + public static void ClassInitialize(TestContext testContext) + { + using (var sqlConnection = new SqlConnection(ConnectionStringForTestUser)) + { + sqlConnection.Open(); + using (var sqlCommand = sqlConnection.CreateCommand()) + { + sqlCommand.CommandText = $"IF OBJECT_ID('[{TableName}]', 'U') IS NOT NULL DROP TABLE [dbo].[{TableName}]"; + sqlCommand.ExecuteNonQuery(); + sqlCommand.CommandText = $"CREATE TABLE [{TableName}]([Id] [int] NULL)"; + sqlCommand.ExecuteNonQuery(); + } + } + } + + [ClassCleanup] + public static void ClassCleanup() + { + using (var sqlConnection = new SqlConnection(ConnectionStringForTestUser)) + { + sqlConnection.Open(); + using (var sqlCommand = sqlConnection.CreateCommand()) + { + sqlCommand.CommandText = $"IF OBJECT_ID('{TableName}', 'U') IS NOT NULL DROP TABLE [{TableName}];"; + sqlCommand.ExecuteNonQuery(); + } + } + } + + [TestCategory("SqlServer")] + [TestMethod] + public void Test() + { + try + { + string naming; + + using (var tableDependency = new SqlTableDependency(ConnectionStringForTestUser, tableName: TableName)) + { + tableDependency.OnChanged += (o, args) => { }; + tableDependency.Start(); + naming = tableDependency.DataBaseObjectsNamingConvention; + + Thread.Sleep(5000); + + TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException; + try + { + tableDependency.Stop(); + + Thread.Sleep(5000); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + finally + { + TaskScheduler.UnobservedTaskException -= TaskSchedulerOnUnobservedTaskException; + } + } + + Assert.IsTrue(base.AreAllDbObjectDisposed(naming)); + Assert.IsTrue(base.CountConversationEndpoints(naming) == 0); + } + catch (Exception exception) + { + Assert.Fail(exception.Message); + } + + Assert.IsNull(_unobservedTaskException); + } + + private void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + { + _unobservedTaskException = e.Exception; + } + } +} \ No newline at end of file diff --git a/TableDependency.SqlClient/Extensions/TaskExtensions.cs b/TableDependency.SqlClient/Extensions/TaskExtensions.cs deleted file mode 100644 index 598a190..0000000 --- a/TableDependency.SqlClient/Extensions/TaskExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -#region License -// TableDependency, SqlTableDependency -// Copyright (c) 2015-2020 Christian Del Bianco. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -#endregion - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace TableDependency.SqlClient.Extensions -{ - public static class TaskExtensions - { - public static async Task WithCancellation(this Task task, CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); - using (cancellationToken.Register(s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) - { - if (task != await Task.WhenAny(task, tcs.Task)) - { - throw new OperationCanceledException(cancellationToken); - } - } - - return task.Result; - } - } -} \ No newline at end of file diff --git a/TableDependency.SqlClient/SqlTableDependency.cs b/TableDependency.SqlClient/SqlTableDependency.cs index 1fa19cc..a6115fd 100644 --- a/TableDependency.SqlClient/SqlTableDependency.cs +++ b/TableDependency.SqlClient/SqlTableDependency.cs @@ -1025,7 +1025,7 @@ protected virtual async Task WaitForNotifications( sqlCommand.CommandTimeout = 0; this.WriteTraceMessage(TraceLevel.Verbose, "Executing WAITFOR command."); - using (var sqlDataReader = await sqlCommand.ExecuteReaderAsync(cancellationToken).WithCancellation(cancellationToken)) + using (var sqlDataReader = await sqlCommand.ExecuteReaderAsync(cancellationToken)) { while (sqlDataReader.Read()) {