remove unnecessary signal management

The shell was doing a log of signal blocking/unblocking that hurts
performance and can be avoided. This reduced the elapsed time for a
simple benchmark by 25%.

Partial fix for #2007
This commit is contained in:
Kurtis Rader 2016-12-28 18:52:33 -08:00
parent 51adf815aa
commit fd6d814ea4
8 changed files with 113 additions and 85 deletions

View File

@ -500,15 +500,23 @@ __sentinel bool contains_internal(const wcstring &needle, int vararg_handle, ...
} }
long read_blocked(int fd, void *buf, size_t count) { long read_blocked(int fd, void *buf, size_t count) {
ssize_t res; long bytes_read = 0;
sigset_t chldset, oldset;
sigemptyset(&chldset); while (count) {
sigaddset(&chldset, SIGCHLD); ssize_t res = read(fd, (char *)buf + bytes_read, count);
VOMIT_ON_FAILURE(pthread_sigmask(SIG_BLOCK, &chldset, &oldset)); if (res == 0) {
res = read(fd, buf, count); break;
VOMIT_ON_FAILURE(pthread_sigmask(SIG_SETMASK, &oldset, NULL)); } else if (res == -1) {
return res; if (errno == EINTR) continue;
if (errno == EAGAIN) return bytes_read ? bytes_read : -1;
return -1;
} else {
bytes_read += res;
count -= res;
}
}
return bytes_read;
} }
/// Loop a write request while failure is non-critical. Return -1 and set errno in case of critical /// Loop a write request while failure is non-critical. Return -1 and set errno in case of critical

View File

@ -260,6 +260,8 @@ extern bool has_working_tty_timestamps;
/// Check if signals are blocked. If so, print an error message and return from the function /// Check if signals are blocked. If so, print an error message and return from the function
/// performing this check. /// performing this check.
#define CHECK_BLOCK(retval)
#if 0
#define CHECK_BLOCK(retval) \ #define CHECK_BLOCK(retval) \
if (signal_is_blocked()) { \ if (signal_is_blocked()) { \
debug(0, "function %s called while blocking signals. ", __func__); \ debug(0, "function %s called while blocking signals. ", __func__); \
@ -267,6 +269,7 @@ extern bool has_working_tty_timestamps;
show_stackframe(L'E'); \ show_stackframe(L'E'); \
return retval; \ return retval; \
} }
#endif
/// Shorthand for wgettext call in situations where a C-style string is needed (e.g., fwprintf()). /// Shorthand for wgettext call in situations where a C-style string is needed (e.g., fwprintf()).
#define _(wstr) wgettext(wstr).c_str() #define _(wstr) wgettext(wstr).c_str()

View File

