Commit d9f910bf authored by Alexey Yurchenko's avatar Alexey Yurchenko Committed by Julius Goryavsky

MDEV-31809 Make SST script interface read-write

Add two-way communication between parent and child in wsp::proc class.
Refactor wsp::thd class to call my_thread_init() conditionally.
Signed-off-by: default avatarJulius Goryavsky <julius.goryavsky@mariadb.com>
parent 203d337a
...@@ -107,8 +107,7 @@ static void* wsrep_sst_donor_monitor_thread(void *arg __attribute__((unused))) ...@@ -107,8 +107,7 @@ static void* wsrep_sst_donor_monitor_thread(void *arg __attribute__((unused)))
WSREP_INFO("Donor monitor thread started to monitor"); WSREP_INFO("Donor monitor thread started to monitor");
wsp::thd thd(FALSE); // we turn off wsrep_on for this THD so that it can wsp::thd thd;
// operate with wsrep_ready == OFF
while (!sst_donor_completed) while (!sst_donor_completed)
{ {
...@@ -144,8 +143,7 @@ static void* wsrep_sst_joiner_monitor_thread(void *arg __attribute__((unused))) ...@@ -144,8 +143,7 @@ static void* wsrep_sst_joiner_monitor_thread(void *arg __attribute__((unused)))
WSREP_INFO("Joiner monitor thread started to monitor"); WSREP_INFO("Joiner monitor thread started to monitor");
wsp::thd thd(FALSE); // we turn off wsrep_on for this THD so that it can wsp::thd thd;
// operate with wsrep_ready == OFF
while (!sst_joiner_completed) while (!sst_joiner_completed)
{ {
...@@ -642,9 +640,9 @@ static void* sst_joiner_thread (void* a) ...@@ -642,9 +640,9 @@ static void* sst_joiner_thread (void* a)
wsp::process proc (arg->cmd, "r", arg->env); wsp::process proc (arg->cmd, "r", arg->env);
if (proc.pipe() && !proc.error()) if (proc.from() && !proc.error())
{ {
const char* tmp= my_fgets (out, out_len, proc.pipe()); const char* tmp= my_fgets (out, out_len, proc.from());
if (!tmp || strlen(tmp) < (magic_len + 2) || if (!tmp || strlen(tmp) < (magic_len + 2) ||
strncasecmp (tmp, magic, magic_len)) strncasecmp (tmp, magic, magic_len))
...@@ -698,7 +696,7 @@ static void* sst_joiner_thread (void* a) ...@@ -698,7 +696,7 @@ static void* sst_joiner_thread (void* a)
err= EINVAL; err= EINVAL;
wait_signal: wait_signal:
tmp= my_fgets (out, out_len, proc.pipe()); tmp= my_fgets (out, out_len, proc.from());
if (tmp) if (tmp)
{ {
...@@ -1534,7 +1532,7 @@ static int sst_run_shell (const char* cmd_str, char** env, int max_tries) ...@@ -1534,7 +1532,7 @@ static int sst_run_shell (const char* cmd_str, char** env, int max_tries)
{ {
wsp::process proc (cmd_str, "r", env); wsp::process proc (cmd_str, "r", env);
if (NULL != proc.pipe()) if (NULL != proc.from())
{ {
proc.wait(); proc.wait();
} }
...@@ -1847,18 +1845,24 @@ static void* sst_donor_thread (void* a) ...@@ -1847,18 +1845,24 @@ static void* sst_donor_thread (void* a)
// We turn off wsrep_on for this THD so that it can // We turn off wsrep_on for this THD so that it can
// operate with wsrep_ready == OFF // operate with wsrep_ready == OFF
// We also set this SST thread THD as system thread // We also set this SST thread THD as system thread
wsp::thd thd(FALSE, true); wsp::thd thd(true, true);
wsp::process proc(arg->cmd, "r", arg->env); wsp::process proc(arg->cmd, "r", arg->env);
err= -proc.error(); err= -proc.error();
if (proc.to() && !err)
{
// Close the pipe, so that the SST process gets an EOF
proc.close_to();
}
/* Inform server about SST script startup and release TO isolation */ /* Inform server about SST script startup and release TO isolation */
mysql_mutex_lock (&arg->lock); mysql_mutex_lock (&arg->lock);
arg->err= -err; arg->err= -err;
mysql_cond_signal (&arg->cond); mysql_cond_signal (&arg->cond);
mysql_mutex_unlock (&arg->lock); //! @note arg is unusable after that. mysql_mutex_unlock (&arg->lock); //! @note arg is unusable after that.
if (proc.pipe() && !err) if (proc.from() && !err)
{ {
long long total= 0; long long total= 0;
long long complete= 0; long long complete= 0;
...@@ -1866,7 +1870,7 @@ static void* sst_donor_thread (void* a) ...@@ -1866,7 +1870,7 @@ static void* sst_donor_thread (void* a)
long long total_prev= 0; long long total_prev= 0;
wait_signal: wait_signal:
out= my_fgets (out_buf, out_len, proc.pipe()); out= my_fgets (out_buf, out_len, proc.from());
if (out) if (out)
{ {
......
...@@ -180,8 +180,8 @@ env::append(const char* val) ...@@ -180,8 +180,8 @@ env::append(const char* val)
} }
#define PIPE_READ 0 #define READ_END 0
#define PIPE_WRITE 1 #define WRITE_END 1
#define STDIN_FD 0 #define STDIN_FD 0
#define STDOUT_FD 1 #define STDOUT_FD 1
...@@ -189,8 +189,88 @@ env::append(const char* val) ...@@ -189,8 +189,88 @@ env::append(const char* val)
# define POSIX_SPAWN_USEVFORK 0 # define POSIX_SPAWN_USEVFORK 0
#endif #endif
static int
add_file_actions(posix_spawn_file_actions_t *fact,
int close_fd, int dup_fd, int unused_fd)
{
// close child's stdout|stdin fd
int err= posix_spawn_file_actions_addclose(fact, close_fd);
if (err)
{
WSREP_ERROR ("posix_spawn_file_actions_addclose() failed: %d (%s)",
err, strerror(err));
return err;
}
// substitute our pipe descriptor in place of the closed one
err= posix_spawn_file_actions_adddup2(fact, dup_fd, close_fd);
if (err)
{
WSREP_ERROR ("posix_spawn_file_actions_addup2() failed: %d (%s)",
err, strerror(err));
return err;
}
// close unused end of the pipe
err= posix_spawn_file_actions_addclose(fact, unused_fd);
if (err)
{
WSREP_ERROR ("posix_spawn_file_actions_addclose(2) failed: %d (%s)",
err, strerror(err));
return err;
}
return 0;
}
void
process::setup_parent_pipe_end(io_direction direction,
int pipe_fds[],
int const pipe_end,
const char* const mode)
{
io_[direction] = fdopen(pipe_fds[pipe_end], mode);
if (io_[direction])
{
pipe_fds[pipe_end]= -1; // skip close on cleanup
}
else
{
err_= errno;
WSREP_ERROR("fdopen() failed on '%s' pipe: %d (%s)",
mode, err_, strerror(err_));
}
}
void
process::close_io(io_direction const direction, bool const warn)
{
if (io_[direction])
{
if (warn)
{
WSREP_WARN("Closing pipe to child process: %s, PID(%ld) "
"which might still be running.", str_, (long)pid_);
}
if (fclose(io_[direction]) == -1)
{
err_= errno;
WSREP_ERROR("fclose(%d) failed: %d (%s)",
direction, err_, strerror(err_));
}
io_[direction]= NULL;
}
}
process::process (const char* cmd, const char* type, char** env) process::process (const char* cmd, const char* type, char** env)
: str_(cmd ? strdup(cmd) : strdup("")), io_(NULL), err_(EINVAL), pid_(0) :
str_(cmd ? strdup(cmd) : strdup("")),
io_{ NULL, NULL },
err_(EINVAL),
pid_(0)
{ {
if (0 == str_) if (0 == str_)
{ {
...@@ -205,33 +285,41 @@ process::process (const char* cmd, const char* type, char** env) ...@@ -205,33 +285,41 @@ process::process (const char* cmd, const char* type, char** env)
return; return;
} }
if (NULL == type || (strcmp (type, "w") && strcmp(type, "r"))) if (NULL == type ||
(strncmp(type, "w", 1) && strncmp(type, "r", 1) && strncmp(type, "rw", 2)))
{ {
WSREP_ERROR ("type argument should be either \"r\" or \"w\"."); WSREP_ERROR ("type argument should be either \"r\", \"w\" or \"rw\".");
return; return;
} }
if (NULL == env) { env= environ; } // default to global environment if (NULL == env) { env= environ; } // default to global environment
int pipe_fds[2]= { -1, }; bool const read_from_child= strchr(type, 'r');
if (::pipe(pipe_fds)) bool const write_to_child= strchr(type, 'w');
{
err_= errno;
WSREP_ERROR ("pipe() failed: %d (%s)", err_, strerror(err_));
return;
}
// which end of pipe will be returned to parent int read_pipe[2]= { -1, -1 };
int const parent_end (strcmp(type,"w") ? PIPE_READ : PIPE_WRITE); int write_pipe[2]= { -1, -1 };
int const child_end (parent_end == PIPE_READ ? PIPE_WRITE : PIPE_READ);
int const close_fd (parent_end == PIPE_READ ? STDOUT_FD : STDIN_FD);
char* const pargv[4]= { strdup("sh"), strdup("-c"), strdup(str_), NULL }; char* const pargv[4]= { strdup("sh"), strdup("-c"), strdup(str_), NULL };
if (!(pargv[0] && pargv[1] && pargv[2])) if (!(pargv[0] && pargv[1] && pargv[2]))
{ {
err_= ENOMEM; err_= ENOMEM;
WSREP_ERROR ("Failed to allocate pargv[] array."); WSREP_ERROR ("Failed to allocate pargv[] array.");
goto cleanup_pipe; goto cleanup_pargv;
}
if (read_from_child && ::pipe(read_pipe))
{
err_= errno;
WSREP_ERROR ("pipe(read_pipe) failed: %d (%s)", err_, strerror(err_));
goto cleanup_pargv;
}
if (write_to_child && ::pipe(write_pipe))
{
err_= errno;
WSREP_ERROR ("pipe(write_pipe) failed: %d (%s)", err_, strerror(err_));
goto cleanup_pipes;
} }
posix_spawnattr_t attr; posix_spawnattr_t attr;
...@@ -240,7 +328,7 @@ process::process (const char* cmd, const char* type, char** env) ...@@ -240,7 +328,7 @@ process::process (const char* cmd, const char* type, char** env)
{ {
WSREP_ERROR ("posix_spawnattr_init() failed: %d (%s)", WSREP_ERROR ("posix_spawnattr_init() failed: %d (%s)",
err_, strerror(err_)); err_, strerror(err_));
goto cleanup_pipe; goto cleanup_pipes;
} }
/* make sure that no signlas are masked in child process */ /* make sure that no signlas are masked in child process */
...@@ -288,23 +376,19 @@ process::process (const char* cmd, const char* type, char** env) ...@@ -288,23 +376,19 @@ process::process (const char* cmd, const char* type, char** env)
goto cleanup_attr; goto cleanup_attr;
} }
// close child's stdout|stdin depending on what we returning /* Add file actions for the child (fd substitution, close unused fds) */
err_= posix_spawn_file_actions_addclose (&fact, close_fd); if (read_from_child)
if (err_)
{ {
WSREP_ERROR ("posix_spawn_file_actions_addclose() failed: %d (%s)", err_= add_file_actions(&fact, STDOUT_FD, read_pipe[WRITE_END],
err_, strerror(err_)); read_pipe[READ_END]);
goto cleanup_fact; if (err_) goto cleanup_fact;
} }
// substitute our pipe descriptor in place of the closed one if (write_to_child)
err_= posix_spawn_file_actions_adddup2 (&fact,
pipe_fds[child_end], close_fd);
if (err_)
{ {
WSREP_ERROR ("posix_spawn_file_actions_addup2() failed: %d (%s)", err_= add_file_actions(&fact, STDIN_FD, write_pipe[READ_END],
err_, strerror(err_)); write_pipe[WRITE_END]);
goto cleanup_fact; if (err_) goto cleanup_fact;
} }
err_= posix_spawnp (&pid_, pargv[0], &fact, &attr, pargv, env); err_= posix_spawnp (&pid_, pargv[0], &fact, &attr, pargv, env);
...@@ -316,16 +400,14 @@ process::process (const char* cmd, const char* type, char** env) ...@@ -316,16 +400,14 @@ process::process (const char* cmd, const char* type, char** env)
goto cleanup_fact; goto cleanup_fact;
} }
io_= fdopen (pipe_fds[parent_end], type); if (read_from_child)
if (io_)
{ {
pipe_fds[parent_end]= -1; // skip close on cleanup setup_parent_pipe_end(READ, read_pipe, READ_END, "r");
} }
else
if (write_to_child)
{ {
err_= errno; setup_parent_pipe_end(WRITE, write_pipe, WRITE_END, "w");
WSREP_ERROR ("fdopen() failed: %d (%s)", err_, strerror(err_));
} }
cleanup_fact: cleanup_fact:
...@@ -345,10 +427,13 @@ process::process (const char* cmd, const char* type, char** env) ...@@ -345,10 +427,13 @@ process::process (const char* cmd, const char* type, char** env)
err, strerror(err)); err, strerror(err));
} }
cleanup_pipe: cleanup_pipes:
if (pipe_fds[0] >= 0) close (pipe_fds[0]); if (read_pipe[0] >= 0) close (read_pipe[0]);
if (pipe_fds[1] >= 0) close (pipe_fds[1]); if (read_pipe[1] >= 0) close (read_pipe[1]);
if (write_pipe[0] >= 0) close (write_pipe[0]);
if (write_pipe[1] >= 0) close (write_pipe[1]);
cleanup_pargv:
free (pargv[0]); free (pargv[0]);
free (pargv[1]); free (pargv[1]);
free (pargv[2]); free (pargv[2]);
...@@ -356,20 +441,8 @@ process::process (const char* cmd, const char* type, char** env) ...@@ -356,20 +441,8 @@ process::process (const char* cmd, const char* type, char** env)
process::~process () process::~process ()
{ {
if (io_) close_io(READ, true);
{ close_io(WRITE, true);
assert (pid_);
assert (str_);
WSREP_WARN("Closing pipe to child process: %s, PID(%ld) "
"which might still be running.", str_, (long)pid_);
if (fclose (io_) == -1)
{
err_= errno;
WSREP_ERROR("fclose() failed: %d (%s)", err_, strerror(err_));
}
}
if (str_) free (const_cast<char*>(str_)); if (str_) free (const_cast<char*>(str_));
} }
...@@ -408,29 +481,42 @@ process::wait () ...@@ -408,29 +481,42 @@ process::wait ()
} }
pid_= 0; pid_= 0;
if (io_) fclose (io_); close_io(READ, false);
io_= NULL; close_io(WRITE, false);
} }
} }
else { else {
assert (NULL == io_); assert (NULL == io_[READ]);
assert (NULL == io_[WRITE]);
WSREP_ERROR("Command did not run: %s", str_); WSREP_ERROR("Command did not run: %s", str_);
} }
return err_; return err_;
} }
thd::thd (my_bool won, bool system_thread) : init(), ptr(new THD(0)) thd::thd (my_bool ini, bool system_thread)
:
init(ini),
ptr(init.err_ ? nullptr : new THD(0))
{ {
if (ptr) if (ptr)
{ {
ptr->thread_stack= (char*) &ptr; ptr->thread_stack= (char*)&ptr;
ptr->real_id= pthread_self();
wsrep_assign_from_threadvars(ptr); wsrep_assign_from_threadvars(ptr);
wsrep_store_threadvars(ptr); wsrep_store_threadvars(ptr);
ptr->variables.option_bits&= ~OPTION_BIN_LOG; // disable binlog
ptr->variables.wsrep_on= won; ptr->variables.tx_isolation= ISO_READ_COMMITTED;
ptr->variables.sql_log_bin = 0;
ptr->variables.option_bits &= ~OPTION_BIN_LOG; // disable binlog
ptr->variables.option_bits |= OPTION_LOG_OFF; // disable general log
ptr->variables.wsrep_on = false;
ptr->security_context()->skip_grants();
if (system_thread) if (system_thread)
{
ptr->system_thread= SYSTEM_THREAD_GENERIC; ptr->system_thread= SYSTEM_THREAD_GENERIC;
}
ptr->security_ctx->master_access= ALL_KNOWN_ACL; ptr->security_ctx->master_access= ALL_KNOWN_ACL;
lex_start(ptr); lex_start(ptr);
} }
......
...@@ -275,19 +275,30 @@ class process ...@@ -275,19 +275,30 @@ class process
{ {
private: private:
const char* const str_; const char* const str_;
FILE* io_; FILE* io_[2];
int err_; int err_;
pid_t pid_; pid_t pid_;
enum io_direction { READ, WRITE };
void setup_parent_pipe_end(io_direction direction,
int pipe_fds[],
int const pipe_end,
const char* const mode);
void close_io(io_direction direction, bool warn = false);
public: public:
/*! @arg type is a pointer to a null-terminated string which must contain /*! @arg type is a pointer to a null-terminated string which must be
either the letter 'r' for reading or the letter 'w' for writing. either "r", "w" or "rw"
@arg env optional null-terminated vector of environment variables @arg env optional null-terminated vector of environment variables
*/ */
process (const char* cmd, const char* type, char** env); process (const char* cmd, const char* type, char** env);
~process (); ~process ();
FILE* pipe () { return io_; } FILE* from () { return io_[READ]; }
FILE* to () { return io_[WRITE]; }
void close_to() { close_io(WRITE, false); }
int error() { return err_; } int error() { return err_; }
int wait (); int wait ();
const char* cmd() { return str_; } const char* cmd() { return str_; }
...@@ -295,11 +306,19 @@ class process ...@@ -295,11 +306,19 @@ class process
class thd class thd
{ {
class thd_init /* Helper class to init/deinit current thread for use with THD */
class my_init
{ {
public: public:
thd_init() { my_thread_init(); } my_bool const init_;
~thd_init() { my_thread_end(); } int const err_;
my_init(my_bool const init) :
init_(init),
err_(init_ ? my_thread_init() : 0)
{}
~my_init() {
if (init_ && !err_) my_thread_end();
}
} }
init; init;
...@@ -307,9 +326,17 @@ class thd ...@@ -307,9 +326,17 @@ class thd
thd& operator= (const thd&); thd& operator= (const thd&);
public: public:
/*
thd(my_bool wsrep_on, bool system_thread=false); @param[in] init Should be set to true if called in a freshly forked
thread to initialize MySQL-specific thread context
and likewise deinitialize on object destruction.
Should be set to false if the thread already has
initialized the context, but original THD* is not
available.
*/
explicit thd(my_bool init=true, bool system_thread=false);
~thd(); ~thd();
int err() const { return init.err_; }
THD* const ptr; THD* const ptr;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment