Skip to content

Commit a26133c

Browse files
Add the ability for EventLoopHandlers to participate in the event loop
1 parent f7a9ce4 commit a26133c

6 files changed

+267
-9
lines changed

src/lib/support/IntrusiveList.h

+10
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,11 @@ class IntrusiveList : public IntrusiveListBase
414414
ConstIterator(IntrusiveListBase::ConstIteratorBase && base) : IntrusiveListBase::ConstIteratorBase(std::move(base)) {}
415415
const T * operator->() { return Hook::ToObject(mCurrent); }
416416
const T & operator*() { return *Hook::ToObject(mCurrent); }
417+
418+
ConstIterator & operator++() { return static_cast<ConstIterator &>(IntrusiveListBase::ConstIteratorBase::operator++()); }
419+
ConstIterator operator++(int) { return IntrusiveListBase::ConstIteratorBase::operator++(1); }
420+
ConstIterator & operator--() { return static_cast<ConstIterator &>(IntrusiveListBase::ConstIteratorBase::operator--()); }
421+
ConstIterator operator--(int) { return IntrusiveListBase::ConstIteratorBase::operator--(1); }
417422
};
418423

419424
class Iterator : public IntrusiveListBase::IteratorBase
@@ -426,6 +431,11 @@ class IntrusiveList : public IntrusiveListBase
426431
Iterator(IntrusiveListBase::IteratorBase && base) : IntrusiveListBase::IteratorBase(std::move(base)) {}
427432
T * operator->() { return Hook::ToObject(mCurrent); }
428433
T & operator*() { return *Hook::ToObject(mCurrent); }
434+
435+
Iterator & operator++() { return static_cast<Iterator &>(IntrusiveListBase::IteratorBase::operator++()); }
436+
Iterator operator++(int) { return IntrusiveListBase::IteratorBase::operator++(1); }
437+
Iterator & operator--() { return static_cast<Iterator &>(IntrusiveListBase::IteratorBase::operator--()); }
438+
Iterator operator--(int) { return IntrusiveListBase::IteratorBase::operator--(1); }
429439
};
430440

431441
ConstIterator begin() const { return IntrusiveListBase::begin(); }

src/system/SystemLayer.h

+37
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include <system/SystemEvent.h>
4242

4343
#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
44+
#include <lib/support/IntrusiveList.h>
4445
#include <system/SocketEvents.h>
4546
#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS
4647

@@ -243,6 +244,7 @@ class LayerSockets : public Layer
243244
* Initialize watching for events on a file descriptor.
244245
*
245246
* Returns an opaque token through @a tokenOut that must be passed to subsequent operations for this file descriptor.
247+
* Multiple calls to start watching the same file descriptor will return the same token.
246248
* StopWatchingSocket() must be called before closing the file descriptor.
247249
*/
248250
virtual CHIP_ERROR StartWatchingSocket(int fd, SocketWatchToken * tokenOut) = 0;
@@ -288,6 +290,32 @@ class LayerSockets : public Layer
288290
virtual SocketWatchToken InvalidSocketWatchToken() = 0;
289291
};
290292

293+
class LayerSocketsLoop;
294+
295+
/**
296+
* Enables the participation of subordinate event loops in the SystemLayer event loop.
297+
*/
298+
class EventLoopHandler : public chip::IntrusiveListNodeBase<>
299+
{
300+
public:
301+
virtual ~EventLoopHandler() {}
302+
303+
/**
304+
* Prepares events and returns the next requested wake time.
305+
*/
306+
virtual Clock::Timestamp PrepareEvents(Clock::Timestamp now) { return Clock::Timestamp::max(); }
307+
308+
/**
309+
* Handles / dispatches pending events.
310+
* Every call to this method will have been preceded by a call to `PrepareEvents`.
311+
*/
312+
virtual void HandleEvents() = 0;
313+
314+
private:
315+
friend class LayerSocketsLoop;
316+
intptr_t mState = 0; // For use by the event loop implementation
317+
};
318+
291319
class LayerSocketsLoop : public LayerSockets
292320
{
293321
public:
@@ -298,13 +326,22 @@ class LayerSocketsLoop : public LayerSockets
298326
virtual void HandleEvents() = 0;
299327
virtual void EventLoopEnds() = 0;
300328

329+
#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
330+
virtual void AddLoopHandler(EventLoopHandler & handler) = 0;
331+
virtual void RemoveLoopHandler(EventLoopHandler & handler) = 0;
332+
#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
333+
301334
#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
302335
virtual void SetDispatchQueue(dispatch_queue_t dispatchQueue) = 0;
303336
virtual dispatch_queue_t GetDispatchQueue() = 0;
304337
#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
305338
virtual void SetLibEvLoop(struct ev_loop * aLibEvLoopP) = 0;
306339
virtual struct ev_loop * GetLibEvLoop() = 0;
307340
#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV
341+
342+
protected:
343+
// Expose EventLoopHandler.mState to sub-classes
344+
decltype(EventLoopHandler::mState) & LoopHandlerState(EventLoopHandler & handler) { return handler.mState; }
308345
};
309346