@ -367,12 +367,10 @@ static bool can_use_posix_spawn_for_job(const job_t *job, const process_t *proce
void exec_job(parser_t &parser, job_t *j) { void exec_job(parser_t &parser, job_t *j) {
pid_t pid = 0; pid_t pid = 0;
sigset_t chldset;
// Set to true if something goes wrong while exec:ing the job, in which case the cleanup code // Set to true if something goes wrong while exec:ing the job, in which case the cleanup code
// will kick in. // will kick in.
bool exec_error = false; bool exec_error = false;
bool needs_keepalive = false; bool needs_keepalive = false;
process_t keepalive; process_t keepalive;
@ -384,9 +382,6 @@ void exec_job(parser_t &parser, job_t *j) {
return; return;
} }
sigemptyset(&chldset);
sigaddset(&chldset, SIGCHLD);
debug(4, L"Exec job '%ls' with id %d", j->command_wcstr(), j->job_id); debug(4, L"Exec job '%ls' with id %d", j->command_wcstr(), j->job_id);
// Verify that all IO_BUFFERs are output. We used to support a (single, hacked-in) magical input // Verify that all IO_BUFFERs are output. We used to support a (single, hacked-in) magical input

View File

@ -392,6 +392,11 @@ int main(int argc, char **argv) {
const io_chain_t empty_ios; const io_chain_t empty_ios;
if (read_init(paths)) { if (read_init(paths)) {
// TODO: Remove this once we're confident that not blocking/unblocking every signal around
// some critical sections is no longer necessary.
env_var_t fish_no_signal_block = env_get_string(L"FISH_NO_SIGNAL_BLOCK");
if (!fish_no_signal_block.missing()) ignore_signal_block = true;
// Stomp the exit status of any initialization commands (issue #635). // Stomp the exit status of any initialization commands (issue #635).
proc_set_last_status(STATUS_BUILTIN_OK); proc_set_last_status(STATUS_BUILTIN_OK);

View File

@ -105,7 +105,11 @@ bool set_child_group(job_t *j, process_t *p, int print_errors) {
} }
if (job_get_flag(j, JOB_TERMINAL) && job_get_flag(j, JOB_FOREGROUND)) { //!OCLINT(early exit) if (job_get_flag(j, JOB_TERMINAL) && job_get_flag(j, JOB_FOREGROUND)) { //!OCLINT(early exit)
int result = tcsetpgrp(STDIN_FILENO, j->pgid); // to avoid "collapsible if statements" warn int result = -1;
errno = EINTR;
while (result == -1 && errno == EINTR) {
result = tcsetpgrp(STDIN_FILENO, j->pgid);
}
if (result == -1) { if (result == -1) {
if (errno == ENOTTY) redirect_tty_output(); if (errno == ENOTTY) redirect_tty_output();
if (print_errors) { if (print_errors) {

View File

@ -792,19 +792,32 @@ static void read_try(job_t *j) {
/// \param cont If this variable is set, we are giving back control to a job that has previously /// \param cont If this variable is set, we are giving back control to a job that has previously
/// been stopped. In that case, we need to set the terminal attributes to those saved in the job. /// been stopped. In that case, we need to set the terminal attributes to those saved in the job.
static bool terminal_give_to_job(job_t *j, int cont) { static bool terminal_give_to_job(job_t *j, int cont) {
if (tcsetpgrp(STDIN_FILENO, j->pgid) == -1) { int result = -1;
errno = EINTR;
while (result == -1 && errno == EINTR) {
result = tcsetpgrp(STDIN_FILENO, j->pgid);
}
if (result == -1) {
if (errno == ENOTTY) redirect_tty_output(); if (errno == ENOTTY) redirect_tty_output();
debug(1, _(L"Could not send job %d ('%ls') to foreground"), j->job_id, j->command_wcstr()); debug(1, _(L"Could not send job %d ('%ls') to foreground"), j->job_id, j->command_wcstr());
wperror(L"tcsetpgrp"); wperror(L"tcsetpgrp");
return false; return false;
} }
if (cont && tcsetattr(STDIN_FILENO, TCSADRAIN, &j->tmodes)) { if (cont) {
int result = -1;
errno = EINTR;
while (result == -1 && errno == EINTR) {
result = tcsetattr(STDIN_FILENO, TCSADRAIN, &j->tmodes);
}
if (result == -1) {
if (errno == ENOTTY) redirect_tty_output(); if (errno == ENOTTY) redirect_tty_output();
debug(1, _(L"Could not send job %d ('%ls') to foreground"), j->job_id, j->command_wcstr()); debug(1, _(L"terminal_give_to_job(): Could not send job %d ('%ls') to foreground"), j->job_id, j->command_wcstr());
wperror(L"tcsetattr"); wperror(L"tcsetattr");
return false; return false;
} }
}
return true; return true;
} }

View File

@ -16,6 +16,9 @@
#include "reader.h" #include "reader.h"
#include "wutil.h" // IWYU pragma: keep #include "wutil.h" // IWYU pragma: keep
// This is a temporary var while we explore whether signal_block() and friends is needed.
bool ignore_signal_block = false;
/// Struct describing an entry for the lookup table used to convert between signal names and signal /// Struct describing an entry for the lookup table used to convert between signal names and signal
/// ids, etc. /// ids, etc.
struct lookup_entry { struct lookup_entry {
@ -252,88 +255,77 @@ void signal_reset_handlers() {
} }
} }
/// Sets appropriate signal handlers. static void set_interactive_handlers() {
void signal_set_handlers() {
struct sigaction act; struct sigaction act;
sigemptyset(&act.sa_mask); sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = &default_handler;
// First reset everything to a use default_handler, a function whose sole action is to fire of // Interactive mode. Ignore interactive signals. We are a shell, we know what is best for
// an event. // the user.
act.sa_handler = SIG_IGN;
sigaction(SIGINT, &act, 0); sigaction(SIGINT, &act, 0);
sigaction(SIGQUIT, &act, 0); sigaction(SIGQUIT, &act, 0);
sigaction(SIGTSTP, &act, 0); sigaction(SIGTSTP, &act, 0);
sigaction(SIGTTIN, &act, 0);
sigaction(SIGTTOU, &act, 0); sigaction(SIGTTOU, &act, 0);
sigaction(SIGCHLD, &act, 0);
// Ignore sigpipe, which we may get from the universal variable notifier. // We don't ignore SIGTTIN because we might send it to ourself.
sigaction(SIGPIPE, &act, 0); act.sa_sigaction = &default_handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGTTIN, &act, 0);
if (shell_is_interactive()) { act.sa_sigaction = &handle_int;
// Interactive mode. Ignore interactive signals. We are a shell, we know what is best for act.sa_flags = SA_SIGINFO;
// the user. sigaction(SIGINT, &act, 0);
act.sa_handler = SIG_IGN;
sigaction(SIGINT, &act, 0); // SIGTERM restores the terminal controlling process before dying.
sigaction(SIGQUIT, &act, 0); act.sa_sigaction = &handle_term;
sigaction(SIGTSTP, &act, 0); act.sa_flags = SA_SIGINFO;
sigaction(SIGTTIN, &act, 0); sigaction(SIGTERM, &act, 0);
sigaction(SIGTTOU, &act, 0);
act.sa_sigaction = &handle_int; act.sa_sigaction = &handle_hup;
act.sa_flags = SA_SIGINFO; act.sa_flags = SA_SIGINFO;
if (sigaction(SIGINT, &act, 0)) { sigaction(SIGHUP, &act, 0);
wperror(L"sigaction");
FATAL_EXIT();
}
act.sa_sigaction = &handle_chld;
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGCHLD, &act, 0)) {
wperror(L"sigaction");
FATAL_EXIT();
}
#ifdef SIGWINCH #ifdef SIGWINCH
act.sa_flags = SA_SIGINFO; act.sa_sigaction = &handle_winch;
act.sa_sigaction = &handle_winch; act.sa_flags = SA_SIGINFO;
if (sigaction(SIGWINCH, &act, 0)) { sigaction(SIGWINCH, &act, 0);
wperror(L"sigaction");
FATAL_EXIT();
}
#endif #endif
}
act.sa_flags = SA_SIGINFO; static void set_non_interactive_handlers() {
act.sa_sigaction = &handle_hup; struct sigaction act;
if (sigaction(SIGHUP, &act, 0)) { sigemptyset(&act.sa_mask);
wperror(L"sigaction");
FATAL_EXIT();
}
// SIGTERM restores the terminal controlling process before dying. // Non-interactive. Ignore interrupt, check exit status of processes to determine result
act.sa_flags = SA_SIGINFO; // instead.
act.sa_sigaction = &handle_term; act.sa_handler = SIG_IGN;
if (sigaction(SIGTERM, &act, 0)) { sigaction(SIGINT, &act, 0);
wperror(L"sigaction"); sigaction(SIGQUIT, &act, 0);
FATAL_EXIT(); }
}
/// Sets up appropriate signal handlers.
void signal_set_handlers() {
struct sigaction act;
sigemptyset(&act.sa_mask);
// Ignore SIGPIPE. We'll detect failed writes and deal with them appropriately. We don't want
// this signal interrupting other syscalls or terminating us.
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, 0);
// Whether or not we're interactive we want SIGCHLD to not interrupt restartable syscalls.
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = &handle_chld;
act.sa_flags = SA_SIGINFO | SA_RESTART;
if (sigaction(SIGCHLD, &act, 0)) {
wperror(L"sigaction");
FATAL_EXIT();
}
if (shell_is_interactive()) {
set_interactive_handlers();
} else { } else {
// Non-interactive. Ignore interrupt, check exit status of processes to determine result set_non_interactive_handlers();
// instead.
act.sa_handler = SIG_IGN;
sigaction(SIGINT, &act, 0);
sigaction(SIGQUIT, &act, 0);
act.sa_handler = SIG_DFL;
act.sa_sigaction = &handle_chld;
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGCHLD, &act, 0)) {
wperror(L"sigaction");
exit_without_destructors(1);
}
} }
} }
@ -367,6 +359,8 @@ void get_signals_with_handlers(sigset_t *set) {
} }
void signal_block() { void signal_block() {
if (ignore_signal_block) return;
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
sigset_t chldset; sigset_t chldset;
@ -376,10 +370,12 @@ void signal_block() {
} }
block_count++; block_count++;
// debug( 0, L"signal block level increased to %d", block_count ); // debug( 0, L"signal block level increased to %d", block_count );
} }
void signal_unblock() { void signal_unblock() {
if (ignore_signal_block) return;
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
sigset_t chldset; sigset_t chldset;
@ -395,7 +391,10 @@ void signal_unblock() {
sigfillset(&chldset); sigfillset(&chldset);
VOMIT_ON_FAILURE(pthread_sigmask(SIG_UNBLOCK, &chldset, 0)); VOMIT_ON_FAILURE(pthread_sigmask(SIG_UNBLOCK, &chldset, 0));
} }
// debug( 0, L"signal block level decreased to %d", block_count ); // debug( 0, L"signal block level decreased to %d", block_count );
} }
bool signal_is_blocked() { return static_cast<bool>(block_count); } bool signal_is_blocked() {
if (ignore_signal_block) return false;
return static_cast<bool>(block_count);
}

View File

@ -38,4 +38,5 @@ bool signal_is_blocked();
/// Returns signals with non-default handlers. /// Returns signals with non-default handlers.
void get_signals_with_handlers(sigset_t *set); void get_signals_with_handlers(sigset_t *set);
extern bool ignore_signal_block;
#endif #endif