aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-05-10 09:48:08 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-05-10 09:48:08 +0200
commitea2b4fb4935627e4dea48f193eeb0019155a3abe (patch)
tree8dd1bbecf9ff634616d7b2717f7b46fcc64fc364
parent3cb2d76c1e2375ecce69f2c3f462be979295c753 (diff)
Use our own implementation of C++14 threads on MinGWmingw-stdthread
-rw-r--r--LICENSE1
-rw-r--r--libbutl/buildfile15
-rw-r--r--libbutl/builtin.cxx4
-rw-r--r--libbutl/builtin.hxx33
-rw-r--r--libbutl/builtin.ixx4
-rw-r--r--libbutl/diagnostics.cxx12
-rw-r--r--libbutl/mingw-condition_variable.hxx267
-rw-r--r--libbutl/mingw-invoke.hxx109
-rw-r--r--libbutl/mingw-mutex.hxx215
-rw-r--r--libbutl/mingw-shared_mutex.hxx124
-rw-r--r--libbutl/mingw-thread.hxx330
-rw-r--r--libbutl/process-details.hxx27
-rw-r--r--manifest2
-rw-r--r--tests/fdstream/driver.cxx13
14 files changed, 1137 insertions, 19 deletions
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<mutex> 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<mutex> 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 <map>
-#include <mutex>
#include <string>
#include <vector>
-#include <thread>
#include <chrono>
#include <memory> // unique_ptr
#include <cstddef> // size_t
#include <utility> // move()
#include <cstdint> // uint8_t
#include <functional>
-#include <condition_variable>
+
+#ifndef LIBBUTL_MINGW_STDTHREAD
+# include <mutex>
+# include <thread>
+# include <condition_variable>
+#else
+# include <libbutl/mingw-mutex.hxx>
+# include <libbutl/mingw-thread.hxx>
+# include <libbutl/mingw-condition_variable.hxx>
+#endif
#include <libbutl/path.hxx>
#include <libbutl/fdstream.hxx>
@@ -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<mutex_type>;
+#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<mutex_type>;
+#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<std::mutex> l (state_->mutex);
+ unique_lock l (state_->mutex);
if (!state_->finished)
return nullopt;
@@ -53,7 +53,7 @@ namespace butl
f ();
{
- std::unique_lock<std::mutex> 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 <cstddef> // size_t
#include <iostream> // cerr
+#ifndef LIBBUTL_MINGW_STDTHREAD
+# include <mutex>
+#else
+# include <libbutl/mingw-mutex.hxx>
+#endif
+
#include <libbutl/ft/lang.hxx> // thread_local
#include <libbutl/utility.hxx>
@@ -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 <condition_variable> // Use std::cv_status, if available.
+
+#include <cassert>
+#include <chrono>
+#include <system_error>
+
+#include <synchapi.h>
+
+#include <libbutl/mingw-mutex.hxx>
+#include <libbutl/mingw-shared_mutex.hxx>
+
+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<mutex> & lock, DWORD time)
+ {
+ mutex * pmutex = lock.release();
+ bool success = wait_unique(pmutex, time);
+ lock = unique_lock<mutex>(*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<mutex> & lock)
+ {
+ wait_impl(lock, kInfinite);
+ }
+
+ template<class Predicate>
+ void wait (unique_lock<mutex> & lock, Predicate pred)
+ {
+ while (!pred())
+ wait(lock);
+ }
+
+ template <class Rep, class Period>
+ cv_status wait_for(unique_lock<mutex>& lock,
+ const std::chrono::duration<Rep, Period>& rel_time)
+ {
+ using namespace std::chrono;
+ auto timeout = duration_cast<milliseconds>(rel_time).count();
+ DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (kInfinite - 1);
+ bool result = wait_impl(lock, waittime) || (timeout >= kInfinite);
+ return result ? cv_status::no_timeout : cv_status::timeout;
+ }
+
+ template <class Rep, class Period, class Predicate>
+ bool wait_for(unique_lock<mutex>& lock,
+ const std::chrono::duration<Rep, Period>& rel_time,
+ Predicate pred)
+ {
+ return wait_until(lock,
+ std::chrono::steady_clock::now() + rel_time,
+ std::move(pred));
+ }
+ template <class Clock, class Duration>
+ cv_status wait_until (unique_lock<mutex>& lock,
+ const std::chrono::time_point<Clock,Duration>& abs_time)
+ {
+ return wait_for(lock, abs_time - Clock::now());
+ }
+ template <class Clock, class Duration, class Predicate>
+ bool wait_until (unique_lock<mutex>& lock,
+ const std::chrono::time_point<Clock, Duration>& 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<class L>
+ bool wait_impl (L & lock, DWORD time)
+ {
+ unique_lock<decltype(internal_mutex_)> 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<mutex> & lock, DWORD time)
+ {
+ return internal_cv_.wait_impl(lock, time);
+ }
+ bool wait_impl (unique_lock<shared_mutex> & lock, DWORD time)
+ {
+ shared_mutex * pmutex = lock.release();
+ bool success = internal_cv_.wait_unique(pmutex, time);
+ lock = unique_lock<shared_mutex>(*pmutex, adopt_lock);
+ return success;
+ }
+ bool wait_impl (shared_lock<shared_mutex> & lock, DWORD time)
+ {
+ shared_mutex * pmutex = lock.release();
+ BOOL success = SleepConditionVariableSRW(native_handle(),
+ pmutex->native_handle(), time,
+ CONDITION_VARIABLE_LOCKMODE_SHARED);
+ lock = shared_lock<shared_mutex>(*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<class L>
+ void wait (L & lock)
+ {
+ wait_impl(lock, kInfinite);
+ }
+
+ template<class L, class Predicate>
+ void wait (L & lock, Predicate pred)
+ {
+ while (!pred())
+ wait(lock);
+ }
+
+ template <class L, class Rep, class Period>
+ cv_status wait_for(L& lock, const std::chrono::duration<Rep,Period>& period)
+ {
+ using namespace std::chrono;
+ auto timeout = duration_cast<milliseconds>(period).count();
+ DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (kInfinite - 1);
+ bool result = wait_impl(lock, waittime) || (timeout >= kInfinite);
+ return result ? cv_status::no_timeout : cv_status::timeout;
+ }
+
+ template <class L, class Rep, class Period, class Predicate>
+ bool wait_for(L& lock, const std::chrono::duration<Rep, Period>& period,
+ Predicate pred)
+ {
+ return wait_until(lock, std::chrono::steady_clock::now() + period,
+ std::move(pred));
+ }
+ template <class L, class Clock, class Duration>
+ cv_status wait_until (L& lock,
+ const std::chrono::time_point<Clock,Duration>& abs_time)
+ {
+ return wait_for(lock, abs_time - Clock::now());
+ }
+ template <class L, class Clock, class Duration, class Predicate>
+ bool wait_until (L& lock,
+ const std::chrono::time_point<Clock, Duration>& 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 <type_traits> // For std::result_of, etc.
+#include <utility> // For std::forward
+#include <functional> // For std::reference_wrapper
+
+namespace mingw_stdthread
+{
+ namespace detail
+ {
+ // For compatibility, implement std::invoke for C++11 and C++14.
+ //
+ template<bool PMemFunc, bool PMemData>
+ struct Invoker
+ {
+ template<class F, class... Args>
+ inline static typename std::result_of<F(Args...)>::type invoke (F&& f, Args&&... args)
+ {
+ return std::forward<F>(f)(std::forward<Args>(args)...);
+ }
+ };
+ template<bool>
+ struct InvokerHelper;
+
+ template<>
+ struct InvokerHelper<false>
+ {
+ template<class T1>
+ inline static auto get (T1&& t1) -> decltype(*std::forward<T1>(t1))
+ {
+ return *std::forward<T1>(t1);
+ }
+
+ template<class T1>
+ inline static auto get (const std::reference_wrapper<T1>& t1) -> decltype(t1.get())
+ {
+ return t1.get();
+ }
+ };
+
+ template<>
+ struct InvokerHelper<true>
+ {
+ template<class T1>
+ inline static auto get (T1&& t1) -> decltype(std::forward<T1>(t1))
+ {
+ return std::forward<T1>(t1);
+ }
+ };
+
+ template<>
+ struct Invoker<true, false>
+ {
+ template<class T, class F, class T1, class... Args>
+ inline static auto invoke (F T::* f, T1&& t1, Args&&... args) -> \
+ decltype((InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...))
+ {
+ return (InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
+ }
+ };
+
+ template<>
+ struct Invoker<false, true>
+ {
+ template<class T, class F, class T1, class... Args>
+ inline static auto invoke (F T::* f, T1&& t1, Args&&... args) -> \
+ decltype(InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f)
+ {
+ return InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f;
+ }
+ };
+
+ template<class F, class... Args>
+ struct InvokeResult
+ {
+ typedef Invoker<std::is_member_function_pointer<typename std::remove_reference<F>::type>::value,
+ std::is_member_object_pointer<typename std::remove_reference<F>::type>::value &&
+ (sizeof...(Args) == 1)> invoker;
+ inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...))
+ {
+ return invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...);
+ }
+ };
+
+ template<class F, class...Args>
+ auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...))
+ {
+ return InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(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 <chrono>
+#include <system_error>
+#include <atomic>
+
+#include <mutex>
+
+#include <synchapi.h> // For InitializeCriticalSection, etc.
+#include <errhandlingapi.h> // For GetLastError
+#include <handleapi.h>
+
+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 <class Rep, class Period>
+ bool try_lock_for(const std::chrono::duration<Rep,Period>& dur)
+ {
+ using namespace std::chrono;
+ auto timeout = duration_cast<milliseconds>(dur).count();
+ while (timeout > 0)
+ {
+ constexpr auto kMaxStep = static_cast<decltype(timeout)>(kInfinite-1);
+ auto step = (timeout < kMaxStep) ? timeout : kMaxStep;
+ if (try_lock_internal(static_cast<DWORD>(step)))
+ return true;
+ timeout -= step;
+ }
+ return false;
+ }
+ template <class Clock, class Duration>
+ bool try_lock_until(const std::chrono::time_point<Clock,Duration>& 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 <cassert>
+// For descriptive errors.
+#include <system_error>
+// For timing in shared_timed_mutex.
+#include <chrono>
+#include <limits>
+
+#include <shared_mutex> // shared_lock
+
+// For defer_lock_t, adopt_lock_t, and try_to_lock_t
+#include <libbutl/mingw-mutex.hxx>
+
+#include <synchapi.h>
+
+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<Clock,Duration>& 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<Rep,Period>& 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<Clock,Duration>& 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<Rep,Period>& 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 <cstddef> // For std::size_t
+#include <cerrno> // Detect error type.
+#include <exception> // For std::terminate
+#include <system_error> // For std::system_error
+#include <functional> // For std::hash, std::invoke (C++17)
+#include <tuple> // For std::tuple
+#include <chrono> // For sleep timing.
+#include <memory> // For std::unique_ptr
+#include <iosfwd> // Stream output for thread ids.
+#include <utility> // For std::swap, std::forward
+
+#include <synchapi.h> // For WaitForSingleObject
+#include <handleapi.h> // For CloseHandle, etc.
+#include <sysinfoapi.h> // For GetNativeSystemInfo
+#include <processthreadsapi.h> // For GetCurrentThreadId
+
+#include <process.h> // For _beginthreadex
+
+#if __cplusplus < 201703L
+# include <libbutl/mingw-invoke.hxx>
+#endif
+
+namespace mingw_stdthread
+{
+ // @@ I think can get rid of this in C++14.
+ //
+ namespace detail
+ {
+ template<std::size_t...>
+ struct IntSeq {};
+
+ template<std::size_t N, std::size_t... S>
+ struct GenIntSeq : GenIntSeq<N-1, N-1, S...> { };
+
+ template<std::size_t... S>
+ struct GenIntSeq<0, S...> { typedef IntSeq<S...> type; };
+
+// Use a template specialization to avoid relying on compiler optimization
+// when determining the parameter integer sequence.
+ template<class Func, class T, typename... Args>
+ class ThreadFuncCall;
+// We can't define the Call struct in the function - the standard forbids template methods in that case
+ template<class Func, std::size_t... S, typename... Args>
+ class ThreadFuncCall<Func, detail::IntSeq<S...>, Args...>
+ {
+ static_assert(sizeof...(S) == sizeof...(Args), "Args must match.");
+ using Tuple = std::tuple<typename std::decay<Args>::type...>;
+ typename std::decay<Func>::type mFunc;
+ Tuple mArgs;
+
+ public:
+ ThreadFuncCall(Func&& aFunc, Args&&... aArgs)
+ : mFunc(std::forward<Func>(aFunc)),
+ mArgs(std::forward<Args>(aArgs)...)
+ {
+ }
+
+ void callFunc()
+ {
+#if __cplusplus < 201703L
+ detail::invoke(std::move(mFunc), std::move(std::get<S>(mArgs)) ...);
+#else
+ std::invoke (std::move(mFunc), std::move(std::get<S>(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<id>;
+ 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<class _CharT, class _Traits>
+ friend std::basic_ostream<_CharT, _Traits>&
+ operator<<(std::basic_ostream<_CharT, _Traits>& __out, id __id)
+ {
+ if (__id.mId == 0)
+ {
+ return __out << "<invalid std::thread::id>";
+ }
+ else
+ {
+ return __out << __id.mId;
+ }
+ }
+ };
+ private:
+ static constexpr HANDLE kInvalidHandle = nullptr;
+ static constexpr DWORD kInfinite = 0xffffffffl;
+ HANDLE mHandle;
+ id mThreadId;
+
+ template <class Call>
+ static unsigned __stdcall threadfunc(void* arg)
+ {
+ std::unique_ptr<Call> call(static_cast<Call*>(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<class Func, typename... Args>
+ explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId()
+ {
+ // Instead of INVALID_HANDLE_VALUE, _beginthreadex returns 0.
+
+ using ArgSequence = typename detail::GenIntSeq<sizeof...(Args)>::type;
+ using Call = detail::ThreadFuncCall<Func, ArgSequence, Args...>;
+ auto call = new Call(std::forward<Func>(func), std::forward<Args>(args)...);
+ unsigned int id_receiver;
+ auto int_handle = _beginthreadex(NULL, 0, threadfunc<Call>,
+ static_cast<LPVOID>(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<HANDLE>(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<Rep,Period>& sleep_duration)
+ {
+ static constexpr DWORD kInfinite = 0xffffffffl;
+ using namespace std::chrono;
+ using rep = milliseconds::rep;
+ rep ms = duration_cast<milliseconds>(sleep_duration).count();
+ while (ms > 0)
+ {
+ constexpr rep kMaxRep = static_cast<rep>(kInfinite - 1);
+ auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep;
+ Sleep(static_cast<DWORD>(sleepTime));
+ ms -= sleepTime;
+ }
+ }
+ template <class Clock, class Duration>
+ void sleep_until(const std::chrono::time_point<Clock,Duration>& 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<mingw_stdthread::thread::id>
+ {
+ 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 <libbutl/ft/shared_mutex.hxx>
+#ifdef LIBBUTL_MINGW_STDTHREAD
-#include <mutex>
-#if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex)
-# include <shared_mutex>
-#endif
+# include <libbutl/mingw-shared_mutex.hxx>
+
+namespace butl
+{
+ using shared_mutex = mingw_stdthread::shared_mutex;
+ using ulock = mingw_stdthread::unique_lock<shared_mutex>;
+ using slock = mingw_stdthread::shared_lock<shared_mutex>;
+}
+
+#else // LIBBUTL_MINGW_STDTHREADS
+
+# include <libbutl/ft/shared_mutex.hxx>
+
+# include <mutex>
+# if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex)
+# include <shared_mutex>
+# endif
namespace butl
{
@@ -36,7 +49,11 @@ namespace butl
using ulock = std::unique_lock<shared_mutex>;
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 <ios>
#include <string>
#include <vector>
-#include <thread>
#include <iomanip>
#include <sstream>
#include <fstream>
@@ -20,6 +19,12 @@
#include <iostream>
#include <exception>
+#ifndef LIBBUTL_MINGW_STDTHREAD
+# include <thread>
+#else
+# include <libbutl/mingw-thread.hxx>
+#endif
+
#include <libbutl/path.hxx>
#include <libbutl/process.hxx>
#include <libbutl/fdstream.hxx>
@@ -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);