Skip to content

Commit fdb1322

Browse files
jeffhostetlergitster
authored andcommitted
run-command: create start_bg_command
Create a variation of `run_command()` and `start_command()` to launch a command into the background and optionally wait for it to become "ready" before returning. Signed-off-by: Jeff Hostetler <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8750249 commit fdb1322

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

run-command.c

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,3 +1901,132 @@ void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir)
19011901
}
19021902
strvec_pushf(env_array, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir);
19031903
}
1904+
1905+
enum start_bg_result start_bg_command(struct child_process *cmd,
1906+
start_bg_wait_cb *wait_cb,
1907+
void *cb_data,
1908+
unsigned int timeout_sec)
1909+
{
1910+
enum start_bg_result sbgr = SBGR_ERROR;
1911+
int ret;
1912+
int wait_status;
1913+
pid_t pid_seen;
1914+
time_t time_limit;
1915+
1916+
/*
1917+
* We do not allow clean-on-exit because the child process
1918+
* should persist in the background and possibly/probably
1919+
* after this process exits. So we don't want to kill the
1920+
* child during our atexit routine.
1921+
*/
1922+
if (cmd->clean_on_exit)
1923+
BUG("start_bg_command() does not allow non-zero clean_on_exit");
1924+
1925+
if (!cmd->trace2_child_class)
1926+
cmd->trace2_child_class = "background";
1927+
1928+
ret = start_command(cmd);
1929+
if (ret) {
1930+
/*
1931+
* We assume that if `start_command()` fails, we
1932+
* either get a complete `trace2_child_start() /
1933+
* trace2_child_exit()` pair or it fails before the
1934+
* `trace2_child_start()` is emitted, so we do not
1935+
* need to worry about it here.
1936+
*
1937+
* We also assume that `start_command()` does not add
1938+
* us to the cleanup list. And that it calls
1939+
* calls `child_process_clear()`.
1940+
*/
1941+
sbgr = SBGR_ERROR;
1942+
goto done;
1943+
}
1944+
1945+
time(&time_limit);
1946+
time_limit += timeout_sec;
1947+
1948+
wait:
1949+
pid_seen = waitpid(cmd->pid, &wait_status, WNOHANG);
1950+
1951+
if (!pid_seen) {
1952+
/*
1953+
* The child is currently running. Ask the callback
1954+
* if the child is ready to do work or whether we
1955+
* should keep waiting for it to boot up.
1956+
*/
1957+
ret = (*wait_cb)(cmd, cb_data);
1958+
if (!ret) {
1959+
/*
1960+
* The child is running and "ready".
1961+
*/
1962+
trace2_child_ready(cmd, "ready");
1963+
sbgr = SBGR_READY;
1964+
goto done;
1965+
} else if (ret > 0) {
1966+
/*
1967+
* The callback said to give it more time to boot up
1968+
* (subject to our timeout limit).
1969+
*/
1970+
time_t now;
1971+
1972+
time(&now);
1973+
if (now < time_limit)
1974+
goto wait;
1975+
1976+
/*
1977+
* Our timeout has expired. We don't try to
1978+
* kill the child, but rather let it continue
1979+
* (hopefully) trying to startup.
1980+
*/
1981+
trace2_child_ready(cmd, "timeout");
1982+
sbgr = SBGR_TIMEOUT;
1983+
goto done;
1984+
} else {
1985+
/*
1986+
* The cb gave up on this child. It is still running,
1987+
* but our cb got an error trying to probe it.
1988+
*/
1989+
trace2_child_ready(cmd, "error");
1990+
sbgr = SBGR_CB_ERROR;
1991+
goto done;
1992+
}
1993+
}
1994+
1995+
else if (pid_seen == cmd->pid) {
1996+
int child_code = -1;
1997+
1998+
/*
1999+
* The child started, but exited or was terminated
2000+
* before becoming "ready".
2001+
*
2002+
* We try to match the behavior of `wait_or_whine()`
2003+
* WRT the handling of WIFSIGNALED() and WIFEXITED()
2004+
* and convert the child's status to a return code for
2005+
* tracing purposes and emit the `trace2_child_exit()`
2006+
* event.
2007+
*
2008+
* We do not want the wait_or_whine() error message
2009+
* because we will be called by client-side library
2010+
* routines.
2011+
*/
2012+
if (WIFEXITED(wait_status))
2013+
child_code = WEXITSTATUS(wait_status);
2014+
else if (WIFSIGNALED(wait_status))
2015+
child_code = WTERMSIG(wait_status) + 128;
2016+
trace2_child_exit(cmd, child_code);
2017+
2018+
sbgr = SBGR_DIED;
2019+
goto done;
2020+
}
2021+
2022+
else if (pid_seen < 0 && errno == EINTR)
2023+
goto wait;
2024+
2025+
trace2_child_exit(cmd, -1);
2026+
sbgr = SBGR_ERROR;
2027+
2028+
done:
2029+
child_process_clear(cmd);
2030+
invalidate_lstat_cache();
2031+
return sbgr;
2032+
}

run-command.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,61 @@ int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,
496496
*/
497497
void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir);
498498

499+
/**
500+
* Possible return values for start_bg_command().
501+
*/
502+
enum start_bg_result {
503+
/* child process is "ready" */
504+
SBGR_READY = 0,
505+
506+
/* child process could not be started */
507+
SBGR_ERROR,
508+
509+
/* callback error when testing for "ready" */
510+
SBGR_CB_ERROR,
511+
512+
/* timeout expired waiting for child to become "ready" */
513+
SBGR_TIMEOUT,
514+
515+
/* child process exited or was signalled before becomming "ready" */
516+
SBGR_DIED,
517+
};
518+
519+
/**
520+
* Callback used by start_bg_command() to ask whether the
521+
* child process is ready or needs more time to become "ready".
522+
*
523+
* The callback will receive the cmd and cb_data arguments given to
524+
* start_bg_command().
525+
*
526+
* Returns 1 is child needs more time (subject to the requested timeout).
527+
* Returns 0 if child is "ready".
528+
* Returns -1 on any error and cause start_bg_command() to also error out.
529+
*/
530+
typedef int(start_bg_wait_cb)(const struct child_process *cmd, void *cb_data);
531+
532+
/**
533+
* Start a command in the background. Wait long enough for the child
534+
* to become "ready" (as defined by the provided callback). Capture
535+
* immediate errors (like failure to start) and any immediate exit
536+
* status (such as a shutdown/signal before the child became "ready")
537+
* and return this like start_command().
538+
*
539+
* We run a custom wait loop using the provided callback to wait for
540+
* the child to start and become "ready". This is limited by the given
541+
* timeout value.
542+
*
543+
* If the child does successfully start and become "ready", we orphan
544+
* it into the background.
545+
*
546+
* The caller must not call finish_command().
547+
*
548+
* The opaque cb_data argument will be forwarded to the callback for
549+
* any instance data that it might require. This may be NULL.
550+
*/
551+
enum start_bg_result start_bg_command(struct child_process *cmd,
552+
start_bg_wait_cb *wait_cb,
553+
void *cb_data,
554+
unsigned int timeout_sec);
555+
499556
#endif

0 commit comments

Comments
 (0)