mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
![Stefan Beller](/assets/img/avatar_default.png)
This allows to run external commands in parallel with ordered output on stderr. If we run external commands in parallel we cannot pipe the output directly to the our stdout/err as it would mix up. So each process's output will flow through a pipe, which we buffer. One subprocess can be directly piped to out stdout/err for a low latency feedback to the user. Example: Let's assume we have 5 submodules A,B,C,D,E and each fetch takes a different amount of time as the different submodules vary in size, then the output of fetches in sequential order might look like this: time --> output: |---A---| |-B-| |-------C-------| |-D-| |-E-| When we schedule these submodules into maximal two parallel processes, a schedule and sample output over time may look like this: process 1: |---A---| |-D-| |-E-| process 2: |-B-| |-------C-------| output: |---A---|B|---C-------|DE So A will be perceived as it would run normally in the single child version. As B has finished by the time A is done, we can dump its whole progress buffer on stderr, such that it looks like it finished in no time. Once that is done, C is determined to be the visible child and its progress will be reported in real time. So this way of output is really good for human consumption, as it only changes the timing, not the actual output. For machine consumption the output needs to be prepared in the tasks, by either having a prefix per line or per block to indicate whose tasks output is displayed, because the output order may not follow the original sequential ordering: |----A----| |--B--| |-C-| will be scheduled to be all parallel: process 1: |----A----| process 2: |--B--| process 3: |-C-| output: |----A----|CB This happens because C finished before B did, so it will be queued for output before B. To detect when a child has finished executing, we check interleaved with other actions (such as checking the liveliness of children or starting new processes) whether the stderr pipe still exists. Once a child closed its stderr stream, we assume it is terminating very soon, and use `finish_command()` from the single external process execution interface to collect the exit status. By maintaining the strong assumption of stderr being open until the very end of a child process, we can avoid other hassle such as an implementation using `waitpid(-1)`, which is not implemented in Windows. Signed-off-by: Stefan Beller <sbeller@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
104 lines
2.4 KiB
Bash
Executable file
104 lines
2.4 KiB
Bash
Executable file
#!/bin/sh
|
|
#
|
|
# Copyright (c) 2009 Ilari Liusvaara
|
|
#
|
|
|
|
test_description='Test run command'
|
|
|
|
. ./test-lib.sh
|
|
|
|
cat >hello-script <<-EOF
|
|
#!$SHELL_PATH
|
|
cat hello-script
|
|
EOF
|
|
>empty
|
|
|
|
test_expect_success 'start_command reports ENOENT' '
|
|
test-run-command start-command-ENOENT ./does-not-exist
|
|
'
|
|
|
|
test_expect_success 'run_command can run a command' '
|
|
cat hello-script >hello.sh &&
|
|
chmod +x hello.sh &&
|
|
test-run-command run-command ./hello.sh >actual 2>err &&
|
|
|
|
test_cmp hello-script actual &&
|
|
test_cmp empty err
|
|
'
|
|
|
|
test_expect_success POSIXPERM 'run_command reports EACCES' '
|
|
cat hello-script >hello.sh &&
|
|
chmod -x hello.sh &&
|
|
test_must_fail test-run-command run-command ./hello.sh 2>err &&
|
|
|
|
grep "fatal: cannot exec.*hello.sh" err
|
|
'
|
|
|
|
test_expect_success POSIXPERM,SANITY 'unreadable directory in PATH' '
|
|
mkdir local-command &&
|
|
test_when_finished "chmod u+rwx local-command && rm -fr local-command" &&
|
|
git config alias.nitfol "!echo frotz" &&
|
|
chmod a-rx local-command &&
|
|
(
|
|
PATH=./local-command:$PATH &&
|
|
git nitfol >actual
|
|
) &&
|
|
echo frotz >expect &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
cat >expect <<-EOF
|
|
preloaded output of a child
|
|
Hello
|
|
World
|
|
preloaded output of a child
|
|
Hello
|
|
World
|
|
preloaded output of a child
|
|
Hello
|
|
World
|
|
preloaded output of a child
|
|
Hello
|
|
World
|
|
EOF
|
|
|
|
test_expect_success 'run_command runs in parallel with more jobs available than tasks' '
|
|
test-run-command run-command-parallel 5 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'run_command runs in parallel with as many jobs as tasks' '
|
|
test-run-command run-command-parallel 4 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'run_command runs in parallel with more tasks than jobs available' '
|
|
test-run-command run-command-parallel 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
cat >expect <<-EOF
|
|
preloaded output of a child
|
|
asking for a quick stop
|
|
preloaded output of a child
|
|
asking for a quick stop
|
|
preloaded output of a child
|
|
asking for a quick stop
|
|
EOF
|
|
|
|
test_expect_success 'run_command is asked to abort gracefully' '
|
|
test-run-command run-command-abort 3 false 2>actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
cat >expect <<-EOF
|
|
no further jobs available
|
|
EOF
|
|
|
|
test_expect_success 'run_command outputs ' '
|
|
test-run-command run-command-no-jobs 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_done
|