WL#7796: WAIT_FOR_EXECUTED_GTID_SET

Affects: Server-5.7   —   Status: Complete   —   Priority: Medium

The function WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS has several shortcomings:
- The function is dependent on the slave to execute. If the slave thread is
  not running (or if is being stopped while the function is executed), then
  the function returns an error. It would be better if the function kept waiting,
  so that it could be used even when the slave is stopped.
- The function returns an event count. This is meaningless and not very
  well-defined. It would be better to just return 0 for success, 1 for timeout,
  and 2 for other failure (i.e., query killed).
- The function is ill-named.

In this worklog we introduce a better replacement:
  WAIT_FOR_EXECUTED_GTID_SET(GTID_SET [, TIMEOUT])
This function shall not care about slaves running and shall return just 0 or 1.
Functional requirement
=======================

FR1 - Wait for the gtid_executed set to be superset of the gtid_set value
      specified (both timed and untimed wait)

FR2 - Should not be dependent on the slave threads.

Non Functional requirement
===========================

NFR1 - Should not cause any performance drop due to extra wait for the gtid set. 
This worklog aims at adding a new sql function WAIT_FOR_EXECUTED_GTID_SET which 
make the current syncing option for the slave with master independent of the 
slave threads. 

The use of the new function will be :

   WAIT_FOR_EXECUTED_GTID_SET(GTID_SET [, TIMEOUT])

Wait until all the transactions whose global transaction identifiers are being 
specified, are contained in gtid_set. 

ie. After this function returns with success, it must hold that 
    GTID_SUBSET(GTID_SET, @@global.gtid_executed) returns true.
    
    The TIMEOUT parameter is used for the maximum duration for which we have to 
    wait for the GTIDS to be synced. If its not able to get to that GTID value
    in this duration we return timeout error. (current unit for timeout is 
    second and it uses integer values for timeout)

Use :

mysql> SELECT WAIT_FOR_EXECUTED_GTID_SET('3E11FA47-71CA-11E1-9E33-C80AA9429562:1-
5');
       -> 0

The return value is a bit different compared to WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS 
return value which is the event count. The return values here are :


  0 - success
  1 - timeout
  
For all other cases we will throw corresponding error messages eg :-

  ER_OUT_OF_RESOURCES
  ER_MALFORMED_GTID_SET_SPECIFICATION
  ER_QUERY_INTERRUPTED 
Low level design
=================

FILE - rpl_gtid.h
==================

## class Mutex_cond_array

Add a new member

  inline int wait(int n, struct timespec* abstime) const

which is a timed version of the already existing wait function. The caller
holds the read or write lock on the sid_lock and it does a cond_timedwait
(ie. waits for the timeout duration) to see whether the condition is met or
timeout is reached. This method is called from the wait for gtid method of
the Gtid_state class.

## class Gtid_state

The following function will take a new parameter ie. Timeout and will call the
corresponding function defined on the Mutex_cond_array class based on whether
the timeout is set or not.

Also it will return error values which will be based on the timeout duration
exceeded or not.

int Gtid_state::wait_for_gtid(THD *thd, const Gtid & gtid, longlong* timeout)

We also add the following new method which does the actual wait for the
gtid_set to be in subset of the executed gtid_set by calling itself repeatedly.

Since the loop might be called multiple times we would like to use the same 
timeout value for the iterations and decrease it after each call, we are passing 
the timeout as pointer.

/**

@param thd     - global thread pointer.
@param Gtid    - Pointer to the Gtid set which gets updated.
@param timeout - Timeout value for which wait should be done in
                 millisecond.

@return 0 - success
        1 - timeout

For all other cases we will throw corresponding error messages using the
  my_error(ER_*, MYF(0)) call and return with value of -1.

eg: my_error(ER_OUT_OF_RESOURCES, MYF(0));

  ER_OUT_OF_RESOURCES
  ER_QUERY_INTERRUPTED.

**/

int wait_for_gtid_set(THD* thd, String* gtid, longlong timeout);

This method is called from the Item_executed_gtid_set::val_int()

## class Owned_gtids

We are adding a new member to the Owned_gtids class which will remove the owned
gtids set from the already executed gtid_set.

@param other - The gtid set from which the owned gtids have to be removed.

enum_return_status remove_from_gtid_set(Gtid_set* other);

This method is called from the wait_for_gtid_set defined in the Gtid_state
class inside a loop. The reason why the owned_gtids set is removed from the
executed gtid_set is gtids are added to executed_gtids when they are flushed to
the binary log, not when they are committed to the storage engine. But they are
kept in owned_gtids until the storage engine commit. Since we consider
transactions that are between flush and commit to not be committed, we have to
remove them from executed_gtid_set before we remove executed_gtid_set from
wait_gtid_set.


/**
  Supporting class and functions for the new SQL function
  WAIT_FOR_EXECUTED_GTID_SET.
*/


FILE - sql/item_create.cc
==========================

/*
  We are implementing the standard framework required by any native function.
*/
class Create_func_executed_gtid_set_wait : public Create_native_func

Item*
Create_func_executed_gtid_set_wait::create_native(THD *thd, LEX_STRING name,
                                                  PT_item_list *item_list)


{ { C_STRING_WITH_LEN("WAIT_FOR_EXECUTED_GTID_SET") },
BUILDER(Create_func_executed_gtid_set_wait)}


FILE - sql/item_func.cc
========================

bool Item_executed_gtid_set_wait::itemize(Parse_context *pc, Item **res)

longlong Item_executed_gtid_set_wait::val_int()
{
  ...
  int ret_val= gtid_state->wait_for_gtid_set(thd, gtid, timeout); // call to the
wait_for_gtid_set function.
  ...
}


FILE - sql/item_func.h
=======================

class Item_executed_gtid_set_wait :public Item_int_func