This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch wl/tests-wlcs
in repository enlightenment.

View the commit online.

commit 768e828ac258d4811944a035e1efa0a29bba5188
Author: Cedric BAIL <[email protected]>
AuthorDate: Sat Aug 8 20:58:11 2026 -0600

    tests - stop leaking a runtime dir per compositor
    
    A full wlcs run left 1167 directories under /tmp, about 1.2GB, and filled
    the filesystem partway through - which is what ended the run rather than
    any test result.
    
    Two causes, both in teardown:
    
      - E's helpers (efreetd and friends) detach into their own session, so
        they survive both kill(pid) and kill(-pgid). They do exit on their own,
        but more slowly than the compositor.
    
      - the removal only retried when rm *failed*. A straggler recreating the
        cache tree a moment after a successful rm left the directory behind
        while the loop happily reported success.
    
    So: put the compositor in its own process group and signal the group, then
    wait for anything still living in the runtime dir to actually be gone
    before deleting, and confirm the directory is gone rather than trusting
    rm's exit status. Stragglers are found by matching HOME against the
    runtime dir, which is exact - the path is a fresh mkdtemp per server, so
    nothing else can match it.
    
    Verified by counting /tmp/e-wlcs-* and /tmp/e-wl-test.* across runs: was
    7 dirs and 2 stray processes for a two-suite wlcs run, now 0 and 0.
    
    Over 1163 tests a teardown that races is a teardown that fills the disk,
    so this is the difference between a suite that can be run and one that
    cannot.
    
    Note run-nested.sh is introduced on wl/browser-base; this hunk should be
    squashed back there before that branch is submitted.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
 src/tests/wayland/run-nested.sh |  36 ++++++++++++-
 src/tests/wlcs/e_wlcs.c         | 112 +++++++++++++++++++++++++++++++++++-----
 2 files changed, 134 insertions(+), 14 deletions(-)

diff --git a/src/tests/wayland/run-nested.sh b/src/tests/wayland/run-nested.sh
index 285aa3bd6..6c16b4605 100755
--- a/src/tests/wayland/run-nested.sh
+++ b/src/tests/wayland/run-nested.sh
@@ -68,6 +68,27 @@ chmod 0700 "$RUNDIR"
 E_PID=""
 E_LOG="$RUNDIR/enlightenment.log"
 
