Commit 5be1c625 authored by Kenton Varda's avatar Kenton Varda

MinGW: Eliminate dependency on winpthread.

kernel32.dll and msvcrt.dll are the only remaining dependencies.
parent 1745aded
......@@ -195,8 +195,7 @@ else
lib_LTLIBRARIES = libkj.la libkj-async.la libcapnp.la libcapnp-rpc.la libcapnpc.la
endif
# -lpthread is here to work around https://bugzilla.redhat.com/show_bug.cgi?id=661333
libkj_la_LIBADD = $(PTHREAD_LIBS) -lpthread
libkj_la_LIBADD = $(PTHREAD_LIBS)
libkj_la_LDFLAGS = -release $(VERSION) -no-undefined
libkj_la_SOURCES= \
src/kj/common.c++ \
......@@ -216,8 +215,7 @@ libkj_la_SOURCES= \
src/kj/parse/char.c++
if !LITE_MODE
# -lpthread is here to work around https://bugzilla.redhat.com/show_bug.cgi?id=661333
libkj_async_la_LIBADD = libkj.la $(PTHREAD_LIBS) -lpthread
libkj_async_la_LIBADD = libkj.la $(PTHREAD_LIBS)
libkj_async_la_LDFLAGS = -release $(VERSION) -no-undefined
libkj_async_la_SOURCES= \
src/kj/async.c++ \
......@@ -233,8 +231,7 @@ heavy_sources = \
src/capnp/stringify.c++
endif !LITE_MODE
# -lpthread is here to work around https://bugzilla.redhat.com/show_bug.cgi?id=661333
libcapnp_la_LIBADD = libkj.la $(PTHREAD_LIBS) -lpthread
libcapnp_la_LIBADD = libkj.la $(PTHREAD_LIBS)
libcapnp_la_LDFLAGS = -release $(VERSION) -no-undefined
libcapnp_la_SOURCES= \
src/capnp/c++.capnp.c++ \
......@@ -252,8 +249,7 @@ libcapnp_la_SOURCES= \
if !LITE_MODE
# -lpthread is here to work around https://bugzilla.redhat.com/show_bug.cgi?id=661333
libcapnp_rpc_la_LIBADD = libcapnp.la libkj-async.la libkj.la $(PTHREAD_LIBS) -lpthread
libcapnp_rpc_la_LIBADD = libcapnp.la libkj-async.la libkj.la $(PTHREAD_LIBS)
libcapnp_rpc_la_LDFLAGS = -release $(VERSION) -no-undefined
libcapnp_rpc_la_SOURCES= \
src/capnp/serialize-async.c++ \
......@@ -266,8 +262,7 @@ libcapnp_rpc_la_SOURCES= \
src/capnp/persistent.capnp.c++ \
src/capnp/ez-rpc.c++
# -lpthread is here to work around https://bugzilla.redhat.com/show_bug.cgi?id=661333
libcapnpc_la_LIBADD = libcapnp.la libkj.la $(PTHREAD_LIBS) -lpthread
libcapnpc_la_LIBADD = libcapnp.la libkj.la $(PTHREAD_LIBS)
libcapnpc_la_LDFLAGS = -release $(VERSION) -no-undefined
libcapnpc_la_SOURCES= \
src/capnp/compiler/md5.h \
......
......@@ -51,7 +51,22 @@ AC_PROG_CC
AC_PROG_CXX
AC_LANG([C++])
AX_CXX_COMPILE_STDCXX_11
ACX_PTHREAD
AS_CASE("${host_os}", *mingw*, [
# We don't use pthreads on MinGW.
PTHREAD_CFLAGS="-mthreads"
PTHREAD_LIBS=""
PTHREAD_CC=""
AC_SUBST(PTHREAD_LIBS)
AC_SUBST(PTHREAD_CFLAGS)
AC_SUBST(PTHREAD_CC)
# Disable pthreads when configuring gtest.
ac_configure_args="$ac_configure_args --without-pthreads"
], *, [
ACX_PTHREAD
])
LT_INIT
AS_IF([test "$external_capnp" != "no"], [
......
......@@ -343,13 +343,52 @@ if test "x$acx_pthread_ok" = xyes; then
acx_pthread_ok=no
fi
CFLAGS="$save_CFLAGS"
LIBS="$save_LIBS"
CC="$save_CC"
CFLAGS="$save_CFLAGS"
LIBS="$save_LIBS"
CC="$save_CC"
else
PTHREAD_CC="$CC"
fi
if test "x$acx_pthread_ok" = xyes; then
# One more check: If we chose to use a compiler flag like -pthread but it is combined with
# -nostdlib then the compiler won't implicitly link against libpthread. This can happen
# in particular when using some versions of libtool on some distros. See:
# https://bugzilla.redhat.com/show_bug.cgi?id=661333
save_CFLAGS="$CFLAGS"
save_LIBS="$LIBS"
save_CC="$CC"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
LIBS="-nostdlib $PTHREAD_LIBS $LIBS -lc"
CC="$PTHREAD_CC"
AC_MSG_CHECKING([whether pthread flag is sufficient with -nostdlib])
AC_TRY_LINK([#include <pthread.h>],
[pthread_t th; pthread_join(th, 0);
pthread_attr_init(0); pthread_cleanup_push(0, 0);
pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
[AC_MSG_RESULT([yes])], [
AC_MSG_RESULT([no])
AC_MSG_CHECKING([whether adding -lpthread fixes that])
LIBS="-nostdlib $PTHREAD_LIBS -lpthread $save_LIBS -lc"
AC_TRY_LINK([#include <pthread.h>],
[pthread_t th; pthread_join(th, 0);
pthread_attr_init(0); pthread_cleanup_push(0, 0);
pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
[
AC_MSG_RESULT([yes])
PTHREAD_LIBS="$PTHREAD_LIBS -lpthread"
], [AC_MSG_RESULT([no])])
])
CFLAGS="$save_CFLAGS"
LIBS="$save_LIBS"
CC="$save_CC"
fi
AC_SUBST(PTHREAD_LIBS)
AC_SUBST(PTHREAD_CFLAGS)
AC_SUBST(PTHREAD_CC)
......
......@@ -22,10 +22,15 @@
#include "mutex.h"
#include "debug.h"
#include "thread.h"
#include <pthread.h>
#include <unistd.h>
#include <gtest/gtest.h>
#if _WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif
namespace kj {
namespace {
......@@ -114,8 +119,10 @@ TEST(Mutex, MutexGuarded) {
EXPECT_EQ(321u, *value.lockExclusive());
#if !_WIN32 // Not checked on win32.
EXPECT_DEBUG_ANY_THROW(value.getAlreadyLockedExclusive());
EXPECT_DEBUG_ANY_THROW(value.getAlreadyLockedShared());
#endif
EXPECT_EQ(321u, value.getWithoutLock());
}
......@@ -133,7 +140,11 @@ TEST(Mutex, Lazy) {
// Spin until the initializer has been entered in the thread.
while (!__atomic_load_n(&initStarted, __ATOMIC_RELAXED)) {
#if _WIN32
Sleep(0);
#else
sched_yield();
#endif
}
EXPECT_EQ(123u, lazy.get([](SpaceFor<uint>& space) { return space.construct(456); }));
......
......@@ -19,6 +19,12 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if _WIN32
#define WIN32_LEAN_AND_MEAN 1 // lolz
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#endif
#include "mutex.h"
#include "debug.h"
......@@ -27,6 +33,8 @@
#include <sys/syscall.h>
#include <linux/futex.h>
#include <limits.h>
#elif _WIN32
#include <windows.h>
#endif
namespace kj {
......@@ -159,7 +167,7 @@ startOver:
}
} else {
for (;;) {
if (state == INITIALIZED || state == DISABLED) {
if (state == INITIALIZED) {
break;
} else if (state == INITIALIZING) {
// Initialization is taking place in another thread. Indicate that we're waiting.
......@@ -189,47 +197,90 @@ void Once::reset() {
uint state = INITIALIZED;
if (!__atomic_compare_exchange_n(&futex, &state, UNINITIALIZED,
false, __ATOMIC_RELEASE, __ATOMIC_RELAXED)) {
KJ_REQUIRE(state == DISABLED, "reset() called while not initialized.");
KJ_FAIL_REQUIRE("reset() called while not initialized.");
}
}
void Once::disable() noexcept {
uint state = __atomic_load_n(&futex, __ATOMIC_ACQUIRE);
for (;;) {
switch (state) {
case DISABLED:
default:
return;
case UNINITIALIZED:
case INITIALIZED:
// Try to transition the state to DISABLED.
if (!__atomic_compare_exchange_n(&futex, &state, DISABLED, true,
__ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
// State changed, retry.
continue;
}
// Success.
return;
#elif _WIN32
// =======================================================================================
// Win32 implementation
case INITIALIZING:
// Initialization is taking place in another thread. Indicate that we're waiting.
if (!__atomic_compare_exchange_n(&futex, &state, INITIALIZING_WITH_WAITERS, true,
__ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE)) {
// State changed, retry.
continue;
}
// no break
#define coercedSrwLock (*reinterpret_cast<SRWLOCK*>(&srwLock))
#define coercedInitOnce (*reinterpret_cast<INIT_ONCE*>(&initOnce))
case INITIALIZING_WITH_WAITERS:
// Wait for initialization.
syscall(SYS_futex, &futex, FUTEX_WAIT_PRIVATE, INITIALIZING_WITH_WAITERS, NULL, NULL, 0);
state = __atomic_load_n(&futex, __ATOMIC_ACQUIRE);
continue;
Mutex::Mutex() {
static_assert(sizeof(SRWLOCK) == sizeof(srwLock), "SRWLOCK is not a pointer?");
InitializeSRWLock(&coercedSrwLock);
}
Mutex::~Mutex() {}
void Mutex::lock(Exclusivity exclusivity) {
switch (exclusivity) {
case EXCLUSIVE:
AcquireSRWLockExclusive(&coercedSrwLock);
break;
case SHARED:
AcquireSRWLockShared(&coercedSrwLock);
break;
}
}
void Mutex::unlock(Exclusivity exclusivity) {
switch (exclusivity) {
case EXCLUSIVE:
ReleaseSRWLockExclusive(&coercedSrwLock);
break;
case SHARED:
ReleaseSRWLockShared(&coercedSrwLock);
break;
}
}
void Mutex::assertLockedByCaller(Exclusivity exclusivity) {
// We could use TryAcquireSRWLock*() here like we do with the pthread version. However, as of
// this writing, my version of Wine (1.6.2) doesn't implement these functions and will abort if
// they are called. Since we were only going to use them as a hacky way to check if the lock is
// held for debug purposes anyway, we just don't bother.
}
static BOOL nullInitializer(PINIT_ONCE initOnce, PVOID parameter, PVOID* context) {
return true;
}
Once::Once(bool startInitialized) {
static_assert(sizeof(INIT_ONCE) == sizeof(initOnce), "INIT_ONCE is not a pointer?");
InitOnceInitialize(&coercedInitOnce);
if (startInitialized) {
InitOnceExecuteOnce(&coercedInitOnce, &nullInitializer, nullptr, nullptr);
}
}
Once::~Once() {}
void Once::runOnce(Initializer& init) {
BOOL needInit;
while (!InitOnceBeginInitialize(&coercedInitOnce, 0, &needInit, nullptr)) {
// Init was occurring in another thread, but then failed with an exception. Retry.
}
if (needInit) {
{
KJ_ON_SCOPE_FAILURE(InitOnceComplete(&coercedInitOnce, INIT_ONCE_INIT_FAILED, nullptr));
init.run();
}
KJ_ASSERT(InitOnceComplete(&coercedInitOnce, 0, nullptr));
}
}
bool Once::isInitialized() noexcept {
BOOL junk;
return InitOnceBeginInitialize(&coercedInitOnce, INIT_ONCE_CHECK_ONLY, &junk, nullptr);
}
void Once::reset() {
InitOnceInitialize(&coercedInitOnce);
}
#else
// =======================================================================================
// Generic pthreads-based implementation
......@@ -316,17 +367,10 @@ void Once::reset() {
State oldState = INITIALIZED;
if (!__atomic_compare_exchange_n(&state, &oldState, UNINITIALIZED,
false, __ATOMIC_RELEASE, __ATOMIC_RELAXED)) {
KJ_REQUIRE(oldState == DISABLED, "reset() called while not initialized.");
KJ_FAIL_REQUIRE("reset() called while not initialized.");
}
}
void Once::disable() noexcept {
KJ_PTHREAD_CALL(pthread_mutex_lock(&mutex));
KJ_DEFER(KJ_PTHREAD_CALL(pthread_mutex_unlock(&mutex)));
__atomic_store_n(&state, DISABLED, __ATOMIC_RELAXED);
}
#endif
} // namespace _ (private)
......
......@@ -23,12 +23,13 @@
#define KJ_MUTEX_H_
#include "memory.h"
#include <inttypes.h>
#if __linux__ && !defined(KJ_USE_FUTEX)
#define KJ_USE_FUTEX 1
#endif
#if !KJ_USE_FUTEX
#if !KJ_USE_FUTEX && !_WIN32
// On Linux we use futex. On other platforms we wrap pthreads.
// TODO(someday): Write efficient low-level locking primitives for other platforms.
#include <pthread.h>
......@@ -75,6 +76,9 @@ private:
static constexpr uint EXCLUSIVE_REQUESTED = 1u << 30;
static constexpr uint SHARED_COUNT_MASK = EXCLUSIVE_REQUESTED - 1;
#elif _WIN32
uintptr_t srwLock; // Actually an SRWLOCK, but don't want to #include <windows.h> in header.
#else
mutable pthread_rwlock_t mutex;
#endif
......@@ -100,6 +104,10 @@ public:
void runOnce(Initializer& init);
#if _WIN32 // TODO(perf): Can we make this inline on win32 somehow?
bool isInitialized() noexcept;
#else
inline bool isInitialized() noexcept {
// Fast path check to see if runOnce() would simply return immediately.
#if KJ_USE_FUTEX
......@@ -108,26 +116,13 @@ public:
return __atomic_load_n(&state, __ATOMIC_ACQUIRE) == INITIALIZED;
#endif
}
#endif
void reset();
// Returns the state from initialized to uninitialized. It is an error to call this when
// not already initialized, or when runOnce() or isInitialized() might be called concurrently in
// another thread.
void disable() noexcept;
// Prevent future calls to runOnce() and reset() from having any effect, and make isInitialized()
// return false forever. If an initializer is currently running, block until it completes.
bool isDisabled() noexcept {
// Returns true if `disable()` has been called.
#if KJ_USE_FUTEX
return __atomic_load_n(&futex, __ATOMIC_ACQUIRE) == DISABLED;
#else
return __atomic_load_n(&state, __ATOMIC_ACQUIRE) == DISABLED;
#endif
}
private:
#if KJ_USE_FUTEX
uint futex;
......@@ -136,15 +131,16 @@ private:
UNINITIALIZED,
INITIALIZING,
INITIALIZING_WITH_WAITERS,
INITIALIZED,
DISABLED
INITIALIZED
};
#elif _WIN32
uintptr_t initOnce; // Actually an INIT_ONCE, but don't want to #include <windows.h> in header.
#else
enum State {
UNINITIALIZED,
INITIALIZED,
DISABLED
INITIALIZED
};
State state;
pthread_mutex_t mutex;
......
......@@ -21,11 +21,50 @@
#include "thread.h"
#include "debug.h"
#if _WIN32
#include <windows.h>
#else
#include <pthread.h>
#include <signal.h>
#endif
namespace kj {
#if _WIN32
Thread::Thread(Function<void()> func): func(kj::mv(func)) {
threadHandle = CreateThread(nullptr, 0, &runThread, this, 0, nullptr);
KJ_ASSERT(threadHandle != nullptr, "CreateThread failed.");
}
Thread::~Thread() noexcept(false) {
if (!detached) {
KJ_ASSERT(WaitForSingleObject(threadHandle, INFINITE) != WAIT_FAILED);
KJ_IF_MAYBE(e, exception) {
kj::throwRecoverableException(kj::mv(*e));
}
}
}
void Thread::detach() {
KJ_ASSERT(CloseHandle(threadHandle));
detached = true;
}
DWORD Thread::runThread(void* ptr) {
Thread* thread = reinterpret_cast<Thread*>(ptr);
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
thread->func();
})) {
thread->exception = kj::mv(*exception);
}
return 0;
}
#else // _WIN32
Thread::Thread(Function<void()> func): func(kj::mv(func)) {
static_assert(sizeof(threadId) >= sizeof(pthread_t),
"pthread_t is larger than a long long on your platform. Please port.");
......@@ -75,4 +114,6 @@ void* Thread::runThread(void* ptr) {
return nullptr;
}
#endif // _WIN32, else
} // namespace kj
......@@ -38,19 +38,29 @@ public:
~Thread() noexcept(false);
#if !_WIN32
void sendSignal(int signo);
// Send a Unix signal to the given thread, using pthread_kill or an equivalent.
#endif
void detach();
// Don't join the thread in ~Thread().
private:
Function<void()> func;
#if _WIN32
void* threadHandle;
#else
unsigned long long threadId; // actually pthread_t
#endif
kj::Maybe<kj::Exception> exception;
bool detached = false;
#if _WIN32
static unsigned long runThread(void* ptr);
#else
static void* runThread(void* ptr);
#endif
};
} // namespace kj
......
......@@ -19,5 +19,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if !_WIN32
#define KJ_USE_PTHREAD_TLS 1
#include "threadlocal-test.c++"
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment