// 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 #include #include #include #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 #include #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 pump) { pump_ = std::move(pump); } const scoped_refptr& 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& AssignInterest(const InterestParams& params); void ClearInterest(); void OnFdReadable(); void OnFdWritable(); raw_ptr watcher_ = nullptr; // If this pointer is non-null when the FdWatchController is destroyed, the // pointee is set to true. raw_ptr was_destroyed_ = nullptr; WeakPtr pump_; scoped_refptr 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(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, 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 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 event; }; static constexpr ssize_t kEpollHistoryWindowSize = 5; std::deque epoll_history_; void PushEpollHistory(std::optional 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); bool WaitForEpollEvents(TimeDelta timeout); bool GetEventsPoll(int epoll_timeout, std::vector* 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::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 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 entries_; // pollfd array passed to poll() when not using epoll. std::vector 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 weak_ptr_factory_{this}; }; } // namespace base #endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_EPOLL_H_