-2

Handling errno is one of the struggle of using POSIX API over a multithreaded environment. Would it be reasonable to use a lock using std::atomic like the following one?

class FastLock{
    std::atomic_int value;
public:
    FastLock()
    : value{0}{}

    void unlock()
    {
        value.store(0,std::memory_order_release);
    }

    bool try_lock()
    {
        int r = value.exchange(1,std::memory_order_acquire);
        return !r;
    }
};

The context would be similar to this:

template<typename function, typename ...args>
auto shield(function _fn){
    static FastLock* lk = new FastLock{};
    return [=](args... _v){
        while(lk->try_lock());
        auto ret = std::forward(_fn, _v...);
        auto errval = errno;
        lk->unlock();
        return std::make_pair(ret,errval);
    };
}

Would this result in any sort of either undefined behaviour or implementation defined behaviour?

  • 2
    POSIX errno is thread-safe - see https://stackoverflow.com/questions/1694164/is-errno-thread-safe –  Dec 03 '18 at 23:02
  • Also, this method is not exception safe. It also burns unnecessary CPU – Mooing Duck Dec 03 '18 at 23:54
  • @NeilButterworth it is only for a posix version high enough, as specified in the answer. – Ludovic Zenohate Lagouardette Dec 03 '18 at 23:58
  • @MooingDuck No exception can occur as long as the function is properly set and the return types are primitives. Also, now I have confirmation of the POSIX version which uses threadlocal variable, I added a check to integrate the lock only if it is needed. Also, it is to be expected that the virtual table access of the function to be obtained at compile time as an optimization. – Ludovic Zenohate Lagouardette Dec 04 '18 at 00:05
  • @LudovicZenohateLagouardette: I believe you're mistaken. `std::forward(_fn, _v...)` can throw an exception – Mooing Duck Dec 04 '18 at 00:25
  • @MooingDuck only and only if the called function happen to be throwing. Which is, in the case of POSIX functions, never the case. Also, this was meant as a demonstration purpose, the actual used implementation is different and uses a templated functor instead – Ludovic Zenohate Lagouardette Dec 04 '18 at 00:48

1 Answers1

3

While the old POSIX standard from 1988 used to require errno to be a global object, that is no longer the case in later revisions. At least POSIX.1-2001 requires errno to be thread local. I suspect that this was already required by POSIX.1c-1995 which specifies POSIX threads, but I don't have access to that document, so I cannot verify.

I would not expect that a POSIX system that supports C++11 wouldn't also support POSIX 2001 and as such, the use of C++11 atomics seems unlikely to be necessary.

That said, while C standard doesn't require errno to be a global object (at least, C99 doesn't) it also doesn't guarantee thread locality of errno. So, locking errno might be necessary on non-POSIX systems or old POSIX systems that don't provide thread locality. And if for some reason the platform does support C++11, then C++11 atomics might be the ideal choice for implementing the lock - or it might not be. At least in theory.

Note that to guarantee thread safety, you would have to make sure that all calls to functions that might set errno must use the lock. If you use any libraries that might use such standard functions, you must lock calls to such functions as well.

eerorika
  • 232,697
  • 12
  • 197
  • 326