From 860e934920195f47d2aefa655d882b802390cd65 Mon Sep 17 00:00:00 2001 From: Lukas Zeller Date: Thu, 1 Jun 2023 21:48:35 +0200 Subject: [PATCH] [Linux]: add libev support when CHIP_SYSTEM_CONFIG_USE_LIBEV=1 (#24232) When CHIP_SYSTEM_CONFIG_USE_LIBEV is set, SystemLayerImplSelect expects a *libev* mainloop to be present to schedule timers and socket watches (similar to CHIP_SYSTEM_CONFIG_USE_DISPATCH for Darwin). A libev mainloop must be passed to SystemLayer using `SetLibEvLoop()` before any timers or socket watches are used - otherwise, `chipDie()` is invoked. Platform manager events are also dispatched via the libev mainloop, using ScheduleWork(). This eliminates the need for a separate thread for dispatching events, and makes sure event handlers cannot run in parallel with timers or I/O handlers. In consequence PostEvent() may not be called without holding the chip lock. The reason for running *matter* under libev in the first place is to allow creating single-threaded apps including all of *matter*. So disallowing PostEvent() "from any thread" is a design choice for the libev case. # Usage The entire project needs to be build with `CHIP_SYSTEM_CONFIG_USE_LIBEV=1` (this can be done via invoking a project-specific extra config via the `default_configs_extra` argument in args.gni) Setting up the libev mainloop and handing it over to SystemLayer must be done in application specific code, outside the code provided by chip examples. Also adding libev as a dependency must be done in the application's BUILD.gn. # Background *libev* is a multi-platform event library often used in embedded linux context to handle events, and builds the basis for various libraries with non-blocking APIs. This changeset allows using the *matter* stack with libev based applications. # Example The opensource bridge project p44mbrd (https://github.com/plan44/p44mbrd) is based on libev and makes use of this changeset. --- .../GenericPlatformManagerImpl_POSIX.h | 14 +- .../GenericPlatformManagerImpl_POSIX.ipp | 76 ++++++++ src/platform/Darwin/PlatformManagerImpl.cpp | 2 + src/platform/Darwin/SystemPlatformConfig.h | 5 + src/system/BUILD.gn | 1 + src/system/SystemLayer.h | 9 +- src/system/SystemLayerImplSelect.cpp | 182 ++++++++++++++++-- src/system/SystemLayerImplSelect.h | 22 ++- src/system/SystemTimer.h | 6 + src/system/system.gni | 5 +- 10 files changed, 302 insertions(+), 20 deletions(-) diff --git a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.h b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.h index acdd2a4d3b762c..e4e5255d5b13bd 100644 --- a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.h +++ b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.h @@ -65,11 +65,14 @@ class GenericPlatformManagerImpl_POSIX : public GenericPlatformManagerImpl mState{ State::kStopped }; + +#if !CHIP_SYSTEM_CONFIG_USE_LIBEV pthread_cond_t mEventQueueStoppedCond; pthread_mutex_t mStateLock; pthread_attr_t mChipTaskAttr; struct sched_param mChipTaskSchedParam; +#endif #if CHIP_STACK_LOCK_TRACKING_ENABLED bool mChipStackIsLocked = false; @@ -101,16 +104,25 @@ class GenericPlatformManagerImpl_POSIX : public GenericPlatformManagerImpl(this); } - void ProcessDeviceEvents(); +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + static void _DispatchEventViaScheduleWork(System::Layer * aLayer, void * appState); +#else DeviceSafeQueue mChipEventQueue; std::atomic mShouldRunEventLoop{ true }; static void * EventLoopTaskMain(void * arg); +#endif + void ProcessDeviceEvents(); }; // Instruct the compiler to instantiate the template only when explicitly told to do so. extern template class GenericPlatformManagerImpl_POSIX; +#if CHIP_SYSTEM_CONFIG_USE_LIBEV +// with external libev mainloop, this should be implemented externally to terminate the mainloop cleanly +// (Note that there is a weak default implementation that just calls chipDie() when the external implementation is missing) +extern void ExitExternalMainLoop(); +#endif } // namespace Internal } // namespace DeviceLayer } // namespace chip diff --git a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.ipp b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.ipp index 5c9061a901a3f2..ee87cfb7726989 100644 --- a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.ipp +++ b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.ipp @@ -46,12 +46,14 @@ namespace chip { namespace DeviceLayer { namespace Internal { +#if !CHIP_SYSTEM_CONFIG_USE_LIBEV namespace { System::LayerSocketsLoop & SystemLayerSocketsLoop() { return static_cast(DeviceLayer::SystemLayer()); } } // anonymous namespace +#endif template CHIP_ERROR GenericPlatformManagerImpl_POSIX::_InitChipStack() @@ -59,6 +61,8 @@ CHIP_ERROR GenericPlatformManagerImpl_POSIX::_InitChipStack() // Call up to the base class _InitChipStack() to perform the bulk of the initialization. ReturnErrorOnFailure(GenericPlatformManagerImpl::_InitChipStack()); +#if !CHIP_SYSTEM_CONFIG_USE_LIBEV + mShouldRunEventLoop.store(true, std::memory_order_relaxed); int ret = pthread_cond_init(&mEventQueueStoppedCond, nullptr); @@ -66,6 +70,7 @@ CHIP_ERROR GenericPlatformManagerImpl_POSIX::_InitChipStack() ret = pthread_mutex_init(&mStateLock, nullptr); VerifyOrReturnError(ret == 0, CHIP_ERROR_POSIX(ret)); +#endif return CHIP_NO_ERROR; } @@ -130,15 +135,44 @@ CHIP_ERROR GenericPlatformManagerImpl_POSIX::_StartChipTimer(System:: return CHIP_NO_ERROR; } +#if CHIP_SYSTEM_CONFIG_USE_LIBEV +template +void GenericPlatformManagerImpl_POSIX::_DispatchEventViaScheduleWork(System::Layer * aLayer, void * appState) +{ + auto * event = static_cast(appState); + PlatformMgrImpl().DispatchEvent(event); + delete event; +} +#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV + template CHIP_ERROR GenericPlatformManagerImpl_POSIX::_PostEvent(const ChipDeviceEvent * event) { +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + // Note: PostEvent() is documented to allow being called "from any thread". + // In the libev mainloop case however, calling from another thread is NOT supported. + // Introducing this restriction is OK because the very goal of using libev is to avoid + // multiple threads by running matter and all application code in the same thread on the + // libev mainloop. So getting called from another thread here is very likely a + // application design error. + VerifyOrDieWithMsg(_IsChipStackLockedByCurrentThread(), DeviceLayer, "PostEvent() not allowed from outside chip stack lock"); + + // Schedule dispatching this event via System Layer's ScheduleWork + ChipDeviceEvent * eventCopyP = new ChipDeviceEvent; + VerifyOrDie(eventCopyP != nullptr); + *eventCopyP = *event; + SystemLayer().ScheduleWork(&_DispatchEventViaScheduleWork, eventCopyP); + return CHIP_NO_ERROR; +#else mChipEventQueue.Push(*event); SystemLayerSocketsLoop().Signal(); // Trigger wake select on CHIP thread return CHIP_NO_ERROR; +#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV } +#if !CHIP_SYSTEM_CONFIG_USE_LIBEV + template void GenericPlatformManagerImpl_POSIX::ProcessDeviceEvents() { @@ -152,6 +186,12 @@ void GenericPlatformManagerImpl_POSIX::ProcessDeviceEvents() template void GenericPlatformManagerImpl_POSIX::_RunEventLoop() { +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + + VerifyOrDieWithMsg(false, DeviceLayer, "libev based app should never try to run a separate event loop"); + +#else + pthread_mutex_lock(&mStateLock); // @@ -203,6 +243,7 @@ void GenericPlatformManagerImpl_POSIX::_RunEventLoop() // Shutdown() method. // mState.store(State::kStopped, std::memory_order_relaxed); +#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV } template @@ -213,9 +254,22 @@ void * GenericPlatformManagerImpl_POSIX::EventLoopTaskMain(void * arg return nullptr; } +#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV + template CHIP_ERROR GenericPlatformManagerImpl_POSIX::_StartEventLoopTask() { + +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + // Note: With libev, we dont need our own mainloop. + // Still, we set State::kRunning to activate lock checking, because + // calls to ScheduleWork and some System Layer methods may not + // occur from other threads (which usually don't exist in a + // libev app) + mState.store(State::kRunning, std::memory_order_relaxed); + return CHIP_NO_ERROR; +#else + int err; err = pthread_attr_init(&mChipTaskAttr); VerifyOrReturnError(err == 0, CHIP_ERROR_POSIX(err)); @@ -247,11 +301,30 @@ CHIP_ERROR GenericPlatformManagerImpl_POSIX::_StartEventLoopTask() pthread_mutex_unlock(&mStateLock); return CHIP_ERROR_POSIX(err); +#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV +} + +#if CHIP_SYSTEM_CONFIG_USE_LIBEV +// fallback implementation +void __attribute__((weak)) ExitExternalMainLoop() +{ + // FIXME: implement better exit + VerifyOrDieWithMsg(false, DeviceLayer, "Missing custom ExitExternalMainLoop() implementation for clean shutdown -> just die"); } +#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV template CHIP_ERROR GenericPlatformManagerImpl_POSIX::_StopEventLoopTask() { + +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + // with libev, the mainloop is set up and managed externally + mState.store(State::kStopping, std::memory_order_relaxed); + ExitExternalMainLoop(); // this callback needs to be implemented. + mState.store(State::kStopped, std::memory_order_relaxed); + return CHIP_NO_ERROR; +#else + int err = 0; // @@ -304,6 +377,7 @@ CHIP_ERROR GenericPlatformManagerImpl_POSIX::_StopEventLoopTask() exit: return CHIP_ERROR_POSIX(err); +#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV } template @@ -316,8 +390,10 @@ void GenericPlatformManagerImpl_POSIX::_Shutdown() // VerifyOrDie(mState.load(std::memory_order_relaxed) == State::kStopped); +#if !CHIP_SYSTEM_CONFIG_USE_LIBEV pthread_mutex_destroy(&mStateLock); pthread_cond_destroy(&mEventQueueStoppedCond); +#endif // // Call up to the base class _Shutdown() to perform the actual stack de-initialization diff --git a/src/platform/Darwin/PlatformManagerImpl.cpp b/src/platform/Darwin/PlatformManagerImpl.cpp index f1d5e4841e761b..bf2dde3d4fe360 100644 --- a/src/platform/Darwin/PlatformManagerImpl.cpp +++ b/src/platform/Darwin/PlatformManagerImpl.cpp @@ -52,8 +52,10 @@ CHIP_ERROR PlatformManagerImpl::_InitChipStack() SuccessOrExit(err); #endif // CHIP_DISABLE_PLATFORM_KVS +#if !CHIP_SYSTEM_CONFIG_USE_LIBEV // Ensure there is a dispatch queue available static_cast(DeviceLayer::SystemLayer()).SetDispatchQueue(GetWorkQueue()); +#endif // Call _InitChipStack() on the generic implementation base class // to finish the initialization process. diff --git a/src/platform/Darwin/SystemPlatformConfig.h b/src/platform/Darwin/SystemPlatformConfig.h index 76995250738a69..5311b538ed7ea2 100644 --- a/src/platform/Darwin/SystemPlatformConfig.h +++ b/src/platform/Darwin/SystemPlatformConfig.h @@ -34,9 +34,14 @@ 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/BUILD.gn b/src/system/BUILD.gn index acc7f790e3471d..7ce311d6499f19 100644 --- a/src/system/BUILD.gn +++ b/src/system/BUILD.gn @@ -85,6 +85,7 @@ buildconfig_header("system_buildconfig") { "CHIP_SYSTEM_CONFIG_TEST=${chip_build_tests}", "CHIP_WITH_NLFAULTINJECTION=${chip_with_nlfaultinjection}", "CHIP_SYSTEM_CONFIG_USE_DISPATCH=${chip_system_config_use_dispatch}", + "CHIP_SYSTEM_CONFIG_USE_LIBEV=${chip_system_config_use_libev}", "CHIP_SYSTEM_CONFIG_USE_LWIP=${chip_system_config_use_lwip}", "CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT=${chip_system_config_use_open_thread_inet_endpoints}", "CHIP_SYSTEM_CONFIG_USE_SOCKETS=${chip_system_config_use_sockets}", diff --git a/src/system/SystemLayer.h b/src/system/SystemLayer.h index 4321edeb02b883..279ac057b05ff7 100644 --- a/src/system/SystemLayer.h +++ b/src/system/SystemLayer.h @@ -43,7 +43,9 @@ #if CHIP_SYSTEM_CONFIG_USE_DISPATCH #include -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV +#include +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV #include @@ -250,7 +252,10 @@ class LayerSocketsLoop : public LayerSockets #if CHIP_SYSTEM_CONFIG_USE_DISPATCH virtual void SetDispatchQueue(dispatch_queue_t dispatchQueue) = 0; virtual dispatch_queue_t GetDispatchQueue() = 0; -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + virtual void SetLibEvLoop(struct ev_loop * aLibEvLoopP) = 0; + virtual struct ev_loop * GetLibEvLoop() = 0; +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV }; #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS diff --git a/src/system/SystemLayerImplSelect.cpp b/src/system/SystemLayerImplSelect.cpp index 171f14f8a6cfb7..513cfff4860f5a 100644 --- a/src/system/SystemLayerImplSelect.cpp +++ b/src/system/SystemLayerImplSelect.cpp @@ -35,6 +35,17 @@ #define PTHREAD_NULL 0 #endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING && !defined(PTHREAD_NULL) +#if CHIP_SYSTEM_CONFIG_USE_LIBEV +// older libev do not yet have ev_io_modify +#ifndef ev_io_modify +#define ev_io_modify(ev, events_) \ + do \ + { \ + (ev)->events = ((ev)->events & EV__IOFDSET) | (events_); \ + } while (0) +#endif // ev_io_modify +#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV + namespace chip { namespace System { @@ -82,11 +93,25 @@ void LayerImplSelect::Shutdown() { w.DisableAndClear(); } +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + TimerList::Node * timer; + while ((timer = mTimerList.PopEarliest()) != nullptr) + { + if (ev_is_active(&timer->mLibEvTimer)) + { + ev_timer_stop(mLibEvLoopP, &timer->mLibEvTimer); + } + } + mTimerPool.ReleaseAll(); -#else // CHIP_SYSTEM_CONFIG_USE_DISPATCH + for (auto & w : mSocketWatchPool) + { + w.DisableAndClear(); + } +#else mTimerList.Clear(); mTimerPool.ReleaseAll(); -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV mWakeEvent.Close(*this); @@ -139,10 +164,7 @@ CHIP_ERROR LayerImplSelect::StartTimer(Clock::Timeout delay, TimerCompleteCallba { (void) mTimerList.Add(timer); dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatchQueue); - if (timerSource == nullptr) - { - chipDie(); - } + VerifyOrDie(timerSource != nullptr); timer->mTimerSource = timerSource; dispatch_source_set_timer( @@ -157,14 +179,25 @@ CHIP_ERROR LayerImplSelect::StartTimer(Clock::Timeout delay, TimerCompleteCallba dispatch_resume(timerSource); return CHIP_NO_ERROR; } -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH - +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + VerifyOrDie(mLibEvLoopP != nullptr); + ev_timer_init(&timer->mLibEvTimer, &LayerImplSelect::HandleLibEvTimer, 1, 0); + timer->mLibEvTimer.data = timer; + auto t = Clock::Milliseconds64(delay).count(); + ev_timer_set(&timer->mLibEvTimer, static_cast(t) / 1E3, 0.); + (void) mTimerList.Add(timer); + ev_timer_start(mLibEvLoopP, &timer->mLibEvTimer); + return CHIP_NO_ERROR; +#endif +#if !CHIP_SYSTEM_CONFIG_USE_LIBEV + // Note: dispatch based implementation needs this as fallback, but not LIBEV (and dead code is not allowed with -Werror) if (mTimerList.Add(timer) == timer) { // The new timer is the earliest, so the time until the next event has probably changed. Signal(); } return CHIP_NO_ERROR; +#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV } void LayerImplSelect::CancelTimer(TimerCompleteCallback onComplete, void * appState) @@ -190,7 +223,10 @@ void LayerImplSelect::CancelTimer(TimerCompleteCallback onComplete, void * appSt dispatch_source_cancel(timer->mTimerSource); dispatch_release(timer->mTimerSource); } -#endif +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + VerifyOrDie(mLibEvLoopP != nullptr); + ev_timer_stop(mLibEvLoopP, &timer->mLibEvTimer); +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV mTimerPool.Release(timer); Signal(); @@ -211,8 +247,21 @@ CHIP_ERROR LayerImplSelect::ScheduleWork(TimerCompleteCallback onComplete, void }); return CHIP_NO_ERROR; } -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH - +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + // schedule as timer with no delay, but do NOT cancel previous timers with same onComplete/appState! + TimerList::Node * timer = mTimerPool.Create(*this, SystemClock().GetMonotonicTimestamp(), onComplete, appState); + VerifyOrReturnError(timer != nullptr, CHIP_ERROR_NO_MEMORY); + VerifyOrDie(mLibEvLoopP != nullptr); + ev_timer_init(&timer->mLibEvTimer, &LayerImplSelect::HandleLibEvTimer, 1, 0); + timer->mLibEvTimer.data = timer; + auto t = Clock::Milliseconds64(0).count(); + ev_timer_set(&timer->mLibEvTimer, static_cast(t) / 1E3, 0.); + (void) mTimerList.Add(timer); + ev_timer_start(mLibEvLoopP, &timer->mLibEvTimer); + return CHIP_NO_ERROR; +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV +#if !CHIP_SYSTEM_CONFIG_USE_LIBEV + // Note: dispatch based implementation needs this as fallback, but not LIBEV (and dead code is not allowed with -Werror) // Ideally we would not use a timer here at all, but if we try to just // ScheduleLambda the lambda needs to capture the following: // 1) onComplete @@ -247,6 +296,7 @@ CHIP_ERROR LayerImplSelect::ScheduleWork(TimerCompleteCallback onComplete, void Signal(); } return CHIP_NO_ERROR; +#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV } CHIP_ERROR LayerImplSelect::StartWatchingSocket(int fd, SocketWatchToken * tokenOut) @@ -268,6 +318,11 @@ CHIP_ERROR LayerImplSelect::StartWatchingSocket(int fd, SocketWatchToken * token VerifyOrReturnError(watch != nullptr, CHIP_ERROR_ENDPOINT_POOL_FULL); watch->mFD = fd; +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + ev_io_init(&watch->mIoWatcher, &LayerImplSelect::HandleLibEvIoWatcher, 0, 0); + watch->mIoWatcher.data = watch; + watch->mLayerImplSelectP = this; +#endif *tokenOut = reinterpret_cast(watch); return CHIP_NO_ERROR; @@ -320,6 +375,24 @@ CHIP_ERROR LayerImplSelect::RequestCallbackOnPendingRead(SocketWatchToken token) dispatch_activate(watch->mRdSource); } } +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + VerifyOrDie(mLibEvLoopP != nullptr); + int evs = (watch->mPendingIO.Has(SocketEventFlags::kRead) ? EV_READ : 0) | + (watch->mPendingIO.Has(SocketEventFlags::kWrite) ? EV_WRITE : 0); + if (!ev_is_active(&watch->mIoWatcher)) + { + // First time actually using that watch + ev_io_set(&watch->mIoWatcher, watch->mFD, evs); + ev_io_start(mLibEvLoopP, &watch->mIoWatcher); + } + else + { + // already active, just change flags + // Note: changing flags only reliably works when the watcher is stopped + ev_io_stop(mLibEvLoopP, &watch->mIoWatcher); + ev_io_modify(&watch->mIoWatcher, evs); + ev_io_start(mLibEvLoopP, &watch->mIoWatcher); + } #endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH return CHIP_NO_ERROR; @@ -359,10 +432,27 @@ CHIP_ERROR LayerImplSelect::RequestCallbackOnPendingWrite(SocketWatchToken token } }); // only now we are sure the source exists and can become active - watch->mPendingIO.Set(SocketEventFlags::kWrite); dispatch_activate(watch->mWrSource); } } +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + VerifyOrDie(mLibEvLoopP != nullptr); + int evs = (watch->mPendingIO.Has(SocketEventFlags::kRead) ? EV_READ : 0) | + (watch->mPendingIO.Has(SocketEventFlags::kWrite) ? EV_WRITE : 0); + if (!ev_is_active(&watch->mIoWatcher)) + { + // First time actually using that watch + ev_io_set(&watch->mIoWatcher, watch->mFD, evs); + ev_io_start(mLibEvLoopP, &watch->mIoWatcher); + } + else + { + // already active, just change flags + // Note: changing flags only reliably works when the watcher is stopped + ev_io_stop(mLibEvLoopP, &watch->mIoWatcher); + ev_io_modify(&watch->mIoWatcher, evs); + ev_io_start(mLibEvLoopP, &watch->mIoWatcher); + } #endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH return CHIP_NO_ERROR; @@ -375,6 +465,14 @@ CHIP_ERROR LayerImplSelect::ClearCallbackOnPendingRead(SocketWatchToken token) watch->mPendingIO.Clear(SocketEventFlags::kRead); +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + if (ev_is_active(&watch->mIoWatcher) && watch->mPendingIO.Raw() == 0) + { + // all flags cleared now, stop watching + ev_io_stop(mLibEvLoopP, &watch->mIoWatcher); + } +#endif + return CHIP_NO_ERROR; } @@ -385,6 +483,14 @@ CHIP_ERROR LayerImplSelect::ClearCallbackOnPendingWrite(SocketWatchToken token) watch->mPendingIO.Clear(SocketEventFlags::kWrite); +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + if (ev_is_active(&watch->mIoWatcher) && watch->mPendingIO.Raw() == 0) + { + // all flags cleared now, stop watching + ev_io_stop(mLibEvLoopP, &watch->mIoWatcher); + } +#endif + return CHIP_NO_ERROR; } @@ -396,7 +502,7 @@ CHIP_ERROR LayerImplSelect::StopWatchingSocket(SocketWatchToken * tokenInOut) VerifyOrReturnError(watch != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(watch->mFD >= 0, CHIP_ERROR_INCORRECT_STATE); -#if CHIP_SYSTEM_CONFIG_USE_DISPATCH +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH || CHIP_SYSTEM_CONFIG_USE_LIBEV watch->DisableAndClear(); #else watch->Clear(); @@ -532,12 +638,47 @@ void LayerImplSelect::HandleEvents() } #if CHIP_SYSTEM_CONFIG_USE_DISPATCH + void LayerImplSelect::HandleTimerComplete(TimerList::Node * timer) { mTimerList.Remove(timer); mTimerPool.Invoke(timer); } -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH + +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + +void LayerImplSelect::HandleLibEvTimer(EV_P_ struct ev_timer * t, int revents) +{ + TimerList::Node * timer = static_cast(t->data); + VerifyOrDie(timer != nullptr); + LayerImplSelect * layerP = dynamic_cast(timer->mCallback.mSystemLayer); + VerifyOrDie(layerP != nullptr); + layerP->mTimerList.Remove(timer); + layerP->mTimerPool.Invoke(timer); +} + +void LayerImplSelect::HandleLibEvIoWatcher(EV_P_ struct ev_io * i, int revents) +{ + SocketWatch * watch = static_cast(i->data); + if (watch != nullptr && watch->mCallback != nullptr && watch->mLayerImplSelectP != nullptr) + { + SocketEvents events; + if (revents & EV_READ) + { + events.Set(SocketEventFlags::kRead); + } + if (revents & EV_WRITE) + { + events.Set(SocketEventFlags::kWrite); + } + if (events.HasAny()) + { + watch->mCallback(events, watch->mCallbackData); + } + } +} + +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV void LayerImplSelect::SocketWatch::Clear() { @@ -548,6 +689,8 @@ void LayerImplSelect::SocketWatch::Clear() #if CHIP_SYSTEM_CONFIG_USE_DISPATCH mRdSource = nullptr; mWrSource = nullptr; +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + mLayerImplSelectP = nullptr; #endif } @@ -566,7 +709,16 @@ void LayerImplSelect::SocketWatch::DisableAndClear() } Clear(); } -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV +void LayerImplSelect::SocketWatch::DisableAndClear() +{ + if (mLayerImplSelectP != nullptr && mLayerImplSelectP->mLibEvLoopP != nullptr) + { + ev_io_stop(mLayerImplSelectP->mLibEvLoopP, &mIoWatcher); + } + Clear(); +} +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV } // namespace System } // namespace chip diff --git a/src/system/SystemLayerImplSelect.h b/src/system/SystemLayerImplSelect.h index 4243d2aee9f5e1..0eb930eeb86c8d 100644 --- a/src/system/SystemLayerImplSelect.h +++ b/src/system/SystemLayerImplSelect.h @@ -29,6 +29,13 @@ #include #endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING +#if CHIP_SYSTEM_CONFIG_USE_LIBEV +#include +#if CHIP_SYSTEM_CONFIG_USE_DISPATCH +#error "CHIP_SYSTEM_CONFIG_USE_LIBEV and CHIP_SYSTEM_CONFIG_USE_DISPATCH are mutually exclusive" +#endif +#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV + #include #include #include @@ -73,7 +80,12 @@ class LayerImplSelect : public LayerSocketsLoop void SetDispatchQueue(dispatch_queue_t dispatchQueue) override { mDispatchQueue = dispatchQueue; }; dispatch_queue_t GetDispatchQueue() override { return mDispatchQueue; }; void HandleTimerComplete(TimerList::Node * timer); -#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + virtual void SetLibEvLoop(struct ev_loop * aLibEvLoopP) override { mLibEvLoopP = aLibEvLoopP; }; + virtual struct ev_loop * GetLibEvLoop() override { return mLibEvLoopP; }; + static void HandleLibEvTimer(EV_P_ struct ev_timer * t, int revents); + static void HandleLibEvIoWatcher(EV_P_ struct ev_io * i, int revents); +#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV // Expose the result of WaitForEvents() for non-blocking socket implementations. bool IsSelectResultValid() const { return mSelectResult >= 0; } @@ -95,6 +107,12 @@ class LayerImplSelect : public LayerSocketsLoop dispatch_source_t mWrSource; void DisableAndClear(); #endif +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + struct ev_io mIoWatcher; + LayerImplSelect * mLayerImplSelectP; + void DisableAndClear(); +#endif + intptr_t mCallbackData; }; SocketWatch mSocketWatchPool[kSocketWatchMax]; @@ -128,6 +146,8 @@ class LayerImplSelect : public LayerSocketsLoop #if CHIP_SYSTEM_CONFIG_USE_DISPATCH dispatch_queue_t mDispatchQueue = nullptr; +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + struct ev_loop * mLibEvLoopP; #endif }; diff --git a/src/system/SystemTimer.h b/src/system/SystemTimer.h index 54b07f8c092f49..cd27fefcf11ef2 100644 --- a/src/system/SystemTimer.h +++ b/src/system/SystemTimer.h @@ -64,6 +64,9 @@ class DLL_EXPORT TimerData Layer * GetSystemLayer() const { return mSystemLayer; } private: +#if CHIP_SYSTEM_CONFIG_USE_LIBEV + friend class LayerImplSelect; +#endif Layer * mSystemLayer; TimerCompleteCallback mOnComplete; void * mAppState; @@ -91,6 +94,9 @@ class DLL_EXPORT TimerData #if CHIP_SYSTEM_CONFIG_USE_DISPATCH friend class LayerImplSelect; dispatch_source_t mTimerSource = nullptr; +#elif CHIP_SYSTEM_CONFIG_USE_LIBEV + friend class LayerImplSelect; + struct ev_timer mLibEvTimer; #endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH // Not defined diff --git a/src/system/system.gni b/src/system/system.gni index e91dd0dee460d3..67bf9b61b8f881 100644 --- a/src/system/system.gni +++ b/src/system/system.gni @@ -37,7 +37,10 @@ declare_args() { } declare_args() { - # use the dispatch library + # do not use libev by default + chip_system_config_use_libev = false + + # use the dispatch library on darwin targets chip_system_config_use_dispatch = chip_system_config_use_sockets && (current_os == "mac" || current_os == "ios") }