blob: e82adb3646d5ce639ed616868c6bcc2967ce74fb [file] [log] [blame]
/*****************************************************************************\
* signal.c - signals for connection manager
*****************************************************************************
* Copyright (C) SchedMD LLC.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm 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; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include <signal.h>
#include "src/common/fd.h"
#include "src/common/macros.h"
#include "src/common/proc_args.h"
#include "src/common/read_config.h"
#include "src/common/xmalloc.h"
#include "src/conmgr/conmgr.h"
#include "src/conmgr/mgr.h"
#define SIGNAL_FD_FAILED -250
typedef struct {
#define MAGIC_SIGNAL_HANDLER 0xC20A444A
int magic; /* MAGIC_SIGNAL_HANDLER */
struct sigaction prior;
struct sigaction new;
int signal;
} signal_handler_t;
/* protects all of the static variables here */
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
/* protected by lock */
static bool one_time_init = false;
/* list of all registered signal handlers */
static signal_handler_t *signal_handlers = NULL;
static int signal_handler_count = 0;
/* list of all registered signal work */
static work_t **signal_work = NULL;
static int signal_work_count = 0;
/* interrupt handler (_signal_handler()) will send signal to this fd */
static volatile sig_atomic_t signal_fd = -1;
static conmgr_fd_t *signal_con = NULL;
static void _signal_handler(int signo)
{
/*
* Per the sigaction man page:
* A child created via fork(2) inherits a copy of its parent's
* signal dispositions.
*
* Signal handler registration survives fork() but the signal_mgr()
* thread will be lost. Gracefully ignore signals when signal_fd_send is
* -1 to avoid trying to write a non-existent file descriptor.
*/
if (signal_fd < 0)
return;
try_again:
if (write(signal_fd, &signo, sizeof(signo)) != sizeof(signo)) {
if ((errno == EPIPE) || (errno == EBADF)) {
/*
* write() after conmgr shutdown before reading that
* signal_fd was closed. Ignoring this race condition
* entirely. Set signal_fd to an invalid value that is
* not -1 to distinguish it from the normal "unset"
* state.
*/
signal_fd = SIGNAL_FD_FAILED;
return;
}
if (errno == EINTR)
goto try_again;
/*
* Drop signal as the buffer is already full which means
* something bad has already happened and having the exact
* signal numbers isn't going to make much difference.
*/
if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
return;
/* TODO: replace with signal_safe_fatal() */
fatal_abort("%s: unable to signal connection manager: %m",
__func__);
}
}
/* caller must hold write lock */
static void _register_signal_handler(int signal)
{
signal_handler_t *handler;
for (int i = 0; i < signal_handler_count; i++) {
xassert(signal_handlers[i].magic == MAGIC_SIGNAL_HANDLER);
if (signal_handlers[i].signal == signal)
return;
}
xrecalloc(signal_handlers, (signal_handler_count + 1),
sizeof(*signal_handlers));
handler = &signal_handlers[signal_handler_count];
handler->magic = MAGIC_SIGNAL_HANDLER;
handler->signal = signal;
handler->new.sa_handler = _signal_handler;
if (sigaction(signal, &handler->new, &handler->prior))
fatal("%s: unable to catch %s: %m",
__func__, strsignal(signal));
if (slurm_conf.debug_flags & DEBUG_FLAG_CONMGR) {
char *signame = sig_num2name(handler->signal);
log_flag(CONMGR, "%s: installed signal %s[%d] handler: Prior=0x%"PRIxPTR" is now replaced with New=0x%"PRIxPTR,
__func__, signame, signal,
(uintptr_t) handler->prior.sa_handler,
(uintptr_t) handler->new.sa_handler);
xfree(signame);
}
signal_handler_count++;
}
/* caller must hold write lock */
static void _init_signal_handler(void)
{
if (signal_handlers)
return;
for (int i = 0; i < signal_work_count; i++) {
work_t *work = signal_work[i];
xassert(work->magic == MAGIC_WORK);
_register_signal_handler(work->control.on_signal_number);
}
}
/* mgr.mutex should be locked */
static void _on_signal(int signal)
{
bool matched = false;
slurm_rwlock_rdlock(&lock);
if (slurm_conf.debug_flags & DEBUG_FLAG_CONMGR) {
char *str = sig_num2name(signal);
log_flag(CONMGR, "%s: [%s] got signal: %s(%d)",
__func__, signal_con->name, str, signal);
xfree(str);
}
for (int i = 0; i < signal_work_count; i++) {
work_t *work = signal_work[i];
xassert(work->magic == MAGIC_WORK);
if (work->control.on_signal_number != signal)
continue;
matched = true;
add_work(true, NULL, work->callback, work->control,
~CONMGR_WORK_DEP_SIGNAL, __func__);
}
slurm_rwlock_unlock(&lock);
if (!matched)
warning("%s: caught and ignoring signal %s",
__func__, strsignal(signal));
}
extern void add_work_signal(work_t *work)
{
xassert(!work->ref);
xassert(work->control.depend_type & CONMGR_WORK_DEP_SIGNAL);
xassert(work->control.on_signal_number > 0);
slurm_rwlock_wrlock(&lock);
xrecalloc(signal_work, (signal_work_count + 1), sizeof(*signal_work));
signal_work[signal_work_count] = work;
signal_work_count++;
/*
* Directly register new signal handler since connection already started
* and init_signal_handler() already ran
*/
if (signal_con)
_register_signal_handler(work->control.on_signal_number);
slurm_rwlock_unlock(&lock);
}
static void *_on_connection(conmgr_fd_t *con, void *arg)
{
slurm_rwlock_wrlock(&lock);
_init_signal_handler();
signal_con = con;
slurm_rwlock_unlock(&lock);
return con;
}
static int _on_data(conmgr_fd_t *con, void *arg)
{
const void *data = NULL;
size_t bytes = 0, read = 0;
int signo;
xassert(arg == con);
conmgr_fd_get_in_buffer(con, &data, &bytes);
slurm_mutex_lock(&mgr.mutex);
while ((read + sizeof(signo)) <= bytes) {
signo = *(int *) (data + read);
_on_signal(signo);
read += sizeof(signo);
}
slurm_mutex_unlock(&mgr.mutex);
conmgr_fd_mark_consumed_in_buffer(con, read);
return SLURM_SUCCESS;
}
static void _on_finish(conmgr_fd_t *con, void *arg)
{
int fd;
xassert(arg == con);
slurm_rwlock_wrlock(&lock);
fd = signal_fd;
signal_fd = -1;
xassert(fd != -1);
fd_close(&fd);
xassert((con == signal_con) || !signal_con);
signal_con = NULL;
slurm_rwlock_unlock(&lock);
}
static void _atfork_child(void)
{
/*
* Force state to return to default state before it was initialized at
* forking as all of the prior state is completely unusable.
*/
lock = (pthread_rwlock_t) PTHREAD_RWLOCK_INITIALIZER;
one_time_init = false;
signal_handlers = NULL;
signal_handler_count = 0;
signal_work = NULL;
signal_work_count = 0;
signal_fd = -1;
signal_con = NULL;
}
extern void signal_mgr_start(conmgr_callback_args_t conmgr_args, void *arg)
{
static const conmgr_events_t events = {
.on_connection = _on_connection,
.on_data = _on_data,
.on_finish = _on_finish,
};
int fd[2] = { -1, -1 };
int rc;
if (conmgr_args.status == CONMGR_WORK_STATUS_CANCELLED)
return;
slurm_rwlock_wrlock(&lock);
if (signal_fd >= 0) {
slurm_rwlock_unlock(&lock);
log_flag(CONMGR, "%s: skipping - already initialized",
__func__);
return;
}
if (pipe(fd))
fatal_abort("%s: pipe() failed: %m", __func__);
if (!one_time_init) {
if ((rc = pthread_atfork(NULL, NULL, _atfork_child)))
fatal_abort("%s: pthread_atfork() failed: %s",
__func__, slurm_strerror(rc));
one_time_init = true;
}
xassert(signal_fd == -1);
xassert(!signal_con);
fd_set_close_on_exec(fd[0]);
fd_set_close_on_exec(fd[1]);
fd_set_nonblocking(fd[1]);
signal_fd = fd[1];
slurm_rwlock_unlock(&lock);
if (add_connection(CON_TYPE_RAW, NULL, fd[0], -1, &events,
CON_FLAG_NONE, NULL, 0, false, NULL, NULL, NULL)) {
fatal_abort("%s: [fd:%d] unable to a register new connection",
__func__, fd[0]);
}
}
extern void signal_mgr_stop(void)
{
slurm_rwlock_wrlock(&lock);
if (signal_con) {
close_con(true, signal_con);
signal_con = NULL;
}
slurm_rwlock_unlock(&lock);
}
extern void signal_mgr_fini(void)
{
signal_handler_t *handlers = NULL;
int signal_fd_close = -1, count = 0;
signal_mgr_stop();
slurm_rwlock_wrlock(&lock);
if (!one_time_init) {
slurm_rwlock_unlock(&lock);
return;
}
xassert(one_time_init);
/* should already be cleaned up by signal_mgr_stop() */
xassert(!signal_con);
/* try to be as atomic as possible to close(signal_fd) */
signal_fd_close = signal_fd;
signal_fd = SIGNAL_FD_FAILED;
fd_close(&signal_fd_close);
/* Swap out handlers array before cleanup */
SWAP(signal_handler_count, count);
SWAP(signal_handlers, handlers);
for (int i = 0; i < signal_handler_count; i++) {
signal_handler_t *handler = &handlers[signal_handler_count];
xassert(handler->magic == MAGIC_SIGNAL_HANDLER);
xassert(handler->signal > 0);
if (sigaction(handler->signal, &handler->prior, &handler->new))
fatal("%s: unable to revert %s: %m",
__func__, strsignal(handler->signal));
/* clear handler entirely */
*handler = (signal_handler_t) {
.magic = ~MAGIC_SIGNAL_HANDLER,
};
}
xfree(handlers);
for (int i = 0; i < signal_work_count; i++) {
work_t *work = NULL;
SWAP(signal_work[i], work);
xassert(work->magic == MAGIC_WORK);
work->status = CONMGR_WORK_STATUS_CANCELLED;
handle_work(true, work);
}
xfree(signal_work);
signal_work_count = 0;
slurm_rwlock_unlock(&lock);
}
extern bool is_signal_connection(conmgr_fd_t *con)
{
bool match;
slurm_rwlock_rdlock(&lock);
match = (signal_con == con);
slurm_rwlock_unlock(&lock);
return match;
}
extern bool signal_mgr_has_incoming(void)
{
bool has_data = false;
slurm_rwlock_rdlock(&lock);
if (signal_con) {
if (signal_con->input_fd >= 0) {
int readable = -1;
/*
* Force (blocking) check of signal_fd since poll() may
* have not run yet but need to make sure to catch any
* pending data if possible.
*
* Ignore failure of FIONREAD here as we only care if
* there is pending data
*/
(void) fd_get_readable_bytes(signal_con->input_fd,
&readable,
signal_con->name);
if (readable > 0)
has_data = true;
}
if (!has_data)
has_data =
con_flag(signal_con, FLAG_CAN_READ) ||
(signal_con->in &&
get_buf_offset(signal_con->in)) ||
(signal_con->work &&
!list_is_empty(signal_con->work)) ||
(signal_con->write_complete_work &&
!list_is_empty(signal_con->write_complete_work));
}
slurm_rwlock_unlock(&lock);
return has_data;
}