Skip to content

ci: add GitHub Actions workflow building against libsrt 1.5.6 - #83

Merged
jeoliva merged 4 commits into
masterfrom
ci/github-actions-workflow
Jul 29, 2026
Merged

ci: add GitHub Actions workflow building against libsrt 1.5.6#83
jeoliva merged 4 commits into
masterfrom
ci/github-actions-workflow

Conversation

@jeoliva

@jeoliva jeoliva commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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's go 1.12 is 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 — not pkg-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 -race is the required step

srtgo 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 pollDesc state 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, and TestListen is 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.
  • No Windows jobgo test cannot even type-check under GOOS=windows, because netutils_test.go imports golang.org/x/sys/unix with no build tag. Shipping a job that fails on its first run would be worse than omitting it. Adding //go:build !windows there is the first step toward changing that.
  • concurrency with cancel-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 pass bash -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 exact go vet ./...go build -v ./...go test -count=1 -race -timeout 5m . sequence runs with CI's own CGO_CFLAGS/CGO_LDFLAGS and 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

Jesus Oliva and others added 4 commits July 29, 2026 14:32
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>
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>
@jeoliva
jeoliva merged commit 31df5b9 into master Jul 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant