Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
#ifndef FISH_TOPIC_MONITOR_H
|
|
|
|
#define FISH_TOPIC_MONITOR_H
|
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "enum_set.h"
|
|
|
|
#include "io.h"
|
|
|
|
|
|
|
|
#include <array>
|
|
|
|
#include <atomic>
|
|
|
|
#include <bitset>
|
|
|
|
#include <condition_variable>
|
|
|
|
#include <limits>
|
|
|
|
#include <numeric>
|
|
|
|
#include <queue>
|
|
|
|
|
|
|
|
/** Topic monitoring support. Topics are conceptually "a thing that can happen." For example,
|
|
|
|
delivery of a SIGINT, a child process exits, etc. It is possible to post to a topic, which means
|
|
|
|
that that thing happened.
|
|
|
|
|
|
|
|
Associated with each topic is a current generation, which is a 64 bit value. When you query a
|
|
|
|
topic, you get back a generation. If on the next query the generation has increased, then it
|
|
|
|
indicates someone posted to the topic.
|
|
|
|
|
|
|
|
For example, if you are monitoring a child process, you can query the sigchld topic. If it has
|
|
|
|
increased since your last query, it is possible that your child process has exited.
|
|
|
|
|
|
|
|
Topic postings may be coalesced. That is there may be two posts to a given topic, yet the
|
|
|
|
generation only increases by 1. The only guarantee is that after a topic post, the current
|
|
|
|
generation value is larger than any value previously queried.
|
|
|
|
|
|
|
|
Tying this all together is the topic_monitor_t. This provides the current topic generations, and
|
|
|
|
also provides the ability to perform a blocking wait for any topic to change in a particular topic
|
|
|
|
set. This is the real power of topics: you can wait for a sigchld signal OR a thread exit.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/// The list of topics that may be observed.
|
|
|
|
enum class topic_t : uint8_t {
|
2019-02-13 23:17:07 +00:00
|
|
|
sigchld, // Corresponds to SIGCHLD signal.
|
|
|
|
sighupint, // Corresponds to both SIGHUP and SIGINT signals.
|
|
|
|
internal_exit, // Corresponds to an internal process exit.
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
COUNT
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Allow enum_iter to be used.
|
|
|
|
template <>
|
|
|
|
struct enum_info_t<topic_t> {
|
|
|
|
static constexpr auto count = topic_t::COUNT;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Set of topics.
|
|
|
|
using topic_set_t = enum_set_t<topic_t>;
|
|
|
|
|
|
|
|
/// Counting iterator for topics.
|
|
|
|
using topic_iter_t = enum_iter_t<topic_t>;
|
|
|
|
|
|
|
|
/// A generation is a counter incremented every time the value of a topic changes.
|
|
|
|
/// It is 64 bit so it will never wrap.
|
|
|
|
using generation_t = uint64_t;
|
|
|
|
|
|
|
|
/// A generation value which is guaranteed to never be set and be larger than any valid generation.
|
|
|
|
constexpr generation_t invalid_generation = std::numeric_limits<generation_t>::max();
|
|
|
|
|
|
|
|
/// List of generation values, indexed by topics.
|
|
|
|
/// The initial value of a generation is always 0.
|
|
|
|
using generation_list_t = enum_array_t<generation_t, topic_t>;
|
|
|
|
|
|
|
|
/// Teh topic monitor class. This permits querying the current generation values for topics,
|
|
|
|
/// optionally blocking until they increase.
|
|
|
|
class topic_monitor_t {
|
|
|
|
private:
|
|
|
|
using topic_set_raw_t = uint8_t;
|
|
|
|
|
|
|
|
static_assert(sizeof(topic_set_raw_t) * CHAR_BIT >= enum_count<topic_t>(),
|
|
|
|
"topic_set_raw is too small");
|
|
|
|
|
|
|
|
/// The current topic generation list, protected by a mutex. Note this may be opportunistically
|
|
|
|
/// updated at the point it is queried.
|
|
|
|
owning_lock<generation_list_t> current_gen_{{}};
|
|
|
|
|
|
|
|
/// The set of topics which have pending increments.
|
|
|
|
/// This is managed via atomics.
|
|
|
|
std::atomic<topic_set_raw_t> pending_updates_{};
|
|
|
|
|
|
|
|
/// When a topic set is queried in a blocking way, the waiters are put into a queue. The waiter
|
|
|
|
/// with the smallest metagen is responsible for announcing the change to the rest of the
|
|
|
|
/// waiters. (The metagen is just the sum of the current generations.) Note that this is a
|
|
|
|
/// max-heap that defaults to std::less; by using std::greater it becomes a min heap. This is
|
|
|
|
/// protected by wait_queue_lock_.
|
|
|
|
std::priority_queue<generation_t, std::vector<generation_t>, std::greater<generation_t>>
|
|
|
|
wait_queue_;
|
|
|
|
|
|
|
|
/// Mutex guarding the wait queue.
|
|
|
|
std::mutex wait_queue_lock_{};
|
|
|
|
|
|
|
|
/// Condition variable for broadcasting notifications.
|
|
|
|
std::condition_variable wait_queue_notifier_{};
|
|
|
|
|
|
|
|
/// Pipes used to communicate changes from the signal handler.
|
|
|
|
autoclose_pipes_t pipes_;
|
|
|
|
|
|
|
|
/// \return the metagen for a topic generation list.
|
|
|
|
/// The metagen is simply the sum of topic generations. Note it is monotone.
|
|
|
|
static inline generation_t metagen_for(const generation_list_t &lst) {
|
|
|
|
return std::accumulate(lst.begin(), lst.end(), generation_t{0});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Wait for the current metagen to become different from \p gen.
|
|
|
|
/// If it is already different, return immediately.
|
|
|
|
void await_metagen(generation_t gen);
|
|
|
|
|
|
|
|
/// Return the current generation list, opportunistically applying any pending updates.
|
2019-05-29 21:20:07 +00:00
|
|
|
generation_list_t updated_gens();
|
Introduce topic monitoring
topic_monitor allows for querying changes posted to one or more topics,
initially sigchld. This will eventually replace the waitpid logic in
process_mark_finished_children().
Comment from the new header:
Topic monitoring support. Topics are conceptually "a thing that can
happen." For example, delivery of a SIGINT, a child process exits, etc. It
is possible to post to a topic, which means that that thing happened.
Associated with each topic is a current generation, which is a 64 bit
value. When you query a topic, you get back a generation. If on the next
query the generation has increased, then it indicates someone posted to
the topic.
For example, if you are monitoring a child process, you can query the
sigchld topic. If it has increased since your last query, it is possible
that your child process has exited.
Topic postings may be coalesced. That is there may be two posts to a given
topic, yet the generation only increases by 1. The only guarantee is that
after a topic post, the current generation value is larger than any value
previously queried.
Tying this all together is the topic_monitor_t. This provides the current
topic generations, and also provides the ability to perform a blocking
wait for any topic to change in a particular topic set. This is the real
power of topics: you can wait for a sigchld signal OR a thread exit.
2019-02-02 23:39:04 +00:00
|
|
|
|
|
|
|
/// \return the metagen for the current topic generation list.
|
|
|
|
inline generation_t current_metagen() { return metagen_for(updated_gens()); }
|
|
|
|
|
|
|
|
public:
|
|
|
|
topic_monitor_t();
|
|
|
|
~topic_monitor_t();
|
|
|
|
|
|
|
|
/// topic_monitors should not be copied, and there should be no reason to move one.
|
|
|
|
void operator=(const topic_monitor_t &) = delete;
|
|
|
|
topic_monitor_t(const topic_monitor_t &) = delete;
|
|
|
|
void operator=(topic_monitor_t &&) = delete;
|
|
|
|
topic_monitor_t(topic_monitor_t &&) = delete;
|
|
|
|
|
|
|
|
/// The principal topic_monitor. This may be fetched from a signal handler.
|
|
|
|
static topic_monitor_t &principal();
|
|
|
|
|
|
|
|
/// Post to a topic, potentially from a signal handler.
|
|
|
|
void post(topic_t topic);
|
|
|
|
|
|
|
|
/// Access the current generations.
|
|
|
|
generation_list_t current_generations() { return updated_gens(); }
|
|
|
|
|
|
|
|
/// Access the generation for a topic.
|
|
|
|
generation_t generation_for_topic(topic_t topic) { return current_generations().at(topic); }
|
|
|
|
|
|
|
|
/// See if for any topic (specified in \p topics) has changed from the values in the generation
|
|
|
|
/// list \p gens. If \p wait is set, then wait if there are no changes; otherwise return
|
|
|
|
/// immediately.
|
|
|
|
/// \return the set of topics that changed, updating the generation list \p gens.
|
|
|
|
topic_set_t check(generation_list_t *gens, topic_set_t topics, bool wait);
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|