Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/macos-diag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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 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"
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
echo "=== sample"
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" | tr -d '\r' | cat -v | cut -c1-200 || true
fi
5 changes: 3 additions & 2 deletions release-notes-1.09.71.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
50 changes: 48 additions & 2 deletions src/platform/lock_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <chrono>
#include <fcntl.h>
#include <thread>
#include <unistd.h>

namespace openads::platform {
Expand All @@ -28,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
Expand Down Expand Up @@ -101,7 +103,51 @@ void ByteLock::release_() noexcept {

util::Result<ByteLock> ByteLock::acquire(File& f, std::uint64_t offset,
std::uint64_t length, LockKind kind) {
#if defined(F_OFD_SETLK) && !defined(__APPLE__)
return do_lock(f, offset, length, kind, kSetLkW);
#else
// 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, kSetLk);
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<off_t>(fold_lock_offset(offset));
q.l_len = static_cast<off_t>(length);
int fd = static_cast<int>(
reinterpret_cast<intptr_t>(f.native_handle()) - 1);
long holder = -1;
#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<long>(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<unsigned long long>(offset),
static_cast<unsigned long long>(length), holder,
static_cast<long>(::getpid()));
return r;
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
#endif
}

util::Result<ByteLock> ByteLock::try_acquire(File& f, std::uint64_t offset,
Expand Down
Loading