|  | 
|  | 1 | +// Licensed to the .NET Foundation under one or more agreements. | 
|  | 2 | +// The .NET Foundation licenses this file to you under the MIT license. | 
|  | 3 | +// See the LICENSE file in the project root for more information. | 
|  | 4 | + | 
|  | 5 | +using System; | 
|  | 6 | +using System.Collections; | 
|  | 7 | +using System.Collections.Generic; | 
|  | 8 | +using System.Data; | 
|  | 9 | +using System.Threading.Tasks; | 
|  | 10 | +using System.Xml; | 
|  | 11 | +using Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals; | 
|  | 12 | +using Xunit; | 
|  | 13 | + | 
|  | 14 | +namespace Microsoft.Data.SqlClient.ManualTesting.Tests | 
|  | 15 | +{ | 
|  | 16 | +    public static class AsyncTimeoutTest | 
|  | 17 | +    { | 
|  | 18 | +        static string delayQuery2s = "WAITFOR DELAY '00:00:02'"; | 
|  | 19 | +        static string delayQuery10s = "WAITFOR DELAY '00:00:10'"; | 
|  | 20 | + | 
|  | 21 | +        public enum AsyncAPI | 
|  | 22 | +        { | 
|  | 23 | +            ExecuteReaderAsync, | 
|  | 24 | +            ExecuteScalarAsync, | 
|  | 25 | +            ExecuteXmlReaderAsync | 
|  | 26 | +        } | 
|  | 27 | + | 
|  | 28 | +        [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] | 
|  | 29 | +        [ClassData(typeof(AsyncTimeoutTestVariations))] | 
|  | 30 | +        public static void TestDelayedAsyncTimeout(AsyncAPI api, string commonObj, int delayPeriod, bool marsEnabled) => | 
|  | 31 | +            RunTest(api, commonObj, delayPeriod, marsEnabled); | 
|  | 32 | + | 
|  | 33 | +        public class AsyncTimeoutTestVariations : IEnumerable<object[]> | 
|  | 34 | +        { | 
|  | 35 | +            public IEnumerator<object[]> GetEnumerator() | 
|  | 36 | +            { | 
|  | 37 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 8000, true }; | 
|  | 38 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 5000, true }; | 
|  | 39 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 0, true }; | 
|  | 40 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 8000, false }; | 
|  | 41 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 5000, false }; | 
|  | 42 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Connection", 0, false }; | 
|  | 43 | + | 
|  | 44 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 8000, true }; | 
|  | 45 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 5000, true }; | 
|  | 46 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 0, true }; | 
|  | 47 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 8000, false }; | 
|  | 48 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 5000, false }; | 
|  | 49 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Connection", 0, false }; | 
|  | 50 | + | 
|  | 51 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 8000, true }; | 
|  | 52 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 5000, true }; | 
|  | 53 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 0, true }; | 
|  | 54 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 8000, false }; | 
|  | 55 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 5000, false }; | 
|  | 56 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Connection", 0, false }; | 
|  | 57 | + | 
|  | 58 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 8000, true }; | 
|  | 59 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 5000, true }; | 
|  | 60 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 0, true }; | 
|  | 61 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 8000, false }; | 
|  | 62 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 5000, false }; | 
|  | 63 | +                yield return new object[] { AsyncAPI.ExecuteReaderAsync, "Command", 0, false }; | 
|  | 64 | + | 
|  | 65 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 8000, true }; | 
|  | 66 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 5000, true }; | 
|  | 67 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 0, true }; | 
|  | 68 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 8000, false }; | 
|  | 69 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 5000, false }; | 
|  | 70 | +                yield return new object[] { AsyncAPI.ExecuteScalarAsync, "Command", 0, false }; | 
|  | 71 | + | 
|  | 72 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 8000, true }; | 
|  | 73 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 5000, true }; | 
|  | 74 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 0, true }; | 
|  | 75 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 8000, false }; | 
|  | 76 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 5000, false }; | 
|  | 77 | +                yield return new object[] { AsyncAPI.ExecuteXmlReaderAsync, "Command", 0, false }; | 
|  | 78 | +            } | 
|  | 79 | + | 
|  | 80 | +            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | 
|  | 81 | +        } | 
|  | 82 | + | 
|  | 83 | +        private static void RunTest(AsyncAPI api, string commonObj, int timeoutDelay, bool marsEnabled) | 
|  | 84 | +        { | 
|  | 85 | +            string connString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) | 
|  | 86 | +            { | 
|  | 87 | +                MultipleActiveResultSets = marsEnabled | 
|  | 88 | +            }.ConnectionString; | 
|  | 89 | + | 
|  | 90 | +            using (SqlConnection sqlConnection = new SqlConnection(connString)) | 
|  | 91 | +            { | 
|  | 92 | +                sqlConnection.Open(); | 
|  | 93 | +                if (timeoutDelay != 0) | 
|  | 94 | +                { | 
|  | 95 | +                    ConnectionHelper.SetEnforcedTimeout(sqlConnection, true, timeoutDelay); | 
|  | 96 | +                } | 
|  | 97 | +                switch (commonObj) | 
|  | 98 | +                { | 
|  | 99 | +                    case "Connection": | 
|  | 100 | +                        QueryAndValidate(api, 1, delayQuery2s, 1, true, true, sqlConnection).Wait(); | 
|  | 101 | +                        QueryAndValidate(api, 2, delayQuery2s, 5, false, true, sqlConnection).Wait(); | 
|  | 102 | +                        QueryAndValidate(api, 3, delayQuery10s, 1, true, true, sqlConnection).Wait(); | 
|  | 103 | +                        QueryAndValidate(api, 4, delayQuery2s, 10, false, true, sqlConnection).Wait(); | 
|  | 104 | +                        break; | 
|  | 105 | +                    case "Command": | 
|  | 106 | +                        using (SqlCommand cmd = sqlConnection.CreateCommand()) | 
|  | 107 | +                        { | 
|  | 108 | +                            QueryAndValidate(api, 1, delayQuery2s, 1, true, false, sqlConnection, cmd).Wait(); | 
|  | 109 | +                            QueryAndValidate(api, 2, delayQuery2s, 5, false, false, sqlConnection, cmd).Wait(); | 
|  | 110 | +                            QueryAndValidate(api, 3, delayQuery10s, 1, true, false, sqlConnection, cmd).Wait(); | 
|  | 111 | +                            QueryAndValidate(api, 4, delayQuery2s, 10, false, false, sqlConnection, cmd).Wait(); | 
|  | 112 | +                        } | 
|  | 113 | +                        break; | 
|  | 114 | +                } | 
|  | 115 | +            } | 
|  | 116 | +        } | 
|  | 117 | + | 
|  | 118 | +        private static async Task QueryAndValidate(AsyncAPI api, int index, string delayQuery, int timeout, | 
|  | 119 | +            bool timeoutExExpected = false, bool useTransaction = false, SqlConnection cn = null, SqlCommand cmd = null) | 
|  | 120 | +        { | 
|  | 121 | +            SqlTransaction tx = null; | 
|  | 122 | +            try | 
|  | 123 | +            { | 
|  | 124 | +                if (cn != null) | 
|  | 125 | +                { | 
|  | 126 | +                    if (cn.State != ConnectionState.Open) | 
|  | 127 | +                    { | 
|  | 128 | +                        await cn.OpenAsync(); | 
|  | 129 | +                    } | 
|  | 130 | +                    cmd = cn.CreateCommand(); | 
|  | 131 | +                    if (useTransaction) | 
|  | 132 | +                    { | 
|  | 133 | +                        tx = cn.BeginTransaction(IsolationLevel.ReadCommitted); | 
|  | 134 | +                        cmd.Transaction = tx; | 
|  | 135 | +                    } | 
|  | 136 | +                } | 
|  | 137 | + | 
|  | 138 | +                cmd.CommandTimeout = timeout; | 
|  | 139 | +                if (api != AsyncAPI.ExecuteXmlReaderAsync) | 
|  | 140 | +                { | 
|  | 141 | +                    cmd.CommandText = delayQuery + $";select {index} as Id;"; | 
|  | 142 | +                } | 
|  | 143 | +                else | 
|  | 144 | +                { | 
|  | 145 | +                    cmd.CommandText = delayQuery + $";select {index} as Id FOR XML PATH;"; | 
|  | 146 | +                } | 
|  | 147 | + | 
|  | 148 | +                var result = -1; | 
|  | 149 | +                switch (api) | 
|  | 150 | +                { | 
|  | 151 | +                    case AsyncAPI.ExecuteReaderAsync: | 
|  | 152 | +                        using (SqlDataReader reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false)) | 
|  | 153 | +                        { | 
|  | 154 | +                            while (await reader.ReadAsync().ConfigureAwait(false)) | 
|  | 155 | +                            { | 
|  | 156 | +                                var columnIndex = reader.GetOrdinal("Id"); | 
|  | 157 | +                                result = reader.GetInt32(columnIndex); | 
|  | 158 | +                                break; | 
|  | 159 | +                            } | 
|  | 160 | +                        } | 
|  | 161 | +                        break; | 
|  | 162 | +                    case AsyncAPI.ExecuteScalarAsync: | 
|  | 163 | +                        result = (int)await cmd.ExecuteScalarAsync().ConfigureAwait(false); | 
|  | 164 | +                        break; | 
|  | 165 | +                    case AsyncAPI.ExecuteXmlReaderAsync: | 
|  | 166 | +                        using (XmlReader reader = await cmd.ExecuteXmlReaderAsync().ConfigureAwait(false)) | 
|  | 167 | +                        { | 
|  | 168 | +                            try | 
|  | 169 | +                            { | 
|  | 170 | +                                Assert.True(reader.Settings.Async); | 
|  | 171 | +                                reader.ReadToDescendant("Id"); | 
|  | 172 | +                                result = reader.ReadElementContentAsInt(); | 
|  | 173 | +                            } | 
|  | 174 | +                            catch (Exception ex) | 
|  | 175 | +                            { | 
|  | 176 | +                                Assert.False(true, "Exception occurred: " + ex.Message); | 
|  | 177 | +                            } | 
|  | 178 | +                        } | 
|  | 179 | +                        break; | 
|  | 180 | +                } | 
|  | 181 | + | 
|  | 182 | +                if (result != index) | 
|  | 183 | +                { | 
|  | 184 | +                    throw new Exception("High Alert! Wrong data received for index: " + index); | 
|  | 185 | +                } | 
|  | 186 | +                else | 
|  | 187 | +                { | 
|  | 188 | +                    Assert.True(!timeoutExExpected && result == index); | 
|  | 189 | +                } | 
|  | 190 | +            } | 
|  | 191 | +            catch (SqlException e) | 
|  | 192 | +            { | 
|  | 193 | +                if (!timeoutExExpected) | 
|  | 194 | +                    throw new Exception("Index " + index + " failed with: " + e.Message); | 
|  | 195 | +                else | 
|  | 196 | +                    Assert.True(timeoutExExpected && e.Class == 11 && e.Number == -2); | 
|  | 197 | +            } | 
|  | 198 | +            finally | 
|  | 199 | +            { | 
|  | 200 | +                if (cn != null) | 
|  | 201 | +                { | 
|  | 202 | +                    if (useTransaction) | 
|  | 203 | +                        tx.Commit(); | 
|  | 204 | +                    cn.Close(); | 
|  | 205 | +                } | 
|  | 206 | +            } | 
|  | 207 | +        } | 
|  | 208 | +    } | 
|  | 209 | +} | 
0 commit comments