Making an ASIO asynchronous operation from flock()
So I need to flock (http://man7.org/linux/man-pages/man2/flock.2.html) something (pre-existing flock in other piece of software to interoperate with) and I though: "I think I'm actually starting to understand ASIO, let's make a proper ASIO asynchronous operation with flock". Long story short, I now know better: I will never get ASIO. So my main issues are with the background thread. According to https://www.boost.org/doc/libs/develop/doc/html/boost_asio/overview/core/thr... "the threads ... must block all signals". OK, it makes sense, but... - I can make pthread_sigmask() the first thing once the std::thread starts executing. But, I can't really guarantee the thread will never receive a signal, can I? There is a small amount of time between the thread starting to be able to receive signals and pthread_sigmask() doing its job. Modifying the signal mask before the background thread is created (so also from the "not-background" thread) doesn't seem a lot better. - How do I cancel the asynchronous operation? flock() is blocking, not a lot I can do to unblock it. I could send a signal to the background thread, but... they must be blocked, so no. I can't join the thread while it's blocked in flock(). So... should it be detached? I could then maybe just destroy the std::thread, but I still need to call the handler with boost::asio::error::operation_aborted... So, my question: I'm making it more difficult than it is? There is some pattern I should be following which makes all this simple? See what I have below (sorry for the camel case) ------------------------------------------------------------------------------- #include <boost/asio.hpp> #include <boost/beast/core/bind_handler.hpp> #include <boost/system/error_code.hpp> #include <boost/system/system_error.hpp> #include <fcntl.h> #include <iostream> #include <sys/file.h> #include <sys/stat.h> #include <sys/types.h> #include <thread> namespace asio = boost::asio; using error_code = boost::system::error_code; using system_error = boost::system::system_error; class FLockablePosixDescriptor : public asio::posix::descriptor { public: enum class FlockType : int { SHARED = LOCK_SH, EXCLUSIVE = LOCK_EX }; using asio::posix::descriptor::descriptor; FLockablePosixDescriptor(FLockablePosixDescriptor &&) = default; FLockablePosixDescriptor &operator=(FLockablePosixDescriptor &&) = default; ~FLockablePosixDescriptor(); template <typename FLockToken> BOOST_ASIO_INITFN_RESULT_TYPE(FLockToken, void(error_code)) asyncFLock(FlockType type, FLockToken &&token); void cancel(); void cancel(error_code &ec); private: template <typename FLockHandler> void doFLock(int fd, FlockType type, FLockHandler &handler); asio::io_context mBackgroundContext{1}; std::thread mThread; }; FLockablePosixDescriptor::~FLockablePosixDescriptor() { mBackgroundContext.stop(); mThread.join(); } template <typename FLockToken> BOOST_ASIO_INITFN_RESULT_TYPE(FLockToken, void(error_code)) FLockablePosixDescriptor::asyncFLock(FlockType type, FLockToken &&token) { asio::async_completion<FLockToken, void(error_code)> init(token); mBackgroundContext.post([this, fd = native_handle(), type, handler = std::move(init.completion_handler), work = asio::make_work_guard(get_executor())] { doFLock(fd, type, handler); }); mThread = std::thread([this] { mBackgroundContext.run(); }); return init.result.get(); } template <typename FLockHandler> void FLockablePosixDescriptor::doFLock(int fd, FlockType type, FLockHandler &handler) { error_code ec; if (flock(fd, static_cast<int>(type)) == -1) { ec = error_code(errno, boost::system::system_category()); } asio::post(get_executor(), boost::beast::bind_handler(handler, ec)); } void FLockablePosixDescriptor::cancel() { error_code ec; cancel(ec); if (ec) { throw system_error(ec, "cancel"); } } void FLockablePosixDescriptor::cancel(error_code &ec) { // TODO: Cancel flock asio::posix::descriptor::cancel(ec); } int main() { boost::asio::io_context ioc; int fd = open("l", O_RDONLY); FLockablePosixDescriptor desc(ioc, fd); desc.asyncFLock(FLockablePosixDescriptor::FlockType::SHARED, [](const error_code &ec) { std::clog << "Done " << ec << std::endl; }); ioc.run(); } -------------------------------------------------------------------------------
I'd suggest to use fcntl http://man7.org/linux/man-pages/man2/fcntl.2.html which immediately returns an error value if a conflicting lock is attempted to be taken (see under Advisory record locking; F_SETLK). Then you can use a timer to implement retry with exponential back-off or similar. Alternately, I'd take "block all signals" with a grain of salt. You can use signals from the real-time signal range that isn't used by the system, and install a no-op signal handler. It will interrupt blocking operations (cause them to return with EINTR), but I strongly doubt that it'll affect non-blocking operations. (Signals are delivered on return from kernel space to user space and checking for them "too often" would be a performance penalty.) -- Stian ________________________________ From: Boost-users <boost-users-bounces@lists.boost.org> on behalf of Cristian Morales Vega via Boost-users <boost-users@lists.boost.org> Sent: Monday, November 19, 2018 6:04:01 PM To: boost-users@lists.boost.org Cc: Cristian Morales Vega Subject: [Boost-users] Making an ASIO asynchronous operation from flock() So I need to flock (http://man7.org/linux/man-pages/man2/flock.2.html) something (pre-existing flock in other piece of software to interoperate with) and I though: "I think I'm actually starting to understand ASIO, let's make a proper ASIO asynchronous operation with flock". Long story short, I now know better: I will never get ASIO. So my main issues are with the background thread. According to https://www.boost.org/doc/libs/develop/doc/html/boost_asio/overview/core/thr... "the threads ... must block all signals". OK, it makes sense, but... - I can make pthread_sigmask() the first thing once the std::thread starts executing. But, I can't really guarantee the thread will never receive a signal, can I? There is a small amount of time between the thread starting to be able to receive signals and pthread_sigmask() doing its job. Modifying the signal mask before the background thread is created (so also from the "not-background" thread) doesn't seem a lot better. - How do I cancel the asynchronous operation? flock() is blocking, not a lot I can do to unblock it. I could send a signal to the background thread, but... they must be blocked, so no. I can't join the thread while it's blocked in flock(). So... should it be detached? I could then maybe just destroy the std::thread, but I still need to call the handler with boost::asio::error::operation_aborted... So, my question: I'm making it more difficult than it is? There is some pattern I should be following which makes all this simple? See what I have below (sorry for the camel case) ------------------------------------------------------------------------------- #include <boost/asio.hpp> #include <boost/beast/core/bind_handler.hpp> #include <boost/system/error_code.hpp> #include <boost/system/system_error.hpp> #include <fcntl.h> #include <iostream> #include <sys/file.h> #include <sys/stat.h> #include <sys/types.h> #include <thread> namespace asio = boost::asio; using error_code = boost::system::error_code; using system_error = boost::system::system_error; class FLockablePosixDescriptor : public asio::posix::descriptor { public: enum class FlockType : int { SHARED = LOCK_SH, EXCLUSIVE = LOCK_EX }; using asio::posix::descriptor::descriptor; FLockablePosixDescriptor(FLockablePosixDescriptor &&) = default; FLockablePosixDescriptor &operator=(FLockablePosixDescriptor &&) = default; ~FLockablePosixDescriptor(); template <typename FLockToken> BOOST_ASIO_INITFN_RESULT_TYPE(FLockToken, void(error_code)) asyncFLock(FlockType type, FLockToken &&token); void cancel(); void cancel(error_code &ec); private: template <typename FLockHandler> void doFLock(int fd, FlockType type, FLockHandler &handler); asio::io_context mBackgroundContext{1}; std::thread mThread; }; FLockablePosixDescriptor::~FLockablePosixDescriptor() { mBackgroundContext.stop(); mThread.join(); } template <typename FLockToken> BOOST_ASIO_INITFN_RESULT_TYPE(FLockToken, void(error_code)) FLockablePosixDescriptor::asyncFLock(FlockType type, FLockToken &&token) { asio::async_completion<FLockToken, void(error_code)> init(token); mBackgroundContext.post([this, fd = native_handle(), type, handler = std::move(init.completion_handler), work = asio::make_work_guard(get_executor())] { doFLock(fd, type, handler); }); mThread = std::thread([this] { mBackgroundContext.run(); }); return init.result.get(); } template <typename FLockHandler> void FLockablePosixDescriptor::doFLock(int fd, FlockType type, FLockHandler &handler) { error_code ec; if (flock(fd, static_cast<int>(type)) == -1) { ec = error_code(errno, boost::system::system_category()); } asio::post(get_executor(), boost::beast::bind_handler(handler, ec)); } void FLockablePosixDescriptor::cancel() { error_code ec; cancel(ec); if (ec) { throw system_error(ec, "cancel"); } } void FLockablePosixDescriptor::cancel(error_code &ec) { // TODO: Cancel flock asio::posix::descriptor::cancel(ec); } int main() { boost::asio::io_context ioc; int fd = open("l", O_RDONLY); FLockablePosixDescriptor desc(ioc, fd); desc.asyncFLock(FLockablePosixDescriptor::FlockType::SHARED, [](const error_code &ec) { std::clog << "Done " << ec << std::endl; }); ioc.run(); } ------------------------------------------------------------------------------- _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org https://lists.boost.org/mailman/listinfo.cgi/boost-users
On Mon, 19 Nov 2018 at 19:10, Stian Zeljko Vrba <vrba@quine.no> wrote:
I'd suggest to use fcntl http://man7.org/linux/man-pages/man2/fcntl.2.html which immediately returns an error value if a conflicting lock is attempted to be taken (see under Advisory record locking; F_SETLK). Then you can use a timer to implement retry with exponential back-off or similar.
Well, I could always call flock with LOCK_NB to implement that same strategy. But I would have liked to let the OS tell me instead of keep asking it.
Alternately, I'd take "block all signals" with a grain of salt. You can use signals from the real-time signal range that isn't used by the system, and install a no-op signal handler. It will interrupt blocking operations (cause them to return with EINTR), but I strongly doubt that it'll affect non-blocking operations. (Signals are delivered on return from kernel space to user space and checking for them "too often" would be a performance penalty.)
I was trying to do it "right" and implement a generic asynchronous operation. The client using such an operation could be using any signal, including the real time signals, for its own purpose. But I tried to do all this thinking "If ASIO does it with the blocking getaddrinfo then I can do it with flock". But after looking in more detail at how getaddrinfo in Linux ASIO works: a) There is a single thread, not one per async_resolv. resolving is not done in parallel, each operation is queued one after the other. b) ip::basic_resolver::cancel() doesn't even really try to cancel the current resolving operation, it just cancels any queued one. It only works because getaddrinfo is "blocking"... with an undocumented, implicit, timeout. 'a' is documented, but 'b' really surprised me since the docs say "This function __forces__ the completion of any pending asynchronous operations on the host resolver". Which I guess is not technically wrong, but still... So I'm leaning towards thinking it can not be done "right" with flock (or getaddrinfo, really), the OS interface is simply not good enough.
participants (2)
-
Cristian Morales Vega
-
Stian Zeljko Vrba