I finally implemented the Windows version of the mutex using a Relacy
test unit. It works fine. However, the POSIX version needs to properly
handle sem_wait return values wrt errno, Relacy simulates this and will
report a deadlock. I omitted all of the spin counting code you had and
just stuck to the main lock logic. Take a look at the code and make sure
I got everything right. Pretty sure I did. I had to add a destructor to
the Semaphore class to close the handle. Relacy reports a memory leak if
this is not accomplished.
Btw, the semaphore logic you use i the mutex itself is basically the
same as a benaphore:
https://www.haiku-os.org/legacy-docs/benewsletter/Issue1-26.html#Engineering1-26
Fwiw, here is the test code:
/*
Mutex with Spin Count
Algorithm by Bonita
Test by Chris M. Thomasson
_____________________________________________________*/
//#define RL_DEBUGBREAK_ON_ASSERT
//#define RL_MSVC_OUTPUT
//#define RL_FORCE_SEQ_CST
//#define RL_GC
#include <relacy/relacy_std.hpp>
#include <iostream>
#include <vector>
#include <algorithm>
#define CT_THREADS 3
struct Semaphore
{
Semaphore();
~Semaphore(); // dtor
void release();
void wait();
private:
HANDLE hSem;
};
Semaphore::Semaphore()
: hSem(CreateSemaphore(nullptr, 0, 0x7FFFFFFF, nullptr))
{}
Semaphore::~Semaphore()
{
CloseHandle(hSem);
}
void Semaphore::release()
{
ReleaseSemaphore(hSem, 1, nullptr);
}
void Semaphore::wait()
{
WaitForSingleObject(hSem, INFINITE);
}
struct SpinMutex
{
std::atomic<unsigned long> lockCounter;
unsigned long spinCount;
Semaphore sem;
SpinMutex(unsigned long spincount)
: lockCounter(0), spinCount(spincount)
{}
void lock()
{
for (unsigned long sc = spinCount; sc; --sc)
{
unsigned long cmp = 0;
if (lockCounter.compare_exchange_weak(
cmp,
1,
std::memory_order_acquire
)) {
return;
}
}
if (lockCounter.fetch_add(1, std::memory_order_acquire) != 0)
{
// slow path
sem.wait();
}
}
void unlock()
{
if (lockCounter.fetch_sub(1, std::memory_order_release) != 1)
{
// slow path
sem.release();
}
}
};
struct ct_experiment
: rl::test_suite<ct_experiment, CT_THREADS>
{
SpinMutex g_lock;
VAR_T(unsigned long) g_state;
ct_experiment() : g_lock(7), g_state(0) {}
void thread(unsigned int tidx)
{
//std::cout << "thread: " << tidx << "\n";
g_lock.lock();
VAR(g_state) += 1;
g_lock.unlock();
}
};
// Test away... Or fly? Humm...
int main()
{
{
rl::test_params p;
p.iteration_count = 10000000;
//p.execution_depth_limit = 33333;
//p.search_type = rl::sched_bound;
//p.search_type = rl::fair_full_search_scheduler_type;
//p.search_type = rl::fair_context_bound_scheduler_type;
rl::simulate<ct_experiment>(p);
}
return 0;
}