libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
eventloop.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_CORE
13#include <variant>
14#include <functional>
15#include <promeki/function.h>
16#include <promeki/namespace.h>
17#include <promeki/atomic.h>
18#include <promeki/duration.h>
19#include <promeki/hashmap.h>
20#include <promeki/mutex.h>
21#include <promeki/queue.h>
22#include <promeki/list.h>
23#include <promeki/string.h>
25#include <promeki/timestamp.h>
26#include <promeki/event.h>
27#include <promeki/uniqueptr.h>
28
29PROMEKI_NAMESPACE_BEGIN
30
31class ObjectBase;
32
33// Defined in eventloop.cpp; owns the platform-specific wake file
34// descriptor (eventfd on Linux, self-pipe elsewhere) used to
35// interrupt poll() when new work is posted.
36class EventLoopWakeFd;
37
38// Defined in eventloop.cpp; caches the poll() descriptor set so a
39// loop servicing many I/O sources doesn't rebuild an N-entry array
40// on every wake. Holds POSIX pollfd state we don't want to leak
41// into this header; rebuilt only when a source is added or removed.
42class EventLoopPollCache;
43
86class EventLoop {
87 public:
89 enum ProcessEventsFlag : uint32_t {
90 ExcludeTimers = 0x01,
91 ExcludePosted = 0x02,
92 WaitForMore = 0x04
93 };
94
111 using Label = StringRegistry<"EventLoopLabel">::Item;
112
117 static EventLoop *current();
118
126 EventLoop();
127
135 ~EventLoop();
136
137 EventLoop(const EventLoop &) = delete;
138 EventLoop &operator=(const EventLoop &) = delete;
139
144 int exec();
145
155 void processEvents(uint32_t flags = 0, unsigned int timeoutMs = 0);
156
165 void quit(int returnCode = 0);
166
175 void postCallable(Function<void()> func);
176
206 void postCallable(Label label, Function<void()> func);
207
236 void postCallable(ObjectBase *owner, Label label, Function<void()> func);
237
248 void postEvent(ObjectBase *receiver, Event *event);
249
264 int startTimer(ObjectBase *receiver, unsigned int intervalMs, bool singleShot = false);
265
278 int startTimer(unsigned int intervalMs, Function<void()> func, bool singleShot = false);
279
294 void stopTimer(int timerId);
295
300 bool isRunning() const { return _running.value(); }
301
306 int exitCode() const { return _exitCode.value(); }
307
320 unsigned int nextTimerTimeout() const;
321
332 static constexpr uint32_t IoRead = 0x01;
333 static constexpr uint32_t IoWrite = 0x02;
334 static constexpr uint32_t IoError = 0x04;
347 using IoCallback = Function<void(int fd, uint32_t events)>;
348
392 int addIoSource(int fd, uint32_t events, IoCallback cb);
393
405 void removeIoSource(int handle);
406
446 struct Report {
458 struct EventStat {
459 Duration elapsed;
460 int64_t count = 0;
461 };
462
463 String loopName;
464 Duration wallElapsed;
465 Duration sleep;
466 Duration queueWait;
467 Duration timers;
468 Duration events;
469 Duration callables;
470 Duration io;
471 Duration overhead;
472
473 int64_t timersCount = 0;
474 int64_t eventsCount = 0;
475 int64_t callablesCount = 0;
476 int64_t ioCount = 0;
477
479 HashMap<int, EventStat> eventsByType;
480
490 HashMap<uint64_t, EventStat> callablesByLabel;
491 };
492
504 using ReportFunction = Function<void(const Report &)>;
505
524 void setName(const String &name);
525
530 String name() const;
531
562 void installMonitor(const Duration &interval, ReportFunction fn = {});
563
583 void removeMonitor();
584
593 bool hasMonitor() const;
594
607 Report peekStats() const;
608
619 Report consumeStats();
620
621 private:
622 struct CallableItem {
623 Function<void()> func;
631 uint64_t labelId = Label::InvalidID;
632 };
633 struct EventItem {
634 ObjectBase *receiver;
635 Event *event;
636 };
637 struct QuitItem {
638 int code;
639 };
640 using Item = std::variant<CallableItem, EventItem, QuitItem>;
641
642 struct TimerInfo {
643 int id;
644 ObjectBase *receiver;
645 Function<void()> func;
646 unsigned int intervalMs;
647 bool singleShot;
648 TimeStamp nextFire;
649 };
650
651 static thread_local EventLoop *_current;
652
653 Queue<Item> _queue;
654 Atomic<bool> _running;
655 Atomic<int> _exitCode;
656
657 // Timer list access is guarded by _timersMutex. Any
658 // thread may install or stop timers via startTimer /
659 // stopTimer, so the mutex is acquired on every touch.
660 // processTimers() takes a snapshot of the ready-to-fire
661 // entries under the lock, releases the lock, and then
662 // invokes callbacks — this avoids deadlocks if a timer
663 // callback calls startTimer() or stopTimer() on the
664 // same event loop, and keeps the lock hold time bounded.
665 mutable Mutex _timersMutex;
666 List<TimerInfo> _timers;
667 Atomic<int> _nextTimerId{1};
668
669 // Platform wake fd (eventfd on Linux, self-pipe
670 // elsewhere). Owned by the EventLoop; opened in the
671 // constructor, closed in the destructor. Written by
672 // postCallable / postEvent / quit / startTimer /
673 // stopTimer to unblock poll() in waitOnSources, and
674 // included in every poll set as index 0.
675 using WakeFdUPtr = UniquePtr<EventLoopWakeFd>;
676 WakeFdUPtr _wake;
677
678 // I/O source registration. Mutation under _ioMutex,
679 // poll set built under _ioMutex into a short-lived
680 // stack copy, callbacks fired after the lock is
681 // released so a callback may call addIoSource /
682 // removeIoSource on the same EventLoop without
683 // deadlocking.
684 struct IoSource {
685 int handle;
686 int fd;
687 uint32_t events;
688 IoCallback cb;
689 bool pendingRemove = false;
690 };
691 mutable Mutex _ioMutex;
692 List<IoSource> _ioSources;
693 Atomic<int> _nextIoHandle{1};
694
695 // Cached poll() descriptor set + dirty flag (see
696 // EventLoopPollCache in eventloop.cpp). Rebuilt under
697 // _ioMutex only when an IoSource is added or removed,
698 // then reused across waitOnSources iterations so the
699 // steady-state wake path costs a lock + flag check
700 // rather than an O(number-of-sources) array rebuild.
701 using PollCacheUPtr = UniquePtr<EventLoopPollCache>;
702 PollCacheUPtr _pollCache;
703
713 void wakeSelf();
714
715 bool dispatchItem(Item &item);
716 void processTimers();
717
718 // Waits on the wake fd + registered I/O sources via
719 // poll(), for up to @p waitMs milliseconds (0 = wait
720 // indefinitely — callers clamp by timers before
721 // calling). On return, drains the wake fd and fires
722 // any ready I/O source callbacks. On non-POSIX
723 // platforms, falls back to the condvar-based
724 // Queue::pop wait so Windows / Emscripten builds
725 // still compile and behave as before (minus
726 // IoSource support).
727 void waitOnSources(unsigned int waitMs);
728
729 // ----------------------------------------------------
730 // Stats / monitor support. The hot-path bracket reads
731 // _monitorActive once at construction; when false it
732 // skips the timestamp grab, the lock, and the
733 // accumulator update. When true it stamps once on
734 // construction and once on destruction, takes the
735 // mutex, adds the elapsed nanoseconds + 1 to the
736 // attributed duration / count buckets, and updates
737 // _eventsByType when an event-type is supplied.
738 // ----------------------------------------------------
739
740 struct EventStatNs {
741 int64_t elapsed = 0;
742 int64_t count = 0;
743 };
744
745 Atomic<bool> _monitorActive;
746 mutable Mutex _statsMutex;
747 TimeStamp _statsLastSnapshot;
748
749 int64_t _sleepNs = 0;
750 int64_t _queueWaitNs = 0;
751 int64_t _timersNs = 0;
752 int64_t _eventsNs = 0;
753 int64_t _callablesNs = 0;
754 int64_t _ioNs = 0;
755
756 int64_t _timersCount = 0;
757 int64_t _eventsCount = 0;
758 int64_t _callablesCount = 0;
759 int64_t _ioCount = 0;
760
761 HashMap<int, EventStatNs> _eventsByType;
762
766 HashMap<uint64_t, EventStatNs> _callablesByLabel;
767
768 int _monitorTimerId = 0;
769 ReportFunction _monitorFn;
770 String _name;
771
772 // RAII helper that brackets a single dispatch site.
773 // Reads _monitorActive ONCE in the constructor and
774 // caches the boolean; both the constructor's
775 // TimeStamp::now() grab and the destructor's
776 // accumulator update key off that cached value so
777 // an enable-mid-bracket cannot leave the destructor
778 // trying to attribute time without a start
779 // timestamp. When the cached gate is false, no
780 // timestamp is stored and the destructor early-outs
781 // without locking.
782 class StatsBracket {
783 public:
784 StatsBracket(EventLoop *loop, int64_t *durBucket,
785 int64_t *countBucket);
786 ~StatsBracket();
787 void attributeEventType(int type) { _eventType = type; }
788 void attributeCallableLabel(uint64_t labelId) {
789 _callableLabel = labelId;
790 }
791
792 private:
793 EventLoop *_loop;
794 int64_t *_durBucket;
795 int64_t *_countBucket;
796 TimeStamp _start;
797 int _eventType = -1;
798 uint64_t _callableLabel = Label::InvalidID;
799 bool _active = false;
800 };
801
802 friend class StatsBracket;
803
804 // Default formatter used when _monitorFn is empty.
805 // Defined in eventloop.cpp and exposed via
806 // installMonitor's "empty fn = default" semantics.
807 static void defaultMonitorReporter(const Report &r);
808};
809
810PROMEKI_NAMESPACE_END
811
812#endif // PROMEKI_ENABLE_CORE