Making an ASIO asynchronous operation from flock()

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Making an ASIO asynchronous operation from flock()

Boost - Users mailing list
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/threads.html
"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
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: Making an ASIO asynchronous operation from flock()

Boost - Users mailing list

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 <[hidden email]> on behalf of Cristian Morales Vega via Boost-users <[hidden email]>
Sent: Monday, November 19, 2018 6:04:01 PM
To: [hidden email]
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/threads.html
"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
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users

_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: Making an ASIO asynchronous operation from flock()

Boost - Users mailing list
On Mon, 19 Nov 2018 at 19:10, Stian Zeljko Vrba <[hidden email]> 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.
_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users