Skip to content

Commit e589fc8

Browse files
committed
Merge master into net6.
2 parents d5c0859 + 8ee4b84 commit e589fc8

File tree

14 files changed

+148
-80
lines changed

14 files changed

+148
-80
lines changed

docs/content/api/MySqlConnector/MySqlErrorCodeType.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,8 @@ public enum MySqlErrorCode
660660
| DebugSyncTimeout | `1639` | ER_DEBUG_SYNC_TIMEOUT |
661661
| DebugSyncHitLimit | `1640` | ER_DEBUG_SYNC_HIT_LIMIT |
662662
| CannotExecuteInReadOnlyTransaction | `1792` | ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION |
663+
| CannotConvertString | `3854` | ER_CANNOT_CONVERT_STRING |
664+
| ClientInteractionTimeout | `4031` | ER_CLIENT_INTERACTION_TIMEOUT |
663665

664666
## See Also
665667

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
date: 2021-04-24
3+
menu:
4+
main:
5+
parent: getting started
6+
title: Schema Collections
7+
customtitle: "Supported Schema Collections"
8+
weight: 80
9+
---
10+
11+
# Supported Schema Collections
12+
13+
`DbConnection.GetSchema` retrieves schema information about the database that is currently connected. For background, see MSDN on [GetSchema and Schema Collections](https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/getschema-and-schema-collections) and [Common Schema Collections](https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/common-schema-collections).
14+
15+
`MySqlConnection.GetSchema` supports the following schemas:
16+
17+
* `MetaDataCollections`—information about available schemas
18+
* `CharacterSets`
19+
* `Collations`
20+
* `CollationCharacterSetApplicability`
21+
* `Columns`
22+
* `Databases`
23+
* `DataTypes`—information about available data types
24+
* `Engines`
25+
* `KeyColumnUsage`
26+
* `KeyWords`
27+
* `Parameters`
28+
* `Partitions`
29+
* `Plugins`
30+
* `Procedures`—information about stored procedures
31+
* `ProcessList`
32+
* `Profiling`
33+
* `ReferentialConstraints`
34+
* `ReservedWords`—information about reserved words in the server's SQL syntax
35+
* `ResourceGroups`
36+
* `SchemaPrivileges`
37+
* `Tables`
38+
* `TableConstraints`
39+
* `TablePrivileges`
40+
* `TableSpaces`
41+
* `Triggers`
42+
* `UserPrivileges`
43+
* `Views`
44+
45+
The `GetSchema(string, string[])` overload that specifies restrictions is not supported.

docs/content/overview/use-with-orms.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Use with ORMs
1414
This library is compatible with popular .NET ORMs including:
1515

