Skip to content

Replace legacy strand with executor-templated strand#10901

Open
jschmidt-icinga wants to merge 1 commit into
masterfrom
replace-legacy-strand
Open

Replace legacy strand with executor-templated strand#10901
jschmidt-icinga wants to merge 1 commit into
masterfrom
replace-legacy-strand

Conversation

@jschmidt-icinga

@jschmidt-icinga jschmidt-icinga commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

This replaces the old boost::asio::io_context::strand with the more modern executor-templated boost::asio::strand<boost::asio::io_context_executor_type>. This should prevent issues like #10825 to occur in the future, because the modern strand does not share implementation pointers between individual strand objects.

Detailed Description

The issue with the legacy strand was that there is a chance for two strand users (like a PerfdataWriterConnection and a JsonRpcConnection) to share an implementation object. This means that even though both objects hold distinct strand objects, one can block the execution of the other. So when one objects holds a lock, waits on an IO-Operation on that strand and the other object resumes on the strand and tries to acquire the same lock, both objects are deadlocked.

The modern strand type is never shared between users and so should never run into this kind of deadlock. It's available for all boost versions we currently support and I've checked the changelog and git history carefully to see if there are any critical bugs that have been fixed since it was introduced. Technically boost-1.66 was the version this new strand has been introduced, but it seems there have been relatively few changes outside of some refactoring since then.

Currently this is all in one commit, because it's hard to switch this over gradually with some critical pieces (Timeout and GracefulDisconnect most notably) using the strand type explicitly and make incompatible calls on it which can't easily be generalized. And I didn't want to go through the effort of adding an overload either, just so it becomes pointless after switching everything over.

Notes

Use of std::shared_ptr<strand> instead of Shared<strand>::Ptr

When a Shared<Foo>::Ptr is dereferenced, the object is of type Shared that inherits from Foo. When passed to SpawnCoroutine(), this keeps older boost versions from using their dedicated boost::asio::spawn(const strand< Executor > & ex, ...) overloads and falls back to the generic Executor based overloads, which then fail somewhere in the machinery (the reason the overload exists).

Newer boost versions don't have this problem and the strand can just be treated as a generic executor, but for now we'll have to use std::shared_ptr<strand> for shared strands, because that correctly dereferences to the original type. The alternative would have been explicit object slicing with static_cast<strand&> at each call-site.

Honestly, I hadn't looked that closely at Shared<>::Ptr, but this makes it a giant ball of anti-patterns in my eyes. Most egregious of all, unconstrained deriving from arbitrary types isn't a good idea and might break all kinds of things. In my opinion we should consider deprecating it and just use std::shared_ptr with std::make_shared and std::shared_from_this in new code.

Closes #10861.

@cla-bot cla-bot Bot added the cla/signed label Jun 24, 2026
@jschmidt-icinga jschmidt-icinga force-pushed the replace-legacy-strand branch 4 times, most recently from bd7aa1a to ca03f7c Compare June 25, 2026 07:49
@jschmidt-icinga jschmidt-icinga added this to the 2.17.0 milestone Jun 25, 2026
@jschmidt-icinga jschmidt-icinga added the core/quality Improve code, libraries, algorithms, inline docs label Jun 25, 2026
@jschmidt-icinga jschmidt-icinga force-pushed the replace-legacy-strand branch from ca03f7c to 238e6b0 Compare June 25, 2026 09:10
@jschmidt-icinga jschmidt-icinga marked this pull request as ready for review June 25, 2026 12:32
);
void NewClientHandlerInternal(
boost::asio::yield_context yc, const Shared<boost::asio::io_context::strand>::Ptr& strand,
boost::asio::yield_context yc, const std::shared_ptr<IoStrand>& strand,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the std::shared_ptr?

@jschmidt-icinga jschmidt-icinga Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a section to the PR description about the reason. In short, it's because on older boost versions boost::asio::spawn() don't know what to do with the Shared<strand>& it gets as a result of dereferencing the pointer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion we should consider deprecating it and just use std::shared_ptr

Absolutely not. I introduced it specifically to save memory. We already had enough memory problems in the past, didn't we?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What memory does Shared<> save vs. std::shared_ptr<>? 🤔
It will save an additional allocation for the reference count in case someone doesn't use std::make_shared() as one should anyways. And in the rare cases where std::shared_from_this is actually needed, I think it stores an additional std::weak_ptr, but that's negligible for most our classes. At first glance I don't even see anything that would require the latter... we mostly use Shared<> for Stream objects, SSL context and things like that. std::shared_ptr should be perfectly fine for those, or am I missing something.

To be clear: I'm not saying deprecate boost::intrusive_ptr for our Object::Ptr, just shoehorning it into a generic shared pointer, which it was never meant to be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla/signed core/quality Improve code, libraries, algorithms, inline docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate from io_context::strand to strand<io_context>

2 participants