[Tarantool-patches] [PATCH 1/2] popen: always free resources in popen_delete()

Alexander Turenko alexander.turenko at tarantool.org
Sat Apr 18 07:13:54 MSK 2020


The function still set a diagnostics when a signal sending fails and
returns -1, but it is purely informational result: for logging or so. It
reflects notes about dealing with failures in Linux's `man 2 close`:

 | Note, however, that a failure return should be used only for
 | diagnostic purposes <...> or remedial purposes <...>.
 |
 | <...> Linux  kernel always releases the file descriptor early in the
 | close operation, freeing it for reuse; the steps that may return an
 | error <...> occur only later in the close operation.
 |
 | Many other implementations similarly always close the file descriptor
 | <...> even if they subsequently report an error on return from
 | close(). POSIX.1 is currently silent on this point, but there are
 | plans to mandate this behavior in the next major release of the
 | standard.

When kill or killpg returns EPERM a caller usually unable to overcome it
somehow: retrying is not sufficient here. So there are no needs to keep
the handle: a caller refuses the handle and don't want to perform any
other operation on it.  The open engine do its best to kill a child
process or a process group, but when it is not possible, just set the a
diagnostic and free handle resources anyway.

Left comments about observed Mac OS behaviour regarding killing a
process group, where all processes are zombies (or just when a process
group leader is zombie, don't sure): it gives EPERM instead of ESRCH
from killpg(). This result should not surprise a user, so it should be
documented. See [1] for another description of the problem (I don't find
any official information about this).

[1]: https://bugzilla.mozilla.org/show_bug.cgi?id=1329528

Part of #4031
---
 src/lib/core/popen.c | 88 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 73 insertions(+), 15 deletions(-)

diff --git a/src/lib/core/popen.c b/src/lib/core/popen.c
index 3a12a439b..da4b89757 100644
--- a/src/lib/core/popen.c
+++ b/src/lib/core/popen.c
@@ -688,11 +688,27 @@ popen_state_str(unsigned int state)
  * Possible errors:
  *
  * - SystemError: a process does not exists anymore
+ *
+ *                Aside of a non-exist process it is also
+ *                set for a zombie process or when all
+ *                processes in a group are zombies (but
+ *                see note re Mac OS below).
+ *
  * - SystemError: invalid signal number
+ *
  * - SystemError: no permission to send a signal to
  *                a process or a process group
  *
- * Set errno to ESRCH when a process does not exist.
+ *                It is set on Mac OS when a signal is sent
+ *                to a process group, where a group leader
+ *                is zombie (or when all processes in it
+ *                are zombies, don't sure).
+ *
+ *                Whether it may appear due to other
+ *                reasons is unclear.
+ *
+ * Set errno to ESRCH when a process does not exist or is
+ * zombie.
  */
 int
 popen_send_signal(struct popen_handle *handle, int signo)
@@ -732,39 +748,74 @@ popen_send_signal(struct popen_handle *handle, int signo)
 /**
  * Delete a popen handle.
  *
- * The function performs the following actions:
+ * Send SIGKILL and free the handle.
  *
- * - Kill a child process or a process group depending of
- *   POPEN_FLAG_GROUP_SIGNAL (if the process is alive and
- *   POPEN_FLAG_KEEP_CHILD flag is not set).
- * - Close all fds occupied by the handle.
- * - Remove the handle from a living list.
- * - Free all occupied memory.
+ * Details about signaling:
  *
- * @see popen_send_signal() for note about ..._GROUP_SIGNAL.
+ * - The signal is sent only when ...KEEP_CHILD is not set.
+ * - The signal is sent only when a process is alive according
+ *   to the information available on current even loop iteration.
+ *   (There is a gap here: a zombie may be signaled; it is
+ *   harmless.)
+ * - The signal is sent to a process or a grocess group depending
+ *   of ..._GROUP_SIGNAL flag. @see popen_send_signal() for note
+ *   about ..._GROUP_SIGNAL.
  *
- * Return 0 at success and -1 at failure (and set a diag).
+ * Resources are released disregarding of whether a signal
+ * sending succeeds: all fds occupied by the handle are closed,
+ * the handle is removed from a living list, all occupied memory
+ * is freed.
  *
- * Possible errors:
+ * The function may return 0 or -1 (and set a diag) as usual,
+ * but it always frees the handle resources. So any return
+ * value usually means success for a caller. The return
+ * value and diagnostics are purely informational: it is
+ * for logging or same kind of reporting.
+ *
+ * Possible diagnostics (don't consider them as errors):
  *
  * - SystemError: no permission to send a signal to
  *                a process or a process group
+ *
+ *                This error may appear due to Mac OS
+ *                behaviour on zombies when
+ *                ..._GROUP_SIGNAL is set,
+ *                @see popen_send_signal().
+ *
+ *                Whether it may appear due to other
+ *                reasons is unclear.
+ *
+ * Always return 0 when a process is known as dead: no signal
+ * will be send, so no 'failure' may appear.
  */
 int
 popen_delete(struct popen_handle *handle)
 {
+	/*
+	 * Save a result and a failure reason of the
+	 * popen_send_signal() call.
+	 */
+	int rc = 0;
+	struct diag *diag = diag_get();
+	struct error *e = NULL;
+
 	size_t i;
 
 	assert(handle != NULL);
 
 	if ((handle->flags & POPEN_FLAG_KEEP_CHILD) == 0) {
 		/*
-		 * Unable to kill the process -- give an error.
+		 * Unable to kill the process -- save the error
+		 * and pass over.
 		 * The process is not exist already -- pass over.
 		 */
 		if (popen_send_signal(handle, SIGKILL) != 0 &&
-		    errno != ESRCH)
-			return -1;
+		    errno != ESRCH) {
+			rc = -1;
+			e = diag_last_error(diag);
+			assert(e != NULL);
+			error_ref(e);
+		}
 	}
 
 	for (i = 0; i < lengthof(handle->ios); i++) {
@@ -787,7 +838,14 @@ popen_delete(struct popen_handle *handle)
 
 	rlist_del(&handle->list);
 	handle_free(handle);
-	return 0;
+
+	/* Restore an error from popen_send_signal() if any. */
+	if (rc != 0) {
+		diag_set_error(diag, e);
+		error_unref(e);
+	}
+
+	return rc;
 }
 
 /**
-- 
2.25.0



More information about the Tarantool-patches mailing list