fish-shell/src/fd_monitor.h
Aaron Gyes 14d2a6d8ff IWYU-guided #include rejiggering.
Let's hope this doesn't causes build failures for e.g. musl: I just
know it's good on macOS and our Linux CI.

It's been a long time.

One fix this brings, is I discovered we #include assert.h or cassert
in a lot of places. If those ever happen to be in a file that doesn't
include common.h, or we are before common.h gets included, we're
unawaringly working with the system 'assert' macro again, which
may get disabled for debug builds or at least has different
behavior on crash. We undef 'assert' and redefine it in common.h.

Those were all eliminated, except in one catch-22 spot for
maybe.h: it can't include common.h. A fix might be to
make a fish_assert.h that *usually* common.h exports.
2022-08-20 23:55:18 -07:00

142 lines
5 KiB
C++

#ifndef FISH_FD_MONITOR_H
#define FISH_FD_MONITOR_H
#include <chrono>
#include <cstdint>
#include <functional>
#include <utility>
#include <vector>
// Needed for musl
#include <sys/select.h> // IWYU pragma: keep
#include "common.h"
#include "fds.h"
#include "maybe.h"
/// Each item added to fd_monitor_t is assigned a unique ID, which is not recycled.
/// Items may have their callback triggered immediately by passing the ID.
/// Zero is a sentinel.
using fd_monitor_item_id_t = uint64_t;
/// Reasons for waking an item.
enum class item_wake_reason_t {
readable, // the fd became readable
timeout, // the requested timeout was hit
poke, // the item was "poked" (woken up explicitly)
};
/// An item containing an fd and callback, which can be monitored to watch when it becomes readable,
/// and invoke the callback.
struct fd_monitor_item_t {
/// The callback type for the item. It is passed \p fd, and the reason for waking \p reason.
/// The callback may close \p fd, in which case the item is removed.
using callback_t = std::function<void(autoclose_fd_t &fd, item_wake_reason_t reason)>;
/// A sentinel value meaning no timeout.
static constexpr uint64_t kNoTimeout = fd_readable_set_t::kNoTimeout;
/// The fd to monitor.
autoclose_fd_t fd{};
/// A callback to be invoked when the fd is readable, or when we are timed out.
/// If we time out, then timed_out will be true.
/// If the fd is invalid on return from the function, then the item is removed.
callback_t callback{};
/// The timeout in microseconds, or kNoTimeout for none.
/// 0 timeouts are unsupported.
uint64_t timeout_usec{kNoTimeout};
/// Construct from a file, callback, and optional timeout.
fd_monitor_item_t(autoclose_fd_t fd, callback_t callback, uint64_t timeout_usec = kNoTimeout)
: fd(std::move(fd)), callback(std::move(callback)), timeout_usec(timeout_usec) {
assert(timeout_usec > 0 && "Invalid timeout");
}
fd_monitor_item_t() = default;
private:
// Fields and methods for the private use of fd_monitor_t.
using time_point_t = std::chrono::time_point<std::chrono::steady_clock>;
// The last time we were called, or the initialization point.
maybe_t<time_point_t> last_time{};
// The ID for this item. This is assigned by the fd monitor.
fd_monitor_item_id_t item_id{0};
// \return the number of microseconds until the timeout should trigger, or kNoTimeout for none.
// A 0 return means we are at or past the timeout.
uint64_t usec_remaining(const time_point_t &now) const;
// Invoke this item's callback if its value is set in fd or has timed out.
// \return true to retain the item, false to remove it.
bool service_item(const fd_readable_set_t &fds, const time_point_t &now);
// Invoke this item's callback with a poke, if its ID is present in the (sorted) pokelist.
// \return true to retain the item, false to remove it.
using poke_list_t = std::vector<fd_monitor_item_id_t>;
bool poke_item(const poke_list_t &pokelist);
friend class fd_monitor_t;
};
/// A class which can monitor a set of fds, invoking a callback when any becomes readable, or when
/// per-item-configurable timeouts are hit.
class fd_monitor_t {
public:
using item_list_t = std::vector<fd_monitor_item_t>;
// A "pokelist" is a sorted list of item IDs which need explicit wakeups.
using poke_list_t = std::vector<fd_monitor_item_id_t>;
fd_monitor_t();
~fd_monitor_t();
/// Add an item to monitor. \return the ID assigned to the item.
fd_monitor_item_id_t add(fd_monitor_item_t &&item);
/// Mark that an item with a given ID needs to be explicitly woken up.
void poke_item(fd_monitor_item_id_t item_id);
private:
// The background thread runner.
void run_in_background();
// If our self-signaller is reported as ready, this reads from it and handles any changes.
// Called in the background thread.
void handle_self_signal_in_background();
// Poke items in the pokelist, removing any items that close their FD.
// The pokelist is consumed after this.
// This is only called in the background thread.
void poke_in_background(const poke_list_t &pokelist);
// The list of items to monitor. This is only accessed on the background thread.
item_list_t items_{};
struct data_t {
/// Pending items. This is set under the lock, then the background thread grabs them.
item_list_t pending{};
/// List of IDs for items that need to be poked (explicitly woken up).
poke_list_t pokelist{};
/// The last ID assigned, or if none.
fd_monitor_item_id_t last_id{0};
/// Whether the thread is running.
bool running{false};
// Set if we should terminate.
bool terminate{false};
};
owning_lock<data_t> data_;
/// Our self-signaller. When this is written to, it means there are new items pending, or new
/// items in the pokelist, or terminate is set.
fd_event_signaller_t change_signaller_;
};
#endif