add wait command

This commit is contained in:
slama 2017-10-22 16:10:23 +09:00 committed by ridiculousfish
parent ea5f3925ea
commit c7a682ed05
9 changed files with 326 additions and 9 deletions

View file

@ -117,7 +117,7 @@ FISH_OBJS := obj/autoload.o obj/builtin.o obj/builtin_bg.o obj/builtin_bind.o ob
obj/builtin_random.o obj/builtin_read.o obj/builtin_realpath.o \
obj/builtin_return.o obj/builtin_set.o obj/builtin_set_color.o \
obj/builtin_source.o obj/builtin_status.o obj/builtin_string.o \
obj/builtin_test.o obj/builtin_ulimit.o obj/color.o obj/common.o \
obj/builtin_test.o obj/builtin_ulimit.o obj/builtin_wait.o obj/color.o obj/common.o \
obj/complete.o obj/env.o obj/env_universal_common.o obj/event.o obj/exec.o \
obj/expand.o obj/fallback.o obj/fish_version.o obj/function.o obj/highlight.o \
obj/history.o obj/input.o obj/input_common.o obj/intern.o obj/io.o \

View file

@ -60,6 +60,7 @@
#include "builtin_string.h"
#include "builtin_test.h"
#include "builtin_ulimit.h"
#include "builtin_wait.h"
#include "common.h"
#include "complete.h"
#include "exec.h"
@ -459,6 +460,7 @@ static const builtin_data_t builtin_datas[] = {
{L"test", &builtin_test, N_(L"Test a condition")},
{L"true", &builtin_true, N_(L"Return a successful result")},
{L"ulimit", &builtin_ulimit, N_(L"Set or get the shells resource usage limits")},
{L"wait", &builtin_wait, N_(L"Wait for background processes completed")},
{L"while", &builtin_generic, N_(L"Perform a command multiple times")}};
#define BUILTIN_COUNT (sizeof builtin_datas / sizeof *builtin_datas)

177
src/builtin_wait.cpp Normal file
View file

