Repository navigation
[2.6.x] Backport reader listener race fixes (#2857, #2898, #6371) - #6591
dbwls99706 wants to merge 4 commits into
Conversation
Signed-off-by: Ricardo González Moreno <ricardo@richiware.dev> (cherry picked from commit 0014b01) Signed-off-by: dbwls99706 <yujinhong3@gmail.com>
* Refs #15339. Fixed StatefulReader access to mp_listener. Signed-off-by: Miguel Company <MiguelCompany@eprosima.com> * Refs #15339. Fixed StatelessReader access to mp_listener. Signed-off-by: Miguel Company <MiguelCompany@eprosima.com> * Refs #15338. Protecting access to DataReaderImpl::listener_ with specific mutex. Signed-off-by: Miguel Company <MiguelCompany@eprosima.com> * Refs #15341. Addresed review suggestions. Signed-off-by: Miguel Company <MiguelCompany@eprosima.com> (cherry picked from commit 2078f7f) Signed-off-by: dbwls99706 <yujinhong3@gmail.com>
* Refs #21355. Added regression test. Signed-off-by: Miguel Company <miguelcompany@eprosima.com> * Refs #21355. Improve readability in EDP.cpp. Signed-off-by: Miguel Company <miguelcompany@eprosima.com> * Refs #21355. Protect `InnerDataWriterListener::on_writer_matched`. Signed-off-by: Miguel Company <miguelcompany@eprosima.com> * Refs #21355. Protect `InnerDataReaderListener::on_reader_matched`. Signed-off-by: Miguel Company <miguelcompany@eprosima.com> --------- Signed-off-by: Miguel Company <miguelcompany@eprosima.com> (cherry picked from commit de70d42) # Conflicts: # src/cpp/fastdds/publisher/DataWriterImpl.hpp # src/cpp/fastdds/subscriber/DataReaderImpl.hpp # src/cpp/rtps/builtin/discovery/endpoint/EDP.cpp # test/blackbox/common/DDSBlackboxTestsDiscovery.cpp Signed-off-by: dbwls99706 <yujinhong3@gmail.com>
Deletes several matched DataReaders while their remote DataWriter is being removed, so that discovery notifies the readers about the removed writer while DataReaderImpl::disable() clears their listeners. Related to eProsima#3303. Signed-off-by: dbwls99706 <yujinhong3@gmail.com>
|
Hello @dbwls99706, First of all, thanks for your contribution. According to our RELEASE_SUPPORT.md and our documentation, branch Apologies for the inconvenience. |
|
@cferreiragonz Thanks for the clarification. RELEASE_SUPPORT.md and the linked documentation list “critical issues and security fixes” for the extended support period. Could this crash be considered a critical issue under that policy? It reproduces with the stock ROS 2 Humble talker and listener during shutdown. The regression test also reproduces it during concurrent DataReader deletion and remote DataWriter removal while the participants remain active. Since ordinary entity deletion can crash the entire process, the potential impact extends beyond shutdown. ROS 2 Humble ships Fast DDS 2.6.x, so moving to a newer branch is not a routine package update for users staying on Humble. This PR backports existing upstream fixes. I understand if it does not meet the criteria! |
Description
Deleting a DataReader while discovery removes a matched remote DataWriter can cause a null listener dereference on 2.6.x. The discovery thread checks the listener and then reads it again to invoke a callback. Between those reads,
DataReaderImpl::disable()can clear it throughsetListener(nullptr).I first encountered this crash while validating generated ROS 2 C++ packages for my ros2-engineering-skills project. I reproduced this during ROS 2 Humble shutdown using the stock
demo_nodes_cpptalker and listener, without custom application nodes. Core dumps and AddressSanitizer reports identified the check-then-re-read paths inStatefulReader::matched_writer_remove()and theEDP::unpairWriterProxy()lambda. The new blackbox regression test also reproduces the correspondingStatelessReaderpath.This PR backports three upstream changes to
2.6.xand adds a regression test for concurrent reader deletion and writer removal. It addresses the null dereference observed in #3303; it does not claim to resolve every listener lifetime race.Changes
0014b017b: store EDP listener pointers locally before use.2078f7f7e: protect DataReader listener access and copy StatefulReader/StatelessReader listener pointers undermp_mutexbefore invoking discovery callbacks outside the lock.on_xxx_matchedthread-safe #6371, cherry-picked fromde70d42f6: store listener pointers in additional EDP pairing paths and serialize matched-status updates and notifications in the inner DataReader/DataWriter listeners. Conflicts were resolved for 2.6.x following the structure of the 2.14.x backport, [21355] Makeon_xxx_matchedthread-safe (backport #6371) #6382. ItsDDSDiscovery.MatchedCallbackListenerMultithreadregression test is included.DDSDiscovery.DataReaderDeletionWhileRemoteWriterIsRemoved: each of 200 iterations creates eight readers, mixing reliable and best-effort QoS, and a writer on another participant. After waiting for matching, two threads concurrently delete the writer and the readers. This exercises both StatefulReader and StatelessReader removal paths.The cherry-picked commits preserve their original authors and sign-offs, with my sign-off added. The new regression test is a separate commit. The experimental callback gate used during diagnosis is not included.
Target branch:
2.6.x. This is a backport of existing upstream changes; no additional backport is requested here.Related to #3303.
Validation
Local 2.6.x tests
Environment: Ubuntu 24.04, GCC 13.3,
RelWithDebInfo,SECURITY=ON, and googletestrelease-1.11.0. The baseline is 2.6.x with the same two tests added but without the three fixes.MatchedCallbackListenerMultithreadThe 50-run baseline count comes from fixed-count execution. A separate
ctest --repeat until-fail:50run stopped on its first execution.In 30 baseline runs under GDB, all crashed: 19 at
StatelessReader.cpp:219, 9 atEDP.cpp:583, and 2 atStatefulReader.cpp:382. All followedEDP::unpairWriterProxy()called byPDP::removeWriterProxyData(). The baseline ASan reports identified null dereferences at these same locations, with no other ASan report in those runs.The upstream matched-callback test checks matched-status consistency. It did not distinguish the baseline from the backport in this environment; the new deletion/unpair test provides the direct regression coverage for the observed null dereference.
The 222 related tests cover DDSDiscovery, DDSStatus, DDSBasic, DDSDataReader, DDSDataWriter, PubSubBasic, LivelinessQos, and DDSContentFilter. The full test suite was not run locally.
ROS 2 Humble / Fast DDS 2.6.12 reproduction
Both versions of the
ros-humble-fastrtps2.6.12 source package were rebuilt in the same CI run: unchanged and with the three backported fixes.The reproduction runs in an Ubuntu 22.04 Docker container without external network access, with
ROS_LOCALHOST_ONLY=1(UDPv4 over loopback plus SHM). A Python launch file starts the stock talker and listener usinglaunch.LaunchService. A separate rclpy process waits for discovery before SIGINT is sent to the launch process and forwarded to the nodes. Each non-ASan build was exercised for 30 minutes per job across four jobs.All 12 analyzed non-ASan cores were in
StatefulReader::matched_writer_remove()or theEDP::unpairWriterProxy()lambda. The baseline ASan reports identifiedStatefulReader.cpp:382andEDP.cpp:583. The patched ASan runs produced no reports, including no heap-use-after-free reports. The patched non-ASan runs had no other abnormal or slow exits.The iteration-level Fisher exact p-values were
1.3e-7overall and2.5e-4for the same-CPU comparison. A job-level permutation analysis gavep = 0.0286, the smallest value possible with four jobs per build. Iterations within a job are not necessarily independent, and runner hardware varied. These results support the fix for the observed crash; zero observed failures is not proof of general lifetime safety.Sanitizer conditions and CI status
The local blackbox ASan runs used
detect_odr_violation=0for both versions. Otherwise the binary stops at startup on an ODR report forenvironment_file_:SystemInfo.cppis compiled into both the library and the blackbox executable. This disables the ODR check, not all global-variable instrumentation. Leak detection remained enabled.The results above are from local runs and from the Humble reproduction. I have not yet run the upstream CI workflows on my fork or completed TSan validation. One note for the ASan CI job on this branch:
asan.metapasses-DSANITIZER=Address, but the 2.6.x CMake files do not read that option, so the job may build without ASan instrumentation.Compatibility, coverage, and style
libfastrtps, compared withnm -D --defined-only, are identical: 10,870 entries.abi-compliance-checkerover the public headers reported zero binary and zero source compatibility problems. The ABI dump used optimized (-O2) builds, so this is supporting interface evidence rather than a complete proof of ABI compatibility.DataReaderImpland the inner DataReader/DataWriter listeners; the implementation classes are only forward-declared in installed headers.DDSDiscoverytests against this branch with the two new tests included:EDP.cpp60.7% to 63.0%,StatefulReader.cpp51.8% to 57.2%,StatelessReader.cpp40.5% to 49.0%,DataReaderImpl.cpp33.0% to 34.9%, andDataWriterImpl.cpp31.4% to 32.8%. The same existing tests also showed no decrease in coverage after the fixes. Of the 76 added code lines, 59 were executed (77.6%). These measurements cover the changed source files, not the full project. I could not compare against the nightly coverage job.The counts, statistical analysis, ABI checks, and coverage figures above come from the collected execution logs and analysis scripts. The reproduction harness, backtraces, sanitizer logs, patches, and analysis are available in the evidence bundle on request.
Known limitations and changes not included
setListener()wait for callbacks already in progress. Listener lifetime synchronization beyond these changes remains outside this PR's scope.MatchedCallbackListenerMultithreadreportedTypeObjectFactoryleaks in 3/70 baseline runs and 2/70 patched runs. Its assertions passed, but those sanitizer runs are not counted as clean passes. The observed leak was attributed to the unsynchronizedTypeObjectFactory::get_instance()initialization addressed upstream by [21672] Fix data race inTypeObjectFactory::get_instance#5238. That separate fix is not included here.Contributor Checklist
on_xxx_matchedthread-safe #6371; the uncommentedlistener_mutex_member is preserved from [15341] Protect access to reader listeners #2898. Full Doxygen compliance has not been confirmed.versions.mdfile (if applicable). Bug-fix backport only.Reviewer Checklist