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 <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff Hostetler committed Sep 20, 2021 at 15:36 UTC fdb1322651a41a4e754bfac23513c0fa32dc1db8
2 files changed +186
run-command.c
+129
@@ -1901,3 +1901,132 @@ void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir)
1901 }
1902 strvec_pushf(env_array, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir);
1903 }
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
+57
@@ -496,4 +496,61 @@ int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,
496 */
497 void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir);
498
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 +
556 #endif