Files
UnrealEngine/Engine/Source/Programs/AutoRTFMTests/Private/MutexTests.cpp
2025-05-18 13:04:45 +08:00

837 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AutoRTFM.h"
#include "AutoRTFMTesting.h"
#include "AutoRTFMTestUtils.h"
#include "Catch2Includes.h"
#include "Async/TransactionallySafeMutex.h"
#include <atomic>
#include <condition_variable>
#include <cstring>
#include <memory>
#include <mutex>
#include <thread>
TEST_CASE("TransactionallySafeMutex.Outside Transaction")
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Abort([&]
{
UE::TScopeLock Lock(Mutex);
AutoRTFM::AbortTransaction();
});
AutoRTFM::Testing::Commit([&]
{
UE::TScopeLock Lock(Mutex);
});
}
TEST_CASE("TransactionallySafeMutex.IsLocked Outside Transaction")
{
UE::FTransactionallySafeMutex Mutex;
REQUIRE(!Mutex.IsLocked());
Mutex.Lock();
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
REQUIRE(!Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.Inside Transaction")
{
AutoRTFM::Testing::Abort([&]
{
UE::FTransactionallySafeMutex Mutex;
UE::TScopeLock Lock(Mutex);
AutoRTFM::AbortTransaction();
});
AutoRTFM::Testing::Commit([&]
{
UE::FTransactionallySafeMutex Mutex;
UE::TScopeLock Lock(Mutex);
});
}
TEST_CASE("TransactionallySafeMutex.IsLocked Inside Transaction")
{
UE::FTransactionallySafeMutex Mutex;
REQUIRE(!Mutex.IsLocked());
AutoRTFM::Testing::Commit([&]
{
REQUIRE(!Mutex.IsLocked());
Mutex.Lock();
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
REQUIRE(Mutex.IsLocked());
});
REQUIRE(!Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.Inside Transaction Used In Nested Transaction")
{
AutoRTFM::Testing::Abort([&]
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Abort([&]
{
UE::TScopeLock Lock(Mutex);
AutoRTFM::CascadingAbortTransaction();
});
});
AutoRTFM::Testing::Commit([&]
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Abort([&]
{
UE::TScopeLock Lock(Mutex);
AutoRTFM::AbortTransaction();
});
});
AutoRTFM::Testing::Abort([&]
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Commit([&]
{
UE::TScopeLock Lock(Mutex);
});
AutoRTFM::AbortTransaction();
});
AutoRTFM::Testing::Commit([&]
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Commit([&]
{
UE::TScopeLock Lock(Mutex);
});
});
}
TEST_CASE("TransactionallySafeMutex.In Static Local Initializer")
{
struct MyStruct final
{
UE::FTransactionallySafeMutex Mutex;
};
auto Lambda = []()
{
static MyStruct Mine;
UE::TScopeLock _(Mine.Mutex);
return 42;
};
AutoRTFM::Testing::Abort([&]
{
REQUIRE(42 == Lambda());
AutoRTFM::AbortTransaction();
});
REQUIRE(42 == Lambda());
AutoRTFM::Testing::Commit([&]
{
REQUIRE(42 == Lambda());
});
REQUIRE(42 == Lambda());
}
TEST_CASE("TransactionallySafeMutex.In Static Local Initializer Called From Open")
{
struct MyStruct final
{
UE::FTransactionallySafeMutex Mutex;
};
auto Lambda = []()
{
static MyStruct Mine;
UE::TScopeLock _(Mine.Mutex);
return 42;
};
AutoRTFM::Testing::Abort([&]
{
AutoRTFM::Open([&] { REQUIRE(42 == Lambda()); });
AutoRTFM::AbortTransaction();
});
REQUIRE(42 == Lambda());
AutoRTFM::Testing::Commit([&]
{
AutoRTFM::Open([&] { REQUIRE(42 == Lambda()); });
});
REQUIRE(42 == Lambda());
}
TEST_CASE("TransactionallySafeMutex.Delete Heap Allocated Mutex")
{
SECTION("SingleThread") // Mutex owned and destructed by this thread
{
UE::FTransactionallySafeMutex* const Mutex = new UE::FTransactionallySafeMutex();
AutoRTFM::Testing::Abort([&]
{
Mutex->Lock();
delete Mutex;
AutoRTFM::AbortTransaction();
});
REQUIRE(!Mutex->IsLocked());
AutoRTFM::Testing::Commit([&]
{
Mutex->Lock();
delete Mutex;
});
}
SECTION("MultiThread") // Mutex owned and destructed by another thread
{
// This test does not support retries due to coordination with another thread.
AutoRTFMTestUtils::FScopedRetry NoRetry(AutoRTFM::ForTheRuntime::EAutoRTFMRetryTransactionState::NoRetry);
enum EState
{
InitializeThread,
MutexReady,
MutexUsed,
MutexDeleted,
};
struct FEvent
{
UE_AUTORTFM_ALWAYS_OPEN
void Signal(EState State)
{
{
std::unique_lock Lock(Mutex);
CurrentState = State;
}
CV.notify_one();
}
UE_AUTORTFM_ALWAYS_OPEN
void Wait(EState State)
{
std::unique_lock Lock(Mutex);
CV.wait(Lock, [this, State] { return CurrentState == State; });
}
private:
EState CurrentState = InitializeThread;
std::mutex Mutex;
std::condition_variable CV;
};
UE::FTransactionallySafeMutex* Mutex = nullptr;
FEvent Event;
std::thread Thread([&Mutex, &Event]
{
Mutex = new UE::FTransactionallySafeMutex();
Event.Signal(MutexReady);
Event.Wait(MutexUsed);
delete Mutex;
Event.Signal(MutexDeleted);
});
Event.Wait(MutexReady);
SECTION("Lock, Abort, Destroy")
{
AutoRTFM::Testing::Abort([&]
{
Mutex->Lock();
AutoRTFM::AbortTransaction();
});
REQUIRE(!Mutex->IsLocked());
Event.Signal(MutexUsed);
Event.Wait(MutexDeleted);
}
SECTION("Lock, Unlock, Destroy, Abort")
{
AutoRTFM::Testing::Abort([&]
{
Mutex->Lock();
Mutex->Unlock();
Event.Signal(MutexUsed);
Event.Wait(MutexDeleted);
AutoRTFM::AbortTransaction();
});
}
SECTION("Lock, Unlock, Destroy, Commit")
{
AutoRTFM::Testing::Commit([&]
{
Mutex->Lock();
Mutex->Unlock();
Event.Signal(MutexUsed);
Event.Wait(MutexDeleted);
});
}
Thread.join();
}
}
TEST_CASE("TransactionallySafeMutex.Lock Within, Unlock Outside")
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Commit([&]
{
Mutex.Lock();
});
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
REQUIRE(!Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.TryLock Within, Unlock Outside")
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Commit([&]
{
REQUIRE(Mutex.TryLock());
});
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
REQUIRE(!Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.Lock Outside, Unlock Within")
{
UE::FTransactionallySafeMutex Mutex = UE::FTransactionallySafeMutex(UE::FAcquireLock());
AutoRTFM::Testing::Commit([&]
{
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
});
REQUIRE(!Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.Lock, Unlock, Lock")
{
UE::FTransactionallySafeMutex Mutex;
SECTION("Commit")
{
AutoRTFM::Testing::Commit([&]
{
Mutex.Lock();
Mutex.Unlock();
Mutex.Lock();
});
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
}
SECTION("Abort")
{
AutoRTFM::Testing::Abort([&]
{
Mutex.Lock();
Mutex.Unlock();
Mutex.Lock();
AutoRTFM::AbortTransaction();
});
REQUIRE(!Mutex.IsLocked());
}
}
TEST_CASE("TransactionallySafeMutex.TryLock, Unlock, TryLock")
{
UE::FTransactionallySafeMutex Mutex;
SECTION("Commit")
{
AutoRTFM::Testing::Commit([&]
{
REQUIRE(Mutex.TryLock());
Mutex.Unlock();
REQUIRE(Mutex.TryLock());
});
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
}
SECTION("Abort")
{
AutoRTFM::Testing::Abort([&]
{
REQUIRE(Mutex.TryLock());
Mutex.Unlock();
REQUIRE(Mutex.TryLock());
AutoRTFM::AbortTransaction();
});
REQUIRE(!Mutex.IsLocked());
}
}
TEST_CASE("TransactionallySafeMutex.Unlock, Lock, Unlock")
{
UE::FTransactionallySafeMutex Mutex;
Mutex.Lock();
SECTION("Commit")
{
AutoRTFM::Testing::Commit([&]
{
Mutex.Unlock();
Mutex.Lock();
Mutex.Unlock();
});
REQUIRE(!Mutex.IsLocked());
}
SECTION("Commit")
{
AutoRTFM::Testing::Abort([&]
{
Mutex.Unlock();
Mutex.Lock();
Mutex.Unlock();
AutoRTFM::AbortTransaction();
});
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
}
}
TEST_CASE("TransactionallySafeMutex.Unlock, TryLock, Unlock")
{
UE::FTransactionallySafeMutex Mutex;
Mutex.Lock();
SECTION("Commit")
{
AutoRTFM::Testing::Commit([&]
{
Mutex.Unlock();
REQUIRE(Mutex.TryLock());
Mutex.Unlock();
});
REQUIRE(!Mutex.IsLocked());
}
SECTION("Commit")
{
AutoRTFM::Testing::Abort([&]
{
Mutex.Unlock();
REQUIRE(Mutex.TryLock());
Mutex.Unlock();
AutoRTFM::AbortTransaction();
});
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
}
}
TEST_CASE("TransactionallySafeMutex.Lock Outside, Unlock & Lock Within")
{
UE::FTransactionallySafeMutex Mutex = UE::FTransactionallySafeMutex(UE::FAcquireLock());
AutoRTFM::Testing::Commit([&]
{
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
Mutex.Lock();
});
REQUIRE(Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.Commit(Lock, Commit(Unlock))")
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Commit([&]
{
Mutex.Lock();
AutoRTFM::Testing::Commit([&]
{
Mutex.Unlock();
});
REQUIRE(Mutex.IsLocked());
});
REQUIRE(!Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.Abort(Lock, Commit(Unlock, Lock))")
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Abort([&]
{
Mutex.Lock();
AutoRTFM::Testing::Commit([&]
{
Mutex.Unlock();
Mutex.Lock();
});
REQUIRE(Mutex.IsLocked());
AutoRTFM::AbortTransaction();
});
REQUIRE(!Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.Contended Lock")
{
struct RAIIeroo final
{
RAIIeroo()
{
State = AutoRTFM::ForTheRuntime::GetRetryTransaction();
AutoRTFM::ForTheRuntime::SetRetryTransaction(AutoRTFM::ForTheRuntime::EAutoRTFMRetryTransactionState::NoRetry);
}
~RAIIeroo()
{
AutoRTFM::ForTheRuntime::SetRetryTransaction(State);
}
private:
AutoRTFM::ForTheRuntime::EAutoRTFMRetryTransactionState State;
} _;
SECTION("Contender Parks Transaction")
{
UE::FTransactionallySafeMutex Mutex;
std::atomic_uint Orderer = 0;
std::thread Contender([&]
{
REQUIRE(0 == Orderer);
Mutex.Lock();
Orderer += 1; // unblock main thread
while (1 == Orderer) {} // wait on main thread
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Mutex.Unlock();
});
while (0 == Orderer) {} // wait on contender
AutoRTFM::Testing::Commit([&]
{
REQUIRE(Mutex.IsLocked());
AutoRTFM::Open([&] { Orderer += 1; }); // unblock contender
Mutex.Lock();
});
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
Contender.join();
}
SECTION("Transaction Parks Contender")
{
UE::FTransactionallySafeMutex Mutex;
std::atomic_uint Orderer = 0;
std::thread Contender([&]
{
while (0 == Orderer) {} // wait on main thread
Mutex.Lock();
});
AutoRTFM::Testing::Commit([&]
{
Mutex.Lock();
AutoRTFM::Open([&]
{
Orderer += 1; // unblock contender
std::this_thread::sleep_for(std::chrono::milliseconds(100));
});
Mutex.Unlock();
});
Contender.join();
Mutex.Unlock();
}
}
TEST_CASE("TransactionallySafeMutex.In On-Commit")
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Commit([&]
{
Mutex.Lock();
AutoRTFM::OnCommit([&]
{
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
REQUIRE(!Mutex.IsLocked());
});
});
AutoRTFM::Testing::Commit([&]
{
AutoRTFM::OnCommit([&]
{
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
REQUIRE(Mutex.IsLocked());
});
Mutex.Lock();
});
AutoRTFM::Testing::Commit([&]
{
REQUIRE(Mutex.TryLock());
REQUIRE(!Mutex.TryLock());
AutoRTFM::OnCommit([&]
{
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
REQUIRE(!Mutex.IsLocked());
});
});
AutoRTFM::Testing::Commit([&]
{
AutoRTFM::OnCommit([&]
{
REQUIRE(Mutex.IsLocked());
Mutex.Unlock();
REQUIRE(Mutex.IsLocked());
});
REQUIRE(Mutex.TryLock());
});
Mutex.Lock();
REQUIRE(Mutex.IsLocked());
AutoRTFM::Testing::Commit([&]
{
AutoRTFM::OnCommit([&]
{
REQUIRE(Mutex.IsLocked());
Mutex.Lock();
REQUIRE(Mutex.IsLocked());
});
Mutex.Unlock();
});
REQUIRE(Mutex.IsLocked());
AutoRTFM::Testing::Commit([&]
{
Mutex.Unlock();
AutoRTFM::OnCommit([&]
{
REQUIRE(!Mutex.IsLocked());
Mutex.Lock();
REQUIRE(Mutex.IsLocked());
});
});
REQUIRE(Mutex.IsLocked());
AutoRTFM::Testing::Commit([&]
{
Mutex.Unlock();
AutoRTFM::OnCommit([&]
{
REQUIRE(Mutex.TryLock());
});
});
REQUIRE(Mutex.IsLocked());
AutoRTFM::Testing::Commit([&]
{
AutoRTFM::OnCommit([&]
{
REQUIRE(Mutex.TryLock());
});
Mutex.Unlock();
});
REQUIRE(Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.In On-Abort")
{
UE::FTransactionallySafeMutex Mutex;
AutoRTFM::Testing::Abort([&]
{
AutoRTFM::OnAbort([&]
{
REQUIRE(!Mutex.IsLocked());
});
Mutex.Lock();
AutoRTFM::OnAbort([&]
{
REQUIRE(Mutex.IsLocked());
});
AutoRTFM::AbortTransaction();
});
AutoRTFM::Testing::Abort([&]
{
AutoRTFM::OnAbort([&]
{
REQUIRE(!Mutex.IsLocked());
});
REQUIRE(Mutex.TryLock());
AutoRTFM::OnAbort([&]
{
REQUIRE(Mutex.IsLocked());
});
AutoRTFM::AbortTransaction();
});
AutoRTFM::Testing::Abort([&]
{
AutoRTFM::OnAbort([&]
{
REQUIRE(!Mutex.IsLocked());
});
REQUIRE(Mutex.TryLock());
AutoRTFM::AbortTransaction();
});
Mutex.Lock();
REQUIRE(Mutex.IsLocked());
AutoRTFM::Testing::Abort([&]
{
AutoRTFM::OnAbort([&]
{
REQUIRE(Mutex.IsLocked());
});
Mutex.Unlock();
AutoRTFM::AbortTransaction();
});
REQUIRE(Mutex.IsLocked());
AutoRTFM::Testing::Abort([&]
{
Mutex.Unlock();
AutoRTFM::OnAbort([&]
{
REQUIRE(Mutex.IsLocked());
});
AutoRTFM::AbortTransaction();
});
REQUIRE(Mutex.IsLocked());
AutoRTFM::Testing::Abort([&]
{
Mutex.Unlock();
AutoRTFM::OnAbort([&]
{
REQUIRE(!Mutex.TryLock());
});
AutoRTFM::AbortTransaction();
});
REQUIRE(Mutex.IsLocked());
AutoRTFM::Testing::Abort([&]
{
AutoRTFM::OnAbort([&]
{
REQUIRE(!Mutex.TryLock());
});
Mutex.Unlock();
AutoRTFM::AbortTransaction();
});
REQUIRE(Mutex.IsLocked());
}
TEST_CASE("TransactionallySafeMutex.Locked Mutex In Destructed Object")
{
struct MyStruct final
{
UE::FTransactionallySafeMutex Mutex;
~MyStruct()
{
memset(this, 0, sizeof(MyStruct));
}
};
std::unique_ptr<MyStruct> Mine(new MyStruct);
AutoRTFM::Testing::Commit([&]
{
Mine->Mutex.Lock();
Mine.reset();
REQUIRE(!Mine);
});
}