]> git.ipfire.org Git - thirdparty/git.git/commitdiff
run-command: create start_bg_command
authorJeff Hostetler <jeffhost@microsoft.com>
Mon, 20 Sep 2021 15:36:17 +0000 (15:36 +0000)
committerJunio C Hamano <gitster@pobox.com>
Mon, 20 Sep 2021 15:57:58 +0000 (08:57 -0700)
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>
run-command.c
run-command.h

index 3e4e082e94d25364937d772b4f645cf0e9726281..76bbef9d96d8ace27c7ab3dd109f3a9c9d428e73 100644 (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;
+}
index af1296769f986242cffe3dd9ac806e4dfffca507..17b1b80c3d7667b5454099d7a5e822d207413449 100644 (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