fish-shell/src/fd_monitor.h

143 lines
5 KiB
C++
Raw Normal View History

#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