git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* 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: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

* 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

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).