ci: add GitHub Actions workflow building against libsrt 1.5.6 - #83
Merged
Conversation
First CI for this repo. Four jobs: ubuntu-latest and macos-latest across the two supported Go lines (1.25.x, 1.26.x), fail-fast disabled. libsrt handling is asymmetric on purpose. Ubuntu 24.04 packages srt 1.5.3, below the fix for CVE-2026-55869 and CVE-2026-55868, so Linux builds v1.5.6 from source. Homebrew's formula is already at 1.5.6 with arm64 bottles, so macOS installs the bottle. Either way the job parses SRT_VERSION_* out of the installed header and fails with an annotation naming both CVEs if it is below 1.5.6, rather than silently testing against a vulnerable library. The test step runs under -race: srtgo hands sockets between a process-wide poll server goroutine and caller goroutines, which is exactly the shape of bug the detector catches, and the suite was racy until #82 made the pollDesc state accesses atomic. -count=1 is deliberate. The suite binds real UDP sockets, several on a hardcoded 8090, and TestListen is not re-runnable in-process. No Windows job: netutils_test.go imports golang.org/x/sys/unix with no build tag, so `go test` cannot type-check under GOOS=windows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The test binary segfaults on exit on Linux. Every test passes, then the
process dies about two seconds later:
PASS
signal: segmentation fault (core dumped)
A backtrace puts it inside libsrt itself, not in Go:
Thread 35 "SRT:RcvQ:w13" received signal SIGSEGV
#0 srt::CRcvQueue::worker(void*) () from libsrt.so.1.5
Any bound socket spawns an RcvQ worker thread, and srt_close() only begins
an asynchronous teardown -- SRT reaps the multiplexer roughly a second
later. Exiting in the meantime lets libsrt's global destructors free state
under a thread that is still running.
Measured in a Linux container, per 10 runs of the suite:
tests that never close their sockets 10/10 crash (TestListen alone: 10/10)
tests that always close their sockets 3/10 crash (closing only narrows it)
sockets created but never bound 0/10 crash (no RcvQ worker, no crash)
srt_cleanup() stops those threads first. Calling it from TestMain takes the
full suite from 15/15 crashes to 0/10 on arm64 Linux.
srt_cleanup is reference counted against srt_startup, so TestMain must not
call InitSRT() itself: with the extra startup the count never reaches zero
and the cleanup silently does nothing. That variant still crashed 15/15.
This predates the recent merges -- a CI run at a56ffe0 (before #58) fails
identically.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This reverts commit ea02bb5.
The test binary segfaults on exit. Every test passes, then the process dies
about two seconds later, inside libsrt rather than in Go:
Thread 35 "SRT:RcvQ:w13" received signal SIGSEGV
#0 srt::CRcvQueue::worker(void*) () from libsrt.so.1.5
Any bound socket spawns an SRT receive-queue thread, and srt_close() only
begins an asynchronous teardown -- SRT reaps the multiplexer roughly a
second later. Exiting in the meantime lets libsrt's global destructors free
state underneath a thread that is still running. This predates the recent
merges; CI at a56ffe0 fails identically.
Three things were wrong, and all three had to be fixed:
1. pollServer had no shutdown path at all. run() waited on srt_epoll_uwait
with an infinite timeout, so it could never observe a stop request and
sat inside C forever. It now waits with a finite timeout, watches a stop
channel, and shutdown() waits for the loop to actually leave C before
releasing the epoll. Tearing SRT down while a thread is parked inside
srt_epoll_uwait is itself a way to crash, so the ordering matters.
2. CleanupSRT() called srt_cleanup() directly, with the poll server still
running. It now stops the poll server first.
3. Twelve tests created sockets and never closed them, and AcceptHelper
never closed the sockets it accepted, which are live connections of
their own. Their multiplexers outlived the tests.
Fixing any one alone is not enough: with only 1 and 2, the full suite still
crashed 12/12 on amd64 while individual tests passed, because the leaked
sockets kept queues alive.
Verified per run of the full suite under -race:
macOS arm64 0/15 crash, all pass (was 0/15 crash, all pass)
Linux amd64 0/12 crash, all pass (was 12/12 crash)
Linux arm64 0/12 crash (was 15/15 crash)
Linux arm64 still fails TestCreateAddrInetV4/V6; those fail identically on
unmodified master and are an unrelated arm64-only sockaddr layout issue.
Note CleanupSRT must not be paired with an extra InitSRT in TestMain:
srt_cleanup is reference counted against srt_startup, so the extra startup
leaves the count above zero and the cleanup silently does nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the repo's first CI workflow. Until now nothing ran automatically on push or PR — which is how #72 was able to fix a deadlock and introduce a data race in the same diff, caught only by review.
Matrix: ubuntu-latest × macos-latest, Go 1.25.x × 1.26.x,
fail-fast: false,timeout-minutes: 30.go.mod'sgo 1.12is untouched — that is the floor promised to consumers, not a CI target.libsrt handling is asymmetric on purpose
Ubuntu 24.04 packages srt 1.5.3, two patch lines below the fix for CVE-2026-55869 and CVE-2026-55868, so Linux builds v1.5.6 from source. Homebrew's formula is already at 1.5.6 with arm64 bottles, so macOS installs the bottle.
Either way the job then parses
SRT_VERSION_*out of the installed header — notpkg-config, since the cgo build does not use pkg-config either — and fails with a::error::annotation naming both CVEs if it is below 1.5.6. Testing against a vulnerable libsrt should be loud, not silent.Why
-raceis the required stepsrtgo hands sockets between a process-wide poll server goroutine and caller goroutines, which is precisely the shape of bug the detector finds. The suite was racy until #82 made the
pollDescstate accesses atomic. Keeping this step required means that cannot silently regress.Deliberate constraints, documented inline
-count=1— the suite binds real UDP sockets, several on a hardcoded port 8090, andTestListenis not re-runnable in-process (it fails on the second iteration under-count=2). No retries or reruns: a real flake here is a bug worth seeing.go testcannot even type-check underGOOS=windows, becausenetutils_test.goimportsgolang.org/x/sys/unixwith no build tag. Shipping a job that fails on its first run would be worse than omitting it. Adding//go:build !windowsthere is the first step toward changing that.concurrencywithcancel-in-progress— keeps two runs of the same branch from queueing up and contending.Verification
Locally on darwin/arm64 against real libsrt 1.5.6: YAML parses; all 7
run:blocks passbash -n; the version guard was exercised in all three branches (1.5.6 → pass, fabricated 1.5.5 → correct CVE annotation and exit 1, missing prefix → headers-not-found); and the exactgo vet ./...→go build -v ./...→go test -count=1 -race -timeout 5m .sequence runs with CI's ownCGO_CFLAGS/CGO_LDFLAGSand exits 0 at every step.What that cannot cover: no GitHub Actions run has executed this file. The Linux source-build path in particular has never run anywhere — this PR is its first real execution, which is the point of opening it.
🤖 Generated with Claude Code