diff --git a/base/filesystem/file_util.h b/base/filesystem/file_util.h index ca17903d..83f605e2 100644 --- a/base/filesystem/file_util.h +++ b/base/filesystem/file_util.h @@ -26,6 +26,13 @@ #include +// maps CreateDirectory to CreateDirectoryW. Declare and call the +// base function under its own name whether or not a Windows header came first, +// as environment_variables.h does for its names. +#if defined(CreateDirectory) +#undef CreateDirectory +#endif + namespace base { // Create a directory within another directory. diff --git a/base/filesystem/platform_file.h b/base/filesystem/platform_file.h index 8bb25552..b663b7ca 100644 --- a/base/filesystem/platform_file.h +++ b/base/filesystem/platform_file.h @@ -7,7 +7,9 @@ #include "build/build_config.h" #if defined(OS_WIN) -#include +// minwin.h, not : this header reaches every user of base::File, and +// 's macros (near, far, MemoryBarrier, ...) must not follow it there. +#include "base/win/minwin.h" #include "base/win/scoped_handle.h" #endif diff --git a/base/filesystem/win/file_util_win.cc b/base/filesystem/win/file_util_win.cc index 67a4f3df..86c0bf53 100644 --- a/base/filesystem/win/file_util_win.cc +++ b/base/filesystem/win/file_util_win.cc @@ -9,6 +9,9 @@ #include "base/strings/format.h" #include +// This file defines base::CreateDirectory; just mapped the name to +// CreateDirectoryW. The Win32 call below is spelled CreateDirectoryW directly. +#undef CreateDirectory #include namespace base { diff --git a/base/filesystem/win/memory_mapped_file_win.cc b/base/filesystem/win/memory_mapped_file_win.cc index 2152ebeb..e1178104 100644 --- a/base/filesystem/win/memory_mapped_file_win.cc +++ b/base/filesystem/win/memory_mapped_file_win.cc @@ -4,6 +4,8 @@ #include #include +#include + namespace base { bool MemoryMappedFile::Map() { diff --git a/base/threading/mutex.h b/base/threading/mutex.h index 2b2b1c09..0505cd06 100644 --- a/base/threading/mutex.h +++ b/base/threading/mutex.h @@ -28,8 +28,6 @@ using SharedMutex = std::shared_mutex; #if defined(__linux__) || defined(OS_LINUX) || defined(OS_POSIX) #include -#elif defined(_WIN32) || defined(OS_WIN) -#include #endif namespace base { @@ -60,24 +58,22 @@ class SharedMutex { #elif defined(_WIN32) || defined(OS_WIN) -class SharedMutex { +// Out of line (mutex_win.cc) so this header never pulls a Windows header and +// its macros into the including file. +class BASE_EXPORT SharedMutex { public: constexpr SharedMutex() noexcept = default; SharedMutex(const SharedMutex&) = delete; SharedMutex& operator=(const SharedMutex&) = delete; - void lock() { ::AcquireSRWLockExclusive(reinterpret_cast(&lock_)); } - void unlock() { ::ReleaseSRWLockExclusive(reinterpret_cast(&lock_)); } - bool try_lock() { - return !!::TryAcquireSRWLockExclusive(reinterpret_cast(&lock_)); - } - - void lock_shared() { ::AcquireSRWLockShared(reinterpret_cast(&lock_)); } - void unlock_shared() { ::ReleaseSRWLockShared(reinterpret_cast(&lock_)); } - bool try_lock_shared() { - return !!::TryAcquireSRWLockShared(reinterpret_cast(&lock_)); - } + void lock(); + void unlock(); + bool try_lock(); + + void lock_shared(); + void unlock_shared(); + bool try_lock_shared(); private: // Matches the SRWLOCK ABI (single void*). SRWLOCK_INIT is {nullptr}. diff --git a/base/threading/mutex_test.cc b/base/threading/mutex_test.cc new file mode 100644 index 00000000..c2c033af --- /dev/null +++ b/base/threading/mutex_test.cc @@ -0,0 +1,80 @@ +// Copyright (C) 2026 Vincent Hengel. +// For licensing information see LICENSE at the root of this distribution. + +// These headers come first, before anything else can pull in : they +// reach nearly every file and must not bring its macros along, or names like +// MemoryBarrier or near get rewritten under them. +#include +#include + +#if defined(MemoryBarrier) || defined(near) || defined(far) || defined(min) || defined(max) +#error "a base lock header leaked a Windows macro" +#endif + +// base::File needs HANDLE, which it takes from minwin.h. On mingw minwin.h is +// all of , so only the other compilers can hold it to this. +#include + +#if !defined(__MINGW32__) && \ + (defined(MemoryBarrier) || defined(near) || defined(far) || defined(min) || defined(max)) +#error "base/filesystem/file.h leaked a Windows macro" +#endif + +#include + +#include +#include +#include + +namespace { + +TEST(SpinningMutex, TryFailsWhileHeld) { + base::SpinningMutex mutex; + mutex.lock(); + EXPECT_FALSE(mutex.try_lock()); + mutex.unlock(); + EXPECT_TRUE(mutex.try_lock()); + mutex.unlock(); +} + +TEST(SpinningMutex, SerializesIncrements) { + constexpr int kThreads = 4; + constexpr int kIterations = 20000; + base::SpinningMutex mutex; + int counter = 0; + base::Thread* threads[kThreads]; + for (auto& t : threads) { + t = new base::Thread( + "mutex-inc", + [&] { + for (int i = 0; i < kIterations; i++) { + base::LockGuard guard(mutex); + ++counter; + } + }, + /*start_now=*/true); + } + for (auto* t : threads) { + EXPECT_TRUE(t->Join()); + delete t; + } + EXPECT_EQ(counter, kThreads * kIterations); +} + +TEST(SharedMutex, ReadersShareWritersExclude) { + base::SharedMutex mutex; + mutex.lock_shared(); + EXPECT_TRUE(mutex.try_lock_shared()); + EXPECT_FALSE(mutex.try_lock()); + mutex.unlock_shared(); + mutex.unlock_shared(); + + mutex.lock(); + EXPECT_FALSE(mutex.try_lock_shared()); + EXPECT_FALSE(mutex.try_lock()); + mutex.unlock(); + EXPECT_TRUE(mutex.try_lock()); + mutex.unlock(); +} + +} // namespace diff --git a/base/threading/mutex_win.cc b/base/threading/mutex_win.cc new file mode 100644 index 00000000..af584cea --- /dev/null +++ b/base/threading/mutex_win.cc @@ -0,0 +1,42 @@ +// Copyright (C) 2026 Vincent Hengel. +// For licensing information see LICENSE at the root of this distribution. +// +// SharedMutex on SRWLOCK. Out of line rather than inline in mutex.h so that +// minwin.h, which on mingw is all of , stays out of every file that +// merely holds a mutex: its macros (MemoryBarrier, near, far, ...) would +// otherwise rewrite unrelated names there. + +#include +#include + +namespace base { + +#if !defined(BASE_USE_STD_MUTEX) || !BASE_USE_STD_MUTEX + +void SharedMutex::lock() { + ::AcquireSRWLockExclusive(reinterpret_cast(&lock_)); +} + +void SharedMutex::unlock() { + ::ReleaseSRWLockExclusive(reinterpret_cast(&lock_)); +} + +bool SharedMutex::try_lock() { + return !!::TryAcquireSRWLockExclusive(reinterpret_cast(&lock_)); +} + +void SharedMutex::lock_shared() { + ::AcquireSRWLockShared(reinterpret_cast(&lock_)); +} + +void SharedMutex::unlock_shared() { + ::ReleaseSRWLockShared(reinterpret_cast(&lock_)); +} + +bool SharedMutex::try_lock_shared() { + return !!::TryAcquireSRWLockShared(reinterpret_cast(&lock_)); +} + +#endif + +} // namespace base diff --git a/base/threading/spinning_mutex.cc b/base/threading/spinning_mutex.cc index 980269ee..6aed3cc2 100644 --- a/base/threading/spinning_mutex.cc +++ b/base/threading/spinning_mutex.cc @@ -7,6 +7,7 @@ #if defined(_WIN32) || defined(OS_WIN) #include +#include #define PA_YIELD_PROCESSOR _mm_pause() #elif defined(__x86_64__) || defined(__i386__) #define PA_YIELD_PROCESSOR __asm__ __volatile__("pause") @@ -25,6 +26,22 @@ namespace base { +#if defined(_WIN32) || defined(OS_WIN) +// Out of line on Windows so spinning_mutex.h never needs minwin.h, which on +// mingw is all of . lock_ has SRWLOCK's layout, a single pointer. +void SpinningMutex::LockSlow() { + ::AcquireSRWLockExclusive(reinterpret_cast(&lock_)); +} + +bool SpinningMutex::Try() { + return !!::TryAcquireSRWLockExclusive(reinterpret_cast(&lock_)); +} + +void SpinningMutex::Release() { + ::ReleaseSRWLockExclusive(reinterpret_cast(&lock_)); +} +#endif + void SpinningMutex::Reinit() { Release(); } diff --git a/base/threading/spinning_mutex.h b/base/threading/spinning_mutex.h index 23409b59..12436b41 100644 --- a/base/threading/spinning_mutex.h +++ b/base/threading/spinning_mutex.h @@ -11,8 +11,6 @@ #include #include #include -#elif defined(_WIN32) || defined(OS_WIN) -#include #endif namespace base { @@ -21,8 +19,11 @@ class BASE_EXPORT SpinningMutex { public: inline constexpr SpinningMutex(); STRONG_INLINE void Acquire(); - STRONG_INLINE void Release(); - STRONG_INLINE bool Try(); + // Inline on POSIX; out of line on Windows (spinning_mutex.cc), so that + // this header, which every base::Mutex user includes, never pulls a Windows + // header and its macros into the including file. + void Release(); + bool Try(); void AssertAcquired() const {} void Reinit(); @@ -50,10 +51,8 @@ class BASE_EXPORT SpinningMutex { base::Atomic state_{kUnlocked}; #elif defined(_WIN32) || defined(OS_WIN) - struct PA_CHROME_SRWLOCK { - void* Ptr; - }; - PA_CHROME_SRWLOCK lock_{SRWLOCK_INIT}; + // SRWLOCK's layout, a single pointer; SRWLOCK_INIT is all zero. + void* lock_ = nullptr; #endif }; @@ -83,19 +82,4 @@ STRONG_INLINE void SpinningMutex::Release() { } #endif -#if defined(_WIN32) || defined(OS_WIN) - -STRONG_INLINE void SpinningMutex::LockSlow() { - ::AcquireSRWLockExclusive(reinterpret_cast(&lock_)); -} - -STRONG_INLINE bool SpinningMutex::Try() { - return !!::TryAcquireSRWLockExclusive(reinterpret_cast(&lock_)); -} - -STRONG_INLINE void SpinningMutex::Release() { - ::ReleaseSRWLockExclusive(reinterpret_cast(&lock_)); -} -#endif - } // namespace base