/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2018 Danny Robson <danny@nerdcruft.net>
 */

#include "flag.hpp"
#include "../cast.hpp"
#include "../posix/except.hpp"

#include <cerrno>
#include <linux/futex.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <limits>

using cruft::thread::flag;


///////////////////////////////////////////////////////////////////////////////
static long
sys_futex (void *addr1, int op, int val1, struct timespec *timeout, void *addr2, int val3)
{
    return syscall (SYS_futex, addr1, op | FUTEX_PRIVATE_FLAG, val1, timeout, addr2, val3);
}

///////////////////////////////////////////////////////////////////////////////
flag::flag ():
    value (0)
{ ; }


///////////////////////////////////////////////////////////////////////////////
void
flag::wait (void)
{
    if (value)
        return;

    while (!value) {
        auto res = sys_futex (&value, FUTEX_WAIT, 0, nullptr, nullptr, 0);

        if (res < 0) {
            switch (errno) {
            case EAGAIN: return;
            case EINTR:  continue;
            }

            posix::error::throw_code ();
        }
    }
}


///////////////////////////////////////////////////////////////////////////////
void
flag::notify_all (void)
{
    value = 1;
    auto res = sys_futex (&value, FUTEX_WAKE, std::numeric_limits<int>::max (), nullptr, nullptr, 0);
    if (res < 0)
        posix::error::throw_code ();
}