Bug #75113 Fail in failover of the connection in MySQL fabric
Submitted: 5 Dec 2014 9:29 Modified: 3 Jun 2015 1:07
Reporter: Akiharu Yamada Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version:5.1.34 OS:Any
Assigned to: Jess Balint CPU Architecture:Any
Tags: fabric, failover, Mysql Fabric

[5 Dec 2014 9:29] Akiharu Yamada
Description:
When execute the following programs in MySQL Server constructed in MySQL Fabric and stop a master, fail in the failover of the connection.

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

public class MySQLFabricTest
{
    private static String hostname = "localhost";
    private static String port = "32274";
    private static String fabricServerGroup = "ha_group";
    private static String fabricUsername = "admin";
    private static String fabricPassword = "admin";
    private static String database = "testdb";
    private static String user = "root";
    private static String password = "test";

    public static boolean ExecuteQuery( Connection conn )
    {
        boolean result = true;
        String server_id = null;
        String sqlState = null;
        long timeMillisStart = 0;
        long timeMillisEnd = 0;

        try
        {
            conn.setAutoCommit( true );
            conn.setReadOnly( false );

            ResultSet rs1 = conn.createStatement().executeQuery(
                "SHOW VARIABLES LIKE 'server\\_id'"
            );
            rs1.next();
            server_id = rs1.getString("Value");
            rs1.close();
            rs1 = null;

            timeMillisStart = System.currentTimeMillis();
            ResultSet rs2 = conn.createStatement().executeQuery(
                "SELECT * from test"
            );
            timeMillisEnd = System.currentTimeMillis();
            rs2.close();

            System.out.println(
                server_id + "," +
                "SELECT" + "," +
                String.valueOf(timeMillisEnd - timeMillisStart)
            );
            Thread.sleep(1000);
        }
        catch( SQLException e )
        {
            System.out.println( "----- SELECT -----" );
            System.out.println( "Error cause  : " + e.getCause() );
            System.out.println( "Error message: " + e.getMessage() );
            System.out.println( "Error status : " + e.getSQLState() );

            result = false;
        }
        catch( InterruptedException e )
        {
        }

        return( result );
    }

    public static Connection db_connect()
    {
        Connection conn = null;
        Properties props = new Properties();
        String baseUrl = "jdbc:mysql:fabric://" + hostname + ":"
                         + Integer.valueOf(port) + "/";

        props.put( "fabricServerGroup", fabricServerGroup );
        props.put( "fabricUsername", fabricUsername );
        props.put( "fabricPassword", fabricPassword );
        props.put( "fabricReportErrors", "true" );
        props.put( "user", user );
        props.put( "password", password );

        try
        {
            if( !com.mysql.jdbc.Util.isJdbc4() )
            {
                Class.forName( "com.mysql.fabric.jdbc.FabricMySQLDriver" );
            }

            conn = DriverManager.getConnection( baseUrl + database,
                props
            );
        }
        catch( SQLException e )
        {
            System.out.println( "----- CONNECT -----" );
            System.out.println( "Error cause  : " + e.getCause() );
            System.out.println( "Error message: " + e.getMessage() );
            System.out.println( "Error status : " + e.getSQLState() );
        }
        catch( ClassNotFoundException e )
        {
        }

        return( conn );
    }

    public static void main(String args[]) throws Exception
    {
        Connection conn = db_connect();

        while( true )
        {
            if( !ExecuteQuery(conn) )
            {
                conn.close();
                conn = db_connect();
            }
        }
    }
}

How to repeat:
1. Infomation in MySQL fabric

$ mysqlfabric group lookup_servers ha_group
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

                         server_uuid        address    status       mode weight
------------------------------------ -------------- --------- ---------- ------
1b795d1b-7084-11e4-a5e3-005056827115 localhost:3306   PRIMARY READ_WRITE    1.0
ffe18ed4-7950-11e4-9f46-005056827115 localhost:3307 SECONDARY  READ_ONLY    1.0

2. Execute a sample program

$ java MySQLFabricTest

3. Stop a master of MySQL server

$ mysqld_multi stop 1
$ mysqlfabric group lookup_servers ha_group
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

                         server_uuid        address  status       mode weight
