chore(release): v1.2.0 (#117) #126
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
| name: ci | |
| # Full enterprise CI gate for pbr-cpp-memory-pool — implements the contract | |
| # fixed by ADR-0005 (toolchain matrix) and the quality bar in AGENTS.md §10. | |
| # Covers ROADMAP §1.8 (build matrix + clang-tidy + sanitizers + CTest), | |
| # §1.10 (ANSI C / C99 verification of the public C header), §1.11 | |
| # (zero-external-dependency verification of the library build), §2.8 | |
| # (Valgrind verification of spec §6.2 — `ERROR SUMMARY: 0 errors from 0 | |
| # contexts`), and §2.9 (microbenchmark smoke run — proves the bench | |
| # binary builds, links, and runs to completion; ADR-0014 §8). | |
| # | |
| # The configure-smoke job in .github/workflows/build-smoke.yml is removed in | |
| # the same PR — its work is now a subset of the per-cell configure step here. | |
| on: | |
| pull_request: | |
| paths: | |
| - 'CMakeLists.txt' | |
| - 'CMakePresets.json' | |
| - 'src/**' | |
| - '.clang-format' | |
| - '.clang-tidy' | |
| - '.github/workflows/ci.yml' | |
| push: | |
| branches: [master] | |
| paths: | |
| - 'CMakeLists.txt' | |
| - 'CMakePresets.json' | |
| - 'src/**' | |
| - '.clang-format' | |
| - '.clang-tidy' | |
| - '.github/workflows/ci.yml' | |
| # Invoked by .github/workflows/release.yml on `v*` tag push to re-run the | |
| # full PR-gating matrix against the tagged commit (ADR-0004 §4 item 1). | |
| # The `paths:` filter above is intentionally not duplicated here — when | |
| # callers reach this workflow via workflow_call they bypass path gating, | |
| # which is the desired behaviour for a tag-time verification. | |
| workflow_call: | |
| permissions: | |
| contents: read | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Cross-platform build matrix per ADR-0005 §4. | |
| # | |
| # Sanitizer presets (asan/ubsan) are POSIX-only by preset condition; the | |
| # matrix omits them for Windows. TSan is deferred until ROADMAP §4 lands | |
| # threading. | |
| # --------------------------------------------------------------------------- | |
| build: | |
| name: build / ${{ matrix.os }} / ${{ matrix.compiler }} / ${{ matrix.preset }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Linux x86_64 | |
| - { os: ubuntu-24.04, compiler: gcc, preset: debug } | |
| - { os: ubuntu-24.04, compiler: gcc, preset: release } | |
| - { os: ubuntu-24.04, compiler: gcc, preset: asan } | |
| - { os: ubuntu-24.04, compiler: gcc, preset: ubsan } | |
| - { os: ubuntu-24.04, compiler: clang, preset: debug } | |
| - { os: ubuntu-24.04, compiler: clang, preset: release } | |
| - { os: ubuntu-24.04, compiler: clang, preset: asan } | |
| - { os: ubuntu-24.04, compiler: clang, preset: ubsan } | |
| # Opt-in debug hardening (ADR-0043) — cross-platform (no sanitizer | |
| # flags); builds the hardened configuration and runs its detection | |
| # tests, the "clean in both configurations" acceptance for #109. | |
| - { os: ubuntu-24.04, compiler: gcc, preset: harden } | |
| - { os: ubuntu-24.04, compiler: clang, preset: harden } | |
| # Windows x86_64 — sanitizer presets are POSIX-only. | |
| - { os: windows-2022, compiler: msvc, preset: debug } | |
| - { os: windows-2022, compiler: msvc, preset: release } | |
| # MSVC has no ASan in this matrix, so hardening is the memory-safety net here. | |
| - { os: windows-2022, compiler: msvc, preset: harden } | |
| # macOS arm64 | |
| - { os: macos-14, compiler: apple-clang, preset: debug } | |
| - { os: macos-14, compiler: apple-clang, preset: release } | |
| - { os: macos-14, compiler: apple-clang, preset: asan } | |
| - { os: macos-14, compiler: apple-clang, preset: ubsan } | |
| - { os: macos-14, compiler: apple-clang, preset: harden } | |
| steps: | |
| - name: Check out the source tree | |
| uses: actions/checkout@v6 | |
| - name: Install a recent CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Set up MSVC environment | |
| if: matrix.compiler == 'msvc' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| arch: x64 | |
| - name: Pin Linux GCC | |
| if: matrix.compiler == 'gcc' | |
| shell: bash | |
| run: | | |
| # ubuntu-24.04 already ships GCC 13 above the ADR-0005 floor of 11. | |
| gcc --version | |
| g++ --version | |
| echo "CC=gcc" >> "$GITHUB_ENV" | |
| echo "CXX=g++" >> "$GITHUB_ENV" | |
| - name: Pin Linux Clang | |
| if: matrix.compiler == 'clang' | |
| shell: bash | |
| run: | | |
| # ubuntu-24.04 ships Clang 18 above the ADR-0005 floor of 14. | |
| clang --version | |
| clang++ --version | |
| echo "CC=clang" >> "$GITHUB_ENV" | |
| echo "CXX=clang++" >> "$GITHUB_ENV" | |
| - name: Configure | |
| shell: bash | |
| run: cmake --preset ${{ matrix.preset }} | |
| - name: Build | |
| shell: bash | |
| run: cmake --build --preset ${{ matrix.preset }} | |
| - name: Test | |
| shell: bash | |
| run: ctest --preset ${{ matrix.preset }} --output-on-failure | |
| # --------------------------------------------------------------------------- | |
| # clang-format — entire repo, dry-run with -Werror. | |
| # --------------------------------------------------------------------------- | |
| format: | |
| name: format / clang-format check | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Ensure clang-format is available | |
| shell: bash | |
| run: clang-format --version | |
| - name: Verify all C and C++ sources are clean | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mapfile -t files < <(git ls-files '*.cpp' '*.hpp' '*.h' '*.c') | |
| if [[ ${#files[@]} -eq 0 ]]; then | |
| echo "No C/C++ sources tracked." | |
| exit 0 | |
| fi | |
| printf '%s\n' "${files[@]}" | |
| clang-format --dry-run --Werror "${files[@]}" | |
| # --------------------------------------------------------------------------- | |
| # clang-tidy — diff-based, gated with --warnings-as-errors='*'. | |
| # --------------------------------------------------------------------------- | |
| tidy: | |
| name: tidy / clang-tidy diff gate | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out (full history needed for diff) | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Ensure clang-tidy is available | |
| shell: bash | |
| run: clang-tidy --version | |
| - name: Configure debug to produce compile_commands.json | |
| shell: bash | |
| run: | | |
| export CC=clang CXX=clang++ | |
| cmake --preset debug | |
| - name: Compute changed C/C++ sources against base | |
| id: changed | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| base_sha="${{ github.event.pull_request.base.sha }}" | |
| else | |
| # Push to master — diff against the previous commit. | |
| base_sha="$(git rev-parse HEAD~1)" | |
| fi | |
| # Limit clang-tidy to C++ sources. .c files belong to the | |
| # ANSI-C / C99 verification job above; running clang-tidy on | |
| # them with the C++ project's compile_commands.json forces it | |
| # into C++ mode and surfaces inapplicable diagnostics | |
| # (modernize-redundant-void-arg, init-variables-with-nullptr, | |
| # etc.). | |
| git diff --name-only --diff-filter=AM "$base_sha"...HEAD \ | |
| -- '*.cpp' '*.hpp' '*.h' > changed.txt || true | |
| echo "Changed sources:" | |
| cat changed.txt || true | |
| # Surface to next step | |
| echo "file=changed.txt" >> "$GITHUB_OUTPUT" | |
| - name: Run clang-tidy on the diff | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! -s "${{ steps.changed.outputs.file }}" ]]; then | |
| echo "No C/C++ source changes — nothing for clang-tidy to do." | |
| exit 0 | |
| fi | |
| mapfile -t files < "${{ steps.changed.outputs.file }}" | |
| clang-tidy -p build/debug --warnings-as-errors='*' "${files[@]}" | |
| # --------------------------------------------------------------------------- | |
| # ROADMAP §1.10 — ANSI C (C89) and C99 compatibility of the public header. | |
| # Uses GCC directly (no CMake) so the standard flag is unambiguous. | |
| # --------------------------------------------------------------------------- | |
| ansi-c-compat: | |
| name: compat / ${{ matrix.std }} pedantic | |
| runs-on: ubuntu-24.04 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| std: [c89, c99] | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Compile the minimal C consumer under -std=${{ matrix.std }} -pedantic -Werror | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| gcc --version | |
| gcc -std=${{ matrix.std }} -pedantic -Werror \ | |
| -Isrc/main/cpp \ | |
| -c src/test/c/it/d4np/memorypool/c_consumer_min.c \ | |
| -o /tmp/c_consumer_min.o | |
| echo "${{ matrix.std }} verification: OK" | |
| # --------------------------------------------------------------------------- | |
| # ROADMAP §1.11 — zero external dependencies in the library build. | |
| # When tests and benchmarks are OFF, the library target must depend only on | |
| # the C/C++ standard library. | |
| # --------------------------------------------------------------------------- | |
| zero-external-deps: | |
| name: deps / zero external dependencies (library only) | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Install CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Configure with tests and benchmarks disabled | |
| shell: bash | |
| run: | | |
| cmake -S . -B build/zero-deps -G Ninja \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -DPBR_MEMORY_POOL_BUILD_TESTS=OFF \ | |
| -DPBR_MEMORY_POOL_BUILD_BENCHMARKS=OFF | |
| - name: Audit CMakeLists for stray find_package outside test/bench scopes | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Allow find_package() only under src/test/** and src/bench/**. | |
| stray=$(grep -RIn --include='CMakeLists.txt' '^[[:space:]]*find_package' \ | |
| CMakeLists.txt src/main/ 2>/dev/null || true) | |
| if [[ -n "$stray" ]]; then | |
| echo "ERROR: find_package() found in the library scope:" | |
| echo "$stray" | |
| exit 1 | |
| fi | |
| echo "OK — no find_package() calls in the library scope." | |
| - name: Audit FetchContent declarations outside test/bench scopes | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| stray=$(grep -RIn --include='CMakeLists.txt' 'FetchContent_Declare' \ | |
| CMakeLists.txt src/main/ 2>/dev/null || true) | |
| # FetchContent_Declare(doctest ...) lives inside the | |
| # if(PBR_MEMORY_POOL_BUILD_TESTS) block — when tests are off it is | |
| # never reached, so it is allowed at the top-level CMakeLists. Flag | |
| # only declarations that are NOT inside the tests/bench guard. | |
| if [[ -n "$stray" ]]; then | |
| echo "FetchContent declarations found (informational):" | |
| echo "$stray" | |
| echo "Ensure each is gated by PBR_MEMORY_POOL_BUILD_TESTS or" | |
| echo "PBR_MEMORY_POOL_BUILD_BENCHMARKS — verify by inspection." | |
| fi | |
| - name: Build the library and inspect linkage | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cmake --build build/zero-deps | |
| # The static archive should contain only objects from our own | |
| # sources; no transitive third-party object files. | |
| archive="build/zero-deps/libpbr_memory_pool.a" | |
| if [[ ! -f "$archive" ]]; then | |
| echo "ERROR: expected static archive at $archive" | |
| ls -la build/zero-deps | |
| exit 1 | |
| fi | |
| ar t "$archive" | |
| # Heuristic: every object in the archive must come from | |
| # src/main/cpp/it/d4np/memorypool/. | |
| stray=$(ar t "$archive" | grep -v '^memory_pool\..*\.o$' || true) | |
| if [[ -n "$stray" ]]; then | |
| echo "ERROR: unexpected objects in the static archive:" | |
| echo "$stray" | |
| exit 1 | |
| fi | |
| echo "OK — library archive contains only project-local objects." | |
| # --------------------------------------------------------------------------- | |
| # ROADMAP §2.8 — Valgrind verification of spec §6.2. | |
| # | |
| # The literal command from spec §6.2 is: | |
| # | |
| # gcc -g -O0 test_pool.c memory_pool.c -o test_pool | |
| # valgrind --leak-check=full --show-leak-kinds=all ./test_pool | |
| # | |
| # Success criterion (verbatim): `ERROR SUMMARY: 0 errors from 0 contexts`. | |
| # | |
| # The implementation is C++17 not C (ADR-0009 §1), so `gcc` is replaced | |
| # by `g++` on the implementation TU and `memory_pool.c` is replaced by | |
| # `memory_pool.cpp` on the source name. The C test program itself | |
| # (src/test/cpp/it/d4np/memorypool/spec_6_2_valgrind/test_pool.c) is | |
| # unchanged ANSI C89. The side-by-side mapping is in the README at | |
| # src/test/cpp/it/d4np/memorypool/spec_6_2_valgrind/README.md. | |
| # | |
| # The `--errors-for-leak-kinds=definite,indirect` flag is the only | |
| # non-spec addition — without it, leaks are reported but do not change | |
| # the exit code, so a regression that leaked the backing buffer would | |
| # pass `--error-exitcode=1` while still violating spec §3.1. | |
| # `still reachable` and `possible` stay informational so global | |
| # libstdc++ state does not trip the gate. | |
| # --------------------------------------------------------------------------- | |
| valgrind: | |
| name: valgrind / spec §6.2 leak-check=full | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Install Valgrind | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y valgrind | |
| valgrind --version | |
| - name: Toolchain versions | |
| shell: bash | |
| run: | | |
| gcc --version | |
| g++ --version | |
| - name: Compile, link, run under Valgrind (literal spec §6.2 pattern) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p build/valgrind | |
| # 1. Compile the C test program under -std=c89 -pedantic. The | |
| # test file is at the spec-named path | |
| # src/test/cpp/.../spec_6_2_valgrind/test_pool.c so that any | |
| # reader of the spec can grep the repo for the source by name. | |
| gcc -std=c89 -pedantic -g -O0 -Isrc/main/cpp \ | |
| -c src/test/cpp/it/d4np/memorypool/spec_6_2_valgrind/test_pool.c \ | |
| -o build/valgrind/test_pool.o | |
| # 2. Compile the C++17 implementation TU. -std=c++17 is the | |
| # project's ADR-0005 §3 standard; -g -O0 matches the spec's | |
| # `gcc -g -O0` for human-readable Valgrind stack traces. | |
| g++ -std=c++17 -g -O0 -Isrc/main/cpp \ | |
| -c src/main/cpp/it/d4np/memorypool/memory_pool.cpp \ | |
| -o build/valgrind/memory_pool.o | |
| # 3. Link with g++ so libstdc++ is brought in for the C++17 | |
| # ::operator new(size, std::align_val_t) overloads. | |
| g++ -g -O0 \ | |
| build/valgrind/test_pool.o build/valgrind/memory_pool.o \ | |
| -o build/valgrind/test_pool | |
| # 4. Run Valgrind with the spec flags plus the leak-kind | |
| # promotion. tee the output so we can grep for the literal | |
| # spec success criterion after the exit code is captured. | |
| set +e | |
| valgrind --leak-check=full --show-leak-kinds=all \ | |
| --errors-for-leak-kinds=definite,indirect \ | |
| --error-exitcode=1 \ | |
| build/valgrind/test_pool \ | |
| 2>&1 | tee build/valgrind/log.txt | |
| rc=${PIPESTATUS[0]} | |
| set -e | |
| if [[ "$rc" -ne 0 ]]; then | |
| echo "FAIL: Valgrind exited with code $rc" | |
| exit "$rc" | |
| fi | |
| # 5. The literal spec success criterion is the canonical sentence; | |
| # grep is a belt-and-braces check against a future Valgrind | |
| # version that changes the exit-code semantics. | |
| if ! grep -E 'ERROR SUMMARY: 0 errors from 0 contexts' \ | |
| build/valgrind/log.txt > /dev/null; then | |
| echo "FAIL: spec §6.2 success criterion not present in output" | |
| exit 1 | |
| fi | |
| echo "OK — spec §6.2 success criterion met." | |
| # --------------------------------------------------------------------------- | |
| # ROADMAP §2.9 — microbenchmark smoke run. Builds the bench binary with the | |
| # `bench` preset (Release + benchmarks ON + tests OFF) and runs it briefly. | |
| # Asserts exit code 0 only — numeric thresholds are not gated because | |
| # shared GHA runners exhibit ±30% variance on memory-bound microbenchmarks | |
| # and would produce flaky red. Committed numbers in docs/bench/v<X.Y.Z>- | |
| # <host>.md come from a controlled host disclosed in the file header. | |
| # Full methodology rationale: ADR-0014 §8. | |
| # --------------------------------------------------------------------------- | |
| bench-smoke: | |
| name: bench / pool vs malloc smoke run | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Install CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Configure (bench preset — Release + benchmarks ON) | |
| shell: bash | |
| run: | | |
| export CC=gcc CXX=g++ | |
| cmake --preset bench | |
| - name: Build the bench binary | |
| shell: bash | |
| run: cmake --build --preset bench | |
| - name: Smoke-run pool_vs_malloc_bench | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bin="build/bench/src/bench/cpp/it/d4np/memorypool/pool_vs_malloc_bench" | |
| if [[ ! -x "$bin" ]]; then | |
| echo "FAIL: expected bench binary at $bin" | |
| ls -la build/bench/src/bench/cpp/it/d4np/memorypool/ | |
| exit 1 | |
| fi | |
| # Reduced iterations / repeats — proves the binary runs end-to-end | |
| # without measuring the noisy runner. The CI gate is exit-code 0, | |
| # not the numbers. | |
| "$bin" --iterations 10000 --repeats 3 | |
| echo "OK — bench binary ran to completion." | |
| # --------------------------------------------------------------------------- | |
| # ROADMAP §9.4 — external-allocator baselines + tail-latency percentiles | |
| # (ADR-0045). External baselines are measured the safe way: re-run the SAME | |
| # bench under LD_PRELOAD, which swaps the whole process allocator (jemalloc / | |
| # tcmalloc take over global malloc on load, so they cannot be linked or dlopen'd | |
| # beside the system allocator). Exercises the opt-in --percentiles table too. | |
| # Linux only; never touches the MSVC leg. Asserts exit code 0, not numbers. | |
| # --------------------------------------------------------------------------- | |
| bench-baselines: | |
| name: bench / external baselines + percentiles | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Install CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Install jemalloc and tcmalloc runtime libraries | |
| shell: bash | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libjemalloc2 libtcmalloc-minimal4t64 | |
| - name: Configure and build (bench preset) | |
| shell: bash | |
| run: | | |
| export CC=gcc CXX=g++ | |
| cmake --preset bench | |
| cmake --build --preset bench | |
| - name: Baseline via LD_PRELOAD + the percentile table | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bin="build/bench/src/bench/cpp/it/d4np/memorypool/pool_vs_malloc_bench" | |
| # LD_PRELOAD resolves a soname through the loader cache (ldconfig ran on | |
| # install), so no filesystem search is needed. | |
| run() { # label, LD_PRELOAD value ("" = none), expected "# allocator:" text | |
| echo "=== allocator: $1 ===" | |
| out="$(LD_PRELOAD="$2" "$bin" --scenario all --iterations 20000 --repeats 3 --percentiles)" | |
| echo "$out" | |
| echo "$out" | grep -q "# allocator: $3" || { echo "FAIL: allocator disclosure ($1)"; exit 1; } | |
| echo "$out" | grep -q "p99_ns/op" || { echo "FAIL: percentile table missing ($1)"; exit 1; } | |
| } | |
| run "system malloc" "" "system malloc" | |
| run "jemalloc" "libjemalloc.so.2" "libjemalloc.so.2" | |
| run "tcmalloc" "libtcmalloc_minimal.so.4" "libtcmalloc_minimal.so.4" | |
| echo "OK — pool benchmarked against system malloc, jemalloc, and tcmalloc; percentile table present." | |
| # --------------------------------------------------------------------------- | |
| # thread-safety — build and run the existing (single-threaded) test suite | |
| # under each non-default PBR_MEMORY_POOL_THREAD_SAFETY policy (ADR-0020 / | |
| # M4.3), proving the compile-time switch compiles and stays single-thread | |
| # correct on both Linux compilers. The concurrent stress tests + TSan land | |
| # in M4.4; this job is the build-correctness gate for the switch. | |
| # --------------------------------------------------------------------------- | |
| thread-safety: | |
| name: thread-safety / ${{ matrix.compiler }} / ${{ matrix.mode }} | |
| runs-on: ubuntu-24.04 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| compiler: [gcc, clang] | |
| mode: [MUTEX, LOCKFREE] | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Install CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Select compiler | |
| shell: bash | |
| run: | | |
| if [[ "${{ matrix.compiler }}" == "gcc" ]]; then | |
| echo "CC=gcc" >> "$GITHUB_ENV" | |
| echo "CXX=g++" >> "$GITHUB_ENV" | |
| else | |
| echo "CC=clang" >> "$GITHUB_ENV" | |
| echo "CXX=clang++" >> "$GITHUB_ENV" | |
| fi | |
| - name: Configure with the thread-safety policy | |
| shell: bash | |
| run: | | |
| cmake -S . -B build/ts -G Ninja \ | |
| -DCMAKE_BUILD_TYPE=Debug \ | |
| -DPBR_MEMORY_POOL_BUILD_TESTS=ON \ | |
| -DPBR_MEMORY_POOL_THREAD_SAFETY=${{ matrix.mode }} | |
| - name: Build | |
| shell: bash | |
| run: cmake --build build/ts | |
| - name: Test (full suite, incl. concurrent stress, under the selected policy) | |
| shell: bash | |
| run: ctest --test-dir build/ts --output-on-failure | |
| # --------------------------------------------------------------------------- | |
| # tsan — ThreadSanitizer over the concurrent stress suite under the MUTEX | |
| # policy (M4.4). MutexPolicy is fully TSan-analyzable, so this verifies the | |
| # thread-safe path is data-race free. LOCKFREE is deliberately NOT run under | |
| # TSan: a Treiber-stack pop reads a node's next-link that another thread may | |
| # have recycled — a benign race (the value is discarded when the tagged CAS | |
| # fails, and the pool backing is never unmapped) that cannot be expressed as | |
| # a well-defined atomic without C++20 atomic_ref / hazard pointers. LOCKFREE | |
| # concurrent correctness is covered by the logical invariants in the | |
| # thread-safety job above + ADR-0020 §3. See concurrency_stress_test.cpp. | |
| # --------------------------------------------------------------------------- | |
| tsan: | |
| name: tsan / clang / MUTEX | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Install CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Configure (Clang + ThreadSanitizer + MutexPolicy) | |
| shell: bash | |
| run: | | |
| export CC=clang CXX=clang++ | |
| cmake -S . -B build/tsan -G Ninja \ | |
| -DCMAKE_BUILD_TYPE=Debug \ | |
| -DPBR_MEMORY_POOL_BUILD_TESTS=ON \ | |
| -DPBR_MEMORY_POOL_THREAD_SAFETY=MUTEX \ | |
| -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer -g" \ | |
| -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread" | |
| - name: Build | |
| shell: bash | |
| run: cmake --build build/tsan | |
| - name: Test under ThreadSanitizer | |
| shell: bash | |
| env: | |
| TSAN_OPTIONS: halt_on_error=1 | |
| run: ctest --test-dir build/tsan --output-on-failure | |
| # --------------------------------------------------------------------------- | |
| # bench-policy-smoke — build the benchmark under each thread-safe policy and | |
| # run every scenario briefly (exit-code gate only; no numeric thresholds — | |
| # shared GHA runners are too noisy, ADR-0014 §8). Proves the concurrent | |
| # (M4.5) and growth (M5.4) scenarios compile, link (incl. libatomic for | |
| # LOCKFREE), and run to completion under MUTEX and LOCKFREE — including the | |
| # growth scenario's clean skip under the lock-free build. Committed numbers | |
| # come from a controlled host (docs/bench/). | |
| # --------------------------------------------------------------------------- | |
| bench-policy-smoke: | |
| name: bench / policy smoke / ${{ matrix.mode }} | |
| runs-on: ubuntu-24.04 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| mode: [MUTEX, LOCKFREE] | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Install CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Configure (bench preset + thread-safety policy) | |
| shell: bash | |
| run: | | |
| export CC=gcc CXX=g++ | |
| cmake --preset bench -B build/bench-${{ matrix.mode }} \ | |
| -DPBR_MEMORY_POOL_THREAD_SAFETY=${{ matrix.mode }} | |
| - name: Build the bench binary | |
| shell: bash | |
| run: cmake --build build/bench-${{ matrix.mode }} | |
| - name: Smoke-run every scenario (incl. concurrent + growth) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bin="build/bench-${{ matrix.mode }}/src/bench/cpp/it/d4np/memorypool/pool_vs_malloc_bench" | |
| if [[ ! -x "$bin" ]]; then | |
| echo "FAIL: expected bench binary at $bin" | |
| exit 1 | |
| fi | |
| # --scenario all covers bulk / interleaved / concurrent / growth. The | |
| # growth scenario grows under MUTEX and cleanly skips under LOCKFREE. | |
| "$bin" --scenario all --threads 4 --iterations 20000 --repeats 3 | |
| echo "OK — all bench scenarios ran to completion under ${{ matrix.mode }}." | |
| # --------------------------------------------------------------------------- | |
| # ROADMAP §9.3 — coverage-guided fuzzing (ADR-0044). Builds the libFuzzer | |
| # target under ASan/UBSan (Clang / POSIX only — libFuzzer is a Clang runtime), | |
| # replays the seed corpus as a regression gate, then fuzzes for a bounded time | |
| # on every PR. A crash fails the job and the offending input is uploaded as a | |
| # reproducer for the bug ledger (ADR-0039). | |
| # --------------------------------------------------------------------------- | |
| fuzz: | |
| name: fuzz / libFuzzer (asan+ubsan) | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v6 | |
| - name: Install CMake and Ninja | |
| uses: lukka/get-cmake@latest | |
| - name: Configure (fuzz preset, Clang) | |
| shell: bash | |
| run: | | |
| export CC=clang CXX=clang++ | |
| cmake --preset fuzz | |
| - name: Build the fuzz target | |
| shell: bash | |
| run: cmake --build build/fuzz --target pool_fuzz | |
| - name: Replay the seed corpus (regression gate) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bin="build/fuzz/src/test/cpp/it/d4np/memorypool/pool_fuzz" | |
| corpus="src/test/cpp/it/d4np/memorypool/pool_fuzz_corpus" | |
| # Replaying named files runs each once and exits; a crash is a finding. | |
| "$bin" "$corpus"/seed_* | |
| echo "OK — seed corpus replayed clean." | |
| - name: Fuzz for a bounded time | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| bin="build/fuzz/src/test/cpp/it/d4np/memorypool/pool_fuzz" | |
| corpus="src/test/cpp/it/d4np/memorypool/pool_fuzz_corpus" | |
| "$bin" -max_total_time=60 -timeout=25 -rss_limit_mb=4096 "$corpus" | |
| echo "OK — libFuzzer ran to the time budget with no crash." | |
| - name: Upload any crash reproducers | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: fuzz-crashes | |
| path: | | |
| crash-* | |
| timeout-* | |
| oom-* | |
| if-no-files-found: ignore |