Util/sync.h: Fix iterator invalidation in futex emulation

This commit is contained in:
Elad 2025-12-16 11:33:57 +02:00
parent 103d580d9a
commit 812d84e7f4
1 changed files with 42 additions and 5 deletions

View File

@ -86,11 +86,21 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t
std::condition_variable cv; std::condition_variable cv;
}; };
std::mutex mutex; struct bucket_t
std::unordered_multimap<volatile void*, waiter*> map; {
std::mutex mutex;
std::unordered_multimap<volatile void*, waiter*> map;
};
// Not a power of 2 on purpose (for alignment optimiations)
bucket_t bucks[63];
int operator()(volatile void* uaddr, int futex_op, uint val, const timespec* timeout, uint mask) int operator()(volatile void* uaddr, int futex_op, uint val, const timespec* timeout, uint mask)
{ {
auto& bucket = bucks[(reinterpret_cast<u64>(uaddr) / 8) % std::size(bucks)];
auto& mutex = bucket.mutex;
auto& map = bucket.map;
std::unique_lock lock(mutex); std::unique_lock lock(mutex);
switch (futex_op) switch (futex_op)
@ -111,7 +121,9 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t
waiter rec; waiter rec;
rec.val = val; rec.val = val;
rec.mask = mask; rec.mask = mask;
const auto itr = map.emplace(uaddr, &rec);
// Announce the waiter
map.emplace(uaddr, &rec);
int res = 0; int res = 0;
@ -127,6 +139,16 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t
{ {
res = -1; res = -1;
errno = ETIMEDOUT; errno = ETIMEDOUT;
// Cleanup
for (auto range = map.equal_range(uaddr); range.first != range.second; range.first++)
{
if (range.first->second == &rec)
{
map.erase(range.first);
break;
}
}
} }
} }
else else
@ -134,7 +156,6 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t
// TODO: absolute timeout // TODO: absolute timeout
} }
map.erase(itr);
return res; return res;
} }
@ -153,13 +174,29 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t
if (entry.mask & mask) if (entry.mask & mask)
{ {
entry.cv.notify_one();
entry.mask = 0; entry.mask = 0;
entry.cv.notify_one();
res++; res++;
val--; val--;
} }
} }
if (res)
{
// Cleanup
for (auto range = map.equal_range(uaddr); range.first != range.second;)
{
if (range.first->second->mask == 0)
{
map.erase(range.first);
range = map.equal_range(uaddr);
continue;
}
range.first++;
}
}
return res; return res;
} }
} }