@ -0,0 +1,177 @@
// Functions for waiting for processes completed.
#include <vector>
#include "builtin.h"
#include "builtin_wait.h"
#include "common.h"
#include "proc.h"
#include "wgetopt.h"
#include "wutil.h"
#include <sys/wait.h>
static int retval;
static bool all_jobs_finished() {
job_t *j;
job_iterator_t jobs;
while ((j = jobs.next())) {
// If any job is not completed, return false.
// If there are stopped jobs, they are ignored.
if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j) && !job_is_stopped(j)) {
return false;
}
}
return true;
}
static bool any_jobs_finished(size_t jobs_len) {
job_t *j;
job_iterator_t jobs;
bool no_jobs_running = true;
// If any job is removed from list, return true.
if (jobs_len != jobs.count()) {
return true;
}
while ((j = jobs.next())) {
// If any job is completed, return true.
if ((j->flags & JOB_CONSTRUCTED) && (job_is_completed(j) || job_is_stopped(j))) {
return true;
}
// Check for jobs running exist or not.
if ((j->flags & JOB_CONSTRUCTED) && !job_is_stopped(j)) {
no_jobs_running = false;
}
}
if (no_jobs_running) {
return true;
}
return false;
}
static void wait_for_backgrounds(bool any_flag) {
job_iterator_t jobs;
size_t jobs_len = jobs.count();
while ((!any_flag && !all_jobs_finished()) || (any_flag && !any_jobs_finished(jobs_len))) {
pid_t pid = proc_wait_any();
if (pid == -1 && errno == EINTR) {
retval = 128 + SIGINT;
return;
}
}
}
static bool all_specified_jobs_finished(std::vector<int> wjobs_pid) {
job_t *j;
for (auto pid : wjobs_pid) {
if ((j = job_get_from_pid(pid))) {
// If any specified job is not completed, return false.
// If there are stopped jobs, they are ignored.
if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j) && !job_is_stopped(j)) {
return false;
}
}
}
return true;
}
static bool any_specified_jobs_finished(std::vector<int> wjobs_pid) {
job_t *j;
for (auto pid : wjobs_pid) {
if ((j = job_get_from_pid(pid))) {
// If any specified job is completed, return true.
if ((j->flags & JOB_CONSTRUCTED) && (job_is_completed(j) || job_is_stopped(j))) {
return true;
}
} else {
// If any specified job is removed from list, return true.
return true;
}
}
return false;
}
static void wait_for_backgrounds_specified(std::vector<int> wjobs_pid, bool any_flag) {
while ((!any_flag && !all_specified_jobs_finished(wjobs_pid)) ||
(any_flag && !any_specified_jobs_finished(wjobs_pid))) {
pid_t pid = proc_wait_any();
if (pid == -1 && errno == EINTR) {
retval = 128 + SIGINT;
return;
}
}
}
int builtin_wait(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
job_t *j;
job_iterator_t jobs;
const wchar_t *cmd = argv[0];
int argc = builtin_count_args(argv);
bool any_flag = false; // flag for -n option
static const wchar_t *short_options = L":n";
static const struct woption long_options[] = {{L"any", no_argument, NULL, 'n'},
{NULL, 0, NULL, 0}};
int opt;
wgetopter_t w;
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
switch (opt) {
case 'n':
any_flag = true;
break;
case ':': {
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
return STATUS_INVALID_ARGS;
}
case '?': {
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
return STATUS_INVALID_ARGS;
}
default: {
DIE("unexpected retval from wgetopt_long");
break;
}
}
}
if (w.woptind == argc) {
// no jobs specified
wait_for_backgrounds(any_flag);
} else {
// jobs specified
std::vector<int> waited_jobs_pid;
for (int i = w.woptind; i < argc; i++) {
int pid = fish_wcstoi(argv[i]);
if (errno || pid < 0) {
streams.err.append_format(_(L"%ls: '%ls' is not a valid job specifier\n"), cmd,
argv[i]);
continue;
}
if (job_get_from_pid(pid)) {
waited_jobs_pid.push_back(pid);
} else {
// If a specified process has already finished but the job hasn't,
// job_get_from_pid(pid) doesn't work properly, so check the pgid here.
while ((j = jobs.next())) {
if (j->pgid == pid) {
waited_jobs_pid.push_back(pid);
break;
}
}
if (!j) {
streams.err.append_format(_(L"%ls: Could not find job '%d'\n"), cmd, pid);
}
}
}
if (waited_jobs_pid.empty()) return STATUS_INVALID_ARGS;
wait_for_backgrounds_specified(waited_jobs_pid, any_flag);
}
return retval;
}

8
src/builtin_wait.h Normal file
View file

@ -0,0 +1,8 @@
// Prototypes for executing builtin_wait function.
#ifndef FISH_BUILTIN_WAIT_H
#define FISH_BUILTIN_WAIT_H
class parser_t;
int builtin_wait(parser_t &parser, io_streams_t &streams, wchar_t **argv);
#endif

View file

