1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00
Files
mariadb/sql/sql_parallel_thread.h

226 lines
6.6 KiB
C++

/**/
#ifndef SQL_PARALLEL_THREAD_H
#define SQL_PARALLEL_THREAD_H
#include "mariadb.h"
#include "sql_class.h"
#include "mysqld.h"
#include "sql_error.h"
// PWT Parallel Worker Thread
/*
Message Types
*/
class pwt_error_message
{
public:
uint worker_errno;
uint code;
Sql_condition::enum_warning_level level;
char *message;
};
/*
Event type. Inherits ilink so it can live in an I_List<pwt_queued_event>.
*/
class pwt_queued_event : public ilink
{
public:
pwt_error_message *error;
};
class pwt_worker;
/*
This is a manager for worker threads.
One can create worker threads (inherited from pwt_thread).
- Worker threads will have the minimum THD environment
- They will be visible in the INFORMATION_SCHEMA.PROCESSLIST
- Warnings/errors emitted in the worker thread are recorded and can be
replayed in their manager using process_pending_warnings().
Not included here are:
- pwt_thread_manager doesn't keep track of the child threads.
- We don't record if there was a fatal error, it just calls
virtual pwt_thread->on_fatal_error().
- Implementation of "error in a worker causes all others to stop"
is also out of scope.
*/
class pwt_thread_manager : public Sql_alloc
{
public:
pwt_thread_manager();
~pwt_thread_manager();
THD *thd; /* Manager thread */
void process_pending_warnings(bool skip_interrupted);
// These are used by class PWT_error_handler:
void record_event(pwt_queued_event *event)
{
mysql_mutex_lock(&LOCK_pwt_manager);
parallel_messages.push_back(event);
mysql_mutex_unlock(&LOCK_pwt_manager);
}
void notify_message_dropped();
private:
mysql_mutex_t LOCK_pwt_manager;
I_List<pwt_queued_event> parallel_messages;
/*
Set under LOCK_pwt_manager when a worker fails to allocate a queued event.
The manager surfaces a single ER_OUTOFMEMORY warning so the user sees
that worker diagnostics were dropped instead of silently disappearing.
*/
bool messages_dropped;
};
#define WORKER_NAME "Parallel Worker"
#define WORKER_ID_LENGTH 3
#define WORKER_NAME_LENGTH 15
#define CONNECTION_NAME_THREAD "For Thread ID"
#define CONNECTION_NAME_THREAD_LENGTH 13
#define THREAD_ID_LENGTH 20 // ull can occupy 20 chars
struct pwt_worker_info
{
char conn_name[MAX_THREAD_NAME+1];
/*
This is displayed in information_schema.processlist.info
Currently "Parallel Worker {1..N} For Thread M"
*/
char process_list[WORKER_NAME_LENGTH + 1 +
WORKER_ID_LENGTH + 1 +
CONNECTION_NAME_THREAD_LENGTH + 1 +
THREAD_ID_LENGTH + 1];
};
/*
Inherit from this your worker threads.
*/
class pwt_thread : public Sql_alloc
{
public:
/*
A team is built as an array and taken apart again from whatever stage the
build reached, so a worker has to be able to say what it holds before
anything has been done to it.
*/
pwt_thread(pwt_thread_manager *manager_arg):
thd(nullptr), manager_base(manager_arg), inited(false),
thread_started(false)
{}
virtual ~pwt_thread() {}
/* Intialize the worker and create its THD (call from master thread) */
bool init_worker_thd(THD *parent_thd, int worker_nr);
/* Create and run the thread */
bool create_thread();
/*
This will be run by the worker thread with all environment properly set up.
One can produce warnings/errors and get them in the master thread.
At the end, one must call thread_func_end().
*/
virtual void thread_func()= 0;
// Every descendant must call at the end of thread_func():
void thread_func_end() {};
/* This will be invoked when a fatal error occurs */
virtual void on_fatal_error() = 0;
/*
Release whatever thread_func() releases at the end of a run, for a worker
whose thread never ran and so never got to do it itself.
*/
virtual void cleanup_without_run() {}
/*
@brief
Release this worker: everything init_worker_thd() and create_thread()
gave it, from whatever stage its startup reached.
@description
The worker knows which stage that was, so the caller does not have to:
one that was never initialised, one holding a THD but no thread, and one
with a thread running are all released by the same call. It is also
idempotent, so unwinding a half-built team is a loop over every worker
rather than a loop that has to be bounded by how far the build got.
reap_worker() waits for a running thread to finish on its own, which is
the normal end of a query -- a worker that stops because it was asked to
raises no error. abort_worker() tells it to stop first, and it exits with
ER_QUERY_INTERRUPTED. That difference is the caller's to make, which is
why it is two names and not a parameter.
*/
void reap_worker() { release_worker(false); }
void abort_worker() { release_worker(true); }
THD *thd;
private:
pwt_thread_manager *manager_base;
/*
What this worker holds, and so what release_worker() has to give back.
'inited' says init_worker_thd() ran to the end: there is a THD registered
in server_threads and LOCK_worker is live. 'thread_started' says
create_thread() did too, which changes who destroys the THD -- a worker
that ran destroys its own on the way out.
*/
bool inited;
bool thread_started;
/*
Guards worker->thd while the worker nulls it on exit, so abort_worker()
sees either a live THD to awake() or nullptr.
See parallel_worker_thread_func.
*/
mysql_mutex_t LOCK_worker;
pwt_worker_info info; // Connection/thread name
pthread_t pthread;
void release_worker(bool abort);
void destroy_worker_thd();
void init_and_run_thread_func();
friend void *pwt_thread_func(void *arg);
friend class pwt_thread_with_stats;
};
class pwt_thread_with_stats : public pwt_thread
{
public:
pwt_thread_with_stats(pwt_thread_manager *arg) :
pwt_thread(arg)
{
bzero(&stats, sizeof(stats)); // TODO: is this really needed?
}
void thread_func_end();
// Call from the main thread after worker is done
void save_worker_stats(THD *parent_thd)
{
DBUG_ASSERT(!thread_started);
add_to_status(&parent_thd->status_var, &stats);
}
private:
/*
This worker's status counters, copied out of its THD just before the THD is
destroyed, so the manager can add them to the session's own once the
workers have been joined. See quiesce_workers().
*/
STATUS_VAR stats;
};
#endif