310347
#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS

src/system/SystemLayerImplSelect.cpp

+72-9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <system/SystemLayer.h>
2929
#include <system/SystemLayerImplSelect.h>
3030

31+
#include <algorithm>
3132
#include <errno.h>
3233

3334
// 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
370371
{
371372
if (w.mFD == fd)
372373
{
373-
// Duplicate registration is an error.
374-
return CHIP_ERROR_INVALID_ARGUMENT;
374+
// Already registered, return the existing token
375+
*tokenOut = reinterpret_cast<SocketWatchToken>(&w);
376+
return CHIP_NO_ERROR;
375377
}
376378
if ((w.mFD == kInvalidFd) && (watch == nullptr))
377379
{
@@ -608,6 +610,32 @@ SocketEvents LayerImplSelect::SocketEventsFromFDs(int socket, const fd_set & rea
608610
return res;
609611
}
610612

613+
#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
614+
enum : intptr_t
615+
{
616+
kLoopHandlerInactive = 0, // default value for EventLoopHandler::mState
617+
kLoopHandlerPending,
618+
kLoopHandlerActive,
619+
};
620+
621+
void LayerImplSelect::AddLoopHandler(EventLoopHandler & handler)
622+
{
623+
// Add the handler as pending because this method can be called at any point
624+
// in a PrepareEvents() / WaitForEvents() / HandleEvents() sequence.
625+
// It will be marked active when we call PrepareEvents() on it for the first time.
626+
auto & state = LoopHandlerState(handler);
627+
VerifyOrDie(state == kLoopHandlerInactive);
628+
state = kLoopHandlerPending;
629+
mLoopHandlers.PushBack(&handler);
630+
}
631+
632+
void LayerImplSelect::RemoveLoopHandler(EventLoopHandler & handler)
633+
{
634+
mLoopHandlers.Remove(&handler);
635+
LoopHandlerState(handler) = kLoopHandlerInactive;
636+
}
637+
#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
638+
611639
void LayerImplSelect::PrepareEvents()
612640
{
613641
assertChipStackLockedByCurrentThread();
@@ -616,10 +644,28 @@ void LayerImplSelect::PrepareEvents()
616644
Clock::Timestamp awakenTime = currentTime + kDefaultMinSleepPeriod;
617645

618646
TimerList::Node * timer = mTimerList.Earliest();
619-
if (timer && timer->AwakenTime() < awakenTime)
647+
if (timer)
648+
{
649+
awakenTime = std::min(awakenTime, timer->AwakenTime());
650+
}
651+
652+
#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
653+
// Activate added EventLoopHandlers and call PrepareEvents on active handlers.
654+
auto loopIter = mLoopHandlers.begin();
655+
while (loopIter != mLoopHandlers.end())
620656
{
621-
awakenTime = timer->AwakenTime();
657+
auto & loop = *loopIter++; // advance before calling out, in case a list modification clobbers the `next` pointer
658+
switch (auto & state = LoopHandlerState(loop))
659+
{
660+
case kLoopHandlerPending:
661+
state = kLoopHandlerActive;
662+
[[fallthrough]];
663+
case kLoopHandlerActive:
664+
awakenTime = std::min(awakenTime, loop.PrepareEvents(currentTime));
665+
break;
666+
}
622667
}
668+
#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
623669

624670
const Clock::Timestamp sleepTime = (awakenTime > currentTime) ? (awakenTime - currentTime) : Clock::kZero;
625671
Clock::ToTimeval(sleepTime, mNextTimeout);
@@ -683,18 +729,35 @@ void LayerImplSelect::HandleEvents()
683729
mTimerPool.Invoke(timer);
684730
}
685731

686-
for (auto & w : mSocketWatchPool)
732+
// Process socket events, if any
733+
if (mSelectResult > 0)
687734
{
688-
if (w.mFD != kInvalidFd)
735+
for (auto & w : mSocketWatchPool)
689736
{
690-
SocketEvents events = SocketEventsFromFDs(w.mFD, mSelected.mReadSet, mSelected.mWriteSet, mSelected.mErrorSet);
691-
if (events.HasAny() && w.mCallback != nullptr)
737+
if (w.mFD != kInvalidFd && w.mCallback != nullptr)
692738
{
693-
w.mCallback(events, w.mCallbackData);
739+
SocketEvents events = SocketEventsFromFDs(w.mFD, mSelected.mReadSet, mSelected.mWriteSet, mSelected.mErrorSet);
740+
if (events.HasAny())
741+
{
742+
w.mCallback(events, w.mCallbackData);
743+
}
694744
}
695745
}
696746
}
697747

748+
#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
749+
// Call HandleEvents for active loop handlers
750+
auto loopIter = mLoopHandlers.begin();
751+
while (loopIter != mLoopHandlers.end())
752+
{
753+
auto & loop = *loopIter++; // advance before calling out, in case a list modification clobbers the `next` pointer
754+
if (LoopHandlerState(loop) == kLoopHandlerActive)
755+
{
756+
loop.HandleEvents();
757+
}
758+
}
759+
#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
760+
698761
#if CHIP_SYSTEM_CONFIG_POSIX_LOCKING
699762
mHandleSelectThread = PTHREAD_NULL;
700763
#endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING

src/system/SystemLayerImplSelect.h

+9
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ class LayerImplSelect : public LayerSocketsLoop
8787
void HandleEvents() override;
8888
void EventLoopEnds() override {}
8989

90+
#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
91+
void AddLoopHandler(EventLoopHandler & handler) override;
92+
void RemoveLoopHandler(EventLoopHandler & handler) override;
93+
#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
94+
9095
#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
9196
void SetDispatchQueue(dispatch_queue_t dispatchQueue) override { mDispatchQueue = dispatchQueue; };
9297
dispatch_queue_t GetDispatchQueue() override { return mDispatchQueue; };
@@ -135,6 +140,10 @@ class LayerImplSelect : public LayerSocketsLoop
135140
TimerList mExpiredTimers;
136141
timeval mNextTimeout;
137142

143+
#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
144+
IntrusiveList<EventLoopHandler> mLoopHandlers;
145+
#endif
146+
138147
// Members for select loop
139148
struct SelectSets
140149
{

src/system/tests/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ chip_test_suite("tests") {
2121
output_name = "libSystemLayerTests"
2222

2323
test_sources = [
24+
"TestEventLoopHandler.cpp",
2425
"TestSystemClock.cpp",
2526
"TestSystemErrorStr.cpp",
2627
"TestSystemPacketBuffer.cpp",
+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <pw_unit_test/framework.h>
18+
#include <system/SystemConfig.h>
19+
20+
#if CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH
21+
22+
#include <lib/support/CodeUtils.h>
23+
#include <platform/CHIPDeviceLayer.h>
24+
25+
#include <functional>
26+
#include <string>
27+
28+
using namespace chip;
29+
using namespace chip::System::Clock;
30+
using namespace chip::System::Clock::Literals;
31+
32+
class TestEventLoopHandler : public ::testing::Test
33+
{
34+
public:
35+
static void SetUpTestSuite()
36+
{
37+
ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR);
38+
ASSERT_EQ(DeviceLayer::PlatformMgr().InitChipStack(), CHIP_NO_ERROR);
39+
}
40+
41+
static void TearDownTestSuite()
42+
{
43+
DeviceLayer::PlatformMgr().Shutdown();
44+
Platform::MemoryShutdown();
45+
}
46+
47+
static System::LayerSocketsLoop & SystemLayer() { return static_cast<System::LayerSocketsLoop &>(DeviceLayer::SystemLayer()); }
48+
49+
template <class Lambda>
50+
static void Schedule(Timeout delay, Lambda lambda)
51+
{
52+
SystemLayer().StartTimer(
53+
delay,
54+
[](System::Layer * layer, void * ctx) {
55+
auto * function = static_cast<std::function<void()> *>(ctx);
56+
(*function)();
57+
delete function;
58+
},
59+
new std::function<void()>(lambda));
60+
}
61+
62+
template <class Lambda>
63+
static void ScheduleNextTick(Lambda lambda)
64+
{
65+
// ScheduleLambda is based on device events, which are greedily processed until the
66+
// queue is empty, so we can't use it to wait for the next event loop tick. Just use
67+
// a timer with a very short delay.
68+
Schedule(1_ms, lambda);
69+
}
70+
};
71+
72+
TEST_F(TestEventLoopHandler, EventLoopHandlerSequence)
73+
{
74+
struct : public System::EventLoopHandler
75+
{
76+
std::string trace;
77+
Timestamp PrepareEvents(Timestamp now) override
78+
{
79+
trace.append("P");
80+
return Timestamp::max();
81+
}
82+
void HandleEvents() override { trace.append("H"); }
83+
} loopHandler;
84+
85+
ScheduleNextTick([&] {
86+
loopHandler.trace.append("1");
87+
SystemLayer().AddLoopHandler(loopHandler);
88+
loopHandler.trace.append("A");
89+
ScheduleNextTick([&] { // "P"
90+
loopHandler.trace.append("2");
91+
ScheduleNextTick([&] { // "H", "P"
92+
loopHandler.trace.append("3");
93+
SystemLayer().RemoveLoopHandler(loopHandler);
94+
loopHandler.trace.append("R");
95+
ScheduleNextTick([&] {
96+
loopHandler.trace.append("4");
97+
DeviceLayer::PlatformMgr().StopEventLoopTask();
98+
});
99+
});
100+
});
101+
});
102+
103+
chip::DeviceLayer::PlatformMgr().RunEventLoop();
104+
EXPECT_EQ(loopHandler.trace, std::string("1AP2HP3R4"));
105+
}
106+
107+
TEST_F(TestEventLoopHandler, EventLoopHandlerWake)
108+
{
109+
struct : public System::EventLoopHandler
110+
{
111+
Timestamp startTimestamp = System::SystemClock().GetMonotonicTimestamp();
112+
Timestamp wakeTimestamp = Timestamp::max();
113+
114+
Timestamp PrepareEvents(Timestamp now) override { return now + 400_ms; }
115+
void HandleEvents() override
116+
{
117+
// StartTimer() (called by Schedule()) is liable to causes an immediate
118+
// wakeup via Signal(), so ignore this call if it's only been a few ms.
119+
auto now = System::SystemClock().GetMonotonicTimestamp();
120+
if (now - startTimestamp >= 100_ms)
121+
{
122+
wakeTimestamp = now;
123+
DeviceLayer::PlatformMgr().StopEventLoopTask();
124+
}
125+
}
126+
} loopHandler;
127+
128+
SystemLayer().AddLoopHandler(loopHandler);
129+
Schedule(1000_ms, [] { DeviceLayer::PlatformMgr().StopEventLoopTask(); });
130+
chip::DeviceLayer::PlatformMgr().RunEventLoop();
131+
SystemLayer().RemoveLoopHandler(loopHandler);
132+
133+
Timestamp sleepDuration = loopHandler.wakeTimestamp - loopHandler.startTimestamp;
134+
EXPECT_GE(sleepDuration.count(), 400u); // loopHandler requested wake-up after 400ms
135+
EXPECT_LE(sleepDuration.count(), 500u); // allow some slack for test machine load
136+
}
137+
138+
#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH

0 commit comments

Comments
 (0)