+# Every process still living in our private runtime dir, found by its HOME.
+#
+# Killing the compositor is not enough: E's helpers (efreetd and friends)
+# detach into their own session, so they survive both the pid and the process
+# group. They do exit on their own, but slowly, and on the way out they
+# recreate the cache tree we are trying to delete. Matching on HOME is exact -
+# the path is a fresh mktemp -d, so nothing else can match it.
+#
+# Prints the count; kills with $1 first if given.
+procs_in_rundir() {
+    n=0
+    for env in /proc/[0-9]*/environ; do
+        grep -qxZ "HOME=$RUNDIR" "$env" 2>/dev/null || continue
+        pid=${env#/proc/}
+        pid=${pid%/environ}
+        [ -n "${1:-}" ] && kill "-$1" "$pid" 2>/dev/null
+        n=$((n + 1))
+    done
+    echo "$n"
+}
+
 cleanup() {
     status=$?
     if [ -n "$E_PID" ] && kill -0 "$E_PID" 2>/dev/null; then
@@ -81,6 +102,16 @@ cleanup() {
         kill -KILL "$E_PID" 2>/dev/null || :
         wait "$E_PID" 2>/dev/null || :
     fi
+
+    # Wait for the stragglers before deleting anything, or they simply put it
+    # back.
+    procs_in_rundir TERM >/dev/null
+    i=0
+    while [ $i -lt 50 ] && [ "$(procs_in_rundir)" != "0" ]; do
+        i=$((i + 1))
+        sleep 0.1
+    done
+    [ "$(procs_in_rundir KILL)" = "0" ] || sleep 0.5
     if [ "${E_TEST_KEEP:-0}" = "1" ]; then
         echo "run-nested.sh: kept $RUNDIR (compositor log: $E_LOG)" >&2
     else
@@ -90,8 +121,9 @@ cleanup() {
         # Retry, and never let the tidy-up decide the exit status: that turns
         # a passing test into a failing one for no reason.
         i=0
-        while [ $i -lt 10 ]; do
-            rm -rf "$RUNDIR" 2>/dev/null && break
+        while [ $i -lt 20 ]; do
+            rm -rf "$RUNDIR" 2>/dev/null
+            [ -d "$RUNDIR" ] || break
             i=$((i + 1))
             sleep 0.1
         done
diff --git a/src/tests/wlcs/e_wlcs.c b/src/tests/wlcs/e_wlcs.c
index c81f30761..09ac2f2bd 100644
--- a/src/tests/wlcs/e_wlcs.c
+++ b/src/tests/wlcs/e_wlcs.c
@@ -30,6 +30,7 @@
  * we send it over - and let the compositor resolve it. See
  * src/protocol/wl-test.xml.
  */
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
@@ -237,6 +238,13 @@ _server_start(WlcsDisplayServer *server)
      {
         char path[PATH_MAX];
 
+        /* Its own process group, so stop() can take down E *and* the
+         * helpers it forks (efreetd and friends) in one go. Killing only the
+         * compositor leaves those running with HOME still pointing into our
+         * temp dir, where they promptly recreate the cache files we are
+         * trying to delete. */
+        setpgid(0, 0);
+
         setenv("XDG_RUNTIME_DIR", s->runtime_dir, 1);
         setenv("HOME", s->runtime_dir, 1);
         setenv("E_WL_FORCE", "buffer", 1);
@@ -302,6 +310,63 @@ _server_start(WlcsDisplayServer *server)
                      "-Dtests=true?\n");
 }
 
+/* Count - and optionally signal - every process still living in our private
+ * runtime dir, identified by its HOME.
+ *
+ * The process group is not enough on its own: E's helpers (efreetd and
+ * friends) detach into their own session, so kill(-pgid) misses them. They do
+ * exit by themselves, but slowly, and on the way out they recreate the cache
+ * tree we are trying to delete - which is how a full suite run left 1167
+ * directories behind and filled /tmp. Matching on HOME is exact: the path is
+ * a fresh mkdtemp per server, so nothing else can match it.
+ */
+static int
+_procs_in_runtime_dir(E_Server *s, int sig)
+{
+   DIR *proc;
+   struct dirent *ent;
+   char want[PATH_MAX + 8];
+   int count = 0;
+
+   if (!s->runtime_dir[0]) return 0;
+   snprintf(want, sizeof(want), "HOME=%s", s->runtime_dir);
+
+   proc = opendir("/proc");
+   if (!proc) return 0;
+
+   while ((ent = readdir(proc)))
+     {
+        char path[PATH_MAX];
+        char buf[8192];
+        ssize_t n, i;
+        int fd, hit = 0;
+        long pid;
+
+        pid = strtol(ent->d_name, NULL, 10);
+        if (pid <= 0) continue;
+
+        snprintf(path, sizeof(path), "/proc/%ld/environ", pid);
+        fd = open(path, O_RDONLY);
+        if (fd < 0) continue;
+        n = read(fd, buf, sizeof(buf) - 1);
+        close(fd);
+        if (n <= 0) continue;
+        buf[n] = 0;
+
+        for (i = 0; i < n; i += (ssize_t)strlen(buf + i) + 1)
+          {
+             if (!strcmp(buf + i, want)) { hit = 1; break; }
+          }
+        if (!hit) continue;
+
+        count++;
+        if (sig) kill((pid_t)pid, sig);
+     }
+
+   closedir(proc);
+   return count;
+}
+
 static void
 _server_stop(WlcsDisplayServer *server)
 {
@@ -321,31 +386,54 @@ _server_stop(WlcsDisplayServer *server)
     * test's compositor cannot leak into the next. */
    if (s->pid)
      {
-        kill(s->pid, SIGTERM);
+        /* Negative pid: the whole process group, not just the compositor. */
+        kill(-s->pid, SIGTERM);
         while (waited < 5000)
           {
              if (waitpid(s->pid, NULL, WNOHANG) == s->pid) break;
              _msleep(50);
              waited += 50;
           }
-        if (waited >= 5000)
-          {
-             kill(s->pid, SIGKILL);
-             waitpid(s->pid, NULL, 0);
-          }
+        if (waited >= 5000) waitpid(s->pid, NULL, WNOHANG);
+
+        /* And again, unconditionally, for anything in the group that
+         * outlived the compositor. */
+        kill(-s->pid, SIGKILL);
         s->pid = 0;
      }
 
-   if (s->runtime_dir[0])
+   if (s->runtime_dir[0] && !getenv("E_WLCS_KEEP"))
      {
-        /* E forks helpers that keep writing into HOME as they wind down, so
-         * a single pass can lose the race. */
-        snprintf(buf, sizeof(buf), "rm -rf '%s' 2>/dev/null", s->runtime_dir);
-        for (waited = 0; waited < 10; waited++)
+        struct stat st;
+        int i;
+
+        /* Do not start deleting until nothing is left that could write here
+         * again. Over a 1163-test run, a teardown that races is a teardown
+         * that fills the disk. */
+        _procs_in_runtime_dir(s, SIGTERM);
+        for (i = 0; i < 50; i++)
           {
-             if (system(buf) == 0) break;
+             if (!_procs_in_runtime_dir(s, 0)) break;
              _msleep(100);
           }
+        if (_procs_in_runtime_dir(s, SIGKILL))
+          {
+             for (i = 0; i < 20; i++)
+               {
+                  if (!_procs_in_runtime_dir(s, 0)) break;
+                  _msleep(100);
+               }
+          }
+
+        snprintf(buf, sizeof(buf), "rm -rf '%s' 2>/dev/null", s->runtime_dir);
+        for (i = 0; i < 20; i++)
+          {
+             system(buf);
+             if (stat(s->runtime_dir, &st) != 0) break;
+             _msleep(100);
+          }
+        if (stat(s->runtime_dir, &st) == 0)
+          fprintf(stderr, "e_wlcs: could not remove %s\n", s->runtime_dir);
         s->runtime_dir[0] = 0;
      }
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to