1616
* [Dapper](https://stackexchange.github.io/Dapper/) ([GitHub](https://github.com/StackExchange/dapper-dot-net), [NuGet](https://www.nuget.org/packages/Dapper))
17+
* [FreeSql](http://freesql.net/) ([GitHub](https://github.com/dotnetcore/FreeSql), [NuGet](https://www.nuget.org/packages/FreeSql.Provider.MySqlConnector/))
1718
* [LINQ to DB](https://linq2db.github.io) ([GitHub](https://github.com/linq2db/linq2db), [NuGet](https://www.nuget.org/packages/linq2db.MySqlConnector))
1819
* [NHibernate](https://www.nhibernate.info) ([GitHub](https://github.com/nhibernate/NHibernate.MySqlConnector), [NuGet](https://www.nuget.org/packages/NHibernate.Driver.MySqlConnector))
1920
* [NReco.Data](https://www.nrecosite.com/dalc_net.aspx) ([GitHub](https://github.com/nreco/data), [NuGet](https://www.nuget.org/packages/NReco.Data))

docs/content/overview/version-history.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
lastmod: 2021-04-24
2+
lastmod: 2021-04-30
33
date: 2017-03-27
44
menu:
55
main:
@@ -10,6 +10,10 @@ weight: 30
1010

1111
# Version History
1212

13+
### 1.3.8
14+
15+
* Reduce latency of resetting connections: [#982](https://github.com/mysql-net/MySqlConnector/pull/982).
16+
1317
### 1.3.7
1418

1519
* Add support for [SingleStore](https://www.singlestore.com/): [#968](https://github.com/mysql-net/MySqlConnector/pull/968).

docs/content/troubleshooting/connection-reuse.md

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,41 +21,54 @@ an open `MySqlDataReader`.
2121
You may not execute multiple operations in parallel, for example:
2222

2323
```csharp
24-
using (var connection = new MySqlConnection("..."))
25-
{
26-
await connection.OpenAsync();
27-
await Task.WhenAll( // don't do this
28-
connection.ExecuteAsync("SELECT 1;"),
29-
connection.ExecuteAsync("SELECT 2;"),
30-
connection.ExecuteAsync("SELECT 3;"));
31-
}
24+
using var connection = new MySqlConnection("...");
25+
await connection.OpenAsync();
26+
await Task.WhenAll( // don't do this
27+
connection.ExecuteAsync("SELECT 1;"),
28+
connection.ExecuteAsync("SELECT 2;"),
29+
connection.ExecuteAsync("SELECT 3;"));
3230
```
3331

3432
### Nested Access on Single Thread
3533

3634
You may not reuse the connection when there is an open `MySqlDataReader:`
3735

3836
```csharp
39-
using (var connection = CreateOpenConnection())
40-
using (var command = new MySqlCommand("SELECT id FROM ...", connection))
41-
using (var reader = command.ExecuteReader())
37+
using var connection = CreateOpenConnection();
38+
using var command = new MySqlCommand("SELECT id FROM ...", connection);
39+
using var reader = command.ExecuteReader();
40+
while (reader.Read())
4241
{
43-
while (reader.Read())
44-
{
45-
var idToUpdate = reader.GetValue(0);
46-
connection.Execute("UPDATE ... SET ..."); // don't do this
47-
}
42+
var idToUpdate = reader.GetValue(0);
43+
connection.Execute("UPDATE ... SET ..."); // don't do this
4844
}
4945
```
5046

47+
### Dispose While in Use
48+
49+
You may not `Dispose` any MySqlConnector objects while they are in use:
50+
51+
```csharp
52+
var connection = new MySqlConnection("...");
53+
await connection.OpenAsync();
54+
var command = new MySqlCommand("SELECT SLEEP(1)", connection);
55+
var task = command.ExecuteScalarAsync();
56+
connection.Dispose(); // don't do this
57+
command.Dispose();
58+
await task;
59+
```
60+
5161
## How to Fix
5262

5363
For the multithreaded scenario, if concurrent access to the database is truly necessary,
5464
create and open a new `MySqlConnection` on each thread. But in most cases, you should
5565
just write code that sequentially `await`s each asychronous operation (without performing
5666
them in parallel).
5767

58-
5968
For the nested access, read all the values from the `MySqlDataReader` into memory, close
6069
the reader, then process the values. (If the data set is large, you may need to use a batching
61-
approach where you read a limited number of rows in each batch.)
70+
approach where you read a limited number of rows in each batch.)
71+
72+
For lifetime management, prefer to use `using` (or `await using`) instead of explicitly
73+
calling `Dispose` (or `DisposeAsync`). Ensure that all outstanding operations have
74+
completed before disposing objects that are in use.

docs/content/troubleshooting/load-data-local-infile.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ If you do this, you may receive one of the following errors:
3232
malicious server or proxy could send a fake “local infile request” packet to the client and
3333
read any file that the client has permission to open.
3434

35-
For more information, see [the MySQL documentation](https://dev.mysql.com/doc/refman/8.0/en/load-data-local.html).
35+
For more information, see [the MySQL documentation](https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html).
3636

3737
## How to Fix
3838

src/MySqlConnector/Core/BackgroundConnectionResetHelper.cs

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ internal static class BackgroundConnectionResetHelper
1111
{
1212
public static void AddSession(ServerSession session, MySqlConnection? owningConnection)
1313
{
14-
var resetTask = session.TryResetConnectionAsync(session.Pool!.ConnectionSettings, IOBehavior.Asynchronous, default);
14+
var resetTask = session.TryResetConnectionAsync(session.Pool!.ConnectionSettings, owningConnection, true, IOBehavior.Asynchronous, default);
1515
lock (s_lock)
16-
s_sessions.Add(new SessionResetTask(session, resetTask, owningConnection));
16+
s_resetTasks.Add(resetTask);
1717

1818
if (Log.IsDebugEnabled())
19-
Log.Debug("Started Session{0} reset in background; waiting SessionCount: {1}.", session.Id, s_sessions.Count);
19+
Log.Debug("Started Session{0} reset in background; waiting TaskCount: {1}.", session.Id, s_resetTasks.Count);
2020

2121
// release only if it is likely to succeed
2222
if (s_semaphore.CurrentCount == 0)
@@ -69,7 +69,6 @@ public static async Task ReturnSessionsAsync()
6969
Log.Info("Started BackgroundConnectionResetHelper worker.");
7070

7171
List<Task<bool>> localTasks = new();
72-
List<SessionResetTask> localSessions = new();
7372

7473
// keep running until stopped
7574
while (!s_cancellationTokenSource.IsCancellationRequested)
@@ -85,36 +84,18 @@ public static async Task ReturnSessionsAsync()
8584
{
8685
lock (s_lock)
8786
{
88-
if (s_sessions.Count == 0)
89-
{
90-
if (localTasks.Count == 0)
91-
break;
92-
}
93-
else
94-
{
95-
foreach (var session in s_sessions)
96-
{
97-
localSessions.Add(session);
98-
localTasks.Add(session.ResetTask);
99-
}
100-
s_sessions.Clear();
101-
}
87+
localTasks.AddRange(s_resetTasks);
88+
s_resetTasks.Clear();
10289
}
10390

91+
if (localTasks.Count == 0)
92+
break;
93+
10494
if (Log.IsDebugEnabled())
105-
Log.Debug("Found SessionCount {0} session(s) to return.", localSessions.Count);
95+
Log.Debug("Found TaskCount {0} task(s) to process.", localTasks.Count);
10696

107-
while (localTasks.Count != 0)
108-
{
109-
var completedTask = await Task.WhenAny(localTasks).ConfigureAwait(false);
110-
var index = localTasks.IndexOf(completedTask);
111-
var session = localSessions[index].Session;
112-
var connection = localSessions[index].OwningConnection;
113-
localSessions.RemoveAt(index);
114-
localTasks.RemoveAt(index);
115-
await session.Pool!.ReturnAsync(IOBehavior.Asynchronous, session).ConfigureAwait(false);
116-
GC.KeepAlive(connection);
117-
}
97+
await Task.WhenAll(localTasks);
98+
localTasks.Clear();
11899
}
119100
}
120101
catch (Exception ex) when (!(ex is OperationCanceledException oce && oce.CancellationToken == s_cancellationTokenSource.Token))
@@ -124,25 +105,11 @@ public static async Task ReturnSessionsAsync()
124105
}
125106
}
126107

127-
internal readonly struct SessionResetTask
128-
{
129-
public SessionResetTask(ServerSession session, Task<bool> resetTask, MySqlConnection? owningConnection)
130-
{
131-
Session = session;
132-
ResetTask = resetTask;
133-
OwningConnection = owningConnection;
134-
}
135-
136-
public ServerSession Session { get; }
137-
public Task<bool> ResetTask { get; }
138-
public MySqlConnection? OwningConnection { get; }
139-
}
140-
141108
static readonly IMySqlConnectorLogger Log = MySqlConnectorLogManager.CreateLogger(nameof(BackgroundConnectionResetHelper));
142109
static readonly object s_lock = new();
143110
static readonly SemaphoreSlim s_semaphore = new(1, 1);
144111
static readonly CancellationTokenSource s_cancellationTokenSource = new();
145-
static readonly List<SessionResetTask> s_sessions = new();
112+
static readonly List<Task<bool>> s_resetTasks = new();
146113
static Task? s_workerTask;
147114
}
148115
}

src/MySqlConnector/Core/ConnectionPool.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
6868
{
6969
if ((ConnectionSettings.ConnectionReset && ConnectionSettings.DeferConnectionReset) || session.DatabaseOverride is not null)
7070
{
71-
reuseSession = await session.TryResetConnectionAsync(ConnectionSettings, ioBehavior, cancellationToken).ConfigureAwait(false);
71+
reuseSession = await session.TryResetConnectionAsync(ConnectionSettings, null, false, ioBehavior, cancellationToken).ConfigureAwait(false);
7272
}
7373
else if ((unchecked((uint) Environment.TickCount) - session.LastReturnedTicks) >= ConnectionSettings.ConnectionIdlePingTime)
7474
{
@@ -158,7 +158,11 @@ private int GetSessionHealth(ServerSession session)
158158
return 0;
159159
}
160160

161-
public async Task ReturnAsync(IOBehavior ioBehavior, ServerSession session)
161+
#if NET45 || NET461 || NET471 || NETSTANDARD1_3 || NETSTANDARD2_0
162+
public async ValueTask<int> ReturnAsync(IOBehavior ioBehavior, ServerSession session)
163+
#else
164+
public async ValueTask ReturnAsync(IOBehavior ioBehavior, ServerSession session)
165+
#endif
162166
{
163167
if (Log.IsDebugEnabled())
164168
Log.Debug("Pool{0} receiving Session{1} back", m_logArguments[0], session.Id);
@@ -188,6 +192,10 @@ public async Task ReturnAsync(IOBehavior ioBehavior, ServerSession session)
188192
{
189193
m_sessionSemaphore.Release();
190194
}
195+
196+
#if NET45 || NET461 || NET471 || NETSTANDARD1_3 || NETSTANDARD2_0
197+
return default;
198+
#endif
191199
}
192200

193201
public async Task ClearAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ public ServerSession(ConnectionPool? pool, int poolGeneration, int id)
6868
public bool SupportsSessionTrack => m_supportsSessionTrack;
6969
public bool ProcAccessDenied { get; set; }
7070

71-
public Task ReturnToPoolAsync(IOBehavior ioBehavior, MySqlConnection? owningConnection)
71+
#if NET45 || NET461 || NET471 || NETSTANDARD1_3 || NETSTANDARD2_0
72+
public ValueTask<int> ReturnToPoolAsync(IOBehavior ioBehavior, MySqlConnection? owningConnection)
73+
#else
74+
public ValueTask ReturnToPoolAsync(IOBehavior ioBehavior, MySqlConnection? owningConnection)
75+
#endif
7276
{
7377
if (Log.IsDebugEnabled())
7478
{
@@ -77,11 +81,11 @@ public Task ReturnToPoolAsync(IOBehavior ioBehavior, MySqlConnection? owningConn
7781
}
7882
LastReturnedTicks = unchecked((uint) Environment.TickCount);
7983
if (Pool is null)
80-
return Utility.CompletedTask;
84+
return default;
8185
if (!Pool.ConnectionSettings.ConnectionReset || Pool.ConnectionSettings.DeferConnectionReset)
8286
return Pool.ReturnAsync(ioBehavior, this);
8387
BackgroundConnectionResetHelper.AddSession(this, owningConnection);
84-
return Utility.CompletedTask;
88+
return default;
8589
}
8690

8791
public bool IsConnected
@@ -529,10 +533,11 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
529533
return statusInfo;
530534
}
531535

532-
public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken)
536+
public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, MySqlConnection? owningConnection, bool returnToPool, IOBehavior ioBehavior, CancellationToken cancellationToken)
533537
{
534538
VerifyState(State.Connected);
535539

540+
var success = false;
536541
try
537542
{
538543
// clear all prepared statements; resetting the connection will clear them on the server
@@ -578,7 +583,7 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
578583
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
579584
OkPayload.Create(payload.Span, SupportsDeprecateEof, SupportsSessionTrack);
580585

581-
return true;
586+
success = true;
582587
}
583588
catch (IOException ex)
584589
{
@@ -597,7 +602,15 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
597602
Log.Debug(ex, "Session{0} ignoring SocketException in TryResetConnectionAsync", m_logArguments);
598603
}
599604

600-
return false;
605+
if (returnToPool && Pool is not null)
606+
{
607+
await Pool.ReturnAsync(ioBehavior, this).ConfigureAwait(false);
608+
609+
// make sure the MySqlConnection is kept alive until the session is returned to the pool; this prevents it from potentially being detected as "leaked"
610+
GC.KeepAlive(owningConnection);
611+
}
612+
613+
return success;
601614
}
602615

603616
private async Task<PayloadData> SwitchAuthenticationAsync(ConnectionSettings cs, PayloadData payload, IOBehavior ioBehavior, CancellationToken cancellationToken)

src/MySqlConnector/MySqlBulkCopy.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -622,13 +622,13 @@ static bool WriteSubstring(string value, ref int inputIndex, ref Encoder? utf8En
622622

623623
utf8Encoder ??= s_utf8Encoding.GetEncoder();
624624
#if NETSTANDARD1_3
625-
if (output.Length <= 4 && utf8Encoder.GetByteCount(value.ToCharArray(), inputIndex, 1, flush: false) > output.Length)
625+
if (output.Length < 4 && utf8Encoder.GetByteCount(value.ToCharArray(), inputIndex, Math.Min(2, nextIndex - inputIndex), flush: false) > output.Length)
626626
return false;
627627
var buffer = new byte[output.Length];
628628
utf8Encoder.Convert(value.ToCharArray(), inputIndex, nextIndex - inputIndex, buffer, 0, buffer.Length, nextIndex == value.Length, out var charsUsed, out var bytesUsed, out var completed);
629629
buffer.AsSpan().CopyTo(output);
630630
#else
631-
if (output.Length <= 4 && utf8Encoder.GetByteCount(value.AsSpan(inputIndex, 1), flush: false) > output.Length)
631+
if (output.Length < 4 && utf8Encoder.GetByteCount(value.AsSpan(inputIndex, Math.Min(2, nextIndex - inputIndex)), flush: false) > output.Length)
632632
return false;
633633
utf8Encoder.Convert(value.AsSpan(inputIndex, nextIndex - inputIndex), output, nextIndex == value.Length, out var charsUsed, out var bytesUsed, out var completed);
634634
#endif

0 commit comments

Comments
 (0)