@ -513,12 +513,12 @@ void proc_fire_event(const wchar_t *msg, int type, pid_t pid, int status) {
event.arguments.resize(0);
}
int job_reap(bool allow_interactive) {
static int process_clean_after_marking(bool allow_interactive) {
ASSERT_IS_MAIN_THREAD();
job_t *jnext;
int found = 0;
// job_reap may fire an event handler, we do not want to call ourselves recursively (to avoid
// this function may fire an event handler, we do not want to call ourselves recursively (to avoid
// infinite recursion).
static bool locked = false;
if (locked) {
@ -530,10 +530,6 @@ int job_reap(bool allow_interactive) {
// don't try to print in that case (#3222)
const bool interactive = allow_interactive && cur_term != NULL;
process_mark_finished_children(false);
// Preserve the exit status.
const int saved_status = proc_get_last_status();
job_iterator_t jobs;
const size_t job_count = jobs.count();
@ -637,11 +633,25 @@ int job_reap(bool allow_interactive) {
if (found) fflush(stdout);
locked = false;
return found;
}
int job_reap(bool allow_interactive) {
ASSERT_IS_MAIN_THREAD();
int found = 0;
process_mark_finished_children(false);
// Preserve the exit status.
const int saved_status = proc_get_last_status();
found = process_clean_after_marking(allow_interactive);
// Restore the exit status.
proc_set_last_status(saved_status);
locked = false;
return found;
}
@ -1095,3 +1105,12 @@ void proc_pop_interactive() {
interactive_stack.pop_back();
if (is_interactive != old) signal_set_handlers();
}
pid_t proc_wait_any() {
int pid_status;
pid_t pid = waitpid(-1, &pid_status, WUNTRACED);
if (pid == -1) return -1;
handle_child_status(pid, pid_status);
process_clean_after_marking(is_interactive);
return pid;
}

View file

@ -364,6 +364,9 @@ void proc_pop_interactive();
/// proc_set_last_status.
int proc_format_status(int status);
/// Wait for any process finishing.
pid_t proc_wait_any();
#endif
bool terminal_give_to_job(job_t *j, int cont);

108
tests/wait.expect Normal file
View file

@ -0,0 +1,108 @@
# vim: set filetype=expect:
spawn $fish
expect_prompt
# one background job
set error_msg "one background job: Fail"
send_line "sleep 1 &"
expect_prompt
send_line "wait"
expect_prompt "Job 1, 'sleep 1 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "jobs"
expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg }
# three background jobs
set error_msg "three background jobs: Fail"
send_line "sleep 3 &; sleep 1 &; sleep 2 &"
expect_prompt
send_line "wait"
expect "Job 2, 'sleep 1 &' has ended" {} timeout { puts stderr $error_msg }
expect "Job 3, 'sleep 2 &' has ended" {} timeout { puts stderr $error_msg }
expect_prompt "Job 1, 'sleep 3 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "jobs"
expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg }
# one job id specified
set error_msg "one job id specified"
send_line "sleep 3 &; sleep 1 &; sleep 2 &"
expect_prompt
send_line "wait %3"
expect "Job 2, 'sleep 1 &' has ended" {} timeout { puts stderr $error_msg }
expect_prompt "Job 3, 'sleep 2 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "wait %1"
expect_prompt "Job 1, 'sleep 3 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "jobs"
expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg }
# three job ids specified
set error_msg "three job ids specified: Fail"
send_line "sleep 3 &; sleep 1 &; sleep 2 &; sleep 4 &;"
expect_prompt
send_line "wait %1 %3 %4"
expect "Job 2, 'sleep 1 &' has ended" {} timeout { puts stderr $error_msg }
expect "Job 3, 'sleep 2 &' has ended" {} timeout { puts stderr $error_msg }
expect "Job 1, 'sleep 3 &' has ended" {} timeout { puts stderr $error_msg }
expect_prompt "Job 4, 'sleep 4 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "jobs"
expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg }
# wait with -n option
set error_msg "wait with -n option: Fail"
send_line "sleep 3 &; sleep 1 &; sleep 2 &"
expect_prompt
send_line "wait -n"
expect_prompt "Job 2, 'sleep 1 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "wait -n"
expect_prompt "Job 3, 'sleep 2 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "wait -n"
expect_prompt "Job 1, 'sleep 3 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "jobs"
expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg }
# specify job ids with -n option
set error_msg "specify job ids with -n option: Fail"
send_line "sleep 3 &; sleep 1 &; sleep 2 &"
expect_prompt
send_line "wait -n %1 %3"
expect "Job 2, 'sleep 1 &' has ended" {} timeout { puts stderr $error_msg }
expect_prompt "Job 3, 'sleep 2 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "wait -n %1"
expect_prompt "Job 1, 'sleep 3 &' has ended" {} unmatched { puts stderr $error_msg }
send_line "jobs"
expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg }
# don't wait stopped jobs
set error_msg "don't wait stopped jobs: Fail"
send_line "sleep 3 &"
expect_prompt
send_line "kill -STOP %1"
expect_prompt
send_line "wait"
expect_prompt
send_line "wait %1"
expect_prompt
send_line "wait -n"
expect_prompt
send_line "bg %1"
expect_prompt
send_line "wait"
expect_prompt
send_line "jobs"
expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg }
# return immediately when no jobs
set error_msg "don't wait stopped jobs: Fail"
send_line "wait"
expect_prompt
send_line "wait -n"
expect_prompt
send_line "jobs"
expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg }

0
tests/wait.expect.err Normal file
View file

0
tests/wait.expect.out Normal file
View file