Skip to content

Commit 823dcdf

Browse files
authored
Merge pull request #216 from bgrainger/max-allowed-packet
Throw MySqlException for oversize packets.
2 parents ad024ba + 963b9bf commit 823dcdf

File tree

7 files changed

+129
-46
lines changed

7 files changed

+129
-46
lines changed

src/MySqlConnector/MySqlClient/CommandExecutors/TextCommandExecutor.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
using System.Data;
1+
using System;
2+
using System.Data;
23
using System.Data.Common;
4+
using System.IO;
5+
using System.Net.Sockets;
36
using System.Threading;
47
using System.Threading.Tasks;
58
using MySql.Data.Protocol.Serialization;
@@ -55,8 +58,18 @@ public virtual async Task<DbDataReader> ExecuteReaderAsync(string commandText, M
5558
statementPreparerOptions |= StatementPreparerOptions.OldGuids;
5659
var preparer = new MySqlStatementPreparer(commandText, parameterCollection, statementPreparerOptions);
5760
var payload = new PayloadData(preparer.ParseAndBindParameters());
58-
await m_command.Connection.Session.SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
59-
return await MySqlDataReader.CreateAsync(m_command, behavior, ioBehavior, cancellationToken).ConfigureAwait(false);
61+
try
62+
{
63+
await m_command.Connection.Session.SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
64+
return await MySqlDataReader.CreateAsync(m_command, behavior, ioBehavior, cancellationToken).ConfigureAwait(false);
65+
}
66+
catch (Exception ex) when (payload.ArraySegment.Count > 4194304 && (ex is SocketException || ex is IOException || ex is MySqlProtocolException))
67+
{
68+
// the default MySQL Server value for max_allowed_packet (in MySQL 5.7) is 4MiB: https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_allowed_packet
69+
// use "decimal megabytes" (to round up) when creating the exception message
70+
int megabytes = payload.ArraySegment.Count / 1000000;
71+
throw new MySqlException("Error submitting {0}MB packet; ensure 'max_allowed_packet' is greater than {0}MB.".FormatInvariant(megabytes), ex);
72+
}
6073
}
6174

6275
readonly MySqlCommand m_command;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace MySql.Data.MySqlClient
4+
{
5+
public sealed class MySqlProtocolException : InvalidOperationException
6+
{
7+
internal static MySqlProtocolException CreateForPacketOutOfOrder(int expectedSequenceNumber, int packetSequenceNumber)
8+
{
9+
return new MySqlProtocolException("Packet received out-of-order. Expected {0}; got {1}.".FormatInvariant(expectedSequenceNumber, packetSequenceNumber));
10+
}
11+
12+
private MySqlProtocolException(string message)
13+
: base(message)
14+
{
15+
}
16+
}
17+
}

