From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:35
Hi,
The first patch does all the work, the second patch uses it; basically, this is
needed so the transport-helper code is able to check if the remote-helper child
is stilll running. Without this support, the status of the remote-helper files
and configuration can end up very badly when errors occur, to the point where
the user is unable to use it any more.
The rest of the patches are for testing purposes only. I ran all the tests with
these, and I didn't see any problems.
Cheers.
Felipe Contreras (4):
run-command: add new check_command helper
transport-helper: check if remote helper is alive
tmp: remote-helper: add timers to catch errors
tmp: run-command: code to exercise check_command
git-remote-testgit | 12 +++++++++++
run-command.c | 52 +++++++++++++++++++++++++++++++++++++++++------
run-command.h | 6 ++++++
t/t5801-remote-helpers.sh | 19 +++++++++++++++++
transport-helper.c | 11 ++++++++++
5 files changed, 94 insertions(+), 6 deletions(-)
--
1.8.2
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:35
And persistent_waitpid() to recover the information from the last run.
Signed-off-by: Felipe Contreras <redacted>
---
run-command.c | 46 ++++++++++++++++++++++++++++++++++++++++------
run-command.h | 6 ++++++
2 files changed, 46 insertions(+), 6 deletions(-)
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:35
Otherwise transport-helper will continue checking for refs and other
things what will confuse the user more.
---
git-remote-testgit | 11 +++++++++++
t/t5801-remote-helpers.sh | 19 +++++++++++++++++++
transport-helper.c | 8 ++++++++
3 files changed, 38 insertions(+)
@@ -166,4 +166,23 @@ test_expect_success 'push ref with existing object' 'compare_refslocaldupserverdup'+test_expect_success'proper failure checks for fetching''+(GIT_REMOTE_TESTGIT_FAILURE=1&&+exportGIT_REMOTE_TESTGIT_FAILURE&&+cdlocal&&+test_must_failgitfetch2>&1|\+grep"Error while running helper"+)+'++# We sleep to give fast-export a chance to catch the SIGPIPE+test_expect_failure'proper failure checks for pushing''+(GIT_REMOTE_TESTGIT_FAILURE=1&&+exportGIT_REMOTE_TESTGIT_FAILURE&&+cdlocal&&+test_must_failgitpush--all2>&1|\+grep"Error while running helper"+)+'+ test_done
@@ -460,6 +460,10 @@ static int fetch_with_import(struct transport *transport,if(finish_command(&fastimport))die("Error while running fast-import");++if(!check_command(data->helper))+die("Error while running helper");+argv_array_free_detached(fastimport.argv);/*
@@ -818,6 +822,10 @@ static int push_refs_with_export(struct transport *transport,if(finish_command(&exporter))die("Error while running fast-export");++if(!check_command(data->helper))+die("Error while running helper");+push_update_refs_status(data,remote_refs);return0;}
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:35
This way the test reliably succeeds (in catching the failure).
Not sure what's the proper way to do this, but here it is for the
record.
Signed-off-by: Felipe Contreras <redacted>
---
git-remote-testgit | 1 +
t/t5801-remote-helpers.sh | 2 +-
transport-helper.c | 3 +++
3 files changed, 5 insertions(+), 1 deletion(-)
@@ -176,7 +176,7 @@ test_expect_success 'proper failure checks for fetching' ''# We sleep to give fast-export a chance to catch the SIGPIPE-test_expect_failure'proper failure checks for pushing''+test_expect_success'proper failure checks for pushing''(GIT_REMOTE_TESTGIT_FAILURE=1&&exportGIT_REMOTE_TESTGIT_FAILURE&&cdlocal&&
@@ -823,6 +823,9 @@ static int push_refs_with_export(struct transport *transport,if(finish_command(&exporter))die("Error while running fast-export");+if(getenv("GIT_REMOTE_TESTGIT_FAILURE"))+sleep(2);+if(!check_command(data->helper))die("Error while running helper");
So it looks we are trying to save the waitpid state from a previous run
and use the saved value. Otherwise, waitpid as normal.
We loop on EINTR when we actually call waitpid(). But we don't check
whether the saved errno is waitpid. What happens if we EINTR during the
saved call to waitpid?
This might return the pid if it has died, -1 if there was an error, or 0
if the process still exists but hasn't died. So...
+ if (waiting != cmd->pid)
+ return 1;
+
+ if (waiting < 0)
+ failed_errno = errno;
How would we ever trigger this second conditional? It makes sense to
return 1 when "waiting == 0", as that is saying "yes, your process is
still running" (though documenting the return either at the top of the
function or in the commit message would be helpful)
But if we get an error from waitpid, we would also return 1, which
doesn't make sense (especially if it is something like EINTR -- I don't
know offhand if we can get EINTR during WNOHANG. It should not block,
but I don't know if it can race with a signal).
Since we can only get here when waiting == cmd->pid, failed_errno is
always 0. We do correctly record the status. Why is code set to -1? It
seems to be used as a flag to say "this structure is valid". Should it
be defined as "unsigned valid:1;" instead?
-Peff
@@ -460,6 +460,10 @@ static int fetch_with_import(struct transport *transport,if(finish_command(&fastimport))die("Error while running fast-import");++if(!check_command(data->helper))+die("Error while running helper");+argv_array_free_detached(fastimport.argv);
Can you be more specific about what happens when we miss the death here,
what happens next, etc?
Checking asynchronously for death like this is subject to a race
condition; the helper may be about to die but not have died yet. In
practice we may catch some cases, but this seems like an indication that
the protocol is not well thought-out. Usually we would wait for a
confirmation over the read pipe from a child, and know that the child
failed when either:
1. It tells us so on the pipe.
2. The pipe closes (at which point we know it is time to reap the
child).
Why doesn't that scheme work here? I am not doubting you that it does
not; the import helper protocol is a bit of a mess, and I can easily
believe it has such a problem. But I'm wondering if it's possible to
improve it in a more robust way.
-Peff
So it looks we are trying to save the waitpid state from a previous run
and use the saved value. Otherwise, waitpid as normal.
We loop on EINTR when we actually call waitpid(). But we don't check
whether the saved errno is waitpid. What happens if we EINTR during the
saved call to waitpid?
Are you saying that even if we have stored the result of a waitpid
command, if errno is EINTR, then we should still loop waitpid()? If
so, I guess this would do the trick:
static pid_t persistent_waitpid(struct child_process *cmd, pid_t pid,
int *stat_loc)
{
pid_t waiting;
if (cmd->last_wait.code) {
errno = cmd->last_wait.failed_errno;
*stat_loc = cmd->last_wait.status;
if (errno != EINTR)
return errno ? -1 : pid;
}
while ((waiting = waitpid(pid, stat_loc, 0)) < 0 && errno == EINTR)
; /* nothing */
return waiting;
}
We now take argv0 into wait_or_whine. But I don't see it being used.
What's it for?
It was there before:
-static int wait_or_whine(pid_t pid, const char *argv0)
+static int wait_or_whine(struct child_process *cmd, pid_t pid, const
char *argv0)
This might return the pid if it has died, -1 if there was an error, or 0
if the process still exists but hasn't died. So...
quoted
+ if (waiting != cmd->pid)
+ return 1;
+
+ if (waiting < 0)
+ failed_errno = errno;
How would we ever trigger this second conditional? It makes sense to
return 1 when "waiting == 0", as that is saying "yes, your process is
still running" (though documenting the return either at the top of the
function or in the commit message would be helpful)
But if we get an error from waitpid, we would also return 1, which
doesn't make sense (especially if it is something like EINTR -- I don't
know offhand if we can get EINTR during WNOHANG. It should not block,
but I don't know if it can race with a signal).
How about this?
if (waiting >= 0 && waiting != cmd->pid)
return 1;
@@ -460,6 +460,10 @@ static int fetch_with_import(struct transport *transport,if(finish_command(&fastimport))die("Error while running fast-import");++if(!check_command(data->helper))+die("Error while running helper");+argv_array_free_detached(fastimport.argv);
Can you be more specific about what happens when we miss the death here,
what happens next, etc?
I have seen problems sporadically, like git trying to update refs to
object that don't exist. I also remember seeing mismatches between the
marks and the remote branches refs.
Checking asynchronously for death like this is subject to a rac
condition; the helper may be about to die but not have died yet. In
practice we may catch some cases, but this seems like an indication that
the protocol is not well thought-out. Usually we would wait for a
confirmation over the read pipe from a child, and know that the child
failed when either:
1. It tells us so on the pipe.
2. The pipe closes (at which point we know it is time to reap the
child).
Why doesn't that scheme work here? I am not doubting you that it does
not; the import helper protocol is a bit of a mess, and I can easily
believe it has such a problem. But I'm wondering if it's possible to
improve it in a more robust way.
The pipe is between fast-export and the remote-helper, "we"
(transport-helper) are not part of the pipe any more. That's the
problem.
Cheers.
--
Felipe Contreras
@@ -166,4 +166,23 @@ test_expect_success 'push ref with existing object' 'compare_refslocaldupserverdup'+test_expect_success'proper failure checks for fetching''+(GIT_REMOTE_TESTGIT_FAILURE=1&&+exportGIT_REMOTE_TESTGIT_FAILURE&&+cdlocal&&+test_must_failgitfetch2>&1|\+grep"Error while running helper"
This will not care if "git fetch" succeeds or fails and returns the
exit code from grep. Perhaps something like this instead?
(
GIT_REMOTE_TESTGIT_FAILURE=1 &&
export GIT_REMOTE_TESTGIT_FAILURE &&
cd local &&
test_must_fail git fetch 2>error &&
grep "Error while running helper" error
)
+# We sleep to give fast-export a chance to catch the SIGPIPE
+test_expect_failure 'proper failure checks for pushing' '
+ (GIT_REMOTE_TESTGIT_FAILURE=1 &&
+ export GIT_REMOTE_TESTGIT_FAILURE &&
+ cd local &&
+ test_must_fail git push --all 2>&1 | \
+ grep "Error while running helper"
@@ -460,6 +460,10 @@ static int fetch_with_import(struct transport *transport,if(finish_command(&fastimport))die("Error while running fast-import");++if(!check_command(data->helper))+die("Error while running helper");+argv_array_free_detached(fastimport.argv);/*
@@ -818,6 +822,10 @@ static int push_refs_with_export(struct transport *transport,if(finish_command(&exporter))die("Error while running fast-export");++if(!check_command(data->helper))+die("Error while running helper");+push_update_refs_status(data,remote_refs);return0;}
OK, so the idea is that fetch_with_import() does
- get_helper(transport), which spawns a helper process;
- get_importer(transport, &fastimport), which spawns a fast-import
and make it read from the output of the helper process;
- we did finish_command() to wait for the fast-import to finish,
expecting that the fast-import would finish when the helper stops
feeding it, which in turn would mean the helper would have died.
The same for the pushing side.
Shouldn't transport_disconnect() have called release_helper() which
in turn calls disconnect_helper() to call finish_command() on the
helper to wait for that procesanyway? Is somebody discarding return
value from transport_disconnect() or the current calling site of
transport_disconnect() is too late to notice the error?
Puzzled...
So it looks we are trying to save the waitpid state from a previous run
and use the saved value. Otherwise, waitpid as normal.
We loop on EINTR when we actually call waitpid(). But we don't check
whether the saved errno is waitpid. What happens if we EINTR during the
saved call to waitpid?
Are you saying that even if we have stored the result of a waitpid
command, if errno is EINTR, then we should still loop waitpid()? If
so, I guess this would do the trick:
Yes, I think that would work. Though I wonder if it is even worth
storing EINTR at all in the first place. It tells us nothing. In fact,
does storing any error condition really tell us anything? The two states
we are interested in at this point are:
1. We have reaped the child via waitpid; here is its status.
2. We have not (either we did not try, it was not dead yet, or we were
not able to due to an error). We should now try it again.
If we got EINTR the first time around, we would likely get the "real"
answer this time. If we get anything else (like EINVAL or ECHILD), then
we would get the same thing again calling waitpid() later.
quoted
We now take argv0 into wait_or_whine. But I don't see it being used.
What's it for?
It was there before:
-static int wait_or_whine(pid_t pid, const char *argv0)
+static int wait_or_whine(struct child_process *cmd, pid_t pid, const
char *argv0)
Ah, sorry, I misread the diff. We are adding "cmd", not "argv0".
quoted
quoted
+ if (waiting != cmd->pid)
+ return 1;
+
+ if (waiting < 0)
+ failed_errno = errno;
How would we ever trigger this second conditional?
[...]
How about this?
if (waiting >= 0 && waiting != cmd->pid)
return 1;
That would trigger the rest of your code in the error case, which I
think was your original intent. But then we return "0" from
check_command. Is that right?
There are three states we can be in from calling waitpid:
1. The process is dead.
2. The process is not dead.
3. We could not determine which because waitpid returned an error.
It is clear that check_command is trying to tell its caller (1) or (2);
but what should it say in case of (3)?
Naively, given how patch 2 uses it, I think it would actually make sense
for it to return 1. That is, the semantics are "return 0 if and only if
the pid is verified to be dead; otherwise return 1".
But if we know from reading waitpid(3) that waitpid should only fail due
to EINTR, or due to bogus arguments (e.g., a pid that does not exist or
has already been reaped), then maybe something like this makes sense:
while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
; /* nothing */
/* pid definitely still going */
if (!waiting)
return 1;
/* pid definitely died */
if (waiting == cmd->pid) {
cmd->last_status.valid = 1;
cmd->last_status.status = status;
return 0;
}
/*
* this should never happen, since we handed waitpid() a single
* pid, so it should either return that pid, 0, or an error.
*/
if (waiting > 0)
die("BUG: waitpid reported a random pid?");
/*
* otherwise, we have an error. Assume the pid is gone, since that
* is the only reason for waitpid to report a problem besides EINTR.
* We don't bother recording errno, since we can just repeat
* the waitpid again later.
*/
return 0;
Since we can only get here when waiting == cmd->pid,
No, also when waiting < 0.
After the fix above, yes; in the original we would always have exited
already.
As an aside, should check_command be able to be called twice? That is,
should it first check for cmd->last_status.valid and return early if
somebody has already reaped the child? It doesn't matter for the code
you add in patch 2, but it seems like it would give the least surprise
to somebody trying to use it later.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 01, 2013 at 06:12:45PM -0600, Felipe Contreras wrote:
quoted
Checking asynchronously for death like this is subject to a rac
condition; the helper may be about to die but not have died yet. In
practice we may catch some cases, but this seems like an indication that
the protocol is not well thought-out. Usually we would wait for a
confirmation over the read pipe from a child, and know that the child
failed when either:
1. It tells us so on the pipe.
2. The pipe closes (at which point we know it is time to reap the
child).
Why doesn't that scheme work here? I am not doubting you that it does
not; the import helper protocol is a bit of a mess, and I can easily
believe it has such a problem. But I'm wondering if it's possible to
improve it in a more robust way.
The pipe is between fast-export and the remote-helper, "we"
(transport-helper) are not part of the pipe any more. That's the
problem.
So in fetch_with_import, we have a remote-helper, and we have a
bidirectional pipe to it. We then call get_importer, which starts
fast-import, whose stdin is connected to the stdout of the remote
helper. We tell the remote-helper to run the import, then we wait for
fast-import to finish (and complain if it fails).
Then what? We seem to do some more work, which I think is what causes
the errors you see; but should we instead be reaping the helper at this
point unconditionally? Its stdout has presumably been flushed out to
fast-import; is there anything else for us to get from it besides its
exit code?
-Peff
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 1, 2013 at 8:30 PM, Jeff King [off-list ref] wrote:
On Mon, Apr 01, 2013 at 06:12:45PM -0600, Felipe Contreras wrote:
quoted
quoted
Checking asynchronously for death like this is subject to a rac
condition; the helper may be about to die but not have died yet. In
practice we may catch some cases, but this seems like an indication that
the protocol is not well thought-out. Usually we would wait for a
confirmation over the read pipe from a child, and know that the child
failed when either:
1. It tells us so on the pipe.
2. The pipe closes (at which point we know it is time to reap the
child).
Why doesn't that scheme work here? I am not doubting you that it does
not; the import helper protocol is a bit of a mess, and I can easily
believe it has such a problem. But I'm wondering if it's possible to
improve it in a more robust way.
The pipe is between fast-export and the remote-helper, "we"
(transport-helper) are not part of the pipe any more. That's the
problem.
So in fetch_with_import, we have a remote-helper, and we have a
bidirectional pipe to it. We then call get_importer, which starts
fast-import, whose stdin is connected to the stdout of the remote
helper. We tell the remote-helper to run the import, then we wait for
fast-import to finish (and complain if it fails).
Then what? We seem to do some more work, which I think is what causes
the errors you see; but should we instead be reaping the helper at this
point unconditionally? Its stdout has presumably been flushed out to
fast-import; is there anything else for us to get from it besides its
exit code?
The problem is not with import, since fast-import would generally wait
properly for a 'done' status, the problem is with export. Also, the
design is such that the remote-helper stays alive, even after
fast-export has finished.
--
Felipe Contreras
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 1, 2013 at 6:26 PM, Junio C Hamano [off-list ref] wrote:
OK, so the idea is that fetch_with_import() does
- get_helper(transport), which spawns a helper process;
- get_importer(transport, &fastimport), which spawns a fast-import
and make it read from the output of the helper process;
- we did finish_command() to wait for the fast-import to finish,
expecting that the fast-import would finish when the helper stops
feeding it, which in turn would mean the helper would have died.
The same for the pushing side.
The difference with the pushing side is that it's the helper the one
waiting for fast-export and it can easily die.
Shouldn't transport_disconnect() have called release_helper() which
in turn calls disconnect_helper() to call finish_command() on the
helper to wait for that procesanyway? Is somebody discarding return
value from transport_disconnect() or the current calling site of
transport_disconnect() is too late to notice the error?
It's too late to notice the error. However, only in the case of pushing.
--
Felipe Contreras
From: Jeff King <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 01, 2013 at 10:51:20PM -0600, Felipe Contreras wrote:
quoted
So in fetch_with_import, we have a remote-helper, and we have a
bidirectional pipe to it. We then call get_importer, which starts
fast-import, whose stdin is connected to the stdout of the remote
helper. We tell the remote-helper to run the import, then we wait for
fast-import to finish (and complain if it fails).
Then what? We seem to do some more work, which I think is what causes
the errors you see; but should we instead be reaping the helper at this
point unconditionally? Its stdout has presumably been flushed out to
fast-import; is there anything else for us to get from it besides its
exit code?
The problem is not with import, since fast-import would generally wait
properly for a 'done' status, the problem is with export.
Your patch modified fetch_with_import. Are you saying that it isn't
necessary to do so?
Also, the design is such that the remote-helper stays alive, even
after fast-export has finished.
So if we expect to be able to communicate with the remote-helper after
fast-export has exited, is it a protocol failure that the helper does
not say "yes, I finished the export" or similar? If so, can we fix that?
I am not too familiar with this protocol, but it looks like we read from
helper->out right after closing the exporter, to get the ref statuses.
Shouldn't we be detecting the error if the helper hangs up there?
-Peff
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 1, 2013 at 8:22 PM, Jeff King [off-list ref] wrote:
On Mon, Apr 01, 2013 at 05:58:55PM -0600, Felipe Contreras wrote:
quoted
Are you saying that even if we have stored the result of a waitpid
command, if errno is EINTR, then we should still loop waitpid()? If
so, I guess this would do the trick:
Yes, I think that would work. Though I wonder if it is even worth
storing EINTR at all in the first place. It tells us nothing. In fact,
does storing any error condition really tell us anything?
Probably not, I just tried to minimize the potential behavior changes.
The two states
we are interested in at this point are:
1. We have reaped the child via waitpid; here is its status.
2. We have not (either we did not try, it was not dead yet, or we were
not able to due to an error). We should now try it again.
If we got EINTR the first time around, we would likely get the "real"
answer this time. If we get anything else (like EINVAL or ECHILD), then
we would get the same thing again calling waitpid() later.
quoted
quoted
We now take argv0 into wait_or_whine. But I don't see it being used.
What's it for?
It was there before:
-static int wait_or_whine(pid_t pid, const char *argv0)
+static int wait_or_whine(struct child_process *cmd, pid_t pid, const
char *argv0)
Ah, sorry, I misread the diff. We are adding "cmd", not "argv0".
Yeah, which in fact was already there before.
That would trigger the rest of your code in the error case, which I
think was your original intent. But then we return "0" from
check_command. Is that right?
There are three states we can be in from calling waitpid:
1. The process is dead.
2. The process is not dead.
3. We could not determine which because waitpid returned an error.
It is clear that check_command is trying to tell its caller (1) or (2);
but what should it say in case of (3)?
Naively, given how patch 2 uses it, I think it would actually make sense
for it to return 1. That is, the semantics are "return 0 if and only if
the pid is verified to be dead; otherwise return 1".
I thought if there was an error that constituted a failure.
But if we know from reading waitpid(3) that waitpid should only fail due
to EINTR, or due to bogus arguments (e.g., a pid that does not exist or
has already been reaped), then maybe something like this makes sense:
while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
; /* nothing */
But we don't want to wait synchronously here, we just want to ping.
/* pid definitely still going */
if (!waiting)
return 1;
/* pid definitely died */
if (waiting == cmd->pid) {
cmd->last_status.valid = 1;
cmd->last_status.status = status;
return 0;
}
/*
* this should never happen, since we handed waitpid() a single
* pid, so it should either return that pid, 0, or an error.
*/
if (waiting > 0)
die("BUG: waitpid reported a random pid?");
/*
* otherwise, we have an error. Assume the pid is gone, since that
* is the only reason for waitpid to report a problem besides EINTR.
* We don't bother recording errno, since we can just repeat
* the waitpid again later.
*/
return 0;
Since we can only get here when waiting == cmd->pid,
No, also when waiting < 0.
After the fix above, yes; in the original we would always have exited
already.
No:
+ if (waiting != cmd->pid)
+ return 1;
If waiting < 0, waiting != cmd->pid, and therefore this return is not
triggered, and there's only one more return at the end of the
function.
Cheers.
--
Felipe Contreras
From: Jeff King <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 01, 2013 at 11:11:20PM -0600, Felipe Contreras wrote:
quoted
But if we know from reading waitpid(3) that waitpid should only fail due
to EINTR, or due to bogus arguments (e.g., a pid that does not exist or
has already been reaped), then maybe something like this makes sense:
while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
; /* nothing */
But we don't want to wait synchronously here, we just want to ping.
Yeah, sorry, I forgot the WNOHANG there.
quoted
After the fix above, yes; in the original we would always have exited
already.
No:
+ if (waiting != cmd->pid)
+ return 1;
If waiting < 0, waiting != cmd->pid, and therefore this return is not
triggered, and there's only one more return at the end of the
function.
Are my eyes not working? If waiting < 0, then waiting != cmd->pid, and
therefore this return _is_ triggered.
-Peff
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 1, 2013 at 11:01 PM, Jeff King [off-list ref] wrote:
On Mon, Apr 01, 2013 at 10:51:20PM -0600, Felipe Contreras wrote:
quoted
quoted
So in fetch_with_import, we have a remote-helper, and we have a
bidirectional pipe to it. We then call get_importer, which starts
fast-import, whose stdin is connected to the stdout of the remote
helper. We tell the remote-helper to run the import, then we wait for
fast-import to finish (and complain if it fails).
Then what? We seem to do some more work, which I think is what causes
the errors you see; but should we instead be reaping the helper at this
point unconditionally? Its stdout has presumably been flushed out to
fast-import; is there anything else for us to get from it besides its
exit code?
The problem is not with import, since fast-import would generally wait
properly for a 'done' status, the problem is with export.
Your patch modified fetch_with_import. Are you saying that it isn't
necessary to do so?
It's not, I added it for symmetry. But that's the case *if* the
remote-helper is properly using the "done" feature.
quoted
Also, the design is such that the remote-helper stays alive, even
after fast-export has finished.
So if we expect to be able to communicate with the remote-helper after
fast-export has exited, is it a protocol failure that the helper does
not say "yes, I finished the export" or similar? If so, can we fix that?
I am not too familiar with this protocol, but it looks like we read from
helper->out right after closing the exporter, to get the ref statuses.
Shouldn't we be detecting the error if the helper hangs up there?
I guess that should be possible, I'll give that a try.
Cheers.
--
Felipe Contreras
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 1, 2013 at 11:14 PM, Jeff King [off-list ref] wrote:
On Mon, Apr 01, 2013 at 11:11:20PM -0600, Felipe Contreras wrote:
quoted
quoted
But if we know from reading waitpid(3) that waitpid should only fail due
to EINTR, or due to bogus arguments (e.g., a pid that does not exist or
has already been reaped), then maybe something like this makes sense:
while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
; /* nothing */
But we don't want to wait synchronously here, we just want to ping.
Yeah, sorry, I forgot the WNOHANG there.
It still can potentially stay in a loop for some cycles.
quoted
quoted
After the fix above, yes; in the original we would always have exited
already.
No:
+ if (waiting != cmd->pid)
+ return 1;
If waiting < 0, waiting != cmd->pid, and therefore this return is not
triggered, and there's only one more return at the end of the
function.
Are my eyes not working? If waiting < 0, then waiting != cmd->pid, and
therefore this return _is_ triggered.
Oh, right, it's only after the modification that the code works.
--
Felipe Contreras
From: Jeff King <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 01, 2013 at 11:22:36PM -0600, Felipe Contreras wrote:
On Mon, Apr 1, 2013 at 11:14 PM, Jeff King [off-list ref] wrote:
quoted
On Mon, Apr 01, 2013 at 11:11:20PM -0600, Felipe Contreras wrote:
quoted
quoted
But if we know from reading waitpid(3) that waitpid should only fail due
to EINTR, or due to bogus arguments (e.g., a pid that does not exist or
has already been reaped), then maybe something like this makes sense:
while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
; /* nothing */
But we don't want to wait synchronously here, we just want to ping.
Yeah, sorry, I forgot the WNOHANG there.
It still can potentially stay in a loop for some cycles.
That should be OK; it's the same loop we use in wait_or_whine (and that
is in fact how I managed to get the WNOHANG wrong, as I copied the loop
from there but forgot to update the flag variable). A few cycles is OK,
as it is really about handling a simultaneous signal; it should be rare
that we loop at all, and even rarer to loop more than a single time. On
Linux, I don't think we will ever get EINTR at all, according to the
manpage; however, POSIX seems to allow EINTR even with WNOHANG.
-Peff
From: Felipe Contreras <hidden> Date: 2016-06-15 22:56:36
On Mon, Apr 1, 2013 at 11:19 PM, Felipe Contreras
[off-list ref] wrote:
On Mon, Apr 1, 2013 at 11:01 PM, Jeff King [off-list ref] wrote:
quoted
On Mon, Apr 01, 2013 at 10:51:20PM -0600, Felipe Contreras wrote:
quoted
quoted
So in fetch_with_import, we have a remote-helper, and we have a
bidirectional pipe to it. We then call get_importer, which starts
fast-import, whose stdin is connected to the stdout of the remote
helper. We tell the remote-helper to run the import, then we wait for
fast-import to finish (and complain if it fails).
Then what? We seem to do some more work, which I think is what causes
the errors you see; but should we instead be reaping the helper at this
point unconditionally? Its stdout has presumably been flushed out to
fast-import; is there anything else for us to get from it besides its
exit code?
The problem is not with import, since fast-import would generally wait
properly for a 'done' status, the problem is with export.
Your patch modified fetch_with_import. Are you saying that it isn't
necessary to do so?
It's not, I added it for symmetry. But that's the case *if* the
remote-helper is properly using the "done" feature.
Actually, it is a problem, because without this check the
transport-helper just goes on without realizing the whole thing has
failed and doesn't produce a proper error message:
fatal: bad object 0000000000000000000000000000000000000000
error: testgit::/home/felipec/dev/git/t/trash
directory.t5801-remote-helpers/server did not send all necessary
objects
It's possible to send a ping command to the remote-helper, but doing
so triggers a SIGPIPE. I would rather show a proper error message as
my patch suggests by just checking if the command is running.
quoted
quoted
Also, the design is such that the remote-helper stays alive, even
after fast-export has finished.
So if we expect to be able to communicate with the remote-helper after
fast-export has exited, is it a protocol failure that the helper does
not say "yes, I finished the export" or similar? If so, can we fix that?
I am not too familiar with this protocol, but it looks like we read from
helper->out right after closing the exporter, to get the ref statuses.
Shouldn't we be detecting the error if the helper hangs up there?
I guess that should be possible, I'll give that a try.
I gave this a try and it does work, but it seems rather convoluted to me:
@@ -25,7 +25,8 @@ struct helper_data {option:1,push:1,connect:1,-no_disconnect_req:1;+no_disconnect_req:1,+done_export:1;char*export_marks;char*import_marks;/* These go from remote name (as in "list") to private name */