* git subcommand sigint gotcha @ 2010-10-19 4:53 Joey Hess 2010-10-19 9:55 ` Dmitry Potapov 0 siblings, 1 reply; 10+ messages in thread From: Joey Hess @ 2010-10-19 4:53 UTC (permalink / raw) To: git [-- Attachment #1: Type: text/plain, Size: 1321 bytes --] I was trying to write a git subcommand, and I noticed that if I ctrl-c'd it, git would return, but leave the subcommand running in the background. You can see the problem with this test case. #!/usr/bin/perl print "first sleep...\n"; $ret=system("sleep", "1m"); print "second sleep...\n"; system("sleep", "1s"); print "done with second sleep\n"; If you put it in path named git-sleep, then run "git sleep" and press ctrl-c, it keeps running: joey@gnu:~>git sleep first sleep... ^Csecond sleep... joey@gnu:~>done with second sleep So what's going on? Well, perl's system() blocks sigint while the child process is running. So if you run this as git-sleep, and press ctrl-c, it will continue on to the second sleep. If the code above checked the return status of system() it could detect that it was killed by SIGINT and itself exit. What I don't understand is, why does git not wait() on the subcommand it ran? Any subcommand that forgets to check exit codes is liable to exhibit this weird behavior sometimes. Ie, imagine the subcommand was running something like "git config --get core.bare" instead of sleep. It'd be easy to forget to check the exit status of that for a SIGINT; if the user ctrl-c'd at just the right instant, weird things would happen. -- see shy jo [-- Attachment #2: Digital signature --] [-- Type: application/pgp-signature, Size: 828 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: git subcommand sigint gotcha 2010-10-19 4:53 git subcommand sigint gotcha Joey Hess @ 2010-10-19 9:55 ` Dmitry Potapov 2010-10-19 11:59 ` RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command Dmitry Potapov 0 siblings, 1 reply; 10+ messages in thread From: Dmitry Potapov @ 2010-10-19 9:55 UTC (permalink / raw) To: Joey Hess; +Cc: git, Jeff King On Tue, Oct 19, 2010 at 8:53 AM, Joey Hess <joey@kitenet.net> wrote: > I was trying to write a git subcommand, and I noticed that if I ctrl-c'd > it, git would return, but leave the subcommand running in the > background. It looks like this regression was introduced in v1.6.4, when Jeff tried to fix one serious issue with a pager. I have bisected the problem to this commit: http://git.kernel.org/?p=git/git.git;a=commit;h=d8e96fd86d415554a9c2e09ffb929a9e22fdad25 Dmitry ^ permalink raw reply [flat|nested] 10+ messages in thread
* RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command 2010-10-19 9:55 ` Dmitry Potapov @ 2010-10-19 11:59 ` Dmitry Potapov 2010-10-19 13:32 ` Jeff King 0 siblings, 1 reply; 10+ messages in thread From: Dmitry Potapov @ 2010-10-19 11:59 UTC (permalink / raw) To: git; +Cc: Jeff King, Johannes Sixt, Joey Hess Before git 1.6.4, we used execvp to run external git dashed commands, thus git did not return until this command is finished. With switching to run_command (which was necessary to fix a pager issue; see d8e96fd86d4), CTRL-C could cause that git returned before than the git dashed command is finished. The solution is to disable SIGINT and SIGQUIT as it is normally done by system(). Disabling these signals is done only when silent_exec_failure is set, which means that the current process is used as a proxy to run another command. Signed-off-by: Dmitry Potapov <dpotapov@gmail.com> --- run-command.c | 19 +++++++++++++++++++ 1 files changed, 19 insertions(+), 0 deletions(-) diff --git a/run-command.c b/run-command.c index 2a1041e..14af035 100644 --- a/run-command.c +++ b/run-command.c @@ -93,6 +93,10 @@ static inline void set_cloexec(int fd) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } +#ifndef WIN32 +static sighandler_t sigint, sigquit; +#endif + static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) { int status, code = -1; @@ -102,6 +106,13 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) ; /* nothing */ +#ifndef WIN32 + if (silent_exec_failure) { + /* Restore signal handlers */ + signal(SIGINT, sigint); + signal(SIGQUIT, sigquit); + } +#endif if (waiting < 0) { failed_errno = errno; error("waitpid for %s failed: %s", argv0, strerror(errno)); @@ -202,8 +213,16 @@ fail_pipe: notify_pipe[0] = notify_pipe[1] = -1; fflush(NULL); + if (cmd->silent_exec_failure) { + sigint = signal(SIGINT, SIG_IGN); + sigquit = signal(SIGQUIT, SIG_IGN); + } cmd->pid = fork(); if (!cmd->pid) { + if (cmd->silent_exec_failure) { + signal(SIGINT, sigint); + signal(SIGQUIT, sigquit); + } /* * Redirect the channel to write syscall error messages to * before redirecting the process's stderr so that all die() -- 1.7.3.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command 2010-10-19 11:59 ` RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command Dmitry Potapov @ 2010-10-19 13:32 ` Jeff King 2010-10-19 13:40 ` Jeff King 2010-10-19 16:31 ` Dmitry Potapov 0 siblings, 2 replies; 10+ messages in thread From: Jeff King @ 2010-10-19 13:32 UTC (permalink / raw) To: Dmitry Potapov; +Cc: git, Johannes Sixt, Joey Hess On Tue, Oct 19, 2010 at 03:59:43PM +0400, Dmitry Potapov wrote: > The solution is to disable SIGINT and SIGQUIT as it is normally done by > system(). Disabling these signals is done only when silent_exec_failure > is set, which means that the current process is used as a proxy to run > another command. I don't understand why we would only do it for silent_exec_failure. You claim that flag means that the current process is a proxy for another command, but: 1. Is that really the case, or do the two things just happen to coincide in the current codebase? 2. Why do we want to do it only for the proxy-command case? If I have a long-running external diff or merge helper, for example, what should happen on SIGINT? Should we exit with the child still potentially running, or should we actually be reaping the child properly? > + if (cmd->silent_exec_failure) { > + sigint = signal(SIGINT, SIG_IGN); > + sigquit = signal(SIGQUIT, SIG_IGN); > + } > cmd->pid = fork(); > if (!cmd->pid) { > + if (cmd->silent_exec_failure) { > + signal(SIGINT, sigint); > + signal(SIGQUIT, sigquit); > + } How does this interact with the sigchain code? If I do: start_command(...); sigchain_push(...); finish_command(...); we will overwrite the function pushed in the sigchain_push with a stale handler. I think you could just replace your signal() calls with: sigchain_push(SIGINT, SIG_IGN); ... sigchain_pop(SIGINT); but I wonder if ignoring is necessarily the right thing. Shouldn't we just reap the child and then run the signal handler that was there before us? That means in general that we will continue to die via SIGINT when we see SIGINT. With your patch, we will ignore it and (presumably) end up dying with a return code indicated that the child had an error. I think both of these things are not problems for executing dashed externals. But as above, I am not sure that we should be limiting this signal handling to those cases. -Peff ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command 2010-10-19 13:32 ` Jeff King @ 2010-10-19 13:40 ` Jeff King 2010-10-19 19:16 ` Jonathan Nieder 2010-10-19 16:31 ` Dmitry Potapov 1 sibling, 1 reply; 10+ messages in thread From: Jeff King @ 2010-10-19 13:40 UTC (permalink / raw) To: Dmitry Potapov; +Cc: git, Johannes Sixt, Joey Hess On Tue, Oct 19, 2010 at 09:32:36AM -0400, Jeff King wrote: > How does this interact with the sigchain code? If I do: > > start_command(...); > sigchain_push(...); > finish_command(...); > > we will overwrite the function pushed in the sigchain_push with a stale > handler. I think you could just replace your signal() calls with: > > sigchain_push(SIGINT, SIG_IGN); > ... > sigchain_pop(SIGINT); Which, FWIW, would look like this: diff --git a/run-command.c b/run-command.c index 2a1041e..24e0f46 100644 --- a/run-command.c +++ b/run-command.c @@ -1,6 +1,7 @@ #include "cache.h" #include "run-command.h" #include "exec_cmd.h" +#include "sigchain.h" static inline void close_pair(int fd[2]) { @@ -102,6 +103,9 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) ; /* nothing */ + sigchain_pop(SIGINT); + sigchain_pop(SIGQUIT); + if (waiting < 0) { failed_errno = errno; error("waitpid for %s failed: %s", argv0, strerror(errno)); @@ -202,8 +206,12 @@ fail_pipe: notify_pipe[0] = notify_pipe[1] = -1; fflush(NULL); + sigchain_push(SIGINT, SIG_IGN); + sigchain_push(SIGQUIT, SIG_IGN); cmd->pid = fork(); if (!cmd->pid) { + sigchain_pop(SIGINT); + sigchain_pop(SIGQUIT); /* * Redirect the channel to write syscall error messages to * before redirecting the process's stderr so that all die() ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command 2010-10-19 13:40 ` Jeff King @ 2010-10-19 19:16 ` Jonathan Nieder 2010-10-19 19:50 ` Jeff King 2010-10-19 21:06 ` Jakub Narebski 0 siblings, 2 replies; 10+ messages in thread From: Jonathan Nieder @ 2010-10-19 19:16 UTC (permalink / raw) To: Jeff King; +Cc: Dmitry Potapov, git, Johannes Sixt, Joey Hess Jeff King wrote: > On Tue, Oct 19, 2010 at 09:32:36AM -0400, Jeff King wrote: >> I think you could just replace your signal() calls with: >> >> sigchain_push(SIGINT, SIG_IGN); >> ... >> sigchain_pop(SIGINT); > > Which, FWIW, would look like this: Something in this direction on top? I think sigchain_push ought to accept a context object. Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> --- diff --git a/run-command.c b/run-command.c index 24e0f46..efdac84 100644 --- a/run-command.c +++ b/run-command.c @@ -103,6 +103,7 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) ; /* nothing */ + the_child = NULL; sigchain_pop(SIGINT); sigchain_pop(SIGQUIT); @@ -139,6 +140,19 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) return code; } +static struct child_process *the_child; + +static void interrupted_with_child(int sig) +{ + if (the_child && the_child->pid > 0) { + while ((waiting = waitpid(pid, NULL, 0)) < 0 && errno == EINTR) + ; /* nothing */ + the_child = NULL; + } + sigchain_pop(sig); + raise(sig); +} + int start_command(struct child_process *cmd) { int need_in, need_out, need_err; @@ -206,8 +220,11 @@ fail_pipe: notify_pipe[0] = notify_pipe[1] = -1; fflush(NULL); - sigchain_push(SIGINT, SIG_IGN); - sigchain_push(SIGQUIT, SIG_IGN); + if (the_child) + die("What? _Two_ children?"); + the_child = cmd; + sigchain_push(SIGINT, interrupted_with_child); + sigchain_push(SIGQUIT, interrupted_with_child); cmd->pid = fork(); if (!cmd->pid) { sigchain_pop(SIGINT); ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command 2010-10-19 19:16 ` Jonathan Nieder @ 2010-10-19 19:50 ` Jeff King 2010-10-19 21:06 ` Jakub Narebski 1 sibling, 0 replies; 10+ messages in thread From: Jeff King @ 2010-10-19 19:50 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Dmitry Potapov, git, Johannes Sixt, Joey Hess On Tue, Oct 19, 2010 at 02:16:38PM -0500, Jonathan Nieder wrote: > I think sigchain_push ought to accept a context object. But signal() doesn't, so we would have to install a wrapper function that gets the signal and calls the sigchain_pushed callback with the context object. But we can't always install the wrapper. We need to check for SIG_IGN and SIG_DFL, and literally install those. So I think it's do-able, but I tried to keep the original sigchain as simple as possible. > +static void interrupted_with_child(int sig) > +{ > + if (the_child && the_child->pid > 0) { > + while ((waiting = waitpid(pid, NULL, 0)) < 0 && errno == EINTR) > + ; /* nothing */ > + the_child = NULL; > + } > + sigchain_pop(sig); > + raise(sig); > +} > + > int start_command(struct child_process *cmd) > { > int need_in, need_out, need_err; > @@ -206,8 +220,11 @@ fail_pipe: > notify_pipe[0] = notify_pipe[1] = -1; > > fflush(NULL); > - sigchain_push(SIGINT, SIG_IGN); > - sigchain_push(SIGQUIT, SIG_IGN); > + if (the_child) > + die("What? _Two_ children?"); > + the_child = cmd; Yuck. You can get around that by pushing onto a linked list of children, though. Thinking about it more, though, I don't think we do necessarily want to always wait for the child. There are really two main types of run_command's we do: 1. The run command is basically the new process. In an ideal world, we would exec into it, but we need the parent to hang around to do some kind of bookkeeping (like waiting for the pager to exit). E.g., running external dashed commands. 2. We are running the command, and if we are killed, the command should go away too (because its point in running is to give us some information). E.g., running textconv filters. And there are a few instances that don't fall into either category (e.g., running the pager). In case (1), we probably want to SIG_IGN, wait for the command to finish, and then die with its exit code. If we do it right, the fact that _it_ was killed by signal will be propagated, and the fact that we weren't will be irrelevant. In case (2), we probably want to keep a linked list of "expendable" processes, and on signal death and atexit, go through the list and make sure all are dead. This is how we handle tempfiles already in diff.c. Given that there is only really one instance of (1), we can just code it there. For (2), there are many such callers, but I don't know that the mechanism necessarily needs to be included as part of run_command. A separate module to manage the list and set up the signal handler would be fine (though there is a race between fork() and signal death, so it perhaps pays to get the newly created pid on the "expendable" list as soon as possible, which may mean cooperating from run_command). -Peff ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command 2010-10-19 19:16 ` Jonathan Nieder 2010-10-19 19:50 ` Jeff King @ 2010-10-19 21:06 ` Jakub Narebski 2010-10-19 21:07 ` Jonathan Nieder 1 sibling, 1 reply; 10+ messages in thread From: Jakub Narebski @ 2010-10-19 21:06 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Jeff King, Dmitry Potapov, git, Johannes Sixt, Joey Hess Jonathan Nieder <jrnieder@gmail.com> writes: > - sigchain_push(SIGINT, SIG_IGN); > - sigchain_push(SIGQUIT, SIG_IGN); > + if (the_child) > + die("What? _Two_ children?"); > + the_child = cmd; > + sigchain_push(SIGINT, interrupted_with_child); > + sigchain_push(SIGQUIT, interrupted_with_child); Please, don't do this. It is almost as bad as error message as "You don't exist. Go away". -- Jakub Narebski Poland ShadeHawk on #git ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command 2010-10-19 21:06 ` Jakub Narebski @ 2010-10-19 21:07 ` Jonathan Nieder 0 siblings, 0 replies; 10+ messages in thread From: Jonathan Nieder @ 2010-10-19 21:07 UTC (permalink / raw) To: Jakub Narebski; +Cc: Jeff King, Dmitry Potapov, git, Johannes Sixt, Joey Hess Jakub Narebski wrote: > Jonathan Nieder <jrnieder@gmail.com> writes: >> - sigchain_push(SIGINT, SIG_IGN); >> - sigchain_push(SIGQUIT, SIG_IGN); >> + if (the_child) >> + die("What? _Two_ children?"); >> + the_child = cmd; >> + sigchain_push(SIGINT, interrupted_with_child); >> + sigchain_push(SIGQUIT, interrupted_with_child); > > Please, don't do this. It is almost as bad as error message as > "You don't exist. Go away". Hopefully it was clear that the behavior (erroring out) is as unacceptable as the message. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command 2010-10-19 13:32 ` Jeff King 2010-10-19 13:40 ` Jeff King @ 2010-10-19 16:31 ` Dmitry Potapov 1 sibling, 0 replies; 10+ messages in thread From: Dmitry Potapov @ 2010-10-19 16:31 UTC (permalink / raw) To: Jeff King; +Cc: git, Johannes Sixt, Joey Hess On Tue, Oct 19, 2010 at 09:32:36AM -0400, Jeff King wrote: > > 2. Why do we want to do it only for the proxy-command case? If I have > a long-running external diff or merge helper, for example, what > should happen on SIGINT? Should we exit with the child still > potentially running, or should we actually be reaping the child > properly? Probably, it should be done in other cases too. However, I am not sure if it should be done unconditionally. For instance, when we run a pager, I don't think we should ignore the signals just because we started a pager. I agree that silent_exec_failure is not the best flag for that -- I was just trying to make minimal changes to the existing behavior, and if this flag is set, you seem always want to ignore these signals, but there are some other cases too as you pointed above. Now, I think we should always ignore these signals when run_command() is used (similar to system()), but do not mask signals if start_command() is used (or make it optional by adding a new flag). > > we will overwrite the function pushed in the sigchain_push with a stale > handler. I think you could just replace your signal() calls with: > > sigchain_push(SIGINT, SIG_IGN); > ... > sigchain_pop(SIGINT); Yes, it is certainly better. I was not aware about these functions. Dmitry ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2010-10-19 21:11 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2010-10-19 4:53 git subcommand sigint gotcha Joey Hess 2010-10-19 9:55 ` Dmitry Potapov 2010-10-19 11:59 ` RFC: [PATCH] ignore SIGINT&QUIT while waiting for external command Dmitry Potapov 2010-10-19 13:32 ` Jeff King 2010-10-19 13:40 ` Jeff King 2010-10-19 19:16 ` Jonathan Nieder 2010-10-19 19:50 ` Jeff King 2010-10-19 21:06 ` Jakub Narebski 2010-10-19 21:07 ` Jonathan Nieder 2010-10-19 16:31 ` Dmitry Potapov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).