encountered a pattern today that I feel is probably common but somehow I haven't had to deal with before.
i've got a component that is receiving events from an event source, an object that it owns. the event source has a pointer back to the owner so it can call back an OnEvent type thing every time an event occurs.
the event source runs a background thread, polling some source for data that it then turns into m_owner->OnEvent() calls.
that callback is actually a weak_ptr, so the code looks more like this:
std::shared_ptr<Owner> owner = m_owner.lock();
if (owner)
{
owner->OnEvent();
}
and it's possible that the background thread itself ends up holding the last strong reference to the owner, such that as soon as owner goes out of scope on the background thread, it destructs owner and in turn, the event source.
so the event source is being destructed on its own background thread. oof.
normally, you'd want the event source to do something like a thread::join() to make sure it's totally cleaned up by the time its dtor is done, so that you don't end up with a runaway thread that's referencing freed memory from the event source object. But of course you can't join() on the very thread you're waiting on.
a colleague gave me a decent workaround for this. you have to do 2 things:
- in Owner's dtor, it should call something like
m_eventSource->Stop()as a signal to stop the event source's background thread. - in event source's background thread proc, hold a shared_ptr to the event source object itself
this guarantees by the end of the thread proc that the event source object hasn't been freed. then you can safely thread::detach() as the last thing you do in the function and allow the event source dtor to free the last reference after you know the thread proc won't execute any further code.
i think most of my previous projects have had a clearer ownership model so that this proliferation of upcalls and weak_ptrs didn't exist. not something I can easily fix here. ah well. but this pattern is neat