From 3711803c6dcb9d39ea4cb66e58740a5e33cad148 Mon Sep 17 00:00:00 2001 From: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:12:41 +1200 Subject: [PATCH] Add the ability for EventLoopHandlers to participate in a Select-based event loop (#34433) * Darwin: Make it possible to build with chip_system_config_use_dispatch=false Note: This is for unit testing / development purposes only. * Add the ability for EventLoopHandlers to participate in the event loop * Avoid leaking the fallback timer in the test * Expand documentation * Fix GN logic so fake platform tests can run on Darwin * Disable TestEventLoopHandler when chip_device_platform == "fake" The fake PlatformManagerImpl doesn't drive the SystemLayer event loop. --- src/lib/support/BUILD.gn | 5 +- src/lib/support/IntrusiveList.h | 10 ++ src/platform/Darwin/PlatformManagerImpl.cpp | 14 +- src/platform/Darwin/PlatformManagerImpl.h | 15 +- src/platform/Darwin/SystemPlatformConfig.h | 8 -- src/system/SystemLayer.h | 49 +++++++ src/system/SystemLayerImplSelect.cpp | 81 +++++++++-- src/system/SystemLayerImplSelect.h | 9 ++ src/system/system.gni | 4 + src/system/tests/BUILD.gn | 1 + src/system/tests/TestEventLoopHandler.cpp | 148 ++++++++++++++++++++ 11 files changed, 316 insertions(+), 28 deletions(-) create mode 100644 src/system/tests/TestEventLoopHandler.cpp diff --git a/src/lib/support/BUILD.gn b/src/lib/support/BUILD.gn index 38ee31fccb94e5..80b1b6d99dc328 100644 --- a/src/lib/support/BUILD.gn +++ b/src/lib/support/BUILD.gn @@ -23,6 +23,7 @@ import("${chip_root}/build/chip/chip_version.gni") import("${chip_root}/build/chip/java/config.gni") import("${chip_root}/build/chip/tests.gni") import("${chip_root}/src/lib/core/core.gni") +import("${chip_root}/src/platform/device.gni") declare_args() { # Set to true to run the PersistentStorageDelegate API compliance audit @@ -255,7 +256,7 @@ static_library("support") { "verhoeff/Verhoeff10.cpp", ] - if (current_os == "android" || matter_enable_java_compilation) { + if (chip_device_platform == "android" || matter_enable_java_compilation) { if (matter_enable_java_compilation) { include_dirs = java_matter_controller_dependent_paths } @@ -304,7 +305,7 @@ static_library("support") { # Platforms that utilize CHIP_SYSTEM_CONFIG_PLATFORM_LOG need to # be pulled in here as public_deps since they hook into logging at # the macro level rather than just providing a LogV implementation. - if (current_os == "mac" || current_os == "ios") { + if (chip_device_platform == "darwin") { public_deps += [ "${chip_root}/src/platform/Darwin:logging" ] } diff --git a/src/lib/support/IntrusiveList.h b/src/lib/support/IntrusiveList.h index 961113e8d1ee6b..a38516cbfb91c8 100644 --- a/src/lib/support/IntrusiveList.h +++ b/src/lib/support/IntrusiveList.h @@ -414,6 +414,11 @@ class IntrusiveList : public IntrusiveListBase ConstIterator(IntrusiveListBase::ConstIteratorBase && base) : IntrusiveListBase::ConstIteratorBase(std::move(base)) {} const T * operator->() { return Hook::ToObject(mCurrent); } const T & operator*() { return *Hook::ToObject(mCurrent); } + + ConstIterator & operator++() { return static_cast(IntrusiveListBase::ConstIteratorBase::operator++()); } + ConstIterator operator++(int) { return IntrusiveListBase::ConstIteratorBase::operator++(1); } + ConstIterator & operator--() { return static_cast(IntrusiveListBase::ConstIteratorBase::operator--()); } + ConstIterator operator--(int) { return IntrusiveListBase::ConstIteratorBase::operator--(1); } }; class Iterator : public IntrusiveListBase::IteratorBase @@ -426,6 +431,11 @@ class IntrusiveList : public IntrusiveListBase Iterator(IntrusiveListBase::IteratorBase && base) : IntrusiveListBase::IteratorBase(std::move(base)) {} T * operator->() { return Hook::ToObject(mCurrent); } T & operator*() { return *Hook::ToObject(mCurrent); } + + Iterator & operator++() { return static_cast(IntrusiveListBase::IteratorBase::operator++()); } + Iterator operator++(int) { return IntrusiveListBase::IteratorBase::operator++(1); } + Iterator & operator--() { return static_cast(IntrusiveListBase::IteratorBase::operator--()); } + Iterator operator--(int) { return IntrusiveListBase::IteratorBase::operator--(1); } }; ConstIterator begin() const { return IntrusiveListBase::begin(); } diff --git a/src/platform/Darwin/PlatformManagerImpl.cpp b/src/platform/Darwin/PlatformManagerImpl.cpp index d1dc5308c869e9..d68304ef696d30 100644 --- a/src/platform/Darwin/PlatformManagerImpl.cpp +++ b/src/platform/Darwin/PlatformManagerImpl.cpp @@ -35,7 +35,11 @@ #include // Include the non-inline definitions for the GenericPlatformManagerImpl<> template, +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH #include +#else +#include +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH #include #include @@ -64,7 +68,7 @@ CHIP_ERROR PlatformManagerImpl::_InitChipStack() ReturnErrorOnFailure(Internal::PosixConfig::Init()); #endif // CHIP_DISABLE_PLATFORM_KVS -#if !CHIP_SYSTEM_CONFIG_USE_LIBEV +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH // Ensure there is a dispatch queue available static_cast(DeviceLayer::SystemLayer()).SetDispatchQueue(GetWorkQueue()); #endif @@ -83,6 +87,7 @@ CHIP_ERROR PlatformManagerImpl::_InitChipStack() return CHIP_NO_ERROR; } +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH CHIP_ERROR PlatformManagerImpl::_StartEventLoopTask() { auto expected = WorkQueueState::kSuspended; @@ -128,12 +133,6 @@ void PlatformManagerImpl::_RunEventLoop() mRunLoopSem = nullptr; } -void PlatformManagerImpl::_Shutdown() -{ - // Call up to the base class _Shutdown() to perform the bulk of the shutdown. - GenericPlatformManagerImpl::_Shutdown(); -} - CHIP_ERROR PlatformManagerImpl::_PostEvent(const ChipDeviceEvent * event) { const ChipDeviceEvent eventCopy = *event; @@ -142,6 +141,7 @@ CHIP_ERROR PlatformManagerImpl::_PostEvent(const ChipDeviceEvent * event) }); return CHIP_NO_ERROR; } +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH #if CHIP_STACK_LOCK_TRACKING_ENABLED bool PlatformManagerImpl::_IsChipStackLockedByCurrentThread() const diff --git a/src/platform/Darwin/PlatformManagerImpl.h b/src/platform/Darwin/PlatformManagerImpl.h index ba37badf4628b5..24357c905f9887 100644 --- a/src/platform/Darwin/PlatformManagerImpl.h +++ b/src/platform/Darwin/PlatformManagerImpl.h @@ -25,7 +25,12 @@ #include #include + +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH #include +#else +#include +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH #include #include @@ -38,7 +43,12 @@ class BleScannerDelegate; /** * Concrete implementation of the PlatformManager singleton object for Darwin platforms. */ -class PlatformManagerImpl final : public PlatformManager, public Internal::GenericPlatformManagerImpl +class PlatformManagerImpl final : public PlatformManager, +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH + public Internal::GenericPlatformManagerImpl +#else + public Internal::GenericPlatformManagerImpl_POSIX +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH { // Allow the PlatformManager interface class to delegate method calls to // the implementation methods provided by this class. @@ -58,8 +68,8 @@ class PlatformManagerImpl final : public PlatformManager, public Internal::Gener private: // ===== Methods that implement the PlatformManager abstract interface. CHIP_ERROR _InitChipStack(); - void _Shutdown(); +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH CHIP_ERROR _StartChipTimer(System::Clock::Timeout delay) { return CHIP_ERROR_NOT_IMPLEMENTED; }; CHIP_ERROR _StartEventLoopTask(); CHIP_ERROR _StopEventLoopTask(); @@ -69,6 +79,7 @@ class PlatformManagerImpl final : public PlatformManager, public Internal::Gener bool _TryLockChipStack() { return false; }; void _UnlockChipStack(){}; CHIP_ERROR _PostEvent(const ChipDeviceEvent * event); +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH #if CHIP_STACK_LOCK_TRACKING_ENABLED bool _IsChipStackLockedByCurrentThread() const; diff --git a/src/platform/Darwin/SystemPlatformConfig.h b/src/platform/Darwin/SystemPlatformConfig.h index 5311b538ed7ea2..242755933c10bb 100644 --- a/src/platform/Darwin/SystemPlatformConfig.h +++ b/src/platform/Darwin/SystemPlatformConfig.h @@ -34,14 +34,6 @@ struct ChipDeviceEvent; // ==================== Platform Adaptations ==================== -#if !CHIP_SYSTEM_CONFIG_USE_LIBEV -// FIXME: these should not be hardcoded here, it is set via build config -// Need to exclude these for now in libev case -#define CHIP_SYSTEM_CONFIG_POSIX_LOCKING 0 -#define CHIP_SYSTEM_CONFIG_FREERTOS_LOCKING 0 -#define CHIP_SYSTEM_CONFIG_NO_LOCKING 1 -#endif - #define CHIP_SYSTEM_CONFIG_PLATFORM_PROVIDES_TIME 1 #define CHIP_SYSTEM_CONFIG_USE_POSIX_TIME_FUNCTS 1 #define CHIP_SYSTEM_CONFIG_POOL_USE_HEAP 1 diff --git a/src/system/SystemLayer.h b/src/system/SystemLayer.h index 76d778a59a6d79..75878ce6a81f50 100644 --- a/src/system/SystemLayer.h +++ b/src/system/SystemLayer.h @@ -41,6 +41,7 @@ #include #if CHIP_SYSTEM_CONFIG_USE_SOCKETS +#include #include #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS @@ -243,6 +244,7 @@ class LayerSockets : public Layer * Initialize watching for events on a file descriptor. * * Returns an opaque token through @a tokenOut that must be passed to subsequent operations for this file descriptor. + * Multiple calls to start watching the same file descriptor will return the same token. * StopWatchingSocket() must be called before closing the file descriptor. */ virtual CHIP_ERROR StartWatchingSocket(int fd, SocketWatchToken * tokenOut) = 0; @@ -288,6 +290,44 @@ class LayerSockets : public Layer virtual SocketWatchToken InvalidSocketWatchToken() = 0; }; +class LayerSocketsLoop; + +/** + * EventLoopHandlers can be registered with a LayerSocketsLoop instance to enable + * participation of those handlers in the processing cycle of the event loop. This makes + * it possible to implement adapters that allow components utilizing a third-party event + * loop API to participate in the Matter event loop, instead of having to run an entirely + * separate event loop on another thread. + * + * Specifically, the `PrepareEvents` and `HandleEvents` methods of registered event loop + * handlers will be called from the LayerSocketsLoop methods of the same names. + * + * @see LayerSocketsLoop::PrepareEvents + * @see LayerSocketsLoop::HandleEvents + */ +class EventLoopHandler : public chip::IntrusiveListNodeBase<> +{ +public: + virtual ~EventLoopHandler() {} + + /** + * Prepares events and returns the next requested wake time. + */ + virtual Clock::Timestamp PrepareEvents(Clock::Timestamp now) { return Clock::Timestamp::max(); } + + /** + * Handles / dispatches pending events. + * Every call to this method will have been preceded by a call to `PrepareEvents`. + */ + virtual void HandleEvents() = 0; + +private: + // mState is provided exclusively for use by the LayerSocketsLoop implementation + // sub-class and can be accessed by it via the LayerSocketsLoop::LoopHandlerState() helper. + friend class LayerSocketsLoop; + intptr_t mState = 0; +}; + class LayerSocketsLoop : public LayerSockets { public: @@ -298,6 +338,11 @@ class LayerSocketsLoop : public LayerSockets virtual void HandleEvents() = 0; virtual void EventLoopEnds() = 0; +#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH + virtual void AddLoopHandler(EventLoopHandler & handler) = 0; + virtual void RemoveLoopHandler(EventLoopHandler & handler) = 0; +#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH + #if CHIP_SYSTEM_CONFIG_USE_DISPATCH virtual void SetDispatchQueue(dispatch_queue_t dispatchQueue) = 0; virtual dispatch_queue_t GetDispatchQueue() = 0; @@ -305,6 +350,10 @@ class LayerSocketsLoop : public LayerSockets virtual void SetLibEvLoop(struct ev_loop * aLibEvLoopP) = 0; virtual struct ev_loop * GetLibEvLoop() = 0; #endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV + +protected: + // Expose EventLoopHandler.mState as a non-const reference to sub-classes + decltype(EventLoopHandler::mState) & LoopHandlerState(EventLoopHandler & handler) { return handler.mState; } }; #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS diff --git a/src/system/SystemLayerImplSelect.cpp b/src/system/SystemLayerImplSelect.cpp index 86ff1cb62da42f..3e8a79f7b6e44f 100644 --- a/src/system/SystemLayerImplSelect.cpp +++ b/src/system/SystemLayerImplSelect.cpp @@ -28,6 +28,7 @@ #include #include +#include #include // Choose an approximation of PTHREAD_NULL if pthread.h doesn't define one. @@ -370,8 +371,9 @@ CHIP_ERROR LayerImplSelect::StartWatchingSocket(int fd, SocketWatchToken * token { if (w.mFD == fd) { - // Duplicate registration is an error. - return CHIP_ERROR_INVALID_ARGUMENT; + // Already registered, return the existing token + *tokenOut = reinterpret_cast(&w); + return CHIP_NO_ERROR; } if ((w.mFD == kInvalidFd) && (watch == nullptr)) { @@ -608,6 +610,32 @@ SocketEvents LayerImplSelect::SocketEventsFromFDs(int socket, const fd_set & rea return res; } +#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH +enum : intptr_t +{ + kLoopHandlerInactive = 0, // default value for EventLoopHandler::mState + kLoopHandlerPending, + kLoopHandlerActive, +}; + +void LayerImplSelect::AddLoopHandler(EventLoopHandler & handler) +{ + // Add the handler as pending because this method can be called at any point + // in a PrepareEvents() / WaitForEvents() / HandleEvents() sequence. + // It will be marked active when we call PrepareEvents() on it for the first time. + auto & state = LoopHandlerState(handler); + VerifyOrDie(state == kLoopHandlerInactive); + state = kLoopHandlerPending; + mLoopHandlers.PushBack(&handler); +} + +void LayerImplSelect::RemoveLoopHandler(EventLoopHandler & handler) +{ + mLoopHandlers.Remove(&handler); + LoopHandlerState(handler) = kLoopHandlerInactive; +} +#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH + void LayerImplSelect::PrepareEvents() { assertChipStackLockedByCurrentThread(); @@ -616,10 +644,28 @@ void LayerImplSelect::PrepareEvents() Clock::Timestamp awakenTime = currentTime + kDefaultMinSleepPeriod; TimerList::Node * timer = mTimerList.Earliest(); - if (timer && timer->AwakenTime() < awakenTime) + if (timer) + { + awakenTime = std::min(awakenTime, timer->AwakenTime()); + } + +#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH + // Activate added EventLoopHandlers and call PrepareEvents on active handlers. + auto loopIter = mLoopHandlers.begin(); + while (loopIter != mLoopHandlers.end()) { - awakenTime = timer->AwakenTime(); + auto & loop = *loopIter++; // advance before calling out, in case a list modification clobbers the `next` pointer + switch (auto & state = LoopHandlerState(loop)) + { + case kLoopHandlerPending: + state = kLoopHandlerActive; + [[fallthrough]]; + case kLoopHandlerActive: + awakenTime = std::min(awakenTime, loop.PrepareEvents(currentTime)); + break; + } } +#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH const Clock::Timestamp sleepTime = (awakenTime > currentTime) ? (awakenTime - currentTime) : Clock::kZero; Clock::ToTimeval(sleepTime, mNextTimeout); @@ -683,18 +729,35 @@ void LayerImplSelect::HandleEvents() mTimerPool.Invoke(timer); } - for (auto & w : mSocketWatchPool) + // Process socket events, if any + if (mSelectResult > 0) { - if (w.mFD != kInvalidFd) + for (auto & w : mSocketWatchPool) { - SocketEvents events = SocketEventsFromFDs(w.mFD, mSelected.mReadSet, mSelected.mWriteSet, mSelected.mErrorSet); - if (events.HasAny() && w.mCallback != nullptr) + if (w.mFD != kInvalidFd && w.mCallback != nullptr) { - w.mCallback(events, w.mCallbackData); + SocketEvents events = SocketEventsFromFDs(w.mFD, mSelected.mReadSet, mSelected.mWriteSet, mSelected.mErrorSet); + if (events.HasAny()) + { + w.mCallback(events, w.mCallbackData); + } } } } +#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH + // Call HandleEvents for active loop handlers + auto loopIter = mLoopHandlers.begin(); + while (loopIter != mLoopHandlers.end()) + { + auto & loop = *loopIter++; // advance before calling out, in case a list modification clobbers the `next` pointer + if (LoopHandlerState(loop) == kLoopHandlerActive) + { + loop.HandleEvents(); + } + } +#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH + #if CHIP_SYSTEM_CONFIG_POSIX_LOCKING mHandleSelectThread = PTHREAD_NULL; #endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING diff --git a/src/system/SystemLayerImplSelect.h b/src/system/SystemLayerImplSelect.h index 1bab3db9b5f39c..ce34ece0115b9b 100644 --- a/src/system/SystemLayerImplSelect.h +++ b/src/system/SystemLayerImplSelect.h @@ -87,6 +87,11 @@ class LayerImplSelect : public LayerSocketsLoop void HandleEvents() override; void EventLoopEnds() override {} +#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH + void AddLoopHandler(EventLoopHandler & handler) override; + void RemoveLoopHandler(EventLoopHandler & handler) override; +#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH + #if CHIP_SYSTEM_CONFIG_USE_DISPATCH void SetDispatchQueue(dispatch_queue_t dispatchQueue) override { mDispatchQueue = dispatchQueue; }; dispatch_queue_t GetDispatchQueue() override { return mDispatchQueue; }; @@ -135,6 +140,10 @@ class LayerImplSelect : public LayerSocketsLoop TimerList mExpiredTimers; timeval mNextTimeout; +#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH + IntrusiveList mLoopHandlers; +#endif + // Members for select loop struct SelectSets { diff --git a/src/system/system.gni b/src/system/system.gni index 67bf9b61b8f881..61efb846d89839 100644 --- a/src/system/system.gni +++ b/src/system/system.gni @@ -80,6 +80,10 @@ assert( chip_system_config_locking == "zephyr", "Please select a valid mutex implementation: posix, freertos, mbed, cmsis-rtos, zephyr, none") +assert( + !chip_system_config_use_dispatch || chip_system_config_locking == "none", + "When chip_system_config_use_dispatch is true, chip_system_config_locking must be 'none'") + assert( chip_system_config_clock == "clock_gettime" || chip_system_config_clock == "gettimeofday", diff --git a/src/system/tests/BUILD.gn b/src/system/tests/BUILD.gn index 521a1f34d859b9..8b91690f47a708 100644 --- a/src/system/tests/BUILD.gn +++ b/src/system/tests/BUILD.gn @@ -21,6 +21,7 @@ chip_test_suite("tests") { output_name = "libSystemLayerTests" test_sources = [ + "TestEventLoopHandler.cpp", "TestSystemClock.cpp", "TestSystemErrorStr.cpp", "TestSystemPacketBuffer.cpp", diff --git a/src/system/tests/TestEventLoopHandler.cpp b/src/system/tests/TestEventLoopHandler.cpp new file mode 100644 index 00000000000000..4d5456098efb2f --- /dev/null +++ b/src/system/tests/TestEventLoopHandler.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +// EventLoopHandlers are only supported by a select-based LayerSocketsLoop +#if CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH +// The fake PlatformManagerImpl does not drive the system layer event loop +#if !CHIP_DEVICE_LAYER_TARGET_FAKE + +#include +#include + +#include +#include + +using namespace chip; +using namespace chip::System::Clock; +using namespace chip::System::Clock::Literals; + +class TestEventLoopHandler : public ::testing::Test +{ +public: + static void SetUpTestSuite() + { + ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); + ASSERT_EQ(DeviceLayer::PlatformMgr().InitChipStack(), CHIP_NO_ERROR); + } + + static void TearDownTestSuite() + { + DeviceLayer::PlatformMgr().Shutdown(); + Platform::MemoryShutdown(); + } + + static System::LayerSocketsLoop & SystemLayer() { return static_cast(DeviceLayer::SystemLayer()); } + + // Schedules a call to the provided lambda and returns a cancel function. + template + static std::function Schedule(Timeout delay, Lambda lambda) + { + System::TimerCompleteCallback callback = [](System::Layer * layer, void * ctx) { + auto * function = static_cast *>(ctx); + (*function)(); + delete function; + }; + auto * function = new std::function(lambda); + SystemLayer().StartTimer(delay, callback, function); + return [=] { + SystemLayer().CancelTimer(callback, function); + delete function; + }; + } + + template + static void ScheduleNextTick(Lambda lambda) + { + // ScheduleLambda is based on device events, which are greedily processed until the + // queue is empty, so we can't use it to wait for the next event loop tick. Just use + // a timer with a very short delay. + Schedule(1_ms, lambda); + } +}; + +TEST_F(TestEventLoopHandler, EventLoopHandlerSequence) +{ + struct : public System::EventLoopHandler + { + std::string trace; + Timestamp PrepareEvents(Timestamp now) override + { + trace.append("P"); + return Timestamp::max(); + } + void HandleEvents() override { trace.append("H"); } + } loopHandler; + + ScheduleNextTick([&] { + loopHandler.trace.append("1"); + SystemLayer().AddLoopHandler(loopHandler); + loopHandler.trace.append("A"); + ScheduleNextTick([&] { // "P" + loopHandler.trace.append("2"); + ScheduleNextTick([&] { // "H", "P" + loopHandler.trace.append("3"); + SystemLayer().RemoveLoopHandler(loopHandler); + loopHandler.trace.append("R"); + ScheduleNextTick([&] { + loopHandler.trace.append("4"); + DeviceLayer::PlatformMgr().StopEventLoopTask(); + }); + }); + }); + }); + + chip::DeviceLayer::PlatformMgr().RunEventLoop(); + EXPECT_EQ(loopHandler.trace, std::string("1AP2HP3R4")); +} + +TEST_F(TestEventLoopHandler, EventLoopHandlerWake) +{ + struct : public System::EventLoopHandler + { + Timestamp startTimestamp = System::SystemClock().GetMonotonicTimestamp(); + Timestamp wakeTimestamp = Timestamp::max(); + + Timestamp PrepareEvents(Timestamp now) override { return now + 400_ms; } + void HandleEvents() override + { + // StartTimer() (called by Schedule()) is liable to causes an immediate + // wakeup via Signal(), so ignore this call if it's only been a few ms. + auto now = System::SystemClock().GetMonotonicTimestamp(); + if (now - startTimestamp >= 100_ms) + { + wakeTimestamp = now; + DeviceLayer::PlatformMgr().StopEventLoopTask(); + } + } + } loopHandler; + + // Schedule a fallback timer to ensure the test stops + auto cancelFallback = Schedule(1000_ms, [] { DeviceLayer::PlatformMgr().StopEventLoopTask(); }); + SystemLayer().AddLoopHandler(loopHandler); + chip::DeviceLayer::PlatformMgr().RunEventLoop(); + SystemLayer().RemoveLoopHandler(loopHandler); + cancelFallback(); // avoid leaking the fallback timer + + Timestamp sleepDuration = loopHandler.wakeTimestamp - loopHandler.startTimestamp; + EXPECT_GE(sleepDuration.count(), 400u); // loopHandler requested wake-up after 400ms + EXPECT_LE(sleepDuration.count(), 500u); // allow some slack for test machine load +} + +#endif // !CHIP_DEVICE_LAYER_TARGET_FAKE +#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH