Skip to content

Commit

Permalink
Merge pull request #51 from nmcclatchey/bugfix-2019-02-22
Browse files Browse the repository at this point in the history
Update certain macros to modern C++ solution.
  • Loading branch information
alxvasilev authored Mar 5, 2019
2 parents 2d0b10a + 079f9e6 commit 4e22f33
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 63 deletions.
32 changes: 24 additions & 8 deletions mingw.mutex.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
#include <atomic>
#include <mutex> //need for call_once()

#if STDMUTEX_RECURSION_CHECKS
#if STDMUTEX_RECURSION_CHECKS || !defined(NDEBUG)
#include <cstdio>
#endif

Expand Down Expand Up @@ -293,7 +293,16 @@ class recursive_timed_mutex
{
inline bool try_lock_internal (DWORD ms) noexcept
{
return (WaitForSingleObject(mHandle, ms) == WAIT_OBJECT_0);
DWORD ret = WaitForSingleObject(mHandle, ms);
#ifndef NDEBUG
if (ret == WAIT_ABANDONED)
{
using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate();
}
#endif
return (ret == WAIT_OBJECT_0) || (ret == WAIT_ABANDONED);
}
protected:
HANDLE mHandle;
Expand All @@ -317,19 +326,26 @@ class recursive_timed_mutex
void lock()
{
DWORD ret = WaitForSingleObject(mHandle, INFINITE);
if (ret != WAIT_OBJECT_0)
// If (ret == WAIT_ABANDONED), then the thread that held ownership was
// terminated. Behavior is undefined, but Windows will pass ownership to this
// thread.
#ifndef NDEBUG
if (ret == WAIT_ABANDONED)
{
using namespace std;
throw system_error(make_error_code((ret == WAIT_ABANDONED) ?
errc::owner_dead :
errc::protocol_error));
fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate();
}
#endif
if ((ret != WAIT_OBJECT_0) && (ret != WAIT_ABANDONED))
{
throw std::system_error(GetLastError(), std::system_category());
}
}
void unlock()
{
using namespace std;
if (!ReleaseMutex(mHandle))
throw system_error(make_error_code(errc::resource_deadlock_would_occur));
throw std::system_error(GetLastError(), std::system_category());
}
bool try_lock()
{
Expand Down
7 changes: 2 additions & 5 deletions mingw.shared_mutex.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <atomic>
// For timing in shared_lock and shared_timed_mutex.
#include <chrono>
#include <limits>

// Use MinGW's shared_lock class template, if it's available. Requires C++14.
// If unavailable (eg. because this library is being used in C++11), then an
Expand All @@ -55,10 +56,6 @@
// Might be able to use native Slim Reader-Writer (SRW) locks.
#ifdef _WIN32
#include <windows.h>

#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501)
#error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher.
#endif
#endif

namespace mingw_stdthread
Expand All @@ -70,7 +67,7 @@ class shared_mutex
{
typedef uint_fast16_t counter_type;
std::atomic<counter_type> mCounter {0};
static constexpr counter_type kWriteBit = 1 << (sizeof(counter_type) * CHAR_BIT - 1);
static constexpr counter_type kWriteBit = 1 << (std::numeric_limits<counter_type>::digits - 1);

#if STDMUTEX_RECURSION_CHECKS
// Runtime checker for verifying owner threads. Note: Exclusive mode only.
Expand Down
107 changes: 57 additions & 50 deletions tests/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include <string>
#include <iostream>
#include <typeinfo>
#include <windows.h>

using namespace std;

Expand All @@ -22,10 +21,17 @@ std::mutex m;
std::shared_mutex sm;
std::condition_variable cv;
std::condition_variable_any cv_any;
#define LOG(fmtString,...) { printf(fmtString "\n", ##__VA_ARGS__); fflush(stdout); }

template<class ... Args>
void log (char const * fmtString, Args ...args) {
printf(fmtString, args...);
printf("\n");
fflush(stdout);
}

void test_call_once(int a, const char* str)
{
LOG("test_call_once called with a=%d, str=%s", a, str);
log("test_call_once called with a=%d, str=%s", a, str);
this_thread::sleep_for(std::chrono::milliseconds(500));
}

Expand Down Expand Up @@ -87,12 +93,12 @@ struct CustomAllocator
typedef T value_type;
T * allocate (size_t n)
{
LOG("Used custom allocator to allocate %zu object(s).", n);
log("Used custom allocator to allocate %zu object(s).", n);
return static_cast<T*>(std::malloc(n * sizeof(T)));
}
void deallocate (T * ptr, size_t n)
{
LOG("Used custom allocator to deallocate %zu object(s).", n);
log("Used custom allocator to deallocate %zu object(s).", n);
std::free(ptr);
}
};
Expand Down Expand Up @@ -127,7 +133,7 @@ void test_future ()
static_assert(is_copy_assignable<shared_future<T> >::value,
"std::shared_future must be copy-assignable.");

LOG("\t%s","Making a few promises, and getting their futures...");
log("\tMaking a few promises, and getting their futures...");
promise<T> promise_value, promise_exception, promise_broken, promise_late;

future<T> future_value = promise_value.get_future();
Expand All @@ -137,12 +143,12 @@ void test_future ()

try {
future<T> impossible_future = promise_value.get_future();
LOG("WARNING: %s","Promise failed to detect that its future was already retrieved.");
log("WARNING: Promise failed to detect that its future was already retrieved.");
} catch(...) {
LOG("\t%s","Promise successfully prevented redundant future retrieval.");
log("\tPromise successfully prevented redundant future retrieval.");
}

LOG("\t%s","Passing promises to a new thread...");
log("\tPassing promises to a new thread...");
thread t ([](promise<T> p_value, promise<T> p_exception, promise<T>, promise<T> p_late)
{
this_thread::sleep_for(std::chrono::seconds(1));
Expand All @@ -167,84 +173,85 @@ void test_future ()

try {
bool was_expected = test_future_get_value(future_value);
LOG("\tReceived %sexpected value.", (was_expected ? "" : "un"));
log("\tReceived %sexpected value.", (was_expected ? "" : "un"));
} catch (...) {
LOG("WARNING: %s","Exception where there should be none!");
log("WARNING: Exception where there should be none!");
throw;
}
try {
test_future_get_value(future_exception);
LOG("WARNING: %s","Got a value where there should be an exception!");
log("WARNING: Got a value where there should be an exception!");
} catch (std::exception & e) {
LOG("\tReceived an exception (\"%s\") as expected.", e.what());
log("\tReceived an exception (\"%s\") as expected.", e.what());
}

LOG("\t%s", "Waiting for the thread to exit...");
log("\tWaiting for the thread to exit...");
try {
test_future_get_value(future_late);
LOG("WARNING: %s","Got a value where there should be an exception!");
log("WARNING: Got a value where there should be an exception!");
} catch (std::exception & e) {
LOG("\tReceived an exception (\"%s\") as expected.", e.what());
log("\tReceived an exception (\"%s\") as expected.", e.what());
}

try {
test_future_get_value(future_broken);
LOG("WARNING: %s","Got a value where there should be an exception!");
log("WARNING: Got a value where there should be an exception!");
} catch (std::future_error & e) {
LOG("\tReceived a future_error (\"%s\") as expected.", e.what());
log("\tReceived a future_error (\"%s\") as expected.", e.what());
}

LOG("\t%s", "Deferring a function...");
log("\tDeferring a function...");
auto async_deferred = async(launch::deferred, [] (void) -> T
{
std::hash<std::thread::id> hasher;
LOG("\t\tDeferred function called on thread %zu", hasher(std::this_thread::get_id()));
log("\t\tDeferred function called on thread %zu", hasher(std::this_thread::get_id()));
if (!is_void<T>::value)
return T(test_int);
});
LOG("\t%s", "Calling a function asynchronously...");
log("\tCalling a function asynchronously...");
auto async_async = async(launch::async, [] (void) -> T
{
std::hash<std::thread::id> hasher;
LOG("\t\tAsynchronous function called on thread %zu", hasher(std::this_thread::get_id()));
log("\t\tAsynchronous function called on thread %zu", hasher(std::this_thread::get_id()));
if (!is_void<T>::value)
return T(test_int);
});
LOG("\t%s", "Letting the implementation decide...");
log("\tLetting the implementation decide...");
auto async_either = async([] (thread::id other_id) -> T
{
std::hash<thread::id> hasher;
LOG("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous");
log("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous");
if (!is_void<T>::value)
return T(test_int);
}, this_thread::get_id());

LOG("\t%s", "Fetching asynchronous result.");
log("\tFetching asynchronous result.");
test_future_get_value(async_async);
LOG("\t%s", "Fetching deferred result.");
log("\tFetching deferred result.");
test_future_get_value(async_deferred);
LOG("\t%s", "Fetching implementation-defined result.");
log("\tFetching implementation-defined result.");
test_future_get_value(async_either);

LOG("\t%s", "Testing async on pointer-to-member-function.");
log("\tTesting async on pointer-to-member-function.");
struct Helper
{
thread::id other_id;
T call (void) const
{
std::hash<thread::id> hasher;
LOG("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous");
log("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous");
if (!is_void<T>::value)
return T(test_int);
}
} test_class { this_thread::get_id() };
auto async_member = async(Helper::call, test_class);
LOG("\t%s", "Fetching result.");
log("\tFetching result.");
test_future_get_value(async_member);
}

#define TEST_SL_MV_CPY(ClassName) if (!std::is_standard_layout<ClassName>::value) \
LOG("WARNING: Class %s does not satisfy concept StandardLayoutType.","ClassName"); \
#define TEST_SL_MV_CPY(ClassName) \
static_assert(std::is_standard_layout<ClassName>::value, \
"ClassName does not satisfy concept StandardLayoutType."); \
static_assert(!std::is_move_constructible<ClassName>::value, \
"ClassName must not be move-constructible."); \
static_assert(!std::is_move_assignable<ClassName>::value, \
Expand Down Expand Up @@ -300,7 +307,7 @@ int main()
}

{
LOG("%s","Testing serialization and hashing for thread::id...");
log("Testing serialization and hashing for thread::id...");
std::cout << "Serialization:\t" << this_thread::get_id() << "\n";
std::hash<thread::id> hasher;
std::cout << "Hash:\t" << hasher(this_thread::get_id()) << "\n";
Expand All @@ -309,7 +316,7 @@ int main()
{
try
{
LOG("%s","Worker thread started, sleeping for a while...");
log("Worker thread started, sleeping for a while...");
// Thread might move the string more than once.
assert(a.mStr.substr(0, 15) == "move test moved");
assert(!strcmp(b, "test message"));
Expand All @@ -319,27 +326,27 @@ int main()
{
lock_guard<mutex> lock(m);
cond = 1;
LOG("%s","Notifying condvar");
log("Notifying condvar");
cv.notify_all();
}

this_thread::sleep_for(std::chrono::milliseconds(500));
{
lock_guard<decltype(sm)> lock(sm);
cond = 2;
LOG("%s","Notifying condvar");
log("Notifying condvar");
cv_any.notify_all();
}

this_thread::sleep_for(std::chrono::milliseconds(500));
{
lock_guard<decltype(sm)> lock(sm);
cond = 3;
LOG("%s","Notifying condvar");
log("Notifying condvar");
cv_any.notify_all();
}

LOG("%s","Worker thread finishing");
log("Worker thread finishing");
}
catch(std::exception& e)
{
Expand All @@ -349,51 +356,51 @@ int main()
TestMove("move test"), "test message", -20);
try
{
LOG("%s","Main thread: Locking mutex, waiting on condvar...");
log("Main thread: Locking mutex, waiting on condvar...");
{
std::unique_lock<decltype(m)> lk(m);
cv.wait(lk, []{ return cond >= 1;} );
LOG("condvar notified, cond = %d", cond);
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
LOG("%s","Main thread: Locking shared_mutex, waiting on condvar...");
log("Main thread: Locking shared_mutex, waiting on condvar...");
{
std::unique_lock<decltype(sm)> lk(sm);
cv_any.wait(lk, []{ return cond >= 2;} );
LOG("condvar notified, cond = %d", cond);
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
LOG("%s","Main thread: Locking shared_mutex in shared mode, waiting on condvar...");
log("Main thread: Locking shared_mutex in shared mode, waiting on condvar...");
{
std::shared_lock<decltype(sm)> lk(sm);
cv_any.wait(lk, []{ return cond >= 3;} );
LOG("condvar notified, cond = %d", cond);
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
LOG("%s","Main thread: Waiting on worker join...");
log("Main thread: Waiting on worker join...");

t.join();
LOG("%s","Main thread: Worker thread joined");
log("Main thread: Worker thread joined");
fflush(stdout);
}
catch(std::exception& e)
{
LOG("EXCEPTION in main thread: %s", e.what());
log("EXCEPTION in main thread: %s", e.what());
}
once_flag of;
call_once(of, test_call_once, 1, "test");
call_once(of, test_call_once, 1, "ERROR! Should not be called second time");
LOG("%s","Test complete");
log("Test complete");

{
LOG("%s","Testing implementation of <future>...");
log("Testing implementation of <future>...");
test_future<int>();
test_future<void>();
test_future<int &>();
test_future<int const &>();
test_future<int volatile &>();
test_future<int const volatile &>();
LOG("%s","Testing <future>'s use of allocators. Should allocate, then deallocate.");
log("Testing <future>'s use of allocators. Should allocate, then deallocate.");
promise<int> allocated_promise (std::allocator_arg, CustomAllocator<unsigned>());
allocated_promise.set_value(7);
}
Expand Down

0 comments on commit 4e22f33

Please sign in to comment.