From 5690e6d3eac5413f6341f1cab62f4f8ee8509ae4 Mon Sep 17 00:00:00 2001 From: Siddharth More Date: Wed, 15 Apr 2026 09:50:24 -0700 Subject: [PATCH 1/3] feat: add ASan/TSan sanitizer support and CI jobs - Add LIVEKIT_SANITIZER CMake option (address, thread, undefined) - 'address' enables ASan + UBSan with -fno-omit-frame-pointer - 'thread' enables TSan - 'undefined' enables UBSan standalone - Blocked on Windows (MSVC lacks TSan/UBSan); works on Linux and macOS - Add linux-debug-asan and linux-debug-tsan CMake presets with corresponding build and test presets - Add 'sanitizers' CI job in builds.yml with ASan and TSan matrix - Fix reversed markdown link syntax in README.md --- .github/workflows/builds.yml | 97 ++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 24 +++++++++ CMakePresets.json | 48 ++++++++++++++++++ README.md | 4 +- 4 files changed, 171 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 27e24aca..480cf4ea 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -331,6 +331,103 @@ jobs: ./build.sh clean-all || true fi + sanitizers: + strategy: + fail-fast: false + matrix: + include: + - name: asan + preset: linux-debug-asan + build_dir: build-debug-asan + env_vars: "" + - name: tsan + preset: linux-debug-tsan + build_dir: build-debug-tsan + env_vars: "TSAN_OPTIONS=suppressions=tsan_suppressions.txt:halt_on_error=1" + + name: Sanitizer (${{ matrix.name }}) + runs-on: ubuntu-latest + + steps: + - name: Checkout (with submodules) + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + submodules: recursive + fetch-depth: 1 + + - name: Pull LFS files + run: git lfs pull + + - name: Install deps (Ubuntu) + run: | + set -eux + sudo apt-get update + sudo apt-get install -y \ + build-essential cmake ninja-build pkg-config \ + llvm-dev libclang-dev clang \ + libva-dev libdrm-dev libgbm-dev libx11-dev libgl1-mesa-dev \ + libxext-dev libxcomposite-dev libxdamage-dev libxfixes-dev \ + libxrandr-dev libxi-dev libxkbcommon-dev \ + libasound2-dev libpulse-dev \ + libssl-dev \ + libprotobuf-dev protobuf-compiler \ + libabsl-dev \ + libwayland-dev libdecor-0-dev \ + libspdlog-dev + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 + with: + toolchain: stable + + - name: Cache Cargo registry + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: Linux-sanitizer-${{ matrix.name }}-cargo-reg-${{ hashFiles('**/Cargo.lock') }} + restore-keys: Linux-sanitizer-${{ matrix.name }}-cargo-reg- + + - name: Cache Cargo target + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: client-sdk-rust/target + key: Linux-sanitizer-${{ matrix.name }}-cargo-target-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + Linux-sanitizer-${{ matrix.name }}-cargo-target- + + - name: Set build environment + run: | + echo "CXXFLAGS=-Wno-deprecated-declarations" >> $GITHUB_ENV + echo "CFLAGS=-Wno-deprecated-declarations" >> $GITHUB_ENV + LLVM_VERSION=$(llvm-config --version | cut -d. -f1) + echo "LIBCLANG_PATH=/usr/lib/llvm-${LLVM_VERSION}/lib" >> $GITHUB_ENV + + - name: Configure + run: cmake --preset ${{ matrix.preset }} + + - name: Build + run: cmake --build --preset ${{ matrix.preset }} + + - name: Run unit tests + run: | + ${{ matrix.env_vars }} \ + ${{ matrix.build_dir }}/bin/livekit_unit_tests \ + --gtest_output=xml:${{ matrix.build_dir }}/unit-test-results.xml + + - name: Upload test results + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: test-results-sanitizer-${{ matrix.name }} + path: ${{ matrix.build_dir }}/unit-test-results.xml + retention-days: 7 + + - name: Cleanup + if: always() + run: rm -rf ${{ matrix.build_dir }} || true + docker-build-x64: name: Build (docker-linux-x64) runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index c6f79932..16ffd4d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,30 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +# ---------- Sanitizer support (Linux only) ---------- +set(LIVEKIT_SANITIZER "" CACHE STRING + "Enable a sanitizer for the C++ code (address, thread, undefined, or leave empty)") +if(LIVEKIT_SANITIZER) + if(WIN32) + message(FATAL_ERROR "LIVEKIT_SANITIZER is not supported on Windows (MSVC lacks TSan/UBSan)") + endif() + set(_SAN_FLAGS "") + if(LIVEKIT_SANITIZER STREQUAL "address") + set(_SAN_FLAGS "-fsanitize=address,undefined -fno-omit-frame-pointer") + elseif(LIVEKIT_SANITIZER STREQUAL "thread") + set(_SAN_FLAGS "-fsanitize=thread") + elseif(LIVEKIT_SANITIZER STREQUAL "undefined") + set(_SAN_FLAGS "-fsanitize=undefined") + else() + message(FATAL_ERROR "Unknown sanitizer: ${LIVEKIT_SANITIZER}. Use address, thread, or undefined.") + endif() + string(APPEND CMAKE_C_FLAGS " ${_SAN_FLAGS}") + string(APPEND CMAKE_CXX_FLAGS " ${_SAN_FLAGS}") + string(APPEND CMAKE_EXE_LINKER_FLAGS " ${_SAN_FLAGS}") + string(APPEND CMAKE_SHARED_LINKER_FLAGS " ${_SAN_FLAGS}") + message(STATUS "Sanitizer enabled: ${LIVEKIT_SANITIZER} (${_SAN_FLAGS})") +endif() + # Set RPATH for Unix systems to find shared libraries in executable directory if(UNIX) if(APPLE) diff --git a/CMakePresets.json b/CMakePresets.json index 5f6b3be3..e8198f73 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -278,6 +278,32 @@ "LIVEKIT_BUILD_EXAMPLES": "OFF", "LIVEKIT_BUILD_TESTS": "ON" } + }, + { + "name": "linux-debug-asan", + "displayName": "Linux Debug with AddressSanitizer", + "description": "Build for Linux Debug with ASan + UBSan and tests", + "inherits": "linux-base", + "binaryDir": "${sourceDir}/build-debug-asan", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "LIVEKIT_BUILD_EXAMPLES": "OFF", + "LIVEKIT_BUILD_TESTS": "ON", + "LIVEKIT_SANITIZER": "address" + } + }, + { + "name": "linux-debug-tsan", + "displayName": "Linux Debug with ThreadSanitizer", + "description": "Build for Linux Debug with TSan and tests", + "inherits": "linux-base", + "binaryDir": "${sourceDir}/build-debug-tsan", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "LIVEKIT_BUILD_EXAMPLES": "OFF", + "LIVEKIT_BUILD_TESTS": "ON", + "LIVEKIT_SANITIZER": "thread" + } } ], "buildPresets": [ @@ -358,6 +384,14 @@ { "name": "macos-debug-tests", "configurePreset": "macos-debug-tests" + }, + { + "name": "linux-debug-asan", + "configurePreset": "linux-debug-asan" + }, + { + "name": "linux-debug-tsan", + "configurePreset": "linux-debug-tsan" } ], "testPresets": [ @@ -404,6 +438,20 @@ "output": { "outputOnFailure": true } + }, + { + "name": "linux-debug-asan", + "configurePreset": "linux-debug-asan", + "output": { + "outputOnFailure": true + } + }, + { + "name": "linux-debug-tsan", + "configurePreset": "linux-debug-tsan", + "output": { + "outputOnFailure": true + } } ] } diff --git a/README.md b/README.md index 04b5ecfa..ac66f6a6 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ Use this SDK to add realtime video, audio and data features to your C++ app. By - **Git LFS** (required for examples) Some example data files (e.g., audio assets) are stored using Git LFS. You must install Git LFS before cloning or pulling the repo if you want to run the examples. -- **livekit-cli** install livekit-cli by following the (official livekit docs)[https://docs.livekit.io/intro/basics/cli/start/] -- **livekit-server** install livekit-server by following the (official livekit docs)[https://docs.livekit.io/transport/self-hosting/local/] +- **livekit-cli** install livekit-cli by following the [official livekit docs](https://docs.livekit.io/intro/basics/cli/start/) +- **livekit-server** install livekit-server by following the [official livekit docs](https://docs.livekit.io/transport/self-hosting/local/) **Platform-Specific Requirements:** From dd64dd0a1483cf12c2743e72758cac0d62a75963 Mon Sep 17 00:00:00 2001 From: Siddharth More Date: Wed, 15 Apr 2026 20:44:09 -0700 Subject: [PATCH 2/3] fix: add TSan suppressions file and use absolute path in CI The TSan CI job referenced tsan_suppressions.txt with a relative path, which resolved against the test binary directory instead of the repo root. - Create tsan_suppressions.txt with Rust FFI suppressions - Use $GITHUB_WORKSPACE absolute path in TSAN_OPTIONS --- .github/workflows/builds.yml | 2 +- tsan_suppressions.txt | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tsan_suppressions.txt diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 480cf4ea..e0c2e896 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -343,7 +343,7 @@ jobs: - name: tsan preset: linux-debug-tsan build_dir: build-debug-tsan - env_vars: "TSAN_OPTIONS=suppressions=tsan_suppressions.txt:halt_on_error=1" + env_vars: "TSAN_OPTIONS=suppressions=$GITHUB_WORKSPACE/tsan_suppressions.txt:halt_on_error=1" name: Sanitizer (${{ matrix.name }}) runs-on: ubuntu-latest diff --git a/tsan_suppressions.txt b/tsan_suppressions.txt new file mode 100644 index 00000000..e12b2b5b --- /dev/null +++ b/tsan_suppressions.txt @@ -0,0 +1,16 @@ +# ThreadSanitizer suppressions for livekit client-sdk-cpp +# +# Rust FFI layer may trigger false positives in TSan because +# TSan cannot see synchronization inside the Rust runtime. +# Add specific suppressions here as needed. +# +# Syntax: +# race: +# deadlock: +# mutex: + +# Rust standard library internals +race:libstd-*.so + +# Rust FFI bridge (livekit_ffi) +race:livekit_ffi_* From be68aa3dd36cc8748c0b105bd3c17e76e368e590 Mon Sep 17 00:00:00 2001 From: Siddharth More Date: Sat, 18 Apr 2026 13:11:49 -0700 Subject: [PATCH 3/3] refactor: merge sanitizer jobs into build matrix Fold the separate 'sanitizers' job into the existing 'build' matrix as two additional Linux entries (linux-x64-asan, linux-x64-tsan). This eliminates ~100 lines of duplicated setup (checkout, apt-get, Rust, Cargo cache) while preserving identical parallel execution behavior. Sanitizer matrix entries use a 'sanitizer: true' flag to conditionally: - Use cmake --preset instead of build.sh - Skip example smoke tests and build artifact uploads - Pass sanitizer-specific env vars (TSAN_OPTIONS) to the test runner --- .github/workflows/builds.yml | 137 +++++++++-------------------------- 1 file changed, 36 insertions(+), 101 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index e0c2e896..461d0b11 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -81,6 +81,19 @@ jobs: name: windows-x64 build_cmd: .\build.cmd release-tests && .\build.cmd release-examples build_dir: build-release + # Sanitizer builds (Linux-only, debug, no examples/artifacts) + - os: ubuntu-latest + name: linux-x64-asan + sanitizer: true + preset: linux-debug-asan + build_dir: build-debug-asan + env_vars: "" + - os: ubuntu-latest + name: linux-x64-tsan + sanitizer: true + preset: linux-debug-tsan + build_dir: build-debug-tsan + env_vars: "TSAN_OPTIONS=suppressions=$GITHUB_WORKSPACE/tsan_suppressions.txt:halt_on_error=1" name: Build (${{ matrix.name }}) runs-on: ${{ matrix.os }} @@ -175,7 +188,7 @@ jobs: # ---------- Build ---------- - name: Build (Unix) - if: runner.os != 'Windows' + if: runner.os != 'Windows' && !matrix.sanitizer shell: bash run: | chmod +x build.sh @@ -186,11 +199,19 @@ jobs: shell: pwsh run: ${{ matrix.build_cmd }} + - name: Configure (sanitizer) + if: matrix.sanitizer + run: cmake --preset ${{ matrix.preset }} + + - name: Build (sanitizer) + if: matrix.sanitizer + run: cmake --build --preset ${{ matrix.preset }} + # ---------- Smoke test cpp-example-collection binaries ---------- # Built under cpp-example-collection-build/ (not build-dir/bin). Visual Studio # multi-config places executables in per-target Release/ (or Debug/) subdirs. - name: Smoke test examples (Unix) - if: runner.os != 'Windows' + if: runner.os != 'Windows' && !matrix.sanitizer shell: bash run: | set -x @@ -288,12 +309,20 @@ jobs: # ---------- Run unit tests ---------- - name: Run unit tests (Unix) - if: runner.os != 'Windows' + if: runner.os != 'Windows' && !matrix.sanitizer shell: bash run: | ${{ matrix.build_dir }}/bin/livekit_unit_tests \ --gtest_output=xml:${{ matrix.build_dir }}/unit-test-results.xml + - name: Run unit tests (sanitizer) + if: matrix.sanitizer + shell: bash + run: | + ${{ matrix.env_vars }} \ + ${{ matrix.build_dir }}/bin/livekit_unit_tests \ + --gtest_output=xml:${{ matrix.build_dir }}/unit-test-results.xml + - name: Run unit tests (Windows) if: runner.os == 'Windows' shell: pwsh @@ -311,6 +340,7 @@ jobs: # ---------- Upload artifacts ---------- - name: Upload build artifacts + if: "!matrix.sanitizer" uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: livekit-sdk-${{ matrix.name }} @@ -325,109 +355,14 @@ jobs: if: always() shell: bash run: | - if [[ "$RUNNER_OS" == "Windows" ]]; then + if [[ -n "${{ matrix.sanitizer }}" ]]; then + rm -rf ${{ matrix.build_dir }} || true + elif [[ "$RUNNER_OS" == "Windows" ]]; then rm -rf build-release build-debug || true else ./build.sh clean-all || true fi - sanitizers: - strategy: - fail-fast: false - matrix: - include: - - name: asan - preset: linux-debug-asan - build_dir: build-debug-asan - env_vars: "" - - name: tsan - preset: linux-debug-tsan - build_dir: build-debug-tsan - env_vars: "TSAN_OPTIONS=suppressions=$GITHUB_WORKSPACE/tsan_suppressions.txt:halt_on_error=1" - - name: Sanitizer (${{ matrix.name }}) - runs-on: ubuntu-latest - - steps: - - name: Checkout (with submodules) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - submodules: recursive - fetch-depth: 1 - - - name: Pull LFS files - run: git lfs pull - - - name: Install deps (Ubuntu) - run: | - set -eux - sudo apt-get update - sudo apt-get install -y \ - build-essential cmake ninja-build pkg-config \ - llvm-dev libclang-dev clang \ - libva-dev libdrm-dev libgbm-dev libx11-dev libgl1-mesa-dev \ - libxext-dev libxcomposite-dev libxdamage-dev libxfixes-dev \ - libxrandr-dev libxi-dev libxkbcommon-dev \ - libasound2-dev libpulse-dev \ - libssl-dev \ - libprotobuf-dev protobuf-compiler \ - libabsl-dev \ - libwayland-dev libdecor-0-dev \ - libspdlog-dev - - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 - with: - toolchain: stable - - - name: Cache Cargo registry - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: Linux-sanitizer-${{ matrix.name }}-cargo-reg-${{ hashFiles('**/Cargo.lock') }} - restore-keys: Linux-sanitizer-${{ matrix.name }}-cargo-reg- - - - name: Cache Cargo target - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: client-sdk-rust/target - key: Linux-sanitizer-${{ matrix.name }}-cargo-target-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - Linux-sanitizer-${{ matrix.name }}-cargo-target- - - - name: Set build environment - run: | - echo "CXXFLAGS=-Wno-deprecated-declarations" >> $GITHUB_ENV - echo "CFLAGS=-Wno-deprecated-declarations" >> $GITHUB_ENV - LLVM_VERSION=$(llvm-config --version | cut -d. -f1) - echo "LIBCLANG_PATH=/usr/lib/llvm-${LLVM_VERSION}/lib" >> $GITHUB_ENV - - - name: Configure - run: cmake --preset ${{ matrix.preset }} - - - name: Build - run: cmake --build --preset ${{ matrix.preset }} - - - name: Run unit tests - run: | - ${{ matrix.env_vars }} \ - ${{ matrix.build_dir }}/bin/livekit_unit_tests \ - --gtest_output=xml:${{ matrix.build_dir }}/unit-test-results.xml - - - name: Upload test results - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: test-results-sanitizer-${{ matrix.name }} - path: ${{ matrix.build_dir }}/unit-test-results.xml - retention-days: 7 - - - name: Cleanup - if: always() - run: rm -rf ${{ matrix.build_dir }} || true - docker-build-x64: name: Build (docker-linux-x64) runs-on: ubuntu-latest