WL#8161: Locking service for read/write named locks

Affects: Server-5.7   —   Status: Complete

WL#6940 "Server version token and check" requires a way to protect global and
session version token lists. This WL is about providing a locking service for
satisfying this requirement. 

This locking service will be implemented as a set of functions that will
be available through a plugin serice as well as a set of UDFs. Both the
plugin service and the UDFs will be provided by this WL.
F1: A lock is identified by a name which is a combination of two strings: 
    (namespace, lock_name). The maximum length of each of the strings shall be 
    64 characters. The "namespace" part is designed to e.g. allow different 
    plugins to use this locking service without interfering with each other.
    In case of invalid name, ER_LOCKING_SERVICE_WRONG_NAME error shall be
    reported to the diagnostics area (DA) of the connection.

F2: Binary comparison will be used for name matching. (Names given via the UDF
    interface are already converted to binary)

F3: These locks shall be separate from existing locks in the server (e.g. 
    current types of metadata locks - including user-level locks).

F4: Each lock can be taken in two modes: Read and write. For a given name:
    Read and read is compatible, other combinations are not.

F5: Both read and write locks shall not be released on commit/rollback of
    transactions.

F6: Both read and write locks shall be released on disconnect.

F7: It shall be possible to acquire multiple read locks atomically. It shall 
    also be possible to take multiple write locks atomically. Acquiring a 
    combination of read and write locks atomically is not required.
    Note that if the names of locks to be taken by the UDF implementation is
    given as an expression (e.g. a column name reference), the locks will be
    taken one by one and thus not atomically. The order the locks are taken
    in this case, depends on how the expression is evaluated. This is a 
    limitation of the current UDF implementation.

F8: It shall be possible to release all locks taken. There is no need for
    releasing individual locks. (This will only affect locks taken by F6).

F9: The functions available for UDFs and the plugin service shall have the
    same functionality:
      - Acquire one or more read locks by name
      - Acquire one or more write locks by name
      - Release all locks

F10: Maximum value lock wait timeout (in seconds) shall be supplied by the 
     caller. If timeout happens, ER_LOCK_WAIT_TIMEOUT shall be reported to
     the diagnostics area (DA) of the connection. A negative timeout will
     be interpreted as "infinite" timeout - similar to what currently happens
     for user-level locks.

F11: In case of deadlock, a connection with pending read locks is preferable
     as victim over a connection with pending write locks. Read lock shall
     be treated as normal DML locks in this respect, and write locks shall be
     treated as normal DDL locks. In case of deadlock,
     ER_LOCKING_SERVICE_DEADLOCK (new error) shall be reported to the
     diagnostics area (DA) of the connection. The ER_LOCK_DEADLOCK shall not
     be used since it implies transaction rollback.

NF1: Privilege checking shall be be the responsibility of the caller.

NF2: Other than F6, acquiring and releasing of locks are always explicit.

NF3: No checking if a lock with the given name is already held will be done
     before lock acquisition. This means it is possible to take a lock on the
     same name twice.


Note to documentation: Because of the limitation listed in F7, it is not
recommended to use expressions as arguments to the UDFs. This means e.g.
avoiding INSERT ... SELECT ... UDF ... FROM ... WHERE ... as the order the
locks are taken will depend on the data in the table and how the query is
optimized. Especially in the case of error, it can result in an unpredictable
number of locks taken.
I-1: Plugin service interface:
/**
  Types of locking service locks.
  LOCK_SERVICE_READ is compatible with LOCKING_SERVICE_READ.
  All other combinations are incompatible.
*/
enum enum_locking_service_lock_type
{ LOCKING_SERVICE_READ, LOCKING_SERVICE_WRITE };

extern struct mysql_locking_service_st {
  /**
    Acquire locking service locks.

    @param opaque_thd      Thread handle.
    @param lock_namespace  Namespace of the locks to acquire.
    @param lock_names      Array of names of the locks to acquire.
    @param lock_num        Number of elements in 'lock_names'.
    @param lock_type       Lock type to acquire. LOCKING_SERVICE_READ or _WRITE.
    @param lock_timeout    Number of seconds to wait before giving up.

    @retval 1              Acquisition failed, error has been reported in the DA.
    @retval 0              Acquisition sucessfull, all locks acquired.
  */
  int (*mysql_acquire_locks)(MYSQL_THD opaque_thd, const char* lock_namespace,
                             const char**lock_names, size_t lock_num,
                             enum enum_locking_service_lock_type lock_type,
                             ulong lock_timeout);

  /**
    Release all lock service locks taken by the given connection
    in the given namespace.

    @param opaque_thd      Thread handle.
    @param lock_namespace  Namespace of the locks to release.

    @retval 1              Release failed, error has been reported in the DA.
    @retval 0              Release sucessfull, all locks acquired.
  */
  int (*mysql_release_locks)(MYSQL_THD opaque_thd, const char* lock_namespace);
} *mysql_locking_service;

I-2: UDFs
  service_get_read_lock(lock_namespace, (lock_name, ...), lock_timeout)
  service_get_write_lock(lock_namespace, (lock_name, ...), lock_timeout)
  service_release_locks(lock_namespace)

All these functions RETURNS INT - with similar meaning as the plugin interface
above.