while Objecter delivers librados completions directly by calling
Context::complete(), neorados completions are passed in as
boost::asio::any_completion_handler and delivered to an asio executor
via boost::asio::defer() on completion
asio handlers may have an "associated executor" so callers can customize
where these completions are delivered. for example, multithreaded
applications often use strand executors to synchronize completion
handlers and prevent data races between concurrent operations
however, applications like radosgw that depend on strands for
thread-safety did not get it due to the fact that Objecter's
Op::complete() delivered all neorados completions to the default
io_context executor
use boost::asio::get_associated_executor() to respect the handler's
executor affinity, if any. but because the Op's handler is the
type-erased any_completion_handler, its associated executor is also
type-erased as any_completion_executor. that any_completion_executor
doesn't support the blocking::never_t property required by defer/post,
so defer() was changed to dispatch() which may call the handler directly
if Objecter is already running on the requested executor. i assume this
is safe, given that librados' Context-based completions already do this
Fixes: https://tracker.ceph.com/issues/76725
Co-Authored-by: Oguzhan Ozmen <oozmen@bloomberg.net>
Signed-off-by: Casey Bodley <cbodley@redhat.com>
fu2::unique_function<OpSig>>) {
std::move(arg)(ec);
} else {
- boost::asio::defer(e,
- boost::asio::append(std::move(arg), ec));
+ auto ex = boost::asio::get_associated_executor(arg, e);
+ boost::asio::dispatch(ex,
+ boost::asio::append(std::move(arg), ec));
}
}, std::move(f));
}