From ea2b4fb4935627e4dea48f193eeb0019155a3abe Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 10 May 2022 09:48:08 +0200 Subject: Use our own implementation of C++14 threads on MinGW --- LICENSE | 1 + libbutl/buildfile | 15 +- libbutl/builtin.cxx | 4 +- libbutl/builtin.hxx | 33 +++- libbutl/builtin.ixx | 4 +- libbutl/diagnostics.cxx | 12 +- libbutl/mingw-condition_variable.hxx | 267 ++++++++++++++++++++++++++++ libbutl/mingw-invoke.hxx | 109 ++++++++++++ libbutl/mingw-mutex.hxx | 215 +++++++++++++++++++++++ libbutl/mingw-shared_mutex.hxx | 124 +++++++++++++ libbutl/mingw-thread.hxx | 330 +++++++++++++++++++++++++++++++++++ libbutl/process-details.hxx | 27 ++- manifest | 2 +- tests/fdstream/driver.cxx | 13 +- 14 files changed, 1137 insertions(+), 19 deletions(-) create mode 100644 libbutl/mingw-condition_variable.hxx create mode 100644 libbutl/mingw-invoke.hxx create mode 100644 libbutl/mingw-mutex.hxx create mode 100644 libbutl/mingw-shared_mutex.hxx create mode 100644 libbutl/mingw-thread.hxx diff --git a/LICENSE b/LICENSE index 27bab99..de2b5a9 100644 --- a/LICENSE +++ b/LICENSE @@ -4,6 +4,7 @@ libbutl/sha1.c: libbutl/{sha256c.c, strptime.c, timelocal.[hc]}: libbutl/{xxhash.[hc], lz4*.[hc]}: +libbutl/mingw-*.hxx: 2-clause BSD License; see the file headers for details. diff --git a/libbutl/buildfile b/libbutl/buildfile index b2dc8f7..499800c 100644 --- a/libbutl/buildfile +++ b/libbutl/buildfile @@ -3,6 +3,7 @@ lib{butl}: {hxx ixx txx cxx}{** -uuid-* +uuid-io \ -win32-utility \ + -mingw-* \ -version \ -builtin-options} \ {hxx}{version} {hxx ixx cxx}{builtin-options} @@ -12,9 +13,15 @@ tsys = $cxx.target.system windows = ($tclass == 'windows') -# Exclude these from compilation on non-Windows targets. +# Whether to use our own implementation of C++14 threads on MinGW (note: +# requires Windows 7 or later). +# +mingw_stdthread = ($tsys == 'mingw32') + +# Exclude these from compilation on targets where does not apply. # lib{butl}: {hxx ixx cxx}{win32-utility}: include = $windows +lib{butl}: hxx{mingw-*}: include = $mingw_stdthread # Our C-files are always included into C++-files that wrap the corresponding # API so treat them as files exclude from the compilation. @@ -72,6 +79,9 @@ hxx{version}: # cxx.poptions =+ "-I$out_root" "-I$src_root" +if $mingw_stdthread + cxx.poptions += -D_WIN32_WINNT=0x0601 -DLIBBUTL_MINGW_STDTHREAD + obja{*} bmia{*}: cxx.poptions += -DLIBBUTL_STATIC_BUILD objs{*} bmis{*}: cxx.poptions += -DLIBBUTL_SHARED_BUILD @@ -79,6 +89,9 @@ objs{*} bmis{*}: cxx.poptions += -DLIBBUTL_SHARED_BUILD # lib{butl}: cxx.export.poptions = "-I$out_root" "-I$src_root" +if $mingw_stdthread + lib{butl}: cxx.export.poptions += -D_WIN32_WINNT=0x0601 -DLIBBUTL_MINGW_STDTHREAD + liba{butl}: cxx.export.poptions += -DLIBBUTL_STATIC libs{butl}: cxx.export.poptions += -DLIBBUTL_SHARED diff --git a/libbutl/builtin.cxx b/libbutl/builtin.cxx index 4d6b60d..b13a59a 100644 --- a/libbutl/builtin.cxx +++ b/libbutl/builtin.cxx @@ -2246,7 +2246,7 @@ namespace butl { if (state_ != nullptr) { - unique_lock l (state_->mutex); + unique_lock l (state_->mutex); if (!state_->finished) state_->condv.wait (l, [this] {return state_->finished;}); @@ -2261,7 +2261,7 @@ namespace butl { if (state_ != nullptr) { - unique_lock l (state_->mutex); + unique_lock l (state_->mutex); if (!state_->finished && !state_->condv.wait_for (l, tm, [this] {return state_->finished;})) diff --git a/libbutl/builtin.hxx b/libbutl/builtin.hxx index 2398c84..b8546be 100644 --- a/libbutl/builtin.hxx +++ b/libbutl/builtin.hxx @@ -4,17 +4,24 @@ #pragma once #include -#include #include #include -#include #include #include // unique_ptr #include // size_t #include // move() #include // uint8_t #include -#include + +#ifndef LIBBUTL_MINGW_STDTHREAD +# include +# include +# include +#else +# include +# include +# include +#endif #include #include @@ -56,12 +63,26 @@ namespace butl ~builtin () {if (state_ != nullptr) state_->thread.join ();} public: +#ifndef LIBBUTL_MINGW_STDTHREAD + using mutex_type = std::mutex; + using condition_variable_type = std::condition_variable; + using thread_type = std::thread; + + using unique_lock = std::unique_lock; +#else + using mutex_type = mingw_stdthread::mutex; + using condition_variable_type = mingw_stdthread::condition_variable; + using thread_type = mingw_stdthread::thread; + + using unique_lock = mingw_stdthread::unique_lock; +#endif + struct async_state { bool finished = false; - std::mutex mutex; - std::condition_variable condv; - std::thread thread; + mutex_type mutex; + condition_variable_type condv; + thread_type thread; // Note that we can't use std::function as an argument type to get rid // of the template since std::function can only be instantiated with a diff --git a/libbutl/builtin.ixx b/libbutl/builtin.ixx index 0356f8b..24fbae3 100644 --- a/libbutl/builtin.ixx +++ b/libbutl/builtin.ixx @@ -25,7 +25,7 @@ namespace butl { if (state_ != nullptr) { - std::unique_lock l (state_->mutex); + unique_lock l (state_->mutex); if (!state_->finished) return nullopt; @@ -53,7 +53,7 @@ namespace butl f (); { - std::unique_lock l (this->mutex); + unique_lock l (this->mutex); finished = true; } diff --git a/libbutl/diagnostics.cxx b/libbutl/diagnostics.cxx index 0826375..f574fd6 100644 --- a/libbutl/diagnostics.cxx +++ b/libbutl/diagnostics.cxx @@ -17,6 +17,12 @@ #include // size_t #include // cerr +#ifndef LIBBUTL_MINGW_STDTHREAD +# include +#else +# include +#endif + #include // thread_local #include @@ -29,7 +35,11 @@ namespace butl { ostream* diag_stream = &cerr; - static mutex diag_mutex; +#ifndef LIBBUTL_MINGW_STDTHREAD + static std::mutex diag_mutex; +#else + static mingw_stdthread::mutex diag_mutex; +#endif string diag_progress; static string diag_progress_blank; // Being printed blanks out the line. diff --git a/libbutl/mingw-condition_variable.hxx b/libbutl/mingw-condition_variable.hxx new file mode 100644 index 0000000..ce94941 --- /dev/null +++ b/libbutl/mingw-condition_variable.hxx @@ -0,0 +1,267 @@ +/** +* std::condition_variable implementation for MinGW-w64 +* +* Copyright (c) 2013-2016 by Mega Limited, Auckland, New Zealand +* Copyright (c) 2022 the build2 authors +* +* Licensed under the simplified (2-clause) BSD License. +* You should have received a copy of the license along with this +* program. +* +* This code is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#ifndef LIBBUTL_MINGW_CONDITION_VARIABLE_HXX +#define LIBBUTL_MINGW_CONDITION_VARIABLE_HXX + +#if !defined(__cplusplus) || (__cplusplus < 201402L) +# error C++14 compiler required +#endif + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601 +# error _WIN32_WINNT should be 0x0601 (Windows 7) or greater +#endif + +#include // Use std::cv_status, if available. + +#include +#include +#include + +#include + +#include +#include + +namespace mingw_stdthread +{ +#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS) + enum class cv_status { no_timeout, timeout }; +#else + using std::cv_status; +#endif + + // Native condition variable-based implementation. + // + class condition_variable + { + static constexpr DWORD kInfinite = 0xffffffffl; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" + CONDITION_VARIABLE cvariable_ = CONDITION_VARIABLE_INIT; +#pragma GCC diagnostic pop + + friend class condition_variable_any; + + bool wait_unique (mutex * pmutex, DWORD time) + { + BOOL success = SleepConditionVariableSRW(native_handle(), + pmutex->native_handle(), + time, +// CONDITION_VARIABLE_LOCKMODE_SHARED has a value not specified by +// Microsoft's Dev Center, but is known to be (convertible to) a ULONG. To +// ensure that the value passed to this function is not equal to Microsoft's +// constant, we can either use a static_assert, or simply generate an +// appropriate value. + !CONDITION_VARIABLE_LOCKMODE_SHARED); + return success; + } + bool wait_impl (unique_lock & lock, DWORD time) + { + mutex * pmutex = lock.release(); + bool success = wait_unique(pmutex, time); + lock = unique_lock(*pmutex, adopt_lock); + return success; + } +public: + using native_handle_type = PCONDITION_VARIABLE; + native_handle_type native_handle () + { + return &cvariable_; + } + + condition_variable () = default; + ~condition_variable () = default; + + condition_variable (const condition_variable &) = delete; + condition_variable & operator= (const condition_variable &) = delete; + + void notify_one () noexcept + { + WakeConditionVariable(&cvariable_); + } + + void notify_all () noexcept + { + WakeAllConditionVariable(&cvariable_); + } + + void wait (unique_lock & lock) + { + wait_impl(lock, kInfinite); + } + + template + void wait (unique_lock & lock, Predicate pred) + { + while (!pred()) + wait(lock); + } + + template + cv_status wait_for(unique_lock& lock, + const std::chrono::duration& rel_time) + { + using namespace std::chrono; + auto timeout = duration_cast(rel_time).count(); + DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (kInfinite - 1); + bool result = wait_impl(lock, waittime) || (timeout >= kInfinite); + return result ? cv_status::no_timeout : cv_status::timeout; + } + + template + bool wait_for(unique_lock& lock, + const std::chrono::duration& rel_time, + Predicate pred) + { + return wait_until(lock, + std::chrono::steady_clock::now() + rel_time, + std::move(pred)); + } + template + cv_status wait_until (unique_lock& lock, + const std::chrono::time_point& abs_time) + { + return wait_for(lock, abs_time - Clock::now()); + } + template + bool wait_until (unique_lock& lock, + const std::chrono::time_point& abs_time, + Predicate pred) + { + while (!pred()) + { + if (wait_until(lock, abs_time) == cv_status::timeout) + { + return pred(); + } + } + return true; + } + }; + + class condition_variable_any + { + static constexpr DWORD kInfinite = 0xffffffffl; + + condition_variable internal_cv_ {}; + mutex internal_mutex_ {}; + + template + bool wait_impl (L & lock, DWORD time) + { + unique_lock internal_lock(internal_mutex_); + lock.unlock(); + bool success = internal_cv_.wait_impl(internal_lock, time); + lock.lock(); + return success; + } + // If the lock happens to be called on a native Windows mutex, skip any + // extra contention. + inline bool wait_impl (unique_lock & lock, DWORD time) + { + return internal_cv_.wait_impl(lock, time); + } + bool wait_impl (unique_lock & lock, DWORD time) + { + shared_mutex * pmutex = lock.release(); + bool success = internal_cv_.wait_unique(pmutex, time); + lock = unique_lock(*pmutex, adopt_lock); + return success; + } + bool wait_impl (shared_lock & lock, DWORD time) + { + shared_mutex * pmutex = lock.release(); + BOOL success = SleepConditionVariableSRW(native_handle(), + pmutex->native_handle(), time, + CONDITION_VARIABLE_LOCKMODE_SHARED); + lock = shared_lock(*pmutex, adopt_lock); + return success; + } + public: + using native_handle_type = typename condition_variable::native_handle_type; + + native_handle_type native_handle () + { + return internal_cv_.native_handle(); + } + + void notify_one () noexcept + { + internal_cv_.notify_one(); + } + + void notify_all () noexcept + { + internal_cv_.notify_all(); + } + + condition_variable_any () = default; + ~condition_variable_any () = default; + + template + void wait (L & lock) + { + wait_impl(lock, kInfinite); + } + + template + void wait (L & lock, Predicate pred) + { + while (!pred()) + wait(lock); + } + + template + cv_status wait_for(L& lock, const std::chrono::duration& period) + { + using namespace std::chrono; + auto timeout = duration_cast(period).count(); + DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (kInfinite - 1); + bool result = wait_impl(lock, waittime) || (timeout >= kInfinite); + return result ? cv_status::no_timeout : cv_status::timeout; + } + + template + bool wait_for(L& lock, const std::chrono::duration& period, + Predicate pred) + { + return wait_until(lock, std::chrono::steady_clock::now() + period, + std::move(pred)); + } + template + cv_status wait_until (L& lock, + const std::chrono::time_point& abs_time) + { + return wait_for(lock, abs_time - Clock::now()); + } + template + bool wait_until (L& lock, + const std::chrono::time_point& abs_time, + Predicate pred) + { + while (!pred()) + { + if (wait_until(lock, abs_time) == cv_status::timeout) + { + return pred(); + } + } + return true; + } + }; +} + +#endif // LIBBUTL_MINGW_CONDITION_VARIABLE_HXX diff --git a/libbutl/mingw-invoke.hxx b/libbutl/mingw-invoke.hxx new file mode 100644 index 0000000..65810e7 --- /dev/null +++ b/libbutl/mingw-invoke.hxx @@ -0,0 +1,109 @@ +/** +* Lightweight std::invoke() implementation for C++11 and C++14 +* +* Copyright (c) 2018-2019 by Nathaniel J. McClatchey, San Jose, CA, United States +* Copyright (c) 2022 the build2 authors +* +* Licensed under the simplified (2-clause) BSD License. +* You should have received a copy of the license along with this +* program. +* +* This code is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#ifndef LIBBUTL_MINGW_INVOKE_HXX +#define LIBBUTL_MINGW_INVOKE_HXX + +#include // For std::result_of, etc. +#include // For std::forward +#include // For std::reference_wrapper + +namespace mingw_stdthread +{ + namespace detail + { + // For compatibility, implement std::invoke for C++11 and C++14. + // + template + struct Invoker + { + template + inline static typename std::result_of::type invoke (F&& f, Args&&... args) + { + return std::forward(f)(std::forward(args)...); + } + }; + template + struct InvokerHelper; + + template<> + struct InvokerHelper + { + template + inline static auto get (T1&& t1) -> decltype(*std::forward(t1)) + { + return *std::forward(t1); + } + + template + inline static auto get (const std::reference_wrapper& t1) -> decltype(t1.get()) + { + return t1.get(); + } + }; + + template<> + struct InvokerHelper + { + template + inline static auto get (T1&& t1) -> decltype(std::forward(t1)) + { + return std::forward(t1); + } + }; + + template<> + struct Invoker + { + template + inline static auto invoke (F T::* f, T1&& t1, Args&&... args) -> \ + decltype((InvokerHelper::type>::value>::get(std::forward(t1)).*f)(std::forward(args)...)) + { + return (InvokerHelper::type>::value>::get(std::forward(t1)).*f)(std::forward(args)...); + } + }; + + template<> + struct Invoker + { + template + inline static auto invoke (F T::* f, T1&& t1, Args&&... args) -> \ + decltype(InvokerHelper::type>::value>::get(t1).*f) + { + return InvokerHelper::type>::value>::get(t1).*f; + } + }; + + template + struct InvokeResult + { + typedef Invoker::type>::value, + std::is_member_object_pointer::type>::value && + (sizeof...(Args) == 1)> invoker; + inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward(f), std::forward(args)...)) + { + return invoker::invoke(std::forward(f), std::forward(args)...); + } + }; + + template + auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult::invoke(std::forward(f), std::forward(args)...)) + { + return InvokeResult::invoke(std::forward(f), std::forward(args)...); + } + } +} + +#endif // LIBBUTL_MINGW_INVOKE_HXX diff --git a/libbutl/mingw-mutex.hxx b/libbutl/mingw-mutex.hxx new file mode 100644 index 0000000..375a572 --- /dev/null +++ b/libbutl/mingw-mutex.hxx @@ -0,0 +1,215 @@ +/** +* std::mutex et al implementation for MinGW-w64 +* +* Copyright (c) 2013-2016 by Mega Limited, Auckland, New Zealand +* Copyright (c) 2022 the build2 authors +* +* Licensed under the simplified (2-clause) BSD License. +* You should have received a copy of the license along with this +* program. +* +* This code is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#ifndef LIBBUTL_MINGW_MUTEX_HXX +#define LIBBUTL_MINGW_MUTEX_HXX + +#if !defined(__cplusplus) || (__cplusplus < 201402L) +# error C++14 compiler required +#endif + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601 +# error _WIN32_WINNT should be 0x0601 (Windows 7) or greater +#endif + +#include +#include +#include + +#include + +#include // For InitializeCriticalSection, etc. +#include // For GetLastError +#include + +namespace mingw_stdthread +{ + // To make this namespace equivalent to the thread-related subset of std, + // pull in the classes and class templates supplied by std but not by this + // implementation. + // + using std::lock_guard; + using std::unique_lock; + using std::adopt_lock_t; + using std::defer_lock_t; + using std::try_to_lock_t; + using std::adopt_lock; + using std::defer_lock; + using std::try_to_lock; + + class recursive_mutex + { + CRITICAL_SECTION mHandle; + public: + typedef LPCRITICAL_SECTION native_handle_type; + native_handle_type native_handle() {return &mHandle;} + recursive_mutex() noexcept : mHandle() + { + InitializeCriticalSection(&mHandle); + } + recursive_mutex (const recursive_mutex&) = delete; + recursive_mutex& operator=(const recursive_mutex&) = delete; + ~recursive_mutex() noexcept + { + DeleteCriticalSection(&mHandle); + } + void lock() + { + EnterCriticalSection(&mHandle); + } + void unlock() + { + LeaveCriticalSection(&mHandle); + } + bool try_lock() + { + return (TryEnterCriticalSection(&mHandle)!=0); + } + }; + + // Slim Reader-Writer (SRW)-based implementation that requires Windows 7. + // +#if !defined(SRWLOCK_INIT) +#error SRWLOCK_INIT macro is not defined +//#define SRWLOCK_INIT {0} +#endif + + class mutex + { + protected: + SRWLOCK mHandle; + public: + typedef PSRWLOCK native_handle_type; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" + constexpr mutex () noexcept : mHandle(SRWLOCK_INIT) { } +#pragma GCC diagnostic pop + mutex (const mutex&) = delete; + mutex & operator= (const mutex&) = delete; + void lock () + { + AcquireSRWLockExclusive(&mHandle); + } + void unlock () + { + ReleaseSRWLockExclusive(&mHandle); + } + // TryAcquireSRW functions are a Windows 7 feature. + bool try_lock () + { + BOOL ret = TryAcquireSRWLockExclusive(&mHandle); + return ret; + } + native_handle_type native_handle () + { + return &mHandle; + } + }; + + class recursive_timed_mutex + { + static constexpr DWORD kWaitAbandoned = 0x00000080l; + static constexpr DWORD kWaitObject0 = 0x00000000l; + static constexpr DWORD kInfinite = 0xffffffffl; + inline bool try_lock_internal (DWORD ms) noexcept + { + DWORD ret = WaitForSingleObject(mHandle, ms); + + /* + @@ TODO +#ifndef NDEBUG + if (ret == kWaitAbandoned) + { + using namespace std; + fprintf(stderr, "FATAL: Thread terminated while holding a mutex."); + terminate(); + } +#endif + */ + + return (ret == kWaitObject0) || (ret == kWaitAbandoned); + } + protected: + HANDLE mHandle; + public: + typedef HANDLE native_handle_type; + native_handle_type native_handle() const {return mHandle;} + recursive_timed_mutex(const recursive_timed_mutex&) = delete; + recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete; + recursive_timed_mutex(): mHandle(CreateMutex(NULL, FALSE, NULL)) {} + ~recursive_timed_mutex() + { + CloseHandle(mHandle); + } + void lock() + { + DWORD ret = WaitForSingleObject(mHandle, kInfinite); + + /* + @@ TODO + +// 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 == kWaitAbandoned) + { + using namespace std; + fprintf(stderr, "FATAL: Thread terminated while holding a mutex."); + terminate(); + } +#endif + */ + + if ((ret != kWaitObject0) && (ret != kWaitAbandoned)) + { + throw std::system_error(GetLastError(), std::system_category()); + } + } + void unlock() + { + if (!ReleaseMutex(mHandle)) + throw std::system_error(GetLastError(), std::system_category()); + } + bool try_lock() + { + return try_lock_internal(0); + } + template + bool try_lock_for(const std::chrono::duration& dur) + { + using namespace std::chrono; + auto timeout = duration_cast(dur).count(); + while (timeout > 0) + { + constexpr auto kMaxStep = static_cast(kInfinite-1); + auto step = (timeout < kMaxStep) ? timeout : kMaxStep; + if (try_lock_internal(static_cast(step))) + return true; + timeout -= step; + } + return false; + } + template + bool try_lock_until(const std::chrono::time_point& timeout_time) + { + return try_lock_for(timeout_time - Clock::now()); + } + }; + + typedef recursive_timed_mutex timed_mutex; +} + +#endif // LIBBUTL_MINGW_MUTEX_HXX diff --git a/libbutl/mingw-shared_mutex.hxx b/libbutl/mingw-shared_mutex.hxx new file mode 100644 index 0000000..aacbaf8 --- /dev/null +++ b/libbutl/mingw-shared_mutex.hxx @@ -0,0 +1,124 @@ +/** +* std::shared_mutex et al implementation for MinGW-w64 +* +* Copyright (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States +* Copyright (c) 2022 the build2 authors +* +* Licensed under the simplified (2-clause) BSD License. +* You should have received a copy of the license along with this +* program. +* +* This code is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#ifndef LIBBUTL_MINGW_SHARED_MUTEX_HXX +#define LIBBUTL_MINGW_SHARED_MUTEX_HXX + +#if !defined(__cplusplus) || (__cplusplus < 201402L) +# error C++14 compiler required +#endif + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601 +# error _WIN32_WINNT should be 0x0601 (Windows 7) or greater +#endif + +#include +// For descriptive errors. +#include +// For timing in shared_timed_mutex. +#include +#include + +#include // shared_lock + +// For defer_lock_t, adopt_lock_t, and try_to_lock_t +#include + +#include + +namespace mingw_stdthread +{ + using std::shared_lock; + + class condition_variable_any; + + // Slim Reader-Writer (SRW)-based implementation that requires Windows 7. + // + class shared_mutex : mutex + { + friend class condition_variable_any; + public: + using mutex::native_handle_type; + using mutex::lock; + using mutex::try_lock; + using mutex::unlock; + using mutex::native_handle; + + void lock_shared () + { + AcquireSRWLockShared(&mHandle); + } + + void unlock_shared () + { + ReleaseSRWLockShared(&mHandle); + } + + bool try_lock_shared () + { + return TryAcquireSRWLockShared(&mHandle) != 0; + } + }; + + class shared_timed_mutex : shared_mutex + { + typedef shared_mutex Base; + public: + using Base::lock; + using Base::try_lock; + using Base::unlock; + using Base::lock_shared; + using Base::try_lock_shared; + using Base::unlock_shared; + + template< class Clock, class Duration > + bool try_lock_until ( const std::chrono::time_point& cutoff ) + { + do + { + if (try_lock()) + return true; + } + while (std::chrono::steady_clock::now() < cutoff); + return false; + } + + template< class Rep, class Period > + bool try_lock_for (const std::chrono::duration& rel_time) + { + return try_lock_until(std::chrono::steady_clock::now() + rel_time); + } + + template< class Clock, class Duration > + bool try_lock_shared_until ( const std::chrono::time_point& cutoff ) + { + do + { + if (try_lock_shared()) + return true; + } + while (std::chrono::steady_clock::now() < cutoff); + return false; + } + + template< class Rep, class Period > + bool try_lock_shared_for (const std::chrono::duration& rel_time) + { + return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time); + } + }; +} + +#endif // LIBBUTL_MINGW_SHARED_MUTEX_HXX diff --git a/libbutl/mingw-thread.hxx b/libbutl/mingw-thread.hxx new file mode 100644 index 0000000..b308dde --- /dev/null +++ b/libbutl/mingw-thread.hxx @@ -0,0 +1,330 @@ +/** +* std::thread implementation for MinGW-w64 +* +* Copyright (c) 2013-2016 by Mega Limited, Auckland, New Zealand +* Copyright (c) 2022 the build2 authors +* +* Licensed under the simplified (2-clause) BSD License. +* You should have received a copy of the license along with this +* program. +* +* This code is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#ifndef LIBBUTL_MINGW_THREAD_HXX +#define LIBBUTL_MINGW_THREAD_HXX + +#if !defined(__cplusplus) || (__cplusplus < 201402L) +# error C++14 compiler required +#endif + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601 +# error _WIN32_WINNT should be 0x0601 (Windows 7) or greater +#endif + +#include // For std::size_t +#include // Detect error type. +#include // For std::terminate +#include // For std::system_error +#include // For std::hash, std::invoke (C++17) +#include // For std::tuple +#include // For sleep timing. +#include // For std::unique_ptr +#include // Stream output for thread ids. +#include // For std::swap, std::forward + +#include // For WaitForSingleObject +#include // For CloseHandle, etc. +#include // For GetNativeSystemInfo +#include // For GetCurrentThreadId + +#include // For _beginthreadex + +#if __cplusplus < 201703L +# include +#endif + +namespace mingw_stdthread +{ + // @@ I think can get rid of this in C++14. + // + namespace detail + { + template + struct IntSeq {}; + + template + struct GenIntSeq : GenIntSeq { }; + + template + struct GenIntSeq<0, S...> { typedef IntSeq type; }; + +// Use a template specialization to avoid relying on compiler optimization +// when determining the parameter integer sequence. + template + class ThreadFuncCall; +// We can't define the Call struct in the function - the standard forbids template methods in that case + template + class ThreadFuncCall, Args...> + { + static_assert(sizeof...(S) == sizeof...(Args), "Args must match."); + using Tuple = std::tuple::type...>; + typename std::decay::type mFunc; + Tuple mArgs; + + public: + ThreadFuncCall(Func&& aFunc, Args&&... aArgs) + : mFunc(std::forward(aFunc)), + mArgs(std::forward(aArgs)...) + { + } + + void callFunc() + { +#if __cplusplus < 201703L + detail::invoke(std::move(mFunc), std::move(std::get(mArgs)) ...); +#else + std::invoke (std::move(mFunc), std::move(std::get(mArgs)) ...); +#endif + } + }; + + // Allow construction of threads without exposing implementation. + class ThreadIdTool; + } + + class thread + { + public: + class id + { + DWORD mId = 0; + friend class thread; + friend class std::hash; + friend class detail::ThreadIdTool; + explicit id(DWORD aId) noexcept : mId(aId){} + public: + id () noexcept = default; + friend bool operator==(id x, id y) noexcept {return x.mId == y.mId; } + friend bool operator!=(id x, id y) noexcept {return x.mId != y.mId; } + friend bool operator< (id x, id y) noexcept {return x.mId < y.mId; } + friend bool operator<=(id x, id y) noexcept {return x.mId <= y.mId; } + friend bool operator> (id x, id y) noexcept {return x.mId > y.mId; } + friend bool operator>=(id x, id y) noexcept {return x.mId >= y.mId; } + + template + friend std::basic_ostream<_CharT, _Traits>& + operator<<(std::basic_ostream<_CharT, _Traits>& __out, id __id) + { + if (__id.mId == 0) + { + return __out << ""; + } + else + { + return __out << __id.mId; + } + } + }; + private: + static constexpr HANDLE kInvalidHandle = nullptr; + static constexpr DWORD kInfinite = 0xffffffffl; + HANDLE mHandle; + id mThreadId; + + template + static unsigned __stdcall threadfunc(void* arg) + { + std::unique_ptr call(static_cast(arg)); + call->callFunc(); + return 0; + } + + static unsigned int _hardware_concurrency_helper() noexcept + { + SYSTEM_INFO sysinfo; + ::GetNativeSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; + } + public: + typedef HANDLE native_handle_type; + id get_id() const noexcept {return mThreadId;} + native_handle_type native_handle() const {return mHandle;} + thread(): mHandle(kInvalidHandle), mThreadId(){} + + thread(thread&& other) + :mHandle(other.mHandle), mThreadId(other.mThreadId) + { + other.mHandle = kInvalidHandle; + other.mThreadId = id{}; + } + + thread(const thread &other) = delete; + + template + explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId() + { + // Instead of INVALID_HANDLE_VALUE, _beginthreadex returns 0. + + using ArgSequence = typename detail::GenIntSeq::type; + using Call = detail::ThreadFuncCall; + auto call = new Call(std::forward(func), std::forward(args)...); + unsigned int id_receiver; + auto int_handle = _beginthreadex(NULL, 0, threadfunc, + static_cast(call), 0, &id_receiver); + if (int_handle == 0) + { + mHandle = kInvalidHandle; + int errnum = errno; + delete call; + // Note: Should only throw EINVAL, EAGAIN, EACCES + throw std::system_error(errnum, std::generic_category()); + } else { + mThreadId.mId = id_receiver; + mHandle = reinterpret_cast(int_handle); + } + } + + bool joinable() const {return mHandle != kInvalidHandle;} + + // Note: Due to lack of synchronization, this function has a race + // condition if called concurrently, which leads to undefined + // behavior. The same applies to all other member functions of this + // class, but this one is mentioned explicitly. + void join() + { + using namespace std; + if (get_id() == id(GetCurrentThreadId())) + throw system_error(make_error_code(errc::resource_deadlock_would_occur)); + if (mHandle == kInvalidHandle) + throw system_error(make_error_code(errc::no_such_process)); + if (!joinable()) + throw system_error(make_error_code(errc::invalid_argument)); + WaitForSingleObject(mHandle, kInfinite); + CloseHandle(mHandle); + mHandle = kInvalidHandle; + mThreadId = id{}; + } + + ~thread() + { + if (joinable()) + { + // @@ TODO + /* +#ifndef NDEBUG + std::printf("Error: Must join() or detach() a thread before \ +destroying it.\n"); +#endif + */ + std::terminate(); + } + } + thread& operator=(const thread&) = delete; + thread& operator=(thread&& other) noexcept + { + if (joinable()) + { + // @@ TODO + /* +#ifndef NDEBUG + std::printf("Error: Must join() or detach() a thread before \ +moving another thread to it.\n"); +#endif + */ + std::terminate(); + } + swap(other); + return *this; + } + void swap(thread& other) noexcept + { + std::swap(mHandle, other.mHandle); + std::swap(mThreadId.mId, other.mThreadId.mId); + } + + static unsigned int hardware_concurrency() noexcept + { + // @@ TODO: this seems like a bad idea. + // + /*static*/ unsigned int cached = _hardware_concurrency_helper(); + return cached; + } + + void detach() + { + if (!joinable()) + { + using namespace std; + throw system_error(make_error_code(errc::invalid_argument)); + } + if (mHandle != kInvalidHandle) + { + CloseHandle(mHandle); + mHandle = kInvalidHandle; + } + mThreadId = id{}; + } + }; + + namespace detail + { + class ThreadIdTool + { + public: + static thread::id make_id (DWORD base_id) noexcept + { + return thread::id(base_id); + } + }; + } + + namespace this_thread + { + inline thread::id get_id() noexcept + { + return detail::ThreadIdTool::make_id(GetCurrentThreadId()); + } + inline void yield() noexcept {Sleep(0);} + template< class Rep, class Period > + void sleep_for( const std::chrono::duration& sleep_duration) + { + static constexpr DWORD kInfinite = 0xffffffffl; + using namespace std::chrono; + using rep = milliseconds::rep; + rep ms = duration_cast(sleep_duration).count(); + while (ms > 0) + { + constexpr rep kMaxRep = static_cast(kInfinite - 1); + auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep; + Sleep(static_cast(sleepTime)); + ms -= sleepTime; + } + } + template + void sleep_until(const std::chrono::time_point& sleep_time) + { + sleep_for(sleep_time-Clock::now()); + } + } +} + +namespace std +{ + // Specialize hash for this implementation's thread::id, even if the + // std::thread::id already has a hash. + template<> + struct hash + { + typedef mingw_stdthread::thread::id argument_type; + typedef size_t result_type; + size_t operator() (const argument_type & i) const noexcept + { + return i.mId; + } + }; +} + +#endif // LIBBUTL_MINGW_THREAD_HXX diff --git a/libbutl/process-details.hxx b/libbutl/process-details.hxx index 571c750..10d5241 100644 --- a/libbutl/process-details.hxx +++ b/libbutl/process-details.hxx @@ -3,12 +3,25 @@ #pragma once -#include +#ifdef LIBBUTL_MINGW_STDTHREAD -#include -#if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex) -# include -#endif +# include + +namespace butl +{ + using shared_mutex = mingw_stdthread::shared_mutex; + using ulock = mingw_stdthread::unique_lock; + using slock = mingw_stdthread::shared_lock; +} + +#else // LIBBUTL_MINGW_STDTHREADS + +# include + +# include +# if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex) +# include +# endif namespace butl { @@ -36,7 +49,11 @@ namespace butl using ulock = std::unique_lock; using slock = ulock; #endif +} +#endif // LIBBUTL_MINGW_STDTHREADS +namespace butl +{ // Mutex that is acquired to make a sequence of operations atomic in regards // to child process spawning. Must be aquired for exclusive access for child // process startup, and for shared access otherwise. Defined in process.cxx. diff --git a/manifest b/manifest index 409b028..3e512c0 100644 --- a/manifest +++ b/manifest @@ -3,7 +3,7 @@ name: libbutl version: 0.15.0-a.0.z project: build2 summary: build2 utility library -license: MIT AND BSD-3-Clause AND BSD-2-Clause ; MIT except for files from the FreeBSD and LZ4 projects. +license: MIT AND BSD-3-Clause AND BSD-2-Clause ; MIT except for files from the FreeBSD, LZ4, and mingw-std-threads projects. topics: build toolchain description-file: README changes-file: NEWS diff --git a/tests/fdstream/driver.cxx b/tests/fdstream/driver.cxx index 254a03e..0b66574 100644 --- a/tests/fdstream/driver.cxx +++ b/tests/fdstream/driver.cxx @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -20,6 +19,12 @@ #include #include +#ifndef LIBBUTL_MINGW_STDTHREAD +# include +#else +# include +#endif + #include #include #include @@ -115,6 +120,12 @@ read_time (const path& p, const T& s, size_t n) int main (int argc, const char* argv[]) { +#ifndef LIBBUTL_MINGW_STDTHREAD + using std::thread; +#else + using mingw_stdthread::thread; +#endif + bool v (false); bool child (false); -- cgit v1.1