mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-12 13:08:49 +00:00
add wait command
This commit is contained in:
parent
ea5f3925ea
commit
c7a682ed05
9 changed files with 326 additions and 9 deletions
|
@ -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 \
|
||||
|
|
|
@ -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
177
src/builtin_wait.cpp
Normal 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
8
src/builtin_wait.h
Normal 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
|
35
src/proc.cpp
35
src/proc.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
108
tests/wait.expect
Normal 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
0
tests/wait.expect.err
Normal file
0
tests/wait.expect.out
Normal file
0
tests/wait.expect.out
Normal file
Loading…
Reference in a new issue