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>
This commit is contained in:
Jeff Hostetler 2021-09-20 15:36:17 +00:00 committed by Junio C Hamano
parent 8750249053
commit fdb1322651
2 changed files with 186 additions and 0 deletions

View File

@ -1901,3 +1901,132 @@ void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir)
}
strvec_pushf(env_array, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir);
}
enum start_bg_result start_bg_command(struct child_process *cmd,
start_bg_wait_cb *wait_cb,
void *cb_data,
unsigned int timeout_sec)
{
enum start_bg_result sbgr = SBGR_ERROR;
int ret;
int wait_status;
pid_t pid_seen;
time_t time_limit;
/*
* We do not allow clean-on-exit because the child process
* should persist in the background and possibly/probably
* after this process exits. So we don't want to kill the
* child during our atexit routine.
*/
if (cmd->clean_on_exit)
BUG("start_bg_command() does not allow non-zero clean_on_exit");
if (!cmd->trace2_child_class)
cmd->trace2_child_class = "background";
ret = start_command(cmd);
if (ret) {
/*
* We assume that if `start_command()` fails, we
* either get a complete `trace2_child_start() /
* trace2_child_exit()` pair or it fails before the
* `trace2_child_start()` is emitted, so we do not
* need to worry about it here.
*
* We also assume that `start_command()` does not add
* us to the cleanup list. And that it calls
* calls `child_process_clear()`.
*/
sbgr = SBGR_ERROR;
goto done;
}
time(&time_limit);
time_limit += timeout_sec;
wait:
pid_seen = waitpid(cmd->pid, &wait_status, WNOHANG);
if (!pid_seen) {
/*
* The child is currently running. Ask the callback
* if the child is ready to do work or whether we
* should keep waiting for it to boot up.
*/
ret = (*wait_cb)(cmd, cb_data);
if (!ret) {
/*
* The child is running and "ready".
*/
trace2_child_ready(cmd, "ready");
sbgr = SBGR_READY;
goto done;
} else if (ret > 0) {
/*
* The callback said to give it more time to boot up
* (subject to our timeout limit).
*/
time_t now;
time(&now);
if (now < time_limit)
goto wait;
/*
* Our timeout has expired. We don't try to
* kill the child, but rather let it continue
* (hopefully) trying to startup.
*/
trace2_child_ready(cmd, "timeout");
sbgr = SBGR_TIMEOUT;
goto done;
} else {
/*
* The cb gave up on this child. It is still running,
* but our cb got an error trying to probe it.
*/
trace2_child_ready(cmd, "error");
sbgr = SBGR_CB_ERROR;
goto done;
}
}
else if (pid_seen == cmd->pid) {
int child_code = -1;
/*
* The child started, but exited or was terminated
* before becoming "ready".
*
* We try to match the behavior of `wait_or_whine()`
* WRT the handling of WIFSIGNALED() and WIFEXITED()
* and convert the child's status to a return code for
* tracing purposes and emit the `trace2_child_exit()`
* event.
*
* We do not want the wait_or_whine() error message
* because we will be called by client-side library
* routines.
*/
if (WIFEXITED(wait_status))
child_code = WEXITSTATUS(wait_status);
else if (WIFSIGNALED(wait_status))
child_code = WTERMSIG(wait_status) + 128;
trace2_child_exit(cmd, child_code);
sbgr = SBGR_DIED;
goto done;
}
else if (pid_seen < 0 && errno == EINTR)
goto wait;
trace2_child_exit(cmd, -1);
sbgr = SBGR_ERROR;
done:
child_process_clear(cmd);
invalidate_lstat_cache();
return sbgr;
}

View File

@ -496,4 +496,61 @@ int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,
*/
void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir);
/**
* Possible return values for start_bg_command().
*/
enum start_bg_result {
/* child process is "ready" */
SBGR_READY = 0,
/* child process could not be started */
SBGR_ERROR,
/* callback error when testing for "ready" */
SBGR_CB_ERROR,
/* timeout expired waiting for child to become "ready" */
SBGR_TIMEOUT,
/* child process exited or was signalled before becomming "ready" */
SBGR_DIED,
};
/**
* Callback used by start_bg_command() to ask whether the
* child process is ready or needs more time to become "ready".
*
* The callback will receive the cmd and cb_data arguments given to
* start_bg_command().
*
* Returns 1 is child needs more time (subject to the requested timeout).
* Returns 0 if child is "ready".
* Returns -1 on any error and cause start_bg_command() to also error out.
*/
typedef int(start_bg_wait_cb)(const struct child_process *cmd, void *cb_data);
/**
* Start a command in the background. Wait long enough for the child
* to become "ready" (as defined by the provided callback). Capture
* immediate errors (like failure to start) and any immediate exit
* status (such as a shutdown/signal before the child became "ready")
* and return this like start_command().
*
* We run a custom wait loop using the provided callback to wait for
* the child to start and become "ready". This is limited by the given
* timeout value.
*
* If the child does successfully start and become "ready", we orphan
* it into the background.
*
* The caller must not call finish_command().
*
* The opaque cb_data argument will be forwarded to the callback for
* any instance data that it might require. This may be NULL.
*/
enum start_bg_result start_bg_command(struct child_process *cmd,
start_bg_wait_cb *wait_cb,
void *cb_data,
unsigned int timeout_sec);
#endif