aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2020-08-03 05:54:48 -0500
committerRob Landley <rob@landley.net>2020-08-03 05:54:48 -0500
commit9552ab89c61726e685254639029a621fa1132f62 (patch)
treec531fcb0c29d41befeb59861c6b2e31b55b23c8e
parentd2ad946f2af729890e80d0e9228f23a0ca62a450 (diff)
downloadtoybox-9552ab89c61726e685254639029a621fa1132f62.tar.gz
Mark Salyzyn implemented support for xargs -P (run parallel jobs) because he
has a build script that goes much faster with it, and added tests for it. I reimplemented it a different way, and did SIGUSR1 and SIGUSR2 support.
-rw-r--r--tests/xargs.test15
-rw-r--r--toys/posix/xargs.c62
2 files changed, 51 insertions, 26 deletions
diff --git a/tests/xargs.test b/tests/xargs.test
index afed8a17..dc3c7b32 100644
--- a/tests/xargs.test
+++ b/tests/xargs.test
@@ -61,6 +61,21 @@ testing "big input forces split" \
'for i in $(seq 1 100);do echo $X;done|{ xargs >/dev/null && echo yes;}' \
"yes\n" "" ""
+# -P max-proc
+testing "max-proc=1" "xargs -n 1 -P 1" "one\ntwo\nthree\n" "" "one\ntwo\nthree\n"
+testing "max-proc=2" "xargs -n 1 -P 2" "y\ny\ny\n" "" "y\ny\ny\n"
+testing "max-proc=3" "xargs -n 1 -P 3" "y\ny\ny\n" "" "y\ny\ny\n"
+# 3 seconds vs 2 seconds vs 1 second parallel sleep
+testing "parallel sleep" "
+ xargs_sleep() {
+ ( yes | head -3 | tr y 1 | time -p xargs -n 1 \${*} sleep ) 2>&1 |
+ sed -n 's/^real *\([0-9]*\)[.]\(.*\)/\1\2/p'
+ } &&
+ A=\`xargs_sleep\` &&
+ B=\`xargs_sleep -P 2\` &&
+ C=\`xargs_sleep -P 0\` &&
+ [ \${A} -gt \${B} -a \${B} -gt \${C} ] && echo OK || echo FAIL" "OK\n" "" ""
+
# TODO: what exactly is -x supposed to do? why does coreutils output "one"?
#testing "-x" "xargs -x -s 9 || echo expected" "one\nexpected\n" "" "one\ntwo\nthree"
diff --git a/toys/posix/xargs.c b/toys/posix/xargs.c
index 79e7dea5..26c1b250 100644
--- a/toys/posix/xargs.c
+++ b/toys/posix/xargs.c
@@ -8,15 +8,14 @@
* TODO: -I Insert mode
* TODO: -L Max number of lines of input per command
* TODO: -x Exit if can't fit everything in one command
- * TODO: -P NUM Run up to NUM processes at once
-USE_XARGS(NEWTOY(xargs, "^E:P#optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_XARGS(NEWTOY(xargs, "^E:P#<0=1optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
config XARGS
bool "xargs"
default y
help
- usage: xargs [-0prt] [-s NUM] [-n NUM] [-E STR] COMMAND...
+ usage: xargs [-0prt] [-snE STR] COMMAND...
Run command line one or more times, appending arguments from stdin.
@@ -27,7 +26,8 @@ config XARGS
-n Max number of arguments per command
-o Open tty for COMMAND's stdin (default /dev/null)
-p Prompt for y/n from tty before running each command
- -r Don't run command with empty input (otherwise always run command once)
+ -P Parallel processes (default 1)
+ -r Don't run with empty input (otherwise always run command once)
-s Size in bytes per command line
-t Trace, print command line to stderr
*/
@@ -39,7 +39,7 @@ GLOBALS(
long s, n, P;
char *E;
- long entries, bytes;
+ long entries, bytes, np;
char delim;
FILE *tty;
)
@@ -91,13 +91,23 @@ static char *handle_entries(char *data, char **entry)
return 0;
}
+// Handle SIGUSR1 and SIGUSR2 for -P
+static void signal_P(int sig)
+{
+ if (sig == SIGUSR2 && TT.P>1) TT.P--;
+ else TT.P++;
+}
+
void xargs_main(void)
{
struct double_list *dlist = 0, *dtemp;
int entries, bytes, done = 0, status;
- char *data = 0, **out;
+ char *data = 0, **out = 0;
pid_t pid = 0;
+ xsignal_flags(SIGUSR1, signal_P, SA_RESTART);
+ xsignal_flags(SIGUSR2, signal_P, SA_RESTART);
+
// POSIX requires that we never hit the ARG_MAX limit, even if we try to
// with -s. POSIX also says we have to reserve 2048 bytes "to guarantee
// that the invoked utility has room to modify its environment variables
@@ -138,7 +148,6 @@ void xargs_main(void)
}
}
dlist_add(&dlist, data);
-
// Count data used
if (!(data = handle_entries(data, 0))) continue;
if (data == (char *)2) done++;
@@ -150,8 +159,7 @@ void xargs_main(void)
if (!TT.entries) {
if (data) error_exit("argument too long");
- else if (pid) return;
- else if (FLAG(r)) continue;
+ if (pid || FLAG(r)) goto reap_children;
}
// Fill out command line to exec
@@ -170,7 +178,7 @@ void xargs_main(void)
if (FLAG(p)) {
fprintf(stderr, "?");
if (!TT.tty) TT.tty = xfopen("/dev/tty", "re");
- if (!fyesno(TT.tty, 0)) goto skip;
+ if (!fyesno(TT.tty, 0)) goto reap_children;
} else fprintf(stderr, "\n");
}
@@ -179,28 +187,30 @@ void xargs_main(void)
xopen_stdio(FLAG(o) ? "/dev/tty" : "/dev/null", O_RDONLY);
xexec(out);
}
- waitpid(pid, &status, 0);
-
- // xargs is yet another weird collection of exit value special cases,
- // different from all the others.
- if (WIFEXITED(status)) {
- if (WEXITSTATUS(status) == 126 || WEXITSTATUS(status) == 127) {
- toys.exitval = WEXITSTATUS(status);
- return;
- } else if (WEXITSTATUS(status) >= 1 && WEXITSTATUS(status) <= 125) {
- toys.exitval = 123;
- } else if (WEXITSTATUS(status) == 255) {
- error_msg("%s: exited with status 255; aborting", out[0]);
+ TT.np++;
+
+reap_children:
+ while (TT.np) {
+ int xv = (TT.np == TT.P) || (!data && done);
+
+ if (1>(xv = waitpid(-1, &status, WNOHANG*!xv))) break;
+ TT.np--;
+ xv = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+128;
+ if (xv == 255) {
+ error_msg("%s: exited with status 255; aborting", *out);
toys.exitval = 124;
- return;
- }
- } else toys.exitval = 127;
+ break;
+ } else if ((xv|1)==127) toys.exitval = xv;
+ else if (xv>127) xv = 125;
+ else if (xv) toys.exitval = 123;
+ }
// Abritrary number of execs, can't just leak memory each time...
-skip:
llist_traverse(dlist, llist_free_double);
dlist = 0;
free(out);
+ out = 0;
}
+ while (TT.np && -1 != wait(&status)) TT.np--;
if (TT.tty) fclose(TT.tty);
}