[igt-dev] [RFC i-g-t v2 1/4] lib/igt_core: add fork for dynamic tests

Kamil Konieczny kamil.konieczny at linux.intel.com
Fri Oct 7 18:48:59 UTC 2022


Add new function igt_fork_dyn which can be used from dynamic
subtest in a way which allow children processes to use
igt_fork(). This is now only PoC with many code repetition.

Cc: Anna Karas <anna.karas at intel.com>
Cc: Zbigniew Kempczyński <zbigniew.kempczynski at intel.com>
Cc: Mauro Carvalho Chehab <mauro.chehab at linux.intel.com>
Cc: Petri Latvala <petri.latvala at intel.com>
Signed-off-by: Kamil Konieczny <kamil.konieczny at linux.intel.com>
---
 lib/igt_core.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++
 lib/igt_core.h |  22 ++++++++
 2 files changed, 163 insertions(+)

diff --git a/lib/igt_core.c b/lib/igt_core.c
index 2aee0d08..001466ef 100644
--- a/lib/igt_core.c
+++ b/lib/igt_core.c
@@ -309,6 +309,12 @@ int num_test_children;
 int test_children_sz;
 bool test_child;
 
+/* fork dynamic support state */
+pid_t *test_dyn_children;
+int num_test_dyn_children;
+int test_dyn_children_sz;
+bool test_dyn_child;
+
 /* For allocator purposes */
 pid_t child_pid  = -1;
 __thread pid_t child_tid  = -1;
@@ -2354,6 +2360,61 @@ bool __igt_fork(void)
 
 }
 
+static void dyn_children_exit_handler(int sig)
+{
+	int status;
+
+	/* The exit handler can be called from a fatal signal, so play safe */
+	while (num_test_dyn_children-- && wait(&status))
+		;
+}
+
+bool __igt_fork_dyn(void)
+{
+	internal_assert(!test_with_subtests || in_subtest,
+			"forking is only allowed in subtests or igt_simple_main\n");
+	internal_assert(!test_child,
+			"forking is not allowed from already forked children\n");
+	internal_assert(!test_dyn_child,
+			"dynamic forking is not allowed from running dynamic\n");
+
+	igt_install_exit_handler(dyn_children_exit_handler);
+
+	if (num_test_dyn_children >= test_dyn_children_sz) {
+		if (!test_dyn_children_sz)
+			test_dyn_children_sz = 4;
+		else
+			test_dyn_children_sz *= 2;
+
+		test_dyn_children = realloc(test_dyn_children,
+					sizeof(pid_t)*test_dyn_children_sz);
+		igt_assert(test_dyn_children);
+	}
+
+	/* ensure any buffers are flushed before fork */
+	fflush(NULL);
+
+	switch (test_dyn_children[num_test_dyn_children++] = fork()) {
+	case -1:
+		num_test_dyn_children--; /* so we won't kill(-1) during cleanup */
+		igt_assert(0);
+	case 0:
+		test_dyn_child = true;
+		pthread_mutex_init(&print_mutex, NULL);
+		//FIXME child_pid = getpid(); /* for allocator */
+		//FIXME child_tid = -1; /* for allocator */
+		exit_handler_count = 0;
+		reset_helper_process_list();
+		oom_adjust_for_doom();
+		igt_unshare_spins();
+
+		return true;
+	default:
+		return false;
+	}
+
+}
+
 int __igt_waitchildren(void)
 {
 	int err = 0;
@@ -2428,6 +2489,86 @@ void igt_waitchildren(void)
 		igt_fail(err);
 }
 
+static int __igt_waitchildren_dyn(void)
+{
+	int err = 0;
+	int count;
+
+	assert(!test_dyn_child);
+
+	if (num_test_dyn_children > 0 && _igt_dynamic_tests_executed < 0)
+		_igt_dynamic_tests_executed = 0;
+	count = 0;
+	while (count < num_test_dyn_children) {
+		int status = -1;
+		int last = 0;
+		pid_t pid;
+		int c;
+
+		pid = wait(&status);
+		if (pid == -1) {
+			if (errno == EINTR)
+				continue;
+
+			printf("wait(num_dyn_children:%d) failed with %m\n",
+			       num_test_dyn_children - count);
+			return IGT_EXIT_FAILURE;
+		}
+
+		for (c = 0; c < num_test_dyn_children; c++)
+			if (pid == test_dyn_children[c])
+				break;
+		if (c == num_test_dyn_children)
+			continue;
+
+		_igt_dynamic_tests_executed++;
+		if (status != 0) {
+			if (WIFEXITED(status)) {
+				printf("dynamic child %i failed with exit status %i\n",
+				       c, WEXITSTATUS(status));
+				last = WEXITSTATUS(status);
+			} else if (WIFSIGNALED(status)) {
+				printf("dynamic child %i died with signal %i, %s\n",
+				       c, WTERMSIG(status),
+				       strsignal(WTERMSIG(status)));
+				last = 128 + WTERMSIG(status);
+			} else {
+				printf("Unhandled failure [%d] in dynamic child %i\n", status, c);
+				last = 256;
+			}
+
+			/* we don't want to overwrite error with skip */
+			if (err == 0 || err == IGT_EXIT_SKIP)
+				err = last;
+			//FIXME igt_kill_dyn_children(SIGKILL); // if non-skip happen
+		}
+
+		count++;
+	}
+
+	num_test_dyn_children = 0;
+
+	return err;
+}
+
+/**
+ * igt_waitchildren_dyn:
+ *
+ * Wait for all children forked with igt_fork_dyn.
+ */
+void igt_waitchildren_dyn(void)
+{
+	int err = __igt_waitchildren_dyn();
+
+	igt_debug("%s: err=%d\n", __func__, err);
+	if (err) {
+		if (err == IGT_EXIT_SKIP)
+			igt_skip("__dynamic__"); //FIXME
+		else
+			igt_fail(err);
+	}
+}
+
 static void igt_alarm_killchildren(int signal)
 {
 	igt_info("Timed out waiting for children\n");
diff --git a/lib/igt_core.h b/lib/igt_core.h
index f21723de..d124364b 100644
--- a/lib/igt_core.h
+++ b/lib/igt_core.h
@@ -1108,6 +1108,28 @@ void igt_waitchildren(void);
 void igt_waitchildren_timeout(int seconds, const char *reason);
 void igt_kill_children(int signal);
 
+bool __igt_fork_dyn(void);
+/**
+ * igt_fork_dyn:
+ * @child: name of the int variable with the child number
+ * @num_children: number of children to fork
+ *
+ * This is a magic control flow block which spawns parallel test with fork()
+ * expecting there will be dynamic subtests run.
+ *
+ * The test children execute in parallel to the main test thread. Joining all
+ * test threads should be done with igt_waitchildren_dyn to ensure that the exit
+ * codes of all children are properly reflected in the test status.
+ *
+ * igt_skip() will be collected from childs.
+ *
+ */
+#define igt_fork_dyn(child, num_children) \
+	for (int child = 0; child < (num_children); child++) \
+		for (; __igt_fork_dyn(); exit(0))
+
+void igt_waitchildren_dyn(void);
+
 /**
  * igt_helper_process:
  * @running: indicates whether the process is currently running
-- 
2.34.1



More information about the igt-dev mailing list