blob: 04d033725a65461b2f0cf71e984a783f1e5e79e7 [file] [log] [blame]
#include <atomic>
#include <cstdlib>
#include <errno.h>
#include <event.h>
#include <locks.hh>
#include <stdint.h>
#include <thread.h>
using Debug = ConditionalDebug<false, "Event groups library">;
struct EventWaiter
{
std::atomic<uint32_t> bitsSeen;
bool waitForAll : 1;
bool clearOnExit : 1;
unsigned int bitsWanted : 24;
bool is_triggered(uint32_t bits)
{
Debug::log("bits wanted: {}, bits: {}, mask: {}",
bitsWanted,
bits,
(bitsWanted & bits));
return (waitForAll ? ((bitsWanted & bits) == bitsWanted)
: ((bitsWanted & bits) != 0));
};
};
struct EventGroup
{
FlagLock lock;
uint32_t bits;
size_t waiterCount;
EventWaiter waiters[];
};
int eventgroup_create(Timeout *timeout,
SObjStruct *heapCapability,
EventGroup **outGroup)
{
auto threads = thread_count();
if (threads == uint16_t(-1))
{
return -ERANGE;
}
size_t size = sizeof(EventGroup) + (threads * sizeof(EventWaiter));
auto group =
static_cast<EventGroup *>(heap_allocate(timeout, heapCapability, size));
*outGroup = group;
if (!__builtin_cheri_tag_get(group))
{
return -ENOMEM;
}
group->waiterCount = threads;
return 0;
}
int eventgroup_wait(Timeout *timeout,
EventGroup *group,
uint32_t *outBits,
uint32_t bitsWanted,
bool waitForAll,
bool clearOnExit)
{
// Bits wanted can be only a 24-bit value.
if (bitsWanted & 0xff000000)
{
return -ERANGE;
}
// Condition that holds if the bits are triggered.
auto isTriggered = [&](uint32_t bits) {
return (waitForAll ? ((bitsWanted & bits) == bitsWanted)
: ((bitsWanted & bits) != 0));
};
auto &waiter = group->waiters[thread_id_get()];
uint32_t bitsSeen;
// Set up our state for the waiter with the lock held.
if (LockGuard g{group->lock, timeout})
{
bitsSeen = group->bits;
// If the condition holds, return immediately
if (isTriggered(bitsSeen))
{
if (clearOnExit)
{
group->bits &= ~bitsWanted;
}
*outBits = bitsSeen;
return 0;
}
waiter.bitsWanted = bitsWanted;
waiter.clearOnExit = clearOnExit;
waiter.waitForAll = waitForAll;
waiter.bitsSeen = bitsSeen;
}
else
{
return -ETIMEDOUT;
}
// If the condition didn't hold, wait for
Debug::log("Waiting on futex {} ({})", &waiter.bitsSeen, bitsSeen);
while (waiter.bitsSeen.wait(timeout, bitsSeen) != -ETIMEDOUT)
{
bitsSeen = waiter.bitsSeen.load();
if (isTriggered(bitsSeen))
{
*outBits = bitsSeen;
return 0;
}
};
waiter.bitsWanted = 0;
*outBits = group->bits;
return -ETIMEDOUT;
}
int eventgroup_clear(Timeout *timeout,
EventGroup *group,
uint32_t *outBits,
uint32_t bitsToClear)
{
if (LockGuard g{group->lock, timeout})
{
Debug::log(
"Bits was {}, clearing with mask {}", group->bits, ~bitsToClear);
group->bits &= ~bitsToClear;
*outBits = group->bits;
return 0;
}
*outBits = group->bits;
return -ETIMEDOUT;
}
int eventgroup_set(Timeout *timeout,
EventGroup *group,
uint32_t *outBits,
uint32_t bitsToSet)
{
if (LockGuard g{group->lock, timeout})
{
Debug::log("Bits was {}, setting {}", group->bits, bitsToSet);
group->bits |= bitsToSet;
uint32_t bits = group->bits;
uint32_t bitsToClear = 0;
Debug::log("Bits {} are set", bits);
for (size_t i = 0; i < group->waiterCount; ++i)
{
auto &waiter = group->waiters[i];
Debug::log("Waiter {} wants bits {}", i, waiter.bitsWanted);
if (waiter.bitsWanted == 0)
{
continue;
}
Debug::log("Triggered? {}", waiter.is_triggered(bitsToSet));
if (waiter.is_triggered(bits))
{
if (waiter.clearOnExit)
{
bitsToClear |= (waiter.bitsWanted & bits);
}
waiter.bitsWanted = 0;
waiter.bitsSeen = bits;
Debug::log("Waking futex {} ({})", &waiter.bitsSeen, bits);
waiter.bitsSeen.notify_one();
}
}
Debug::log("Clearing bits {}", bitsToClear);
group->bits &= ~bitsToClear;
*outBits = group->bits;
return 0;
}
*outBits = group->bits;
return -ETIMEDOUT;
}
int eventgroup_get(EventGroup *group, uint32_t *outBits)
{
*outBits = group->bits;
return 0;
}
int eventgroup_destroy_force(SObjStruct *heapCapability, EventGroup *group)
{
group->lock.upgrade_for_destruction();
// Force all waiters to wake.
for (size_t i = 0; i < group->waiterCount; ++i)
{
// Notifying is not enough, we need to ensure that waiters get
// appropriate bits to leave the loop.
auto &waiter = group->waiters[i];
uint32_t bits = waiter.bitsWanted;
waiter.bitsWanted = 0;
waiter.bitsSeen = bits;
waiter.bitsSeen.notify_one();
}
heap_free(heapCapability, group);
return 0;
}
int eventgroup_destroy(SObjStruct *heapCapability, EventGroup *group)
{
group->lock.lock();
return eventgroup_destroy_force(heapCapability, group);
}