From 0c46615f044f4257d55d69da7894affd38e45f8e Mon Sep 17 00:00:00 2001 From: FiveTech Software Date: Thu, 1 Oct 2026 09:23:43 +0200 Subject: [PATCH 1/8] Release notes 1.09.71: macOS package was built, mark as untested Added macOS package note indicating it is untested due to CI issues. --- release-notes-1.09.71.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/release-notes-1.09.71.md b/release-notes-1.09.71.md index 2dfca873..80133502 100644 --- a/release-notes-1.09.71.md +++ b/release-notes-1.09.71.md @@ -28,6 +28,7 @@ Reported by Tim (via Antonio) while converting an application's data to `.adt`. * `openads-1.09.71-windows-x64.zip` * `openads-1.09.71-windows-x86.zip` * `openads-1.09.71-linux-x64.tar.gz` -* macOS: NOT included in this release / sin binario macOS esta vez - (macOS CI stalls in the unit tests; under investigation). +* `openads-1.09.71-macos-universal.tar.gz` - built, but its unit tests + stall in CI and did not complete, so this package is UNTESTED / + paquete macOS sin pruebas completas (under investigation). `release.yml` treats the macOS leg as optional until that is fixed. From 2ae3487f40f75cb1182ee895662bccafeead600f Mon Sep 17 00:00:00 2001 From: FiveTech Software Date: Thu, 1 Oct 2026 12:31:31 +0200 Subject: [PATCH 2/8] ci: manual macOS diagnostic workflow (stall dump) This workflow is designed to run unit tests on macOS, providing detailed output and handling test stalls by dumping thread stacks. --- .github/workflows/macos-diag.yml | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/macos-diag.yml diff --git a/.github/workflows/macos-diag.yml b/.github/workflows/macos-diag.yml new file mode 100644 index 00000000..b736f874 --- /dev/null +++ b/.github/workflows/macos-diag.yml @@ -0,0 +1,49 @@ +name: macos-diag + +# Manual diagnostic only: builds the unit tests on macOS, runs them with +# per-assertion output and, if they stall, dumps thread stacks. +on: + workflow_dispatch: + +jobs: + diag: + runs-on: macos-14 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + with: + fetch-tags: true + fetch-depth: 0 + - uses: seanmiddleditch/gha-setup-ninja@v6 + - name: Configure + run: cmake --preset default + - name: Build + run: cmake --build build/default --config Release --target openads_unit_tests + - name: Run unit tests with stall dump + shell: bash + run: | + BIN=$(find build -name openads_unit_tests -type f -perm -u+x | head -1) + echo "binary: $BIN" + cd "$(dirname "$BIN")" + script -q "$GITHUB_WORKSPACE/run.log" ./openads_unit_tests -tce='*[slow]*,*flaky*' -s --no-colors > /dev/null 2>&1 & + SP=$! + for i in $(seq 1 56); do + sleep 10 + if ! kill -0 $SP 2>/dev/null; then echo "finished early at ~$((i*10))s"; break; fi + done + if kill -0 $SP 2>/dev/null; then + echo "=== STILL RUNNING after ~560s: stall dump" + PID=$(pgrep -f 'openads_unit_tests' | grep -v "^$SP$" | tail -1) + echo "test pid: $PID" + ps -o pid,stat,%cpu,etime,command -p "$PID" || true + echo "=== last 60 lines of output" + tail -n 60 "$GITHUB_WORKSPACE/run.log" || true + echo "=== lldb backtraces" + lldb -p "$PID" -b -o 'thread backtrace all' 2>&1 | head -150 || true + echo "=== sample" + sample "$PID" 5 2>&1 | head -120 || true + kill -9 "$PID" || true + else + echo "=== finished; tail of output" + tail -n 30 "$GITHUB_WORKSPACE/run.log" || true + fi From 9891c772bf301a890caee7d2d99e68ae03865992 Mon Sep 17 00:00:00 2001 From: FiveTech Software Date: Thu, 1 Oct 2026 12:58:36 +0200 Subject: [PATCH 3/8] ci: macos-diag - readable stall dump --- .github/workflows/macos-diag.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/macos-diag.yml b/.github/workflows/macos-diag.yml index b736f874..8004d1cc 100644 --- a/.github/workflows/macos-diag.yml +++ b/.github/workflows/macos-diag.yml @@ -33,17 +33,20 @@ jobs: done if kill -0 $SP 2>/dev/null; then echo "=== STILL RUNNING after ~560s: stall dump" - PID=$(pgrep -f 'openads_unit_tests' | grep -v "^$SP$" | tail -1) + PID=$(pgrep -f './openads_unit_tests' | grep -v "^$SP$" | tail -1) echo "test pid: $PID" ps -o pid,stat,%cpu,etime,command -p "$PID" || true - echo "=== last 60 lines of output" - tail -n 60 "$GITHUB_WORKSPACE/run.log" || true + echo "=== last 40 lines of output" + tail -n 40 "$GITHUB_WORKSPACE/run.log" | tr -d '\r' | cat -v | cut -c1-200 || true echo "=== lldb backtraces" - lldb -p "$PID" -b -o 'thread backtrace all' 2>&1 | head -150 || true + sudo lldb -p "$PID" -b -o 'thread backtrace all' > "$GITHUB_WORKSPACE/lldb.txt" 2>&1 || true + tr -d '\r' < "$GITHUB_WORKSPACE/lldb.txt" | cat -v | cut -c1-220 | head -120 echo "=== sample" - sample "$PID" 5 2>&1 | head -120 || true + sudo sample "$PID" 5 -file "$GITHUB_WORKSPACE/sample.txt" > /dev/null 2>&1 || true + tr -d '\r' < "$GITHUB_WORKSPACE/sample.txt" | cat -v | cut -c1-200 | head -100 + echo "=== end of dump" kill -9 "$PID" || true else echo "=== finished; tail of output" - tail -n 30 "$GITHUB_WORKSPACE/run.log" || true + tail -n 30 "$GITHUB_WORKSPACE/run.log" | tr -d '\r' | cat -v | cut -c1-200 || true fi From b2797fc6417f48393a531ce3471a7052482fd666 Mon Sep 17 00:00:00 2001 From: FiveTech Software Date: Thu, 1 Oct 2026 13:26:52 +0200 Subject: [PATCH 4/8] platform: bounded byte-lock wait on POSIX without OFD locks (macOS CI hang) Implement a timeout mechanism for file locking on macOS to prevent indefinite blocking. --- src/platform/lock_posix.cpp | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/platform/lock_posix.cpp b/src/platform/lock_posix.cpp index e7d42bbe..689573df 100644 --- a/src/platform/lock_posix.cpp +++ b/src/platform/lock_posix.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include #include namespace openads::platform { @@ -101,7 +103,44 @@ void ByteLock::release_() noexcept { util::Result ByteLock::acquire(File& f, std::uint64_t offset, std::uint64_t length, LockKind kind) { +#ifdef F_OFD_SETLK return do_lock(f, offset, length, kind, kSetLkW); +#else + // No OFD locks here (macOS): a blocking F_SETLKW can wait forever on a + // stale holder and hang the caller (CI test runs sat on it until the + // job timeout). Poll with F_SETLK and give up after a bounded wait, so + // a stall becomes a lock error naming the holder instead of a hang. + const auto deadline = + std::chrono::steady_clock::now() + std::chrono::seconds(30); + for (;;) { + auto r = do_lock(f, offset, length, kind, F_SETLK); + if (r) return r; + if (r.error().sub_code != EAGAIN && r.error().sub_code != EACCES) { + return r; + } + if (std::chrono::steady_clock::now() >= deadline) { + struct flock q{}; + q.l_type = (kind == LockKind::Exclusive) ? F_WRLCK : F_RDLCK; + q.l_whence = SEEK_SET; + q.l_start = static_cast(fold_lock_offset(offset)); + q.l_len = static_cast(length); + int fd = static_cast( + reinterpret_cast(f.native_handle()) - 1); + long holder = -1; + if (::fcntl(fd, F_GETLK, &q) == 0 && q.l_type != F_UNLCK) { + holder = static_cast(q.l_pid); + } + std::fprintf(stderr, + "openads: byte lock wait timed out (offset=%llu len=%llu " + "holder_pid=%ld self_pid=%ld)\n", + static_cast(offset), + static_cast(length), holder, + static_cast(::getpid())); + return r; + } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } +#endif } util::Result ByteLock::try_acquire(File& f, std::uint64_t offset, From 8a3d5391204ea3b47600ab5c99b86c5fac2aa91a Mon Sep 17 00:00:00 2001 From: FiveTech Software Date: Thu, 1 Oct 2026 13:27:08 +0200 Subject: [PATCH 5/8] ci: macos-diag - lsof and process list at stall Add commands to display lsof output and all processes related to openads. --- .github/workflows/macos-diag.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/macos-diag.yml b/.github/workflows/macos-diag.yml index 8004d1cc..e3ece5a7 100644 --- a/.github/workflows/macos-diag.yml +++ b/.github/workflows/macos-diag.yml @@ -38,6 +38,10 @@ jobs: ps -o pid,stat,%cpu,etime,command -p "$PID" || true echo "=== last 40 lines of output" tail -n 40 "$GITHUB_WORKSPACE/run.log" | tr -d '\r' | cat -v | cut -c1-200 || true + echo "=== lsof (tmp files)" + lsof -p "$PID" 2>/dev/null | grep -i openads | cut -c1-200 | head -40 || true + echo "=== all processes" + ps -axo pid,ppid,stat,etime,command | grep -i openads | grep -v grep | cut -c1-200 || true echo "=== lldb backtraces" sudo lldb -p "$PID" -b -o 'thread backtrace all' > "$GITHUB_WORKSPACE/lldb.txt" 2>&1 || true tr -d '\r' < "$GITHUB_WORKSPACE/lldb.txt" | cat -v | cut -c1-220 | head -120 From d2f57d23314992d013d06a4d3805830982b3b7bc Mon Sep 17 00:00:00 2001 From: FiveTech Software Date: Thu, 1 Oct 2026 13:50:36 +0200 Subject: [PATCH 6/8] platform: bounded byte-lock wait on macOS (SDK defines OFD, blocking wait hangs CI) Refactor locking mechanism for macOS to avoid indefinite blocking by using non-blocking commands and a timeout. --- src/platform/lock_posix.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/platform/lock_posix.cpp b/src/platform/lock_posix.cpp index 689573df..83216603 100644 --- a/src/platform/lock_posix.cpp +++ b/src/platform/lock_posix.cpp @@ -103,17 +103,18 @@ void ByteLock::release_() noexcept { util::Result ByteLock::acquire(File& f, std::uint64_t offset, std::uint64_t length, LockKind kind) { -#ifdef F_OFD_SETLK +#if defined(F_OFD_SETLK) && !defined(__APPLE__) return do_lock(f, offset, length, kind, kSetLkW); #else - // No OFD locks here (macOS): a blocking F_SETLKW can wait forever on a - // stale holder and hang the caller (CI test runs sat on it until the - // job timeout). Poll with F_SETLK and give up after a bounded wait, so - // a stall becomes a lock error naming the holder instead of a hang. + // macOS: its SDK defines the OFD commands, but a blocking F_OFD_SETLKW + // waits forever when a leaked holder lives in this same process (CI + // test runs sat on it until the job timeout). Poll the non-blocking + // command and give up after a bounded wait, so a stall becomes a lock + // error instead of a hang. const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(30); for (;;) { - auto r = do_lock(f, offset, length, kind, F_SETLK); + auto r = do_lock(f, offset, length, kind, kSetLk); if (r) return r; if (r.error().sub_code != EAGAIN && r.error().sub_code != EACCES) { return r; @@ -127,7 +128,13 @@ util::Result ByteLock::acquire(File& f, std::uint64_t offset, int fd = static_cast( reinterpret_cast(f.native_handle()) - 1); long holder = -1; - if (::fcntl(fd, F_GETLK, &q) == 0 && q.l_type != F_UNLCK) { +#ifdef F_OFD_GETLK + q.l_pid = 0; + const int kGetLk = F_OFD_GETLK; +#else + const int kGetLk = F_GETLK; +#endif + if (::fcntl(fd, kGetLk, &q) == 0 && q.l_type != F_UNLCK) { holder = static_cast(q.l_pid); } std::fprintf(stderr, From 6fbae1b9d038d2ad7126166e9e813921f4663e18 Mon Sep 17 00:00:00 2001 From: FiveTech Software Date: Thu, 1 Oct 2026 14:32:51 +0200 Subject: [PATCH 7/8] platform: fix unused kSetLkW warning on macOS Mark kSetLkW as maybe unused to avoid compiler warnings. --- src/platform/lock_posix.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/lock_posix.cpp b/src/platform/lock_posix.cpp index 83216603..579070e0 100644 --- a/src/platform/lock_posix.cpp +++ b/src/platform/lock_posix.cpp @@ -30,10 +30,10 @@ util::Error os_error(const char* op) { // semantics used by the engine. #ifdef F_OFD_SETLK constexpr int kSetLk = F_OFD_SETLK; -constexpr int kSetLkW = F_OFD_SETLKW; +[[maybe_unused]] constexpr int kSetLkW = F_OFD_SETLKW; #else constexpr int kSetLk = F_SETLK; -constexpr int kSetLkW = F_SETLKW; +[[maybe_unused]] constexpr int kSetLkW = F_SETLKW; #endif // off_t is signed: lock offsets at or above 2^63 (the ADT lock base is From 8a00a1fed48f86febc1ded010688ba1437c021ff Mon Sep 17 00:00:00 2001 From: FiveTech Software Date: Thu, 1 Oct 2026 15:16:21 +0200 Subject: [PATCH 8/8] ci: macos-diag - report lock-timeout lines --- .github/workflows/macos-diag.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/macos-diag.yml b/.github/workflows/macos-diag.yml index e3ece5a7..cad62c24 100644 --- a/.github/workflows/macos-diag.yml +++ b/.github/workflows/macos-diag.yml @@ -38,6 +38,11 @@ jobs: ps -o pid,stat,%cpu,etime,command -p "$PID" || true echo "=== last 40 lines of output" tail -n 40 "$GITHUB_WORKSPACE/run.log" | tr -d '\r' | cat -v | cut -c1-200 || true + echo "=== lock timeouts in output" + tr -d '\r' < "$GITHUB_WORKSPACE/run.log" | grep -c 'byte lock wait' || true + tr -d '\r' < "$GITHUB_WORKSPACE/run.log" | grep -n -B6 'byte lock wait' | cut -c1-200 | head -40 || true + echo "=== tests started" + tr -d '\r' < "$GITHUB_WORKSPACE/run.log" | grep -c '^TEST CASE' || true echo "=== lsof (tmp files)" lsof -p "$PID" 2>/dev/null | grep -i openads | cut -c1-200 | head -40 || true echo "=== all processes"