| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_EPOLL_H_ |
| #define BASE_MESSAGE_LOOP_MESSAGE_PUMP_EPOLL_H_ |
| |
| #include <poll.h> |
| #include <sys/epoll.h> |
| |
| #include <cstdint> |
| #include <map> |
| |
| #include "base/base_export.h" |
| #include "base/dcheck_is_on.h" |
| #include "base/feature_list.h" |
| #include "base/files/scoped_file.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ptr_exclusion.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_pump.h" |
| #include "base/message_loop/watchable_io_message_pump_posix.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/time/time.h" |
| #include "third_party/abseil-cpp/absl/container/inlined_vector.h" |
| |
| #if DCHECK_IS_ON() |
| #include <deque> |
| #include <optional> |
| |
| #include "base/debug/stack_trace.h" |
| #endif |
| |
| namespace base { |
| |
| // Use poll() rather than epoll(). |
| // |
| // Why? epoll() is supposed to be strictly better. But it has one consequence |
| // we don't necessarily want: when writing to a AF_UNIX socket, the kernel |
| // will wake up the waiter with a "sync" wakeup. The concept of a "sync" |
| // wakeup has various consequences, but on Android it tends to bias the |
| // scheduler towards a "baton passing" mode, where the current thread yields |
| // its CPU to the target. This is desirable to lower latency. |
| // |
| // However, when using epoll_wait(), the "sync" flag is dropped from the |
| // wakeup path. This is not the case with poll(). So let's use it to preserve |
| // this behavior. |
| // |
| // Caveat: Since both we and the kernel need to walk the list of all fds at |
| // every call, don't do it when we have too many FDs. |
| BASE_FEATURE(kUsePollForMessagePumpEpoll, |
| "UsePollForMessagePumpEpoll", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| // A MessagePump implementation suitable for I/O message loops on Linux-based |
| // systems with epoll API support. |
| class BASE_EXPORT MessagePumpEpoll : public MessagePump, |
| public WatchableIOMessagePumpPosix { |
| class Interest; |
| struct InterestParams; |
| |
| public: |
| // Object which FD-watching clients must keep alive to continue watching |
| // their FD. See WatchFileDescriptor() below. |
| class FdWatchController : public FdWatchControllerInterface { |
| public: |
| explicit FdWatchController(const Location& from_here); |
| |
| FdWatchController(const FdWatchController&) = delete; |
| FdWatchController& operator=(const FdWatchController&) = delete; |
| |
| // Implicitly calls StopWatchingFileDescriptor. |
| ~FdWatchController() override; |
| |
| // FdWatchControllerInterface: |
| bool StopWatchingFileDescriptor() override; |
| |
| private: |
| friend class MessagePumpEpoll; |
| friend class MessagePumpEpollTest; |
| |
| void set_watcher(FdWatcher* watcher) { watcher_ = watcher; } |
| void set_pump(WeakPtr<MessagePumpEpoll> pump) { pump_ = std::move(pump); } |
| const scoped_refptr<Interest>& interest() const { return interest_; } |
| |
| // Creates a new Interest described by `params` and adopts it as this |
| // controller's exclusive interest. Any prior interest is dropped by the |
| // controller and should be unregistered on the MessagePumpEpoll. |
| const scoped_refptr<Interest>& AssignInterest(const InterestParams& params); |
| void ClearInterest(); |
| |
| void OnFdReadable(); |
| void OnFdWritable(); |
| |
| raw_ptr<FdWatcher> watcher_ = nullptr; |
| |
| // If this pointer is non-null when the FdWatchController is destroyed, the |
| // pointee is set to true. |
| raw_ptr<bool> was_destroyed_ = nullptr; |
| |
| WeakPtr<MessagePumpEpoll> pump_; |
| scoped_refptr<Interest> interest_; |
| }; |
| |
| MessagePumpEpoll(); |
| MessagePumpEpoll(const MessagePumpEpoll&) = delete; |
| MessagePumpEpoll& operator=(const MessagePumpEpoll&) = delete; |
| ~MessagePumpEpoll() override; |
| |
| // Initializes features for this class. See `base::features::Init()`. |
| static void InitializeFeatures(); |
| |
| // Starts watching `fd` for events as prescribed by `mode` (see |
| // WatchableIOMessagePumpPosix). When an event occurs, `watcher` is notified. |
| // |
| // If `persistent` is false, the watch only persists until a matching event |
| // is observed, and `watcher` will only see at most one event; otherwise it |
| // remains active until explicitly cancelled and `watcher` may see multiple |
| // events over time. |
| // |
| // The watch can be cancelled at any time by destroying the `controller` or |
| // explicitly calling StopWatchingFileDescriptor() on it. |
| // |
| // IMPORTANT: `fd` MUST remain open as long as controller is alive and not |
| // stopped. If `fd` is closed while the watch is still active, this will |
| // result in memory bugs. |
| bool WatchFileDescriptor(int fd, |
| bool persistent, |
| int mode, |
| FdWatchController* controller, |
| FdWatcher* watcher); |
| |
| // MessagePump methods: |
| void Run(Delegate* delegate) override; |
| void Quit() override; |
| void ScheduleWork() override; |
| void ScheduleDelayedWork( |
| const Delegate::NextWorkInfo& next_work_info) override; |
| |
| private: |
| friend class MessagePumpEpollTest; |
| |
| // The WatchFileDescriptor API supports multiple FdWatchControllers watching |
| // the same file descriptor, potentially for different events; but the epoll |
| // API only supports a single interest list entry per unique file descriptor. |
| // |
| // EpollEventEntry tracks all epoll state relevant to a single file |
| // descriptor, including references to all active and inactive Interests |
| // concerned with that descriptor. This is used to derive a single aggregate |
| // interest entry for the descriptor when manipulating epoll. |
| struct EpollEventEntry { |
| explicit EpollEventEntry(int fd); |
| EpollEventEntry(const EpollEventEntry&) = delete; |
| EpollEventEntry& operator=(const EpollEventEntry&) = delete; |
| ~EpollEventEntry(); |
| |
| static EpollEventEntry& FromEpollEvent(epoll_event& e) { |
| return *static_cast<EpollEventEntry*>(e.data.ptr); |
| } |
| |
| // Returns the combined set of epoll event flags which should be monitored |
| // by the epoll instance for `fd`. This is based on a combination of the |
| // parameters of all currently active elements in `interests`. Namely: |
| // - EPOLLIN is set if any active Interest wants to `read`. |
| // - EPOLLOUT is set if any active Interest wants to `write`. |
| // - EPOLLONESHOT is set if all active Interests are one-shot. |
| uint32_t ComputeActiveEvents() const; |
| |
| // The file descriptor to which this entry pertains. |
| const int fd; |
| |
| // A cached copy of the last known epoll event bits registered for this |
| // descriptor on the epoll instance. |
| uint32_t registered_events = 0; |
| |
| // A collection of all the interests regarding `fd` on this message pump. |
| // The small amount of inline storage avoids heap allocation in virtually |
| // all real scenarios, since there's little practical value in having more |
| // than two controllers (e.g. one reader and one writer) watch the same |
| // descriptor on the same thread. |
| absl::InlinedVector<scoped_refptr<Interest>, 2> interests; |
| |
| // Temporary pointer to an active epoll_event structure which refers to |
| // this entry. This is set immediately upon returning from epoll_wait() and |
| // cleared again immediately before dispatching to any registered interests, |
| // so long as this entry isn't destroyed in the interim. |
| raw_ptr<epoll_event> active_event = nullptr; |
| |
| // If the file descriptor is disconnected and no active `interests`, remove |
| // it from the epoll interest list to avoid unconditionally epoll_wait |
| // return, and prevent any future update on this `EpollEventEntry`. |
| bool stopped = false; |
| |
| #if DCHECK_IS_ON() |
| struct EpollHistory { |
| base::debug::StackTrace stack_trace; |
| std::optional<epoll_event> event; |
| }; |
| static constexpr ssize_t kEpollHistoryWindowSize = 5; |
| std::deque<EpollHistory> epoll_history_; |
| |
| void PushEpollHistory(std::optional<epoll_event> event) { |
| EpollHistory info = {.stack_trace = base::debug::StackTrace(), |
| .event = event}; |
| epoll_history_.push_back(info); |
| if (epoll_history_.size() > kEpollHistoryWindowSize) { |
| epoll_history_.pop_front(); |
| } |
| } |
| #endif |
| }; |
| |
| // State which lives on the stack within Run(), to support nested run loops. |
| struct RunState { |
| explicit RunState(Delegate* delegate) : delegate(delegate) {} |
| |
| // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of sampling |
| // profiler data and tab_search:top100:2020). |
| RAW_PTR_EXCLUSION Delegate* const delegate; |
| |
| // Used to flag that the current Run() invocation should return ASAP. |
| bool should_quit = false; |
| }; |
| |
| void AddEpollEvent(EpollEventEntry& entry); |
| void UpdateEpollEvent(EpollEventEntry& entry); |
| void StopEpollEvent(EpollEventEntry& entry); |
| void UnregisterInterest(const scoped_refptr<Interest>& interest); |
| bool WaitForEpollEvents(TimeDelta timeout); |
| bool GetEventsPoll(int epoll_timeout, std::vector<epoll_event>* epoll_events); |
| void OnEpollEvent(EpollEventEntry& entry, uint32_t events); |
| void HandleEvent(int fd, |
| bool can_read, |
| bool can_write, |
| FdWatchController* controller); |
| void HandleWakeUp(); |
| |
| void BeginNativeWorkBatch(); |
| void RecordPeriodicMetrics(); |
| |
| std::vector<struct pollfd>::iterator FindPollEntry(int fd); |
| void RemovePollEntry(int fd); |
| |
| // Null if Run() is not currently executing. Otherwise it's a pointer into the |
| // stack of the innermost nested Run() invocation. |
| raw_ptr<RunState> run_state_ = nullptr; |
| |
| // This flag is set when starting to process native work; reset after every |
| // `DoWork()` call. See crbug.com/1500295. |
| bool native_work_started_ = false; |
| |
| // Mapping of all file descriptors currently watched by this message pump. |
| // std::map was chosen because (1) the number of elements can vary widely, |
| // (2) we don't do frequent lookups, and (3) values need stable addresses |
| // across insertion or removal of other elements. |
| std::map<int, EpollEventEntry> entries_; |
| |
| // pollfd array passed to poll() when not using epoll. |
| std::vector<struct pollfd> pollfds_; |
| |
| // The epoll instance used by this message pump to monitor file descriptors. |
| ScopedFD epoll_; |
| |
| // An eventfd object used to wake the pump's thread when scheduling new work. |
| ScopedFD wake_event_; |
| |
| // Tracks when we should next record periodic metrics. |
| base::TimeTicks next_metrics_time_; |
| |
| // WatchFileDescriptor() must be called from this thread, and so must |
| // FdWatchController::StopWatchingFileDescriptor(). |
| THREAD_CHECKER(thread_checker_); |
| |
| WeakPtrFactory<MessagePumpEpoll> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace base |
| |
| #endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_EPOLL_H_ |