Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f2af644
feat(rtps): banded transport channels + dedicated per-endpoint unicas…
finger563 Aug 24, 2026
414af11
feat(rtps): band + dscp on the espp facade configs, deferred banded d…
finger563 Aug 24, 2026
e675dca
feat(rtps): band/dscp on the python RtpsParticipant bindings
finger563 Aug 24, 2026
f7d1833
test(rtps): dedicated-port SEDP locator, banded loopbacks, deferred d…
finger563 Aug 24, 2026
919aa47
fix(rtps): esp32 GCC 15 -Wfree-nonheap-object + cppcheck style findings
finger563 Aug 24, 2026
4aa03b9
doc(rtps): document priority bands, dedicated endpoint ports, and the…
finger563 Aug 24, 2026
d4cdd1e
test(rtps): banded-subscriber entry in the FastDDS/ROS 2 interop matrix
finger563 Aug 24, 2026
245afcc
doc(rtps): RtpsParticipant class doc covers priority scheduling; drop…
finger563 Aug 24, 2026
c573c39
fix(socket): bound the reactor's UDP receive so a stale/spurious read…
finger563 Aug 24, 2026
d7aef80
fix(rtps): make runtime endpoint deletion teardown-safe (CI shutdown …
finger563 Aug 24, 2026
45594f4
test(rtps): teardown-under-load stress (rtps_banded_churn) + interop …
finger563 Aug 24, 2026
ef323f4
fix(rtps): PR review - add_reader docstring covers dscp; validate int…
finger563 Aug 24, 2026
057d3a5
feat(socket): removal-completion notification on SocketReactor::remove()
finger563 Aug 24, 2026
c4544b5
fix(rtps): destroy retired sockets promptly via the reactor's removal…
finger563 Aug 24, 2026
18d617b
fix(socket): a bounded read is a REQUIREMENT of add_udp_receiver regi…
finger563 Aug 24, 2026
46a01fd
fix(rtps): dedicated-port ration is a true fd bound; failed probe win…
finger563 Aug 24, 2026
2663af3
fix(rtps): exception boundary on deferred deliveries; transactional s…
finger563 Aug 24, 2026
d376d78
test(rtps): churn iterations REQUIRE live samples on their dedicated …
finger563 Aug 24, 2026
94b1558
test(rtps): run rtps_service_rollback in the interop matrix
finger563 Aug 24, 2026
48fc862
fix(rtps): hold m_mutex across the whole StatefulWriter::heartbeatTick
finger563 Aug 25, 2026
2b68a3e
fix(rtps): deferred-work lifetime model + guaranteed arm recovery + p…
finger563 Aug 25, 2026
c495df4
test(rtps): deferred-arm recovery, duplicate-action precision, rollba…
finger563 Aug 25, 2026
8623e7c
test(rtps): make rtps_deferred_recovery's saturation deterministic
finger563 Aug 25, 2026
0b63854
fix(rtps): band writer progress end-to-end; guaranteed submit; engine…
finger563 Aug 25, 2026
6c970ab
test(rtps): make the banded queue-jump phase robust (CI flake)
finger563 Aug 25, 2026
9487baf
test(rtps): drop redundant order[0] recheck (cppcheck knownConditionT…
finger563 Aug 25, 2026
1cea109
fix(rtps): race-free retry, lossless guaranteed arming, quiesce handl…
finger563 Aug 26, 2026
057673f
fix(rtps): remove_reader must not hold mutex_ across deferred close()
finger563 Aug 26, 2026
d43d80a
fix(rtps): quiesce writer progress() jobs on delete; Winsock SO_RCVTIMEO
finger563 Aug 26, 2026
f3b9ea7
fix(rtps): generation-guard writer progress() jobs; clamp Winsock sub…
finger563 Aug 26, 2026
da3e400
fix(rtps): fair, priority-aware guaranteed-job retry drain
finger563 Aug 26, 2026
9aa985c
fix(rtps,socket): guard rollback thread/endpoint lifetimes; contain r…
finger563 Aug 27, 2026
81a9585
fix(rtps): band-agnostic retry admission; lock writer init(); atomic …
finger563 Aug 27, 2026
652f39d
test(rtps,socket): CI regression tests + sanitizer leg; fix bugs they…
finger563 Aug 27, 2026
865d1c3
fix(rtps): pin engine across unlocked removals; generation-guard inbo…
finger563 Aug 27, 2026
50a1b69
fix(rtps): resolve the lock-order inversions found by the TSan CI leg
finger563 Aug 27, 2026
846ec16
test(rtps): fix cv notify-vs-destroy races; defer TSan deadlock detector
finger563 Aug 27, 2026
e010e21
fix(rtps): callback-delivery lifecycle + pinned action transactions; …
finger563 Aug 27, 2026
e93d74e
style(rtps): use std::find_if for the proxy claim lookup (static anal…
finger563 Aug 27, 2026
f5831c3
feat(rtps): actionable endpoint-capacity diagnostics
finger563 Aug 27, 2026
e4e2579
test(thread_pool): fix lost-wakeup hangs exposed by the TSan CI leg
finger563 Aug 27, 2026
70be208
feat(rtps): per-limit capacity overrides on top of the limits profiles
finger563 Aug 27, 2026
a822853
fix(rtps): best-effort saturation must degrade, not silently collapse
finger563 Aug 27, 2026
1cd63c8
test(rtps): fix unguarded join of deferred-reply workers (TSan CI)
finger563 Aug 27, 2026
e95dee3
fix(rtps): validate limit overrides; reset drop counter on reuse; sha…
finger563 Aug 27, 2026
bd34010
fix(rtps): self-removal-safe teardown; override export + ranges; stat…
finger563 Aug 27, 2026
a5f2819
fix(rtps): reentrant-removal-safe callbacks; builtin-aware override m…
finger563 Aug 27, 2026
954c99c
fix(rtps): cross-limit participant-budget validation + null-safe buil…
finger563 Aug 27, 2026
58a6934
fix(rtps): bounded drain-mode writer pokes; DSCP validation; endpoint…
finger563 Aug 27, 2026
673ae02
fix(rtps): SEDP-order stale-participant removal; participant-id cap b…
finger563 Aug 28, 2026
34c90b3
fix(rtps): channel-pool cross-limit validation + host channel default…
finger563 Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions .github/workflows/host_sanitizers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: Host sanitizers (TSan / ASan)

# Build the host library + pc test suite under ThreadSanitizer and
# AddressSanitizer and run the standalone (non-interop) tests. The RTPS stack
# is heavily concurrent (transport worker pool, socket reactor, protocol
# scheduler, deferred dispatchers), and most defects found in review have been
# data races, deadlocks, and use-after-frees - exactly the classes these
# sanitizers detect mechanically. Runs whenever RTPS or its concurrency
# building blocks change.

# Minimal token scope: build + run tests, never writes.
permissions:
contents: read

on:
pull_request:
paths:
- "components/rtps/**"
- "components/socket/**"
- "components/thread_pool/**"
- "components/task/**"
- "components/timer/**"
- "components/cdr/**"
- "lib/espp.cmake"
- "pc/tests/rtps_*"
- "pc/tests/socket_reactor.cpp"
- "pc/tests/thread_pool.cpp"
- ".github/workflows/host_sanitizers.yml"
workflow_dispatch:

# Supersede in-progress runs (same rationale as rtps_interop.yml): keyed by
# workflow + PR number; never runs on push to main, so cancel is always safe.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
sanitize:
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- sanitizer: thread
cflags: "-fsanitize=thread -fno-omit-frame-pointer"
ldflags: "-fsanitize=thread"
- sanitizer: address
cflags: "-fsanitize=address -fno-omit-frame-pointer"
ldflags: "-fsanitize=address"
name: ${{ matrix.sanitizer }}
steps:
- uses: actions/checkout@v7
with:
submodules: "recursive"

- name: Build library (${{ matrix.sanitizer }})
run: |
cmake -S lib -B lib/build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DESPP_INSTALL=ON \
-DESPP_BUILD_PYTHON=OFF \
-DCMAKE_INSTALL_PREFIX="$PWD/install" \
-DCMAKE_C_FLAGS="${{ matrix.cflags }}" \
-DCMAKE_CXX_FLAGS="${{ matrix.cflags }}" \
-DCMAKE_EXE_LINKER_FLAGS="${{ matrix.ldflags }}" \
-DCMAKE_SHARED_LINKER_FLAGS="${{ matrix.ldflags }}"
cmake --build lib/build --target install --parallel 4

- name: Build pc tests (${{ matrix.sanitizer }})
run: |
cmake -S pc -B pc/build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_PREFIX_PATH="$PWD/install" \
-DCMAKE_CXX_FLAGS="${{ matrix.cflags }}" \
-DCMAKE_EXE_LINKER_FLAGS="${{ matrix.ldflags }}"
cmake --build pc/build --parallel 4

- name: Run standalone tests under ${{ matrix.sanitizer }} sanitizer
run: |
# halt_on_error=0: report every finding, fail via the exit code.
# detect_leaks=0: the engine deliberately holds pooled/static
# allocations for its lifetime; LSan end-of-process reports would be
# noise. ASan still catches use-after-free / overflow.
#
# The lock-order (deadlock) detector is ENABLED. Its first runs found
# four real ABBA cycles, all now fixed: checkAndResetHeartbeats
# agent->participant order, addBuiltInEndpoints lock scope,
# executeCallbacks snapshot-invoke, and StatefulReader delivering
# user callbacks under a leaf delivery mutex instead of the proxies
# mutex (which user callbacks could cycle back into via the
# facade/SEDP lock chain).
export TSAN_OPTIONS="second_deadlock_stack=1"
export ASAN_OPTIONS="detect_leaks=0"
fails=0
# The interop client/server binaries need a FastDDS/ROS 2 peer (the
# docker matrix covers them); everything else runs standalone. The
# generous per-test timeout absorbs the sanitizer's slowdown; a hang
# (e.g. a deadlock TSan cannot see) still fails the job.
for t in pc/build/rtps_* pc/build/socket_reactor pc/build/thread_pool; do
[ -x "$t" ] || continue
name=$(basename "$t")
case "$name" in *interop*) continue;; esac
echo "::group::$name"
if timeout 300 "$t"; then
echo "PASS: $name"
else
rc=$?
echo "FAIL: $name (exit $rc)"
fails=$((fails+1))
fi
echo "::endgroup::"
done
echo "==================== ${{ matrix.sanitizer }} summary ===================="
if [ "$fails" -ne 0 ]; then
echo "FAILED: $fails test(s) under ${{ matrix.sanitizer }} sanitizer"
exit 1
fi
echo "ALL PASS under ${{ matrix.sanitizer }} sanitizer"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@ docs/

# Local example-build helper script (not part of the repo).
build_examples.sh

# local sanitizer / variant build trees (host TSan/ASan, static-storage validation)
lib/build-*/
pc/build-*/
install-*/
114 changes: 113 additions & 1 deletion components/rtps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ idf_component_register(
INCLUDE_DIRS
"include"
REQUIRES
base_component cdr task thread_pool socket
base_component cdr task thread_pool timer socket
)

# Select the RTPS static-limits profile from Kconfig (see Kconfig in this
Expand All @@ -40,6 +40,118 @@ endif()
# limits profile never silently switches the MCU to heap-backed history. The
# limits headers no longer define RTPS_STORAGE_DYNAMIC themselves; it is set here
# on ESP and defaulted on in config.hpp for host/PC builds.
# Per-limit capacity overrides (Kconfig "Custom capacity overrides"): a
# nonzero value overrides that single cap of the selected profile via its
# RTPS_CFG_* macro. PUBLIC so application translation units see the same
# values as the engine (the pools are sized in this component's sources).
set(RTPS_LIMIT_KNOBS
NUM_STATELESS_WRITERS
NUM_STATELESS_READERS
NUM_STATEFUL_WRITERS
NUM_STATEFUL_READERS
MAX_NUM_PARTICIPANTS
NUM_WRITERS_PER_PARTICIPANT
NUM_READERS_PER_PARTICIPANT
NUM_WRITER_PROXIES_PER_READER
NUM_READER_PROXIES_PER_WRITER
MAX_NUM_UNMATCHED_REMOTE_WRITERS
MAX_NUM_UNMATCHED_REMOTE_READERS
MAX_NUM_READER_CALLBACKS
HISTORY_SIZE_STATELESS
HISTORY_SIZE_STATEFUL
MAX_TYPENAME_LENGTH
MAX_TOPICNAME_LENGTH
MAX_NUM_UDP_CONNECTIONS
)
foreach(knob ${RTPS_LIMIT_KNOBS})
if(DEFINED CONFIG_RTPS_LIMIT_${knob} AND NOT "${CONFIG_RTPS_LIMIT_${knob}}" STREQUAL "" AND NOT "${CONFIG_RTPS_LIMIT_${knob}}" STREQUAL "0")
# Builtin-aware minima (0 stays the "use profile default" sentinel): the
# discovery endpoints draw from these pools, so a nonzero value below the
# reservation would crash startup (the second SEDP allocation returns null)
# or silently omit the builtins (per-participant caps < 3).
if((knob STREQUAL "NUM_STATEFUL_WRITERS" OR knob STREQUAL "NUM_STATEFUL_READERS")
AND CONFIG_RTPS_LIMIT_${knob} LESS 2)
message(FATAL_ERROR
"CONFIG_RTPS_LIMIT_${knob}=${CONFIG_RTPS_LIMIT_${knob}} is below the minimum of 2 "
"(2 slots are reserved for the builtin SEDP endpoints)")
endif()
if((knob STREQUAL "NUM_WRITERS_PER_PARTICIPANT" OR knob STREQUAL "NUM_READERS_PER_PARTICIPANT")
AND CONFIG_RTPS_LIMIT_${knob} LESS 3)
message(FATAL_ERROR
"CONFIG_RTPS_LIMIT_${knob}=${CONFIG_RTPS_LIMIT_${knob}} is below the minimum of 3 "
"(the 3 builtin discovery endpoints count against the per-participant cap)")
endif()
Comment thread
finger563 marked this conversation as resolved.
if(knob STREQUAL "MAX_NUM_UDP_CONNECTIONS" AND CONFIG_RTPS_LIMIT_${knob} LESS 4)
message(FATAL_ERROR
"CONFIG_RTPS_LIMIT_${knob}=${CONFIG_RTPS_LIMIT_${knob}} is below the minimum of 4 "
"(2 shared multicast + 2 unicast channels for a single participant)")
endif()
target_compile_definitions(${COMPONENT_LIB} PUBLIC "RTPS_CFG_${knob}=${CONFIG_RTPS_LIMIT_${knob}}")
set(RTPS_EFFECTIVE_${knob} ${CONFIG_RTPS_LIMIT_${knob}})
endif()
endforeach()

# Cross-limit capacity check (mirrors lib/espp.cmake): every participant
# consumes 1 stateless writer/reader + 2 stateful writers/readers from the
# GLOBAL pools for its builtin discovery endpoints. Effective value = the
# Kconfig override if nonzero, else the selected profile's default.
if(CONFIG_RTPS_LIMITS_PROFILE_HOST)
set(_rtps_defaults 8 16 16 32 32 24)
elseif(CONFIG_RTPS_LIMITS_PROFILE_HOST_LARGE)
set(_rtps_defaults 32 64 64 128 128 72)
else() # embedded (default)
set(_rtps_defaults 1 5 5 5 5 10)
endif()
list(GET _rtps_defaults 0 _rtps_d_participants)
list(GET _rtps_defaults 1 _rtps_d_stateless_w)
list(GET _rtps_defaults 2 _rtps_d_stateless_r)
list(GET _rtps_defaults 3 _rtps_d_stateful_w)
list(GET _rtps_defaults 4 _rtps_d_stateful_r)
list(GET _rtps_defaults 5 _rtps_d_channels)
foreach(pair
"MAX_NUM_PARTICIPANTS;_rtps_d_participants"
"NUM_STATELESS_WRITERS;_rtps_d_stateless_w"
"NUM_STATELESS_READERS;_rtps_d_stateless_r"
"NUM_STATEFUL_WRITERS;_rtps_d_stateful_w"
"NUM_STATEFUL_READERS;_rtps_d_stateful_r"
"MAX_NUM_UDP_CONNECTIONS;_rtps_d_channels")
list(GET pair 0 _rtps_k)
list(GET pair 1 _rtps_dvar)
if(NOT DEFINED RTPS_EFFECTIVE_${_rtps_k})
set(RTPS_EFFECTIVE_${_rtps_k} ${${_rtps_dvar}})
endif()
endforeach()
math(EXPR _rtps_need_stateful "2 * ${RTPS_EFFECTIVE_MAX_NUM_PARTICIPANTS}")
if(RTPS_EFFECTIVE_NUM_STATELESS_WRITERS LESS RTPS_EFFECTIVE_MAX_NUM_PARTICIPANTS
OR RTPS_EFFECTIVE_NUM_STATELESS_READERS LESS RTPS_EFFECTIVE_MAX_NUM_PARTICIPANTS
OR RTPS_EFFECTIVE_NUM_STATEFUL_WRITERS LESS _rtps_need_stateful
OR RTPS_EFFECTIVE_NUM_STATEFUL_READERS LESS _rtps_need_stateful)
message(FATAL_ERROR
"RTPS limits cannot host the participant budget: MAX_NUM_PARTICIPANTS="
"${RTPS_EFFECTIVE_MAX_NUM_PARTICIPANTS} needs >= ${RTPS_EFFECTIVE_MAX_NUM_PARTICIPANTS} "
"stateless writers/readers (have ${RTPS_EFFECTIVE_NUM_STATELESS_WRITERS}/"
"${RTPS_EFFECTIVE_NUM_STATELESS_READERS}) and >= ${_rtps_need_stateful} stateful "
"writers/readers (have ${RTPS_EFFECTIVE_NUM_STATEFUL_WRITERS}/"
"${RTPS_EFFECTIVE_NUM_STATEFUL_READERS}) for the builtin discovery endpoints "
"(1 stateless W/R + 2 stateful W/R per participant). Raise the pool overrides "
"(menuconfig 'Custom capacity overrides') or lower MAX_NUM_PARTICIPANTS.")
endif()
# Channel-pool constraint (mirrors lib/espp.cmake): 2 shared multicast + 2
# unicast channels per participant are permanently bound from the same
# MAX_NUM_UDP_CONNECTIONS pool that runtime dedicated endpoint ports draw
# from; without this check a combination can pass the endpoint-pool math yet
# createParticipant() still fails on channels first.
math(EXPR _rtps_need_channels "2 + 2 * ${RTPS_EFFECTIVE_MAX_NUM_PARTICIPANTS}")
if(RTPS_EFFECTIVE_MAX_NUM_UDP_CONNECTIONS LESS _rtps_need_channels)
message(FATAL_ERROR
"RTPS limits cannot host the participant budget: MAX_NUM_PARTICIPANTS="
"${RTPS_EFFECTIVE_MAX_NUM_PARTICIPANTS} needs >= ${_rtps_need_channels} transport channels "
"(2 shared multicast + 2 unicast per participant) but MAX_NUM_UDP_CONNECTIONS is "
"${RTPS_EFFECTIVE_MAX_NUM_UDP_CONNECTIONS}. Raise MAX_NUM_UDP_CONNECTIONS via menuconfig "
"'Custom capacity overrides' (leave headroom for dedicated endpoint ports, which draw "
"from the same pool at runtime) or lower MAX_NUM_PARTICIPANTS.")
endif()

if(CONFIG_RTPS_STORAGE_DYNAMIC)
target_compile_definitions(${COMPONENT_LIB} PUBLIC RTPS_STORAGE_DYNAMIC)
endif()
Expand Down
Loading
Loading