Files
o3de/Code/Framework/AzCore/Tests/EventTests.cpp
T
lumberyard-employee-dm 9060423592 Settings registry notification deadlock fix (#3065)
* Added a StealHandlers function to AZ Event

The StealHandlers function is able to take all the handlers from an AZ
Event parameter and register them with the current AZ Event

This allows stealing handlers from expiring AZ Events, which is useful
for a lock and swap algorithm for thread safety.
1. Lock persistent AZ::Event
2. Swap persistent AZ::Event with local AZ::Event
3. Unlock persistent AZ::Event - Other threads can now add to this
   AZ::Event
4. Invoke handlers from local AZ::Event
5. Relock persistent AZ::Event
5. Swap local AZ::Event with persistent AZ::Event
6. Local AZ::Event now contains handlers that were added when the lock
   was free
7. Persistent AZ::Event now steals from local AZ::Event
8. Unlock persistent AZ::Event

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Separated SettingRegistry update/query mutex from Notifier update mutex

The Settings Registry update/query mutex is also better scoped to reduce
the amount of lock time.

The Notifier mutex being separate allows the Settings Registry to signal
a notification event without being under any mutex, by locking and
swapping the notifier event with a local instance

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Renamed StealHandlers function to ClaimHandlers
Replaced decltype keywords in ClaimHandlers to auto

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-08-13 16:20:08 -05:00

458 lines
14 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/EBus/Event.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
class EventTests
: public ScopedAllocatorSetupFixture
{
};
TEST_F(EventTests, TestHasCallback)
{
AZ::Event<int32_t> testEvent;
AZ::Event<int32_t>::Handler testHandler([]([[maybe_unused]] int32_t value) {});
EXPECT_TRUE(!testEvent.HasHandlerConnected());
testHandler.Connect(testEvent);
EXPECT_TRUE(testEvent.HasHandlerConnected());
}
TEST_F(EventTests, TestScopedConnect)
{
AZ::Event<int32_t> testEvent;
{
AZ::Event<int32_t>::Handler testHandler([]([[maybe_unused]] int32_t value) {});
testHandler.Connect(testEvent);
EXPECT_TRUE(testEvent.HasHandlerConnected());
}
EXPECT_TRUE(!testEvent.HasHandlerConnected());
}
TEST_F(EventTests, TestEvent)
{
int32_t invokedValue = 0;
AZ::Event<int32_t> testEvent;
AZ::Event<int32_t>::Handler testHandler([&invokedValue](int32_t value) { invokedValue = value; });
testHandler.Connect(testEvent);
EXPECT_TRUE(invokedValue == 0);
testEvent.Signal(1);
EXPECT_TRUE(invokedValue == 1);
testEvent.Signal(-1);
EXPECT_TRUE(invokedValue == -1);
}
TEST_F(EventTests, TestEventRValueParam)
{
int32_t invokedValue = 0;
AZ::Event<int32_t> testEvent;
AZ::Event<int32_t>::Handler testHandler([&invokedValue](int32_t value) { invokedValue = value; });
testHandler.Connect(testEvent);
int32_t value = 1;
EXPECT_TRUE(invokedValue == 0);
testEvent.Signal(value);
EXPECT_TRUE(invokedValue == 1);
}
TEST_F(EventTests, TestEventRefParam)
{
int32_t invokedValue = 0;
AZ::Event<int32_t&> testEvent;
AZ::Event<int32_t&>::Handler testHandler([&invokedValue](int32_t& value) { invokedValue = value++; });
testHandler.Connect(testEvent);
int32_t value = 1;
EXPECT_TRUE(invokedValue == 0);
testEvent.Signal(value);
EXPECT_TRUE(invokedValue == 1);
EXPECT_TRUE(value == 2);
testEvent.Signal(value);
EXPECT_TRUE(invokedValue == 2);
EXPECT_TRUE(value == 3);
}
TEST_F(EventTests, TestEventConstRefParam)
{
int32_t invokedValue = 0;
AZ::Event<const int32_t&> testEvent;
AZ::Event<const int32_t&>::Handler testHandler([&invokedValue](const int32_t& value) { invokedValue = value; });
testHandler.Connect(testEvent);
int32_t value = 1;
EXPECT_TRUE(invokedValue == 0);
testEvent.Signal(value);
EXPECT_TRUE(invokedValue == 1);
}
TEST_F(EventTests, TestEventPointerParam)
{
int32_t invokedValue = 0;
AZ::Event<int32_t*> testEvent;
AZ::Event<int32_t*>::Handler testHandler([&invokedValue](int32_t* value) { invokedValue = (*value)++; });
testHandler.Connect(testEvent);
int32_t value = 1;
EXPECT_TRUE(invokedValue == 0);
testEvent.Signal(&value);
EXPECT_TRUE(invokedValue == 1);
EXPECT_TRUE(value == 2);
testEvent.Signal(&value);
EXPECT_TRUE(invokedValue == 2);
EXPECT_TRUE(value == 3);
}
TEST_F(EventTests, TestEventConstPointerParam)
{
int32_t invokedValue = 0;
AZ::Event<const int32_t*> testEvent;
AZ::Event<const int32_t*>::Handler testHandler([&invokedValue](const int32_t* value) { invokedValue = *value; });
testHandler.Connect(testEvent);
int32_t value = 1;
EXPECT_TRUE(invokedValue == 0);
testEvent.Signal(&value);
EXPECT_TRUE(invokedValue == 1);
}
TEST_F(EventTests, TestEventMultiParam)
{
int32_t invokedValue1 = 0;
bool invokedValue2 = false;
AZ::Event<int32_t, bool> testEvent;
AZ::Event<int32_t, bool>::Handler testHandler([&invokedValue1, &invokedValue2](int32_t value1, bool value2) { invokedValue1 = value1; invokedValue2 = value2; });
testHandler.Connect(testEvent);
EXPECT_TRUE(invokedValue1 == 0);
EXPECT_TRUE(invokedValue2 == false);
testEvent.Signal(1, true);
EXPECT_TRUE(invokedValue1 == 1);
EXPECT_TRUE(invokedValue2 == true);
testEvent.Signal(-1, false);
EXPECT_TRUE(invokedValue1 == -1);
EXPECT_TRUE(invokedValue2 == false);
}
TEST_F(EventTests, TestConnectDuringEvent)
{
AZ::Event<int32_t> testEvent;
{
int32_t testHandler2Data = 0;
AZ::Event<int32_t>::Handler testHandler2([&testHandler2Data](int32_t value) { testHandler2Data = value; });
AZ::Event<int32_t>::Handler testHandler([&testHandler2, &testEvent]([[maybe_unused]] int32_t value) { testHandler2.Connect(testEvent); });
testHandler.Connect(testEvent);
testEvent.Signal(1);
EXPECT_TRUE(testHandler2Data == 0);
testHandler.Disconnect();
EXPECT_TRUE(testEvent.HasHandlerConnected());
testEvent.Signal(2);
EXPECT_TRUE(testHandler2Data == 2);
}
EXPECT_TRUE(!testEvent.HasHandlerConnected());
}
TEST_F(EventTests, TestDisconnectDuringEvent)
{
AZ::Event<int32_t> testEvent;
{
int32_t testHandler2Data = 0;
AZ::Event<int32_t>::Handler testHandler2([&testHandler2Data](int32_t value) { testHandler2Data = value; });
AZ::Event<int32_t>::Handler testHandler([&testHandler2]([[maybe_unused]] int32_t value) { testHandler2.Disconnect(); });
testHandler2.Connect(testEvent);
testHandler.Connect(testEvent);
testEvent.Signal(1);
EXPECT_TRUE(testHandler2Data == 1);
EXPECT_TRUE(testEvent.HasHandlerConnected());
testEvent.Signal(2);
EXPECT_TRUE(testHandler2Data == 1);
}
EXPECT_TRUE(!testEvent.HasHandlerConnected());
}
TEST_F(EventTests, TestDisconnectDuringEventReversed)
{
AZ::Event<int32_t> testEvent;
// Same test as above, but connected using reversed ordering
{
int32_t testHandler2Data = 0;
AZ::Event<int32_t>::Handler testHandler2([&testHandler2Data](int32_t value) { testHandler2Data = value; });
AZ::Event<int32_t>::Handler testHandler([&testHandler2]([[maybe_unused]] int32_t value) { testHandler2.Disconnect(); });
testHandler.Connect(testEvent);
testHandler2.Connect(testEvent);
testEvent.Signal(1);
EXPECT_TRUE(testHandler2Data == 0);
EXPECT_TRUE(testEvent.HasHandlerConnected());
testEvent.Signal(2);
EXPECT_TRUE(testHandler2Data == 0);
}
EXPECT_TRUE(!testEvent.HasHandlerConnected());
}
TEST_F(EventTests, CopyConstructorAndCopyAssignmentOperator_AreNotCallable)
{
static_assert(!AZStd::is_copy_constructible_v<AZ::Event<int32_t>>, "AZ Events should not be copy constructible");
static_assert(!AZStd::is_copy_assignable_v<AZ::Event<int32_t>>, "AZ Events should not be copy assignable");
}
TEST_F(EventTests, TestClaimHandlers_TakesAllSourceHandlers)
{
AZ::Event<> testEvent1;
AZ::Event<> testEvent2;
int32_t handlerInvokeCount{};
auto handlerCallback = [&handlerInvokeCount]()
{
++handlerInvokeCount;
};
AZ::Event<>::Handler testHandler1(handlerCallback);
AZ::Event<>::Handler testHandler2(handlerCallback);
testHandler1.Connect(testEvent1);
testHandler2.Connect(testEvent2);
EXPECT_TRUE(testEvent1.HasHandlerConnected());
EXPECT_TRUE(testEvent2.HasHandlerConnected());
testEvent1.ClaimHandlers(AZStd::move(testEvent2));
EXPECT_TRUE(testEvent1.HasHandlerConnected());
EXPECT_FALSE(testEvent2.HasHandlerConnected());
// testEvent1 should have both handlers
testEvent1.Signal();
EXPECT_EQ(2, handlerInvokeCount);
// testEvent2 should have neither of the handlers
testEvent2.Signal();
EXPECT_EQ(2, handlerInvokeCount);
}
TEST_F(EventTests, HandlerMoveAssignment_ProperlyDisconnectsFromOldEvent)
{
AZ::Event<> testEvent1;
AZ::Event<> testEvent2;
AZ::Event<>::Handler testHandler1([]() {});
AZ::Event<>::Handler testHandler2([]() {});
testHandler1.Connect(testEvent1);
testHandler2.Connect(testEvent2);
EXPECT_TRUE(testEvent1.HasHandlerConnected());
EXPECT_TRUE(testEvent2.HasHandlerConnected());
testHandler1 = AZStd::move(testHandler2);
EXPECT_FALSE(testEvent1.HasHandlerConnected());
EXPECT_TRUE(testEvent2.HasHandlerConnected());
}
TEST_F(EventTests, TestHandlerCopyConstructorOperator)
{
int32_t invokedCounter = 0;
AZ::Event<> testEvent;
AZ::Event<>::Handler testHandler([&invokedCounter]() { invokedCounter++; });
testHandler.Connect(testEvent);
AZ::Event<>::Handler testHandler2(testHandler);
EXPECT_TRUE(testHandler.IsConnected());
EXPECT_TRUE(testHandler2.IsConnected());
EXPECT_TRUE(invokedCounter == 0);
testEvent.Signal();
EXPECT_TRUE(invokedCounter == 2);
}
TEST_F(EventTests, TestHandlerCopyAssignmentOperator)
{
int32_t invokedCounter = 0;
AZ::Event<> testEvent;
AZ::Event<>::Handler testHandler([&invokedCounter]() { invokedCounter++; });
testHandler.Connect(testEvent);
AZ::Event<>::Handler testHandler2;
EXPECT_TRUE(testHandler.IsConnected());
EXPECT_FALSE(testHandler2.IsConnected());
testHandler2 = testHandler;
EXPECT_TRUE(testHandler2.IsConnected());
EXPECT_TRUE(invokedCounter == 0);
testEvent.Signal();
EXPECT_TRUE(invokedCounter == 2);
}
}
#if defined(HAVE_BENCHMARK)
//-------------------------------------------------------------------------
// PERF TESTS
//-------------------------------------------------------------------------
#include <benchmark/benchmark.h>
namespace Benchmark
{
static constexpr int32_t NumHandlers = 10000;
static void BM_EventPerf_EventEmpty(benchmark::State& state)
{
AZ::Event<int32_t> testEvent;
AZ::Event<int32_t>::Handler testHandler[NumHandlers];
for (int32_t i = 0; i < NumHandlers; ++i)
{
testHandler[i] = AZ::Event<int32_t>::Handler([]([[maybe_unused]] int32_t value) {});
testHandler[i].Connect(testEvent);
}
while (state.KeepRunning())
{
testEvent.Signal(1);
}
}
BENCHMARK(BM_EventPerf_EventEmpty);
static void BM_EventPerf_EventIncrement(benchmark::State& state)
{
AZ::Event<int32_t> testEvent;
AZ::Event<int32_t>::Handler testHandler[NumHandlers];
int32_t incrementCounter = 0;
for (int32_t i = 0; i < NumHandlers; ++i)
{
testHandler[i] = AZ::Event<int32_t>::Handler([&incrementCounter]([[maybe_unused]] int32_t value) { ++incrementCounter; });
testHandler[i].Connect(testEvent);
}
while (state.KeepRunning())
{
testEvent.Signal(1);
}
}
BENCHMARK(BM_EventPerf_EventIncrement);
class EBusPerfBaseline
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual void OnSignal(int32_t) = 0;
};
using EBusPerfBaselineBus = AZ::EBus<EBusPerfBaseline>;
class EBusPerfBaselineImplEmpty
: public EBusPerfBaselineBus::Handler
{
public:
EBusPerfBaselineImplEmpty() { EBusPerfBaselineBus::Handler::BusConnect(); }
~EBusPerfBaselineImplEmpty() { EBusPerfBaselineBus::Handler::BusDisconnect(); }
void OnSignal(int32_t) override {}
};
static void BM_EventPerf_EBusEmpty(benchmark::State& state)
{
EBusPerfBaselineImplEmpty testHandler[NumHandlers];
while (state.KeepRunning())
{
EBusPerfBaselineBus::Broadcast(&EBusPerfBaseline::OnSignal, 1);
}
}
BENCHMARK(BM_EventPerf_EBusEmpty);
class EBusPerfBaselineImplIncrement
: public EBusPerfBaselineBus::Handler
{
public:
EBusPerfBaselineImplIncrement() { EBusPerfBaselineBus::Handler::BusConnect(); }
~EBusPerfBaselineImplIncrement() { EBusPerfBaselineBus::Handler::BusDisconnect(); }
void SetIncrementCounter(int32_t* incrementCounter) { m_incrementCounter = incrementCounter; }
void OnSignal(int32_t) override { ++(*m_incrementCounter); }
int32_t* m_incrementCounter;
};
static void BM_EventPerf_EBusIncrement(benchmark::State& state)
{
int32_t incrementCounter = 0;
EBusPerfBaselineImplIncrement testHandler[NumHandlers];
for (int32_t i = 0; i < NumHandlers; ++i)
{
testHandler[i].SetIncrementCounter(&incrementCounter);
}
while (state.KeepRunning())
{
EBusPerfBaselineBus::Broadcast(&EBusPerfBaseline::OnSignal, 1);
}
}
BENCHMARK(BM_EventPerf_EBusIncrement);
static void BM_EventPerf_EBusIncrementLambda(benchmark::State& state)
{
int32_t incrementCounter = 0;
EBusPerfBaselineImplEmpty testHandler[NumHandlers];
auto invokeFunc = [&incrementCounter](EBusPerfBaseline*, [[maybe_unused]] int32_t value) { ++incrementCounter; };
while (state.KeepRunning())
{
EBusPerfBaselineBus::Broadcast(invokeFunc, 1);
}
}
BENCHMARK(BM_EventPerf_EBusIncrementLambda);
}
#endif // HAVE_BENCHMARK