Detach conditions from WaitSets before the vtable is rewritten - #6510
Open
PavelGuzenfeld wants to merge 1 commit into
Open
PavelGuzenfeld wants to merge 1 commit into
PavelGuzenfeld wants to merge 1 commit into
Conversation
PavelGuzenfeld
force-pushed
the
fix/condition-vptr-race-3883
branch
from
August 21, 2026 21:14
142dd66 to
5d6ff3d
Compare
Condition::~Condition() calls ConditionNotifier::will_be_deleted(), which runs after the derived destructor has already rewritten the vtable. Until that call takes the WaitSet mutex the condition is still in entries_, so a concurrent WaitSetImpl::wait() can call get_trigger_value() on a partially destroyed object. Detach in each derived destructor instead, while the object is still fully constructed. ResourceLimitedVector::remove() returns false for a missing element, so the later call from Condition::~Condition() is a no-op. Signed-off-by: Pavel Guzenfeld <me@pavelguzenfeld.com>
PavelGuzenfeld
force-pushed
the
fix/condition-vptr-race-3883
branch
from
August 21, 2026 21:27
5d6ff3d to
71ccd94
Compare
This branch has not been deployed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #3883.
Condition::~Condition()callsConditionNotifier::will_be_deleted(), which iswhere a condition detaches itself from any WaitSet it is still attached to. That
runs after the derived destructor, so the vtable has already been rewritten by
the time the detach happens. Until
will_be_deleted()manages to take theWaitSet mutex, the condition is still in
entries_, and aWaitSetImpl::wait()holding that mutex will call
get_trigger_value()on a partially destroyedobject.
TSan on a fully instrumented build, on master (2bad9bc):
WaitSetImpl.cpp:124 is
c->get_trigger_value()insidefill_active_conditions.Condition.cpp:32 is the vptr store on entry to
~Condition.The change detaches in each derived destructor instead, while the object is still
fully constructed.
ResourceLimitedVector::remove()returns false for a missingelement, so the later call from
Condition::~Condition()is a no-op, andnotifier_is aConditionmember so it is still alive in a derived destructorbody. Applied to
GuardCondition,StatusConditionandReadCondition— everyConditionsubclass in the tree. No ABI or API change.How this was verified
Standalone reproducer below, Fast-DDS + Fast-CDR + foonathan_memory all built
with
-fsanitize=thread, Debug, GCC 13.3, Ubuntu 24.04:Note it needs reduced ASLR on 6.x kernels (
setarch $(uname -m) -R) or TSanaborts with "unexpected memory mapping".
Only
GuardConditionwas exercised by the reproducer. TheStatusConditionandReadConditionchanges are the same pattern applied by inspection: bothdestructors were empty, both dereference an
impl_member inget_trigger_value(), and neitherStatusConditionImpl::~StatusConditionImpl()nor
ReadConditionImpltouches the notifier during destruction, so the orderingof notifier interactions is unchanged.
Contributor Checklist
Commit messages follow the project guidelines.
The code follows the style guidelines of this project.
❌ Tests that thoroughly check the new feature have been added/Regression tests checking the bug and its fix have been added; the added tests pass locally
No regression test, because I could not build one that reliably fails on the
unfixed code inside a sane CI budget, and a test that passes on broken code
would be worse than none. Measured detection rate with the reproducer above is
roughly one TSan report per 200k destroy cycles (1-2 per 20s run at ~10k
cycles/s). Two attempts at a bounded gtest both came up 0/3 against the unfixed
library: 20000 iterations with one attached condition (~2s/run), and 2000
iterations with 2000 attached conditions (~25s/run). Widening the pass over
entries_makes it worse, because contending on the WaitSet mutexanti-correlates the destroying thread with the waiter holding it.
A deterministic test does not look reachable through the public API either. The
window is between
~GuardConditioncompleting and~Conditionstarting, and atest-local subclass cannot observe it — entering the derived destructor writes
the vptr before
~GuardConditionruns, so such a test reports a race on fixedand unfixed builds alike.
Happy to add the reproducer to the nightly TSan job instead if you want it
somewhere, or to take a suggestion on a test shape I have missed.
Any new/modified methods have been properly documented using Doxygen.
N/A Any new configuration API has an equivalent XML API
Changes are backport compatible: they do NOT break ABI nor change library core behavior.
Changes are API compatible.
N/A New feature has been added to the
versions.mdfile.N/A New feature has been documented/Current behavior is correctly described in the documentation.
Applicable backports have been included in the description.