-
Notifications
You must be signed in to change notification settings - Fork 0
706 lines (635 loc) · 28 KB
/
Copy pathci.yml
File metadata and controls
706 lines (635 loc) · 28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
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