Skip to content

Commit

Permalink
Fix Connect call on Linux.
Browse files Browse the repository at this point in the history
Socket.Connect overloads that might attempt to create connections to multiple IP addresses are not supported: https://github.com/dotnet/corefx/issues/5829.

As per dotnet/corefx@30bd4b7, the workaround is to resolve the name to a collection of IP addresses and try them sequentially.

Also implemented support for comma-delimited hostnames (even though this appears to have been dropped from the official connector in mysql/mysql-connector-net@03f3a23.)
  • Loading branch information
bgrainger committed Apr 8, 2016
1 parent 46cb8d1 commit 8bcfc3b
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 7 deletions.
8 changes: 7 additions & 1 deletion src/MySql.Data/MySqlClient/MySqlConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@ public override async Task OpenAsync(CancellationToken cancellationToken)
if (m_session == null)
{
m_session = new MySqlSession(pool);
await m_session.ConnectAsync(m_connectionStringBuilder.Server, (int) m_connectionStringBuilder.Port).ConfigureAwait(false);
var connected = await 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 reader = new ByteArrayReader(payload.ArraySegment.Array, payload.ArraySegment.Offset, payload.ArraySegment.Count);
var initialHandshake = new InitialHandshakePacket(reader);
Expand Down
46 changes: 40 additions & 6 deletions src/MySql.Data/Serialization/MySqlSession.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -34,16 +36,48 @@ public void Dispose()
m_state = State.Closed;
}

public async Task ConnectAsync(string hostname, int port)
public async Task<bool> ConnectAsync(IEnumerable<string> hostnames, int port)
{
m_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
foreach (var hostname in hostnames)
{
IPAddress[] ipAddresses;
try
{
ipAddresses = await Dns.GetHostAddressesAsync(hostname).ConfigureAwait(false);
}
catch (SocketException)
{
// name couldn't be resolved
continue;
}

// need to try IP Addresses one at a time: https://github.com/dotnet/corefx/issues/5829
foreach (var ipAddress in ipAddresses)
{
Socket socket = null;
try
{
socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
#if NETSTANDARD1_3
await m_socket.ConnectAsync(hostname, port).ConfigureAwait(false);
await socket.ConnectAsync(ipAddress, port).ConfigureAwait(false);
#else
await Task.Factory.FromAsync(m_socket.BeginConnect, m_socket.EndConnect, hostname, port, null).ConfigureAwait(false);
await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, hostname, port, null).ConfigureAwait(false);
#endif
m_transmitter = new PacketTransmitter(m_socket);
m_state = State.Connected;
}
catch (SocketException)
{
Utility.Dispose(ref socket);
continue;
}

m_socket = socket;
m_transmitter = new PacketTransmitter(m_socket);
m_state = State.Connected;
return true;
}
}

return false;
}

// Starts a new conversation with the server by sending the first packet.
Expand Down
1 change: 1 addition & 0 deletions src/MySql.Data/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"System.Data.Common": "4.0.1-*",
"System.Globalization": "4.0.11-*",
"System.IO": "4.1.0-*",
"System.Net.NameResolution": "4.0.0-*",
"System.Net.Sockets": "4.1.0-*",
"System.Runtime": "4.1.0-*",
"System.Runtime.Extensions": "4.1.0-*",
Expand Down
22 changes: 22 additions & 0 deletions tests/SideBySide.New/ConnectAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,28 @@ public async Task State()
}
}

#if BASELINE
[Fact(Skip = "Doesn't implement \"Multiple hosts can be specified separated by commas.\" behavior as documented.")]
#else
[Fact]
#endif
public async Task ConnectMultipleHostNames()
{
var csb = new MySqlConnectionStringBuilder
{
Server = "www.mysql.com,invalid.example.net,localhost",
Port = 3306,
UserID = Constants.UserName,
Password = Constants.Password,
};
using (var connection = new MySqlConnection(csb.ConnectionString))
{
Assert.Equal(ConnectionState.Closed, connection.State);
await connection.OpenAsync();
Assert.Equal(ConnectionState.Open, connection.State);
}
}

[Fact]
public async Task ServerVersion()
{
Expand Down
24 changes: 24 additions & 0 deletions tests/SideBySide.New/ConnectSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ public void PersistSecurityInfo(bool persistSecurityInfo)
public void State()
{
using (var connection = new MySqlConnection(m_database.Connection.ConnectionString))
{
Assert.Equal(ConnectionState.Closed, connection.State);
connection.Open();
Assert.Equal(ConnectionState.Open, connection.State);
connection.Close();
Assert.Equal(ConnectionState.Closed, connection.State);
}
}

#if BASELINE
[Fact(Skip = "Doesn't implement \"Multiple hosts can be specified separated by commas.\" behavior as documented.")]
#else
[Fact]
#endif
public void ConnectMultipleHostNames()
{
var csb = new MySqlConnectionStringBuilder
{
Server = "www.mysql.com,invalid.example.net,localhost",
Port = 3306,
UserID = Constants.UserName,
Password = Constants.Password,
};
using (var connection = new MySqlConnection(csb.ConnectionString))
{
Assert.Equal(ConnectionState.Closed, connection.State);
connection.Open();
Expand Down

0 comments on commit 8bcfc3b

Please sign in to comment.