560 lines
12 KiB
C++

/* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
#ifndef PLUGIN_UTILS_INCLUDED
#define PLUGIN_UTILS_INCLUDED
#include <map>
#include <queue>
#include <string>
#include <vector>
#include "plugin_psi.h"
#include <mysql/group_replication_priv.h>
void log_primary_member_details();
void abort_plugin_process(const char *message);
class Blocked_transaction_handler
{
public:
Blocked_transaction_handler();
virtual ~Blocked_transaction_handler();
/**
This method instructs all local transactions to rollback when certification is
no longer possible.
*/
void unblock_waiting_transactions();
private:
/* The lock that disallows concurrent method executions */
mysql_mutex_t unblocking_process_lock;
};
template <typename T>
class Synchronized_queue
{
public:
Synchronized_queue()
{
mysql_mutex_init(key_GR_LOCK_synchronized_queue, &lock, MY_MUTEX_INIT_FAST);
mysql_cond_init(key_GR_COND_synchronized_queue, &cond);
}
/**
Checks if the queue is empty
@return if is empty
@retval true empty
@retval false not empty
*/
bool empty()
{
bool res= true;
mysql_mutex_lock(&lock);
res= queue.empty();
mysql_mutex_unlock(&lock);
return res;
}
/**
Inserts an element in the queue.
Alerts any other thread lock on pop() or front()
@param value The value to insert
*/
void push(const T& value)
{
mysql_mutex_lock(&lock);
queue.push(value);
mysql_mutex_unlock(&lock);
mysql_cond_broadcast(&cond);
}
/**
Fetches the front of the queue and removes it.
@note The method will block if the queue is empty until a element is pushed
@param out The fetched reference.
*/
void pop(T* out)
{
*out= NULL;
mysql_mutex_lock(&lock);
while (queue.empty())
mysql_cond_wait(&cond, &lock); /* purecov: inspected */
*out= queue.front();
queue.pop();
mysql_mutex_unlock(&lock);
}
/**
Pops the front of the queue removing it.
@note The method will block if the queue is empty until a element is pushed
*/
void pop()
{
mysql_mutex_lock(&lock);
while (queue.empty())
mysql_cond_wait(&cond, &lock); /* purecov: inspected */
queue.pop();
mysql_mutex_unlock(&lock);
}
/**
Fetches the front of the queue but does not remove it.
@note The method will block if the queue is empty until a element is pushed
@param out The fetched reference.
*/
void front(T* out)
{
*out= NULL;
mysql_mutex_lock(&lock);
while (queue.empty())
mysql_cond_wait(&cond, &lock);
*out= queue.front();
mysql_mutex_unlock(&lock);
}
/**
Checks the queue size
@return the size of the queue
*/
size_t size()
{
size_t qsize= 0;
mysql_mutex_lock(&lock);
qsize= queue.size();
mysql_mutex_unlock(&lock);
return qsize;
}
private:
mysql_mutex_t lock;
mysql_cond_t cond;
std::queue<T> queue;
};
/**
Synchronization auxiliary class that allows one or more threads
to wait on a given number of requirements.
Usage:
CountDownLatch(count):
Create the latch with the number of requirements to wait.
wait():
Block until the number of requirements reaches zero.
countDown():
Decrease the number of requirements by one.
*/
class CountDownLatch
{
public:
/**
Create the latch with the number of requirements to wait.
@param count The number of requirements to wait
*/
CountDownLatch(uint count) : count(count)
{
mysql_mutex_init(key_GR_LOCK_count_down_latch, &lock, MY_MUTEX_INIT_FAST);
mysql_cond_init(key_GR_COND_count_down_latch, &cond);
}
virtual ~CountDownLatch()
{
mysql_cond_destroy(&cond);
mysql_mutex_destroy(&lock);
}
/**
Block until the number of requirements reaches zero.
*/
void wait()
{
mysql_mutex_lock(&lock);
while (count > 0)
mysql_cond_wait(&cond, &lock);
mysql_mutex_unlock(&lock);
}
/**
Decrease the number of requirements by one.
*/
void countDown()
{
mysql_mutex_lock(&lock);
--count;
if (count == 0)
mysql_cond_broadcast(&cond);
mysql_mutex_unlock(&lock);
}
/**
Get current number requirements.
@return the number of requirements
*/
uint getCount()
{
uint res= 0;
mysql_mutex_lock(&lock);
res= count;
mysql_mutex_unlock(&lock);
return res;
}
private:
mysql_mutex_t lock;
mysql_cond_t cond;
int count;
};
/**
Ticket register/wait auxiliary class.
Usage:
registerTicket(k):
create a ticket with key k with status ongoing.
releaseTicket(k):
set ticket with key k status to done.
waitTicket(k):
wait until ticket with key k status is changed to done.
*/
template <typename K>
class Wait_ticket
{
public:
Wait_ticket()
:blocked(false), waiting(false)
{
mysql_mutex_init(key_GR_LOCK_wait_ticket, &lock, MY_MUTEX_INIT_FAST);
mysql_cond_init(key_GR_COND_wait_ticket, &cond);
}
virtual ~Wait_ticket()
{
for (typename std::map<K,CountDownLatch*>::iterator it= map.begin();
it != map.end();
++it)
delete it->second; /* purecov: inspected */
map.clear();
mysql_cond_destroy(&cond);
mysql_mutex_destroy(&lock);
}
/**
Register ticker with status ongoing.
@param key The key that identifies the ticket
@return
@retval 0 success
@retval !=0 key already exists, error on insert or it is blocked
*/
int registerTicket(const K& key)
{
int error= 0;
mysql_mutex_lock(&lock);
if (blocked)
{
mysql_mutex_unlock(&lock); /* purecov: inspected */
return 1; /* purecov: inspected */
}
typename std::map<K,CountDownLatch*>::iterator it= map.find(key);
if (it != map.end())
{
mysql_mutex_unlock(&lock); /* purecov: inspected */
return 1; /* purecov: inspected */
}
CountDownLatch *cdl = new CountDownLatch(1);
std::pair<typename std::map<K,CountDownLatch*>::iterator,bool> ret;
ret= map.insert(std::pair<K,CountDownLatch*>(key,cdl));
if (ret.second == false)
{
error= 1; /* purecov: inspected */
delete cdl; /* purecov: inspected */
}
mysql_mutex_unlock(&lock);
return error;
}
/**
Wait until ticket status is done.
@note The ticket is removed after the wait.
@param key The key that identifies the ticket
@return
@retval 0 success
@retval !=0 key doesn't exist, or the Ticket is blocked
*/
int waitTicket(const K& key)
{
int error= 0;
CountDownLatch *cdl= NULL;
mysql_mutex_lock(&lock);
if (blocked)
{
mysql_mutex_unlock(&lock); /* purecov: inspected */
return 1; /* purecov: inspected */
}
typename std::map<K,CountDownLatch*>::iterator it= map.find(key);
if (it == map.end())
error= 1;
else
cdl= it->second;
mysql_mutex_unlock(&lock);
if (cdl != NULL)
{
cdl->wait();
mysql_mutex_lock(&lock);
delete cdl;
map.erase(it);
if (waiting)
{
if (map.empty())
{
mysql_cond_broadcast(&cond);
}
}
mysql_mutex_unlock(&lock);
}
return error;
}
/**
Set ticket status to done.
@param key The key that identifies the ticket
@return
@retval 0 success
@retval !=0 (key doesn't exist)
*/
int releaseTicket(const K& key)
{
int error= 0;
mysql_mutex_lock(&lock);
typename std::map<K,CountDownLatch*>::iterator it= map.find(key);
if (it == map.end())
error= 1;
else
it->second->countDown();
mysql_mutex_unlock(&lock);
return error;
}
/**
Gets all the waiting keys.
@param[out] key_list all the keys to return
*/
void get_all_waiting_keys(std::vector<K>& key_list)
{
mysql_mutex_lock(&lock);
for(typename std::map<K,CountDownLatch*>::iterator iter = map.begin();
iter != map.end();
++iter)
{
K key= iter->first;
key_list.push_back(key);
}
mysql_mutex_unlock(&lock);
}
/**
Blocks or unblocks the class from receiving waiting requests.
@param[in] blocked_flag if the class should block or not
*/
void set_blocked_status(bool blocked_flag)
{
mysql_mutex_lock(&lock);
blocked= blocked_flag;
mysql_mutex_unlock(&lock);
}
int block_until_empty(int timeout)
{
mysql_mutex_lock(&lock);
waiting= true;
while (!map.empty())
{
struct timespec abstime;
set_timespec(&abstime, 1);
#ifndef DBUG_OFF
int error=
#endif
mysql_cond_timedwait(&cond, &lock, &abstime);
DBUG_ASSERT(error == ETIMEDOUT || error == 0);
if (timeout >= 1)
{
timeout= timeout - 1;
}
else if (!map.empty())
{
//time out
waiting= false;
mysql_mutex_unlock(&lock);
return 1;
}
}
waiting= false;
mysql_mutex_unlock(&lock);
return 0;
}
private:
mysql_mutex_t lock;
mysql_cond_t cond;
std::map<K,CountDownLatch*> map;
bool blocked;
bool waiting;
};
class Mutex_autolock
{
public:
Mutex_autolock(mysql_mutex_t *arg) : ptr_mutex(arg)
{
DBUG_ENTER("Mutex_autolock::Mutex_autolock");
DBUG_ASSERT(arg != NULL);
mysql_mutex_lock(ptr_mutex);
DBUG_VOID_RETURN;
}
~Mutex_autolock()
{
mysql_mutex_unlock(ptr_mutex);
}
private:
mysql_mutex_t *ptr_mutex;
Mutex_autolock(Mutex_autolock const&); // no copies permitted
void operator=(Mutex_autolock const&);
};
class Shared_writelock
{
public:
Shared_writelock(Checkable_rwlock *arg)
: shared_write_lock(arg), write_lock_in_use(false)
{
DBUG_ENTER("Shared_writelock::Shared_writelock");
DBUG_ASSERT(arg != NULL);
mysql_mutex_init(key_GR_LOCK_write_lock_protection, &write_lock, MY_MUTEX_INIT_FAST);
DBUG_VOID_RETURN;
}
virtual ~Shared_writelock()
{
mysql_mutex_destroy(&write_lock);
}
int try_grab_write_lock()
{
int res= 0;
mysql_mutex_lock(&write_lock);
if (write_lock_in_use)
res= 1; /* purecov: inspected */
else
{
shared_write_lock->wrlock();
write_lock_in_use= true;
}
mysql_mutex_unlock(&write_lock);
return res;
}
void grab_write_lock()
{
mysql_mutex_lock(&write_lock);
shared_write_lock->wrlock();
write_lock_in_use= true;
mysql_mutex_unlock(&write_lock);
}
void release_write_lock()
{
mysql_mutex_lock(&write_lock);
shared_write_lock->unlock();
write_lock_in_use= false;
mysql_mutex_unlock(&write_lock);
}
/**
Grab a read lock only if there is no write lock acquired.
@return
@retval 0 read lock acquired
@retval !=0 there is a write lock acquired
*/
int try_grab_read_lock()
{
int res= 0;
mysql_mutex_lock(&write_lock);
if (write_lock_in_use)
res= 1;
else
shared_write_lock->rdlock();
mysql_mutex_unlock(&write_lock);
return res;
}
void grab_read_lock()
{
shared_write_lock->rdlock();
}
void release_read_lock()
{
shared_write_lock->unlock();
}
private:
Checkable_rwlock *shared_write_lock;
mysql_mutex_t write_lock;
bool write_lock_in_use;
};
#endif /* PLUGIN_UTILS_INCLUDED */