src/MySqlConnector/MySqlClient/Results/ResultSet.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public async Task<ResultSet> ReadResultSetHeaderAsync(IOBehavior ioBehavior, Can
2929
m_readBuffer.Clear();
3030
m_row = null;
3131
m_rowBuffered = null;
32-
MySqlException exception = null;
3332

3433
try
3534
{
@@ -71,7 +70,7 @@ public async Task<ResultSet> ReadResultSetHeaderAsync(IOBehavior ioBehavior, Can
7170
catch (Exception ex)
7271
{
7372
// store the exception, to be thrown after reading the response packet from the server
74-
exception = new MySqlException("Error during LOAD DATA LOCAL INFILE", ex);
73+
ReadResultSetHeaderException = new MySqlException("Error during LOAD DATA LOCAL INFILE", ex);
7574
}
7675

7776
await Session.SendReplyAsync(EmptyPayload.Create(), ioBehavior, cancellationToken).ConfigureAwait(false);
@@ -98,9 +97,6 @@ public async Task<ResultSet> ReadResultSetHeaderAsync(IOBehavior ioBehavior, Can
9897
break;
9998
}
10099
}
101-
102-
if (exception != null)
103-
throw exception;
104100
}
105101
catch (Exception ex)
106102
{

src/MySqlConnector/Protocol/Serialization/CompressedPayloadHandler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using System.IO.Compression;
44
using System.Threading.Tasks;
5+
using MySql.Data.MySqlClient;
56
using MySql.Data.Serialization;
67

78
namespace MySql.Data.Protocol.Serialization
@@ -87,7 +88,7 @@ private ValueTask<int> ReadBytesAsync(ArraySegment<byte> buffer, ProtocolErrorBe
8788
if (protocolErrorBehavior == ProtocolErrorBehavior.Ignore)
8889
return default(ValueTask<int>);
8990

90-
var exception = new InvalidOperationException("Packet received out-of-order. Expected {0}; got {1}.".FormatInvariant(expectedSequenceNumber, packetSequenceNumber));
91+
var exception = MySqlProtocolException.CreateForPacketOutOfOrder(expectedSequenceNumber, packetSequenceNumber);
9192
return ValueTaskExtensions.FromException<int>(exception);
9293
}
9394

src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Buffers;
33
using System.IO;
44
using System.Threading.Tasks;
5+
using MySql.Data.MySqlClient;
56
using MySql.Data.Serialization;
67

78
namespace MySql.Data.Protocol.Serialization
@@ -29,7 +30,7 @@ public static ValueTask<Packet> ReadPacketAsync(BufferedByteReader bufferedByteR
2930
if (protocolErrorBehavior == ProtocolErrorBehavior.Ignore)
3031
return default(ValueTask<Packet>);
3132

32-
var exception = new InvalidOperationException("Packet received out-of-order. Expected {0}; got {1}.".FormatInvariant(expectedSequenceNumber.Value, packetSequenceNumber));
33+
var exception = MySqlProtocolException.CreateForPacketOutOfOrder(expectedSequenceNumber.Value, packetSequenceNumber);
3334
return ValueTaskExtensions.FromException<Packet>(exception);
3435
}
3536

src/MySqlConnector/Serialization/MySqlSession.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,16 @@ private void ShutdownSocket()
447447
private ValueTask<int> TryAsync<TArg>(Func<TArg, IOBehavior, ValueTask<int>> func, TArg arg, IOBehavior ioBehavior, CancellationToken cancellationToken)
448448
{
449449
VerifyConnected();
450-
var task = func(arg, ioBehavior);
450+
ValueTask<int> task;
451+
try
452+
{
453+
task = func(arg, ioBehavior);
454+
}
455+
catch (Exception ex)
456+
{
457+
task = ValueTaskExtensions.FromException<int>(ex);
458+
}
459+
451460
if (task.IsCompletedSuccessfully)
452461
return task;
453462

@@ -468,7 +477,16 @@ private int TryAsyncContinuation(Task<int> task)
468477
private ValueTask<PayloadData> TryAsync(Func<ProtocolErrorBehavior, IOBehavior, ValueTask<ArraySegment<byte>>> func, IOBehavior ioBehavior, CancellationToken cancellationToken)
469478
{
470479
VerifyConnected();
471-
var task = func(ProtocolErrorBehavior.Throw, ioBehavior);
480+
ValueTask<ArraySegment<byte>> task;
481+
try
482+
{
483+
task = func(ProtocolErrorBehavior.Throw, ioBehavior);
484+
}
485+
catch (Exception ex)
486+
{
487+
task = ValueTaskExtensions.FromException<ArraySegment<byte>>(ex);
488+
}
489+
472490
if (task.IsCompletedSuccessfully)
473491
{
474492
var payload = new PayloadData(task.Result);

tests/SideBySide/DataTypes.cs

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -510,30 +510,50 @@ public void QueryBlob(string column, int padLength)
510510
[InlineData("TinyBlob", 255)]
511511
[InlineData("Blob", 65535)]
512512
[InlineData("MediumBlob", 16777215)]
513+
[InlineData("LongBlob", 33554432)]
513514
[InlineData("LongBlob", 67108864)]
515+
[InlineData("LongBlob", 134217728)]
514516
public async Task InsertLargeBlobAsync(string column, int size)
515517
{
516-
// verify that this amount of data can be sent to MySQL successfully
517-
var maxAllowedPacket = (await m_database.Connection.QueryAsync<int>("select @@max_allowed_packet").ConfigureAwait(false)).Single();
518-
if (maxAllowedPacket < size)
519-
return; // TODO: Use [SkippableFact]
518+
// NOTE: MySQL Server will reset the connection when it receives an oversize packet, so we need to create a test-specific connection here
519+
using (var connection = new MySqlConnection(AppConfig.CreateConnectionStringBuilder().ConnectionString))
520+
{
521+
await connection.OpenAsync();
520522

521-
var data = CreateByteArray(size);
523+
// verify that this amount of data can be sent to MySQL successfully
524+
var maxAllowedPacket = (await connection.QueryAsync<int>("select @@max_allowed_packet").ConfigureAwait(false)).Single();
525+
var shouldFail = maxAllowedPacket < size + 100;
522526

523-
long lastInsertId;
524-
using (var cmd = new MySqlCommand(Invariant($"insert into datatypes_blobs(`{column}`) values(?)"), m_database.Connection)
525-
{
526-
Parameters = { new MySqlParameter { Value = data } }
527-
})
528-
{
529-
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
530-
lastInsertId = cmd.LastInsertedId;
531-
}
527+
var data = CreateByteArray(size);
528+
529+
long lastInsertId;
530+
using (var cmd = new MySqlCommand(Invariant($"insert into datatypes_blobs(`{column}`) values(?)"), connection)
531+
{
532+
Parameters = { new MySqlParameter { Value = data } }
533+
})
534+
{
535+
try
536+
{
537+
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
538+
lastInsertId = cmd.LastInsertedId;
539+
Assert.False(shouldFail);
540+
}
541+
catch (MySqlException ex)
542+
{
543+
lastInsertId = -1;
544+
Assert.True(shouldFail);
545+
Assert.Contains("packet", ex.Message);
546+
}
547+
}
532548

533-
var queryResult = (await m_database.Connection.QueryAsync<byte[]>(Invariant($"select `{column}` from datatypes_blobs where rowid = {lastInsertId}")).ConfigureAwait(false)).Single();
534-
TestUtilities.AssertEqual(data, queryResult);
549+
if (!shouldFail)
550+
{
551+
var queryResult = (await connection.QueryAsync<byte[]>(Invariant($"select `{column}` from datatypes_blobs where rowid = {lastInsertId}")).ConfigureAwait(false)).Single();
552+
TestUtilities.AssertEqual(data, queryResult);
535553

536-
await m_database.Connection.ExecuteAsync(Invariant($"delete from datatypes_blobs where rowid = {lastInsertId}")).ConfigureAwait(false);
554+
await connection.ExecuteAsync(Invariant($"delete from datatypes_blobs where rowid = {lastInsertId}")).ConfigureAwait(false);
555+
}
556+
}
537557
}
538558

539559
[Theory]
@@ -543,27 +563,44 @@ public async Task InsertLargeBlobAsync(string column, int size)
543563
[InlineData("LongBlob", 67108864)]
544564
public void InsertLargeBlobSync(string column, int size)
545565
{
546-
// verify that this amount of data can be sent to MySQL successfully
547-
var maxAllowedPacket = m_database.Connection.Query<int>("select @@max_allowed_packet").Single();
548-
if (maxAllowedPacket < size)
549-
return; // TODO: Use [SkippableFact]
566+
// NOTE: MySQL Server will reset the connection when it receives an oversize packet, so we need to create a test-specific connection here
567+
using (var connection = new MySqlConnection(AppConfig.CreateConnectionStringBuilder().ConnectionString))
568+
{
569+
connection.Open();
550570

551-
var data = CreateByteArray(size);
571+
// verify that this amount of data can be sent to MySQL successfully
572+
var maxAllowedPacket = m_database.Connection.Query<int>("select @@max_allowed_packet").Single();
573+
var shouldFail = maxAllowedPacket < size + 100;
552574

553-
long lastInsertId;
554-
using (var cmd = new MySqlCommand(Invariant($"insert into datatypes_blobs(`{column}`) values(?)"), m_database.Connection)
555-
{
556-
Parameters = { new MySqlParameter { Value = data } }
557-
})
558-
{
559-
cmd.ExecuteNonQuery();
560-
lastInsertId = cmd.LastInsertedId;
561-
}
575+
var data = CreateByteArray(size);
562576

563-
var queryResult = m_database.Connection.Query<byte[]>(Invariant($"select `{column}` from datatypes_blobs where rowid = {lastInsertId}")).Single();
564-
TestUtilities.AssertEqual(data, queryResult);
577+
long lastInsertId;
578+
using (var cmd = new MySqlCommand(Invariant($"insert into datatypes_blobs(`{column}`) values(?)"), connection)
579+
{
580+
Parameters = { new MySqlParameter { Value = data } }
581+
})
582+
{
583+
try
584+
{
585+
cmd.ExecuteNonQuery();
586+
lastInsertId = cmd.LastInsertedId;
587+
}
588+
catch (MySqlException ex)
589+
{
590+
lastInsertId = -1;
591+
Assert.True(shouldFail);
592+
Assert.Contains("packet", ex.Message);
593+
}
594+
}
565595

566-
m_database.Connection.Execute(Invariant($"delete from datatypes_blobs where rowid = {lastInsertId}"));
596+
if (!shouldFail)
597+
{
598+
var queryResult = connection.Query<byte[]>(Invariant($"select `{column}` from datatypes_blobs where rowid = {lastInsertId}")).Single();
599+
TestUtilities.AssertEqual(data, queryResult);
600+
601+
connection.Execute(Invariant($"delete from datatypes_blobs where rowid = {lastInsertId}"));
602+
}
603+
}
567604
}
568605

569606
private static byte[] CreateByteArray(int size)

0 commit comments

Comments
 (0)