From d300616f0d3f7b1b684fbcc39af3d31b687607e3 Mon Sep 17 00:00:00 2001 From: Ed Ball Date: Sun, 10 Jul 2016 17:38:21 -0700 Subject: [PATCH] Add Synchronous to MySqlConnectionStringBuilder. Setting Synchronous to true forces async requests to block on the calling thread to improve debugging. --- .../MySqlClient/MySqlCommand.cs | 2 +- .../MySqlClient/MySqlConnection.cs | 46 +++++++++++++++---- .../MySqlConnectionStringBuilder.cs | 11 +++++ .../MySqlClient/MySqlDataReader.cs | 8 ++-- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/MySqlConnector/MySqlClient/MySqlCommand.cs b/src/MySqlConnector/MySqlClient/MySqlCommand.cs index 03b2edd9c..c0ee43f45 100644 --- a/src/MySqlConnector/MySqlClient/MySqlCommand.cs +++ b/src/MySqlConnector/MySqlClient/MySqlCommand.cs @@ -146,7 +146,7 @@ protected override async Task ExecuteDbDataReaderAsync(CommandBeha var preparer = new MySqlStatementPreparer(CommandText, m_parameterCollection, connection.AllowUserVariables ? StatementPreparerOptions.AllowUserVariables : StatementPreparerOptions.None); preparer.BindParameters(); var payload = new PayloadData(new ArraySegment(Payload.CreateEofStringPayload(CommandKind.Query, preparer.PreparedSql))); - await Session.SendAsync(payload, cancellationToken).ConfigureAwait(false); + await Connection.AdaptTask(Session.SendAsync(payload, cancellationToken)).ConfigureAwait(false); reader = await MySqlDataReader.CreateAsync(this, behavior, cancellationToken).ConfigureAwait(false); return reader; } diff --git a/src/MySqlConnector/MySqlClient/MySqlConnection.cs b/src/MySqlConnector/MySqlClient/MySqlConnection.cs index efdaf2528..165e1ba11 100644 --- a/src/MySqlConnector/MySqlClient/MySqlConnection.cs +++ b/src/MySqlConnector/MySqlClient/MySqlConnection.cs @@ -114,14 +114,14 @@ public override async Task OpenAsync(CancellationToken cancellationToken) if (m_session == null) { m_session = new MySqlSession(pool); - var connected = await m_session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int) m_connectionStringBuilder.Port).ConfigureAwait(false); + var connected = await AdaptTask(m_session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int) m_connectionStringBuilder.Port)).ConfigureAwait(false); if (!connected) { SetState(ConnectionState.Closed); throw new MySqlException("Unable to connect to any of the specified MySQL hosts."); } - var payload = await m_session.ReceiveAsync(cancellationToken).ConfigureAwait(false); + var payload = await AdaptTask(m_session.ReceiveAsync(cancellationToken)).ConfigureAwait(false); var reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count); var initialHandshake = new InitialHandshakePacket(reader); if (initialHandshake.AuthPluginName != "mysql_native_password") @@ -130,16 +130,16 @@ public override async Task OpenAsync(CancellationToken cancellationToken) var response = HandshakeResponse41Packet.Create(initialHandshake, m_connectionStringBuilder.UserID, m_connectionStringBuilder.Password, m_database); payload = new PayloadData(new ArraySegment(response)); - await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); - await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); + await AdaptTask(m_session.SendReplyAsync(payload, cancellationToken)).ConfigureAwait(false); + await AdaptTask(m_session.ReceiveReplyAsync(cancellationToken)).ConfigureAwait(false); // TODO: Check success } else if (m_connectionStringBuilder.ConnectionReset) { if (m_session.ServerVersion.Version.CompareTo(ServerVersions.SupportsResetConnection) >= 0) { - await m_session.SendAsync(ResetConnectionPayload.Create(), cancellationToken).ConfigureAwait(false); - var payload = await m_session.ReceiveReplyAsync(cancellationToken); + await AdaptTask(m_session.SendAsync(ResetConnectionPayload.Create(), cancellationToken)).ConfigureAwait(false); + var payload = await AdaptTask(m_session.ReceiveReplyAsync(cancellationToken)); OkPayload.Create(payload); } else @@ -147,15 +147,15 @@ public override async Task OpenAsync(CancellationToken cancellationToken) // MySQL doesn't appear to accept a replayed hashed password (using the challenge from the initial handshake), so just send zeroes // and expect to get a new challenge var payload = ChangeUserPayload.Create(m_connectionStringBuilder.UserID, new byte[20], m_database); - await m_session.SendAsync(payload, cancellationToken).ConfigureAwait(false); - payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); + await AdaptTask(m_session.SendAsync(payload, cancellationToken)).ConfigureAwait(false); + payload = await AdaptTask(m_session.ReceiveReplyAsync(cancellationToken)).ConfigureAwait(false); var switchRequest = AuthenticationMethodSwitchRequestPayload.Create(payload); if (switchRequest.Name != "mysql_native_password") throw new NotSupportedException("Only 'mysql_native_password' authentication method is supported."); var hashedPassword = AuthenticationUtility.HashPassword(switchRequest.Data, 0, m_connectionStringBuilder.Password); payload = new PayloadData(new ArraySegment(hashedPassword)); - await m_session.SendReplyAsync(payload, cancellationToken).ConfigureAwait(false); - payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); + await AdaptTask(m_session.SendReplyAsync(payload, cancellationToken)).ConfigureAwait(false); + payload = await AdaptTask(m_session.ReceiveReplyAsync(cancellationToken)).ConfigureAwait(false); OkPayload.Create(payload); } } @@ -240,6 +240,32 @@ internal MySqlSession Session internal bool AllowUserVariables => m_connectionStringBuilder.AllowUserVariables; internal bool ConvertZeroDateTime => m_connectionStringBuilder.ConvertZeroDateTime; internal bool OldGuids => m_connectionStringBuilder.OldGuids; + internal bool Synchronous => m_connectionStringBuilder.Synchronous; + + internal Task AdaptTask(Task task) + { + if (!Synchronous) + return task; + + task.GetAwaiter().GetResult(); + return Task.FromResult(null); + } + + internal Task AdaptTask(Task task) + { + if (!Synchronous) + return task; + + return Task.FromResult(task.GetAwaiter().GetResult()); + } + + internal ValueTask AdaptTask(ValueTask task) + { + if (!Synchronous) + return task; + + return new ValueTask(task.AsTask().GetAwaiter().GetResult()); + } private void SetState(ConnectionState newState) { diff --git a/src/MySqlConnector/MySqlClient/MySqlConnectionStringBuilder.cs b/src/MySqlConnector/MySqlClient/MySqlConnectionStringBuilder.cs index e7184a52d..f45c810ad 100644 --- a/src/MySqlConnector/MySqlClient/MySqlConnectionStringBuilder.cs +++ b/src/MySqlConnector/MySqlClient/MySqlConnectionStringBuilder.cs @@ -106,6 +106,12 @@ public uint MaximumPoolSize set { MySqlConnectionStringOption.MaximumPoolSize.SetValue(this, value); } } + public bool Synchronous + { + get { return MySqlConnectionStringOption.Synchronous.GetValue(this); } + set { MySqlConnectionStringOption.Synchronous.SetValue(this, value); } + } + public override bool ContainsKey(string key) { var option = MySqlConnectionStringOption.TryGetOptionForKey(key); @@ -165,6 +171,7 @@ internal abstract class MySqlConnectionStringOption public static readonly MySqlConnectionStringOption ConnectionReset; public static readonly MySqlConnectionStringOption MinimumPoolSize; public static readonly MySqlConnectionStringOption MaximumPoolSize; + public static readonly MySqlConnectionStringOption Synchronous; public static MySqlConnectionStringOption TryGetOptionForKey(string key) { @@ -259,6 +266,10 @@ static MySqlConnectionStringOption() AddOption(MaximumPoolSize = new MySqlConnectionStringOption( keys: new[] { "Maximum Pool Size", "Max Pool Size", "MaximumPoolSize", "maxpoolsize" }, defaultValue: 100)); + + AddOption(Synchronous = new MySqlConnectionStringOption( + keys: new[] { "Synchronous" }, + defaultValue: false)); } static readonly Dictionary s_options; diff --git a/src/MySqlConnector/MySqlClient/MySqlDataReader.cs b/src/MySqlConnector/MySqlClient/MySqlDataReader.cs index 25b737a74..81b53f307 100644 --- a/src/MySqlConnector/MySqlClient/MySqlDataReader.cs +++ b/src/MySqlConnector/MySqlClient/MySqlDataReader.cs @@ -50,7 +50,7 @@ public override Task ReadAsync(CancellationToken cancellationToken) if (m_state != State.AlreadyReadFirstRow) { - var payloadTask = m_session.ReceiveReplyAsync(cancellationToken); + var payloadTask = Connection.AdaptTask(m_session.ReceiveReplyAsync(cancellationToken)); if (payloadTask.IsCompletedSuccessfully) return ReadAsyncRemainder(payloadTask.Result) ? s_trueTask : s_falseTask; return ReadAsyncAwaited(payloadTask.AsTask()); @@ -566,7 +566,7 @@ private async Task ReadResultSetHeaderAsync(CancellationToken cancellationToken) { while (true) { - var payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); + var payload = await Connection.AdaptTask(m_session.ReceiveReplyAsync(cancellationToken)).ConfigureAwait(false); var firstByte = payload.HeaderByte; if (firstByte == OkPayload.Signature) @@ -593,11 +593,11 @@ private async Task ReadResultSetHeaderAsync(CancellationToken cancellationToken) for (var column = 0; column < m_columnDefinitions.Length; column++) { - payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); + payload = await Connection.AdaptTask(m_session.ReceiveReplyAsync(cancellationToken)).ConfigureAwait(false); m_columnDefinitions[column] = ColumnDefinitionPayload.Create(payload); } - payload = await m_session.ReceiveReplyAsync(cancellationToken).ConfigureAwait(false); + payload = await Connection.AdaptTask(m_session.ReceiveReplyAsync(cancellationToken)).ConfigureAwait(false); EofPayload.Create(payload); m_command.LastInsertedId = -1;