------------------------------------ -------------- ------- ---------- ------
1b795d1b-7084-11e4-a5e3-005056827115 localhost:3306  FAULTY READ_WRITE    1.0
ffe18ed4-7950-11e4-9f46-005056827115 localhost:3307 PRIMARY READ_WRITE    1.0

4. Result of the sample program
1,SELECT,1
1,SELECT,1
1,SELECT,0
1,SELECT,1
----- SELECT -----
Error cause  : null
Error message: Server shutdown in progress
Error status : 08S01
----- SELECT -----
Error cause  : java.net.ConnectException: Connection refused
Error message: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
Error status : 08S01
----- SELECT -----
Error cause  : java.net.ConnectException: Connection refused
Error message: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
Error status : 08S01
  :
  :

Suggested fix:
An old connection does not seem to be deleted. I succeeded in failover by changing the source code of the driver as follows.

connectorj/src/com/mysql/jdbc/ReplicationConnectionGroupManager.java:
--- 206,213 ----

      }

+     public static ReplicationConnectionGroup removeGroup( String groupName )
+     {
+         return( GROUP_MAP.remove(groupName) );
+     }
  }

connectorj/src/com/mysql/jdbc/ReplicationConnection.java:
--- 275,281 ----
          }

          if (this.connectionGroup != null) {
+             ReplicationConnectionGroupManager.removeGroup( this.connectionGroup.getGroupName() );
              this.connectionGroup.handleCloseConnection(this);
          }
[5 Dec 2014 9:48] Akiharu Yamada
The results when I succeeded in fail over are as follows.

1,SELECT,275
1,SELECT,0
1,SELECT,1
1,SELECT,1
1,SELECT,0
----- SELECT -----
Error cause  : null
Error message: Server shutdown in progress
Error status : 08S01
2,SELECT,0
2,SELECT,0
2,SELECT,1
[8 Dec 2014 1:56] Akiharu Yamada
Result of diff command was incorrect. Correctly is as follows. also removes GROUP_MAP when close the handle of the connection.

$ diff -c ReplicationConnectionGroupManager.java.old ReplicationConnectionGroupManager.java.new
*** ReplicationConnectionGroupManager.java.old  2014-10-17 15:05:36.000000000 +0900
--- ReplicationConnectionGroupManager.java.new  2014-12-08 10:21:16.049604801 +0900
***************
*** 204,207 ****
--- 204,211 ----

      }

+      public static ReplicationConnectionGroup removeGroup( String groupName )
+      {
+          return( GROUP_MAP.remove(groupName) );
+      }
  }

$ diff -c ReplicationConnection.java.old ReplicationConnection.java.new
*** ReplicationConnection.java.old      2014-10-17 15:05:36.000000000 +0900
--- ReplicationConnection.java.new      2014-12-08 10:12:35.378693020 +0900
***************
*** 243,248 ****
--- 243,249 ----

          if (this.connectionGroup != null) {
              this.connectionGroup.handleCloseConnection(this);
+             ReplicationConnectionGroupManager.removeGroup( this.connectionGroup.getGroupName() );
          }

      }
***************
*** 2839,2844 ****
--- 2840,2846 ----
          getCurrentConnection().abort(executor);
          if (this.connectionGroup != null) {
              this.connectionGroup.handleCloseConnection(this);
+             ReplicationConnectionGroupManager.removeGroup( this.connectionGroup.getGroupName() );
          }
      }

***************
*** 2878,2883 ****
--- 2880,2886 ----
          getCurrentConnection().abortInternal();
          if (this.connectionGroup != null) {
              this.connectionGroup.handleCloseConnection(this);
+             ReplicationConnectionGroupManager.removeGroup( this.connectionGroup.getGroupName() );
          }
      }
[2 Apr 2015 15:31] Jess Balint
Thank you for your bug report.
[2 Jun 2015 19:08] Daniel So
Added the following entry to the Connector/J 5.1.36 changelog:

"A failover did not occur for a MySQL Fabric connection during the failure of a master server. It was because the state change of the Fabric connection group was not handled properly, which hs been corrected by this fix."
[3 Jun 2015 1:07] Akiharu Yamada
Thank you for bug fixes.