Repository navigation
Conversation
Implements ServerSocket_Qt (a QTcpServer on the Qt event thread pool, mirroring ClientSocket_Qt; the implementation lives in ServerSocket_Qt.cpp, following the split of the client backends) and adds loopback tests that combine a ClientSocket and a ServerSocket against each other on 127.0.0.1. The test logic is written against the abstract AbstractClientSocket/AbstractServerSocket classes and is instantiated per backend; this PR instantiates the Qt backend on all platforms. The POSIX and WinSocket backends (and their test instantiations) land in separate PRs on top of this one, so that every platform runs 2 of the 3 backends and CI as a whole covers all 3. Scenarios: echo round trip, zero-length sends, a 1 MiB transfer with integrity check, rapid reconnect cycles, a send/close race, rejection of a second client, and a port-in-use start failure. Also documents that ServerSocket::close_connection() is safe to call from within a listener callback (close() is not: it joins the worker threads).
Pending: the crash-report / lldb workflow commitThe token used here lacks the `workflow\ Patch (commit 37133db): From 37133db7a3f157c97eb34f5ac199a4318986c18c Mon Sep 17 00:00:00 2001
From: PA Dev Bot <344001113+pa-devbot@users.noreply.github.com>
Date: Sun, 1 Nov 2026 06:47:46 +0000
Subject: [PATCH] Mac CI: capture crash reports and lldb backtrace when the
test run dies
On a segfault the macOS crash reporter writes a .ips report to
~/Library/Logs/DiagnosticReports/, which is not in the Actions log and
the runner is wiped afterwards. Dump the SerialPrograms crash reports
to the log (and upload them as an artifact) on test failure, then rerun
the suite under lldb and print a backtrace of every thread.
Used to investigate the intermittent PaddleOCR/ONNX Runtime segfault in
the Mac Intel CI; see the PR description.
---
.../workflows/cpp-ci-serial-programs-base.yml | 54 +++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/.github/workflows/cpp-ci-serial-programs-base.yml b/.github/workflows/cpp-ci-serial-programs-base.yml
index 58f1a9d89..b71425319 100644
--- a/.github/workflows/cpp-ci-serial-programs-base.yml
+++ b/.github/workflows/cpp-ci-serial-programs-base.yml
@@ -293,7 +293,61 @@ jobs:
if: startsWith(inputs.os, 'mac') && inputs.run-tests
run: |
cd Arduino-Source/SerialPrograms/bin
+ set +e
./SerialPrograms.app/Contents/MacOS/SerialPrograms --command-line-test-mode
+ TEST_EXIT=$?
+ set -e
+ echo "Tests exited with code $TEST_EXIT (139 = segfault, 134 = abort)"
+
+ # The macOS crash reporter (ReportCrash) writes .ips reports for crashed processes to
+ # ~/Library/Logs/DiagnosticReports/. They are not part of the Actions log and the runner
+ # is wiped, so dump them here when the test run dies. Each .ips holds the faulting
+ # thread's stack (symbol names + imageOffset) and the image UUIDs, which can be matched
+ # against 3rdPartyBinaries/onnxruntime-osx-x86_64-*.tgz.
+ - name: Dump macOS crash reports (on test failure)
+ if: startsWith(inputs.os, 'mac') && inputs.run-tests && failure()
+ run: |
+ # ReportCrash writes the report shortly after the crash; give it a moment.
+ sleep 10
+ mkdir -p crash-reports
+ echo "=== $HOME/Library/Logs/DiagnosticReports ==="
+ ls -la "$HOME/Library/Logs/DiagnosticReports/" || true
+ FOUND=0
+ for f in "$HOME/Library/Logs/DiagnosticReports/"*.ips; do
+ [ -e "$f" ] || continue
+ case "$(basename "$f")" in
+ SerialPrograms*|onnxruntime*) ;;
+ *) continue ;;
+ esac
+ FOUND=1
+ cp "$f" crash-reports/
+ echo "=== $f ==="
+ cat "$f"
+ echo
+ done
+ if [ "$FOUND" -eq 0 ]; then
+ echo "No SerialPrograms crash reports found in DiagnosticReports."
+ fi
+
+ - name: Upload crash reports
+ if: startsWith(inputs.os, 'mac') && inputs.run-tests && failure()
+ uses: actions/upload-artifact@v7
+ with:
+ name: Mac crash reports
+ path: crash-reports/
+ if-no-files-found: ignore
+
+ # Rerun the test suite under lldb and print a backtrace of every thread when it dies.
+ # This step is expected to fail again; the value is the "thread backtrace all" output.
+ - name: Rerun tests under lldb for a backtrace (on test failure)
+ if: startsWith(inputs.os, 'mac') && inputs.run-tests && failure()
+ run: |
+ cd Arduino-Source/SerialPrograms/bin
+ lldb -b \
+ -o run \
+ -k "thread backtrace all" \
+ -k quit \
+ ./SerialPrograms.app/Contents/MacOS/SerialPrograms --command-line-test-mode
# The list of files that passed clang-query, from the latest run on this branch or on main.
# See .github/scripts/clang-query-cached.py. Unlike ccache, every run saves it: it is only
--
2.43.0
|
AGENTS.md forbids using std::mutex/std::condition_variable directly; use the PokemonAutomation::Mutex/ConditionVariable wrappers from Common/Cpp/Concurrency instead. Migrate the Qt server's completion structs and the test harness's Waiter/rng lock.
The method bodies kept their in-class 8-space indentation from the monolithic header; un-dedent them to file scope (and drop the stray blank lines the split left behind).
77e2f4f to
427b871
Compare
The 5 s completion waits in send()/close_connection() were only safe while the queued call ran within the window: the completion objects were stack-local, so if the wait expired and the event thread delivered the call later, on_send/on_close_connection wrote through a dead stack pointer (stack-use-after-return; reproduced under ASAN by stalling the event thread in on_receive_data for 6 s). The signals now pass QSharedPointer<SendData>/QSharedPointer<CloseCompletion> by value: the queued call's own copy keeps the payload alive for as long as the call is pending or running (no UAF after a timed-out caller), and if close() destroys the QTcpServer first, Qt discards the queued call and the caller's copy is the last reference (no leak). The 5 s bounds remain as plain safety valves. Also add the regression test 'Sockets (Qt) - Completion lifetime under event-thread stall', which stalls the event thread inside on_receive_data, verifies close_connection() times out at ~5 s instead of hanging, and that the late handler still restores LISTENING. Takes ~12 s to run.
Implements ServerSocket_POSIX (an accept-loop thread plus one connection thread at a time, both from the given thread pool via plain dispatch(), with non-blocking sockets; the implementation lives in ServerSocket_POSIX.cpp, following the split of the client backends) and enables the POSIX instantiation of the loopback tests on non-Windows. close()/close_connection() only flip the state: the connection thread notices within ~1 ms and closes the socket itself, so close_connection() is safe to call from within a listener callback. The accept loop keeps running while a connection is active and rejects new clients via on_connect_rejected. The connection task is started and published without holding the main lock: assigning to the task slot destroys the previous connection task's AsyncTask, whose destructor waits for that task to finish - and the task may be blocked on the main lock.
send() with MSG_DONTWAIT can return after writing only part of the buffer when the kernel's send buffer is full. macOS does this much more readily than Linux, so the 1 MiB loopback transfer test failed on the Mac CI: client.send() returned early with fewer bytes. Loop until the whole buffer has been written, matching the Qt backend's send() behavior.
The method bodies kept their in-class 8-space indentation from the monolithic header; un-dedent them to file scope.
427b871 to
ca15f0a
Compare
While investigating the intermittent Mac Intel PaddleOCR segfault: - Print the ONNX Runtime version, hardware_concurrency() and the chosen intra/inter-op thread counts to stderr (the file logger is not in the CI log). - Run the global Ort::Env at ORT_LOGGING_LEVEL_INFO so ONNX Runtime's own session-creation progress (Session Options, thread pool params, graph transforms, BFCArena, ...) lands in the CI log. - Add [ORT-DIAG] stderr markers around create_session() and the first PaddleOCRPipeline::recognize_line() Run() calls, to pin down which stage the crash is in. All of this is temporary: revert when the crash is understood.
…t mode The CI log lost the output right up to the segfault because stdout is fully buffered when piped: in the #1812 Mac Intel run the crash happened ~2.5 s after 'Running parallel unit tests...' and not a single line after that was captured. Two temporary diagnostics: - A SIGSEGV/SIGBUS/SIGABRT/SIGILL/SIGFPE handler (command-line test mode only) that writes a native backtrace of the crashing thread straight to fd 2 with backtrace_symbols_fd(), then restores the default disposition and re-raises so the OS crash report and exit code are unchanged. - setvbuf(stdout, _IOLBF) in test mode, so the test 'Starting:'/'Passed:' lines reach the log as they happen and the log shows the last thing the crashing process was doing.
Apple's execinfo.h declares backtrace_symbols_fd() as void (glibc's returns int), so ignore its result on both. Also restore the CRLF line endings the previous commit accidentally rewrote to LF.
setvbuf(stdout, nullptr, _IOLBF, 0) crashed the Windows CI runner with 0xC0000409 (stack-buffer-overrun) ~0.7 s into the test run, on the first stdout write after the call: with size 0, MSVC's ucrt appears to install a zero-size stdio buffer for stdout. 8192 is the conventional default.
The Mac Intel crash is probabilistic (~60% of runs) and lands in the PaddleOCR ONNX session creation, which happens only once per process because the sessions are cached. Run the full suite three times and clear the PaddleOCR cache between runs so the sessions are re-created each time, raising the per-CI-run reproduction odds to ~93%. Stop and fail on the first reported test failure.
b3e20f5 to
48dc961
Compare
|
Reopening to re-trigger CI |
5dd0859 to
eb2f223
Compare
ca15f0a to
c3a6503
Compare
Investigation complete — the "PaddleOCR crash" was a stack-use-after-return in the Qt socket testsRoot cause. The probabilistic exit-139 on Proof (A/B).
Also fixed along the way (in this branch's commits, in case you want to cherry-pick any):
Recommendation. Nothing here needs merging: the crash is already fixed on the |
1eee4a5 to
d5c7b98
Compare
What this is
A [DO NOT MERGE] experiment branch for the handoff in #1811 (PaddleOCR/ONNX Runtime segfault in the C++ CI Serial Programs Mac Intel job, exit 139 during the first PaddleOCR ONNX session creation).
Based on
sockets-server-posix@ 77e2f4f because that is the tree where the crash has been observed (runs 41266441051 / 41275162708). The diff against that base is diagnostics only - no behavior changes.What it does
Temporary [ORT-DIAG] stderr markers (revert when done):
(Pending - see below) CI workflow changes that, on a Mac test failure:
Status / next steps