33#include " behaviortree_cpp/json_export.h"
44
55#include < algorithm>
6+ #include < atomic>
67#include < mutex>
78#include < tuple>
89#include < unordered_set>
@@ -29,6 +30,7 @@ struct RetiredEntries
2930{
3031 std::mutex mutex;
3132 std::vector<Blackboard::Entry*> entries;
33+ std::atomic_size_t count = 0 ;
3234};
3335
3436RetiredEntries& GetRetiredEntries ()
@@ -40,31 +42,44 @@ RetiredEntries& GetRetiredEntries()
4042 return *instance;
4143}
4244
43- // Deleter of every shared_ptr<Entry>. Being attached at allocation time, it
44- // covers all the removal paths, including those inlined in the user's binary.
45- void RetireEntry (Blackboard::Entry* retired)
45+ // No shared_ptr is left when this is called, so nobody can lock the entry
46+ // anymore: if try_lock() succeeds, the entry is unreachable.
47+ // Note: the calling thread may be the one holding the AnyPtrLocked. A
48+ // try_lock() on a std::mutex owned by the caller is formally undefined, but
49+ // it simply fails on all the supported platforms (pthread: EBUSY).
50+ bool IsUnlocked (Blackboard::Entry* entry)
51+ {
52+ if (entry->entry_mutex .try_lock ())
53+ {
54+ entry->entry_mutex .unlock ();
55+ return true ;
56+ }
57+ return false ;
58+ }
59+
60+ // Destroy the parked entries that have been unlocked in the meantime.
61+ // If "retired" is not null, it is parked first.
62+ void SweepRetiredEntries (Blackboard::Entry* retired = nullptr ) noexcept
4663{
64+ auto & parked = GetRetiredEntries ();
4765 std::vector<Blackboard::Entry*> unlocked;
66+ try
4867 {
49- auto & parked = GetRetiredEntries ();
5068 const std::scoped_lock lock (parked.mutex );
51- parked.entries .push_back (retired);
52- // No shared_ptr is left, so nobody can lock these entries anymore: once
53- // try_lock() succeeds, the entry is unreachable.
54- // Note: the calling thread may be the one holding the AnyPtrLocked. A
55- // try_lock() on a std::mutex owned by the caller is formally undefined, but
56- // it simply fails on all the supported platforms (pthread: EBUSY).
69+ if (retired != nullptr )
70+ {
71+ parked.entries .push_back (retired);
72+ }
5773 const auto it = std::partition (parked.entries .begin (), parked.entries .end (),
58- [](Blackboard::Entry* entry) {
59- if (entry->entry_mutex .try_lock ())
60- {
61- entry->entry_mutex .unlock ();
62- return false ;
63- }
64- return true ;
65- });
74+ [](auto * entry) { return !IsUnlocked (entry); });
6675 unlocked.assign (it, parked.entries .end ());
6776 parked.entries .erase (it, parked.entries .end ());
77+ parked.count = parked.entries .size ();
78+ }
79+ catch (...) // NOLINT(bugprone-empty-catch)
80+ {
81+ // Out of memory. Whatever could not be moved to "unlocked" remains parked
82+ // (or leaks, in the case of "retired"), which is safe.
6883 }
6984 // Destroyed outside the lock: the destructor of a stored value may remove
7085 // other entries, i.e. call this function again.
@@ -74,6 +89,19 @@ void RetireEntry(Blackboard::Entry* retired)
7489 }
7590}
7691
92+ // Deleter of every shared_ptr<Entry>. Being attached at allocation time, it
93+ // covers all the removal paths, including those inlined in the user's binary.
94+ void RetireEntry (Blackboard::Entry* retired) noexcept
95+ {
96+ // Common case: nothing is parked and nobody holds this entry
97+ if (GetRetiredEntries ().count == 0 && IsUnlocked (retired))
98+ {
99+ delete retired; // NOLINT(cppcoreguidelines-owning-memory)
100+ return ;
101+ }
102+ SweepRetiredEntries (retired);
103+ }
104+
77105std::shared_ptr<Blackboard::Entry> MakeEntry (const TypeInfo& info)
78106{
79107 return { new Blackboard::Entry (info), &RetireEntry };
@@ -221,8 +249,10 @@ std::vector<StringView> Blackboard::getKeys() const
221249
222250void Blackboard::clear ()
223251{
252+ // the entries (i.e. the stored values) are destroyed outside the lock
253+ decltype (storage_) removed;
224254 const std::unique_lock storage_lock (storage_mutex_);
225- storage_. clear ( );
255+ removed. swap (storage_ );
226256}
227257
228258void Blackboard::createEntry (const std::string& key, const TypeInfo& info)
@@ -322,14 +352,20 @@ void Blackboard::cloneInto(Blackboard& dst) const
322352 // Step 3: insert new entries and remove stale ones under dst.storage_mutex_.
323353 if (!new_entries.empty () || !keys_to_remove.empty ())
324354 {
355+ // the stale entries are destroyed outside the lock
356+ std::vector<std::shared_ptr<Entry>> removed;
325357 const std::unique_lock dst_lock (dst.storage_mutex_ );
326358 for (auto & [key, entry] : new_entries)
327359 {
328360 dst.storage_ .try_emplace (key, std::move (entry));
329361 }
330362 for (const auto & key : keys_to_remove)
331363 {
332- dst.storage_ .erase (key);
364+ if (auto it = dst.storage_ .find (key); it != dst.storage_ .end ())
365+ {
366+ removed.push_back (std::move (it->second ));
367+ dst.storage_ .erase (it);
368+ }
333369 }
334370 }
335371}
@@ -355,6 +391,12 @@ std::shared_ptr<Blackboard::Entry> Blackboard::createEntryImpl(const std::string
355391 return rootBlackboard ()->createEntryImpl (key.substr (1 , key.size () - 1 ), info);
356392 }
357393
394+ // Bound the lifetime of the entries that were removed while locked
395+ if (GetRetiredEntries ().count != 0 )
396+ {
397+ SweepRetiredEntries ();
398+ }
399+
358400 const std::unique_lock storage_lock (storage_mutex_);
359401 // This function might be called recursively, when we do remapping, because we move
360402 // to the top scope to find already existing entries
0 commit comments