+2021-04-27 Michael Weghorn <m.weghorn@posteo.de>
+ Simon Marchi <simon.marchi@polymtl.ca>
+
+ * unittests/observable-selftests.c (dependency_test_counters):
+ New.
+ (observer_token0, observer_token1, observer_token2,
+ observer_token3, observer_token4, observer_token5): New.
+ (struct dependency_observer_data): New struct.
+ (observer_dependency_test_callback): New function.
+ (test_observers): New.
+ (run_dependency_test): New function.
+ (test_dependency): New.
+ (_initialize_observer_selftest): Register dependency test.
+
2021-04-26 Simon Marchi <simon.marchi@polymtl.ca>
PR gdb/27773
static int test_second_observer = 0;
static int test_third_observer = 0;
+/* Counters for observers used for dependency tests. */
+static std::vector<int> dependency_test_counters;
+
+/* Tokens for observers used for dependency tests. */
+static gdb::observers::token observer_token0;
+static gdb::observers::token observer_token1;
+static gdb::observers::token observer_token2;
+static gdb::observers::token observer_token3;
+static gdb::observers::token observer_token4;
+static gdb::observers::token observer_token5;
+
+/* Data for one observer used for checking that dependencies work as expected;
+ dependencies are specified using their indices into the 'test_observers'
+ vector here for simplicity and mapped to corresponding tokens later. */
+struct dependency_observer_data
+{
+ gdb::observers::token *token;
+
+ /* Name of the observer to use on attach. */
+ const char *name;
+
+ /* Indices of observers that this one directly depends on. */
+ std::vector<int> direct_dependencies;
+
+ /* Indices for all dependencies, including transitive ones. */
+ std::vector<int> all_dependencies;
+
+ /* Function to attach to the observable for this observer. */
+ std::function<void (int)> callback;
+};
+
+static void observer_dependency_test_callback (size_t index);
+
+/* Data for observers to use for dependency tests, using some sample
+ dependencies between the observers. */
+static std::vector<dependency_observer_data> test_observers = {
+ {&observer_token0, "test0", {}, {},
+ [] (int) { observer_dependency_test_callback (0); }},
+ {&observer_token1, "test1", {0}, {0},
+ [] (int) { observer_dependency_test_callback (1); }},
+ {&observer_token2, "test2", {1}, {0, 1},
+ [] (int) { observer_dependency_test_callback (2); }},
+ {&observer_token3, "test3", {1}, {0, 1},
+ [] (int) { observer_dependency_test_callback (3); }},
+ {&observer_token4, "test4", {2, 3, 5}, {0, 1, 2, 3, 5},
+ [] (int) { observer_dependency_test_callback (4); }},
+ {&observer_token5, "test5", {0}, {0},
+ [] (int) { observer_dependency_test_callback (5); }},
+ {nullptr, "test6", {4}, {0, 1, 2, 3, 4, 5},
+ [] (int) { observer_dependency_test_callback (6); }},
+ {nullptr, "test7", {0}, {0},
+ [] (int) { observer_dependency_test_callback (7); }},
+};
+
static void
test_first_notification_function (int arg)
{
SELF_CHECK (three == test_third_observer);
}
+/* Function for each observer to run when being notified during the dependency
+ tests. Verify that the observer's dependencies have been notified before the
+ observer itself by checking their counters, then increase the observer's own
+ counter. */
+static void
+observer_dependency_test_callback (size_t index)
+{
+ /* Check that dependencies have already been notified. */
+ for (int i : test_observers[index].all_dependencies)
+ SELF_CHECK (dependency_test_counters[i] == 1);
+
+ /* Increase own counter. */
+ dependency_test_counters[index]++;
+}
+
+/* Run a dependency test by attaching the observers in the specified order
+ then notifying them. */
+static void
+run_dependency_test (std::vector<int> insertion_order)
+{
+ gdb::observers::observable<int> dependency_test_notification
+ ("dependency_test_notification");
+
+ /* Reset counters. */
+ dependency_test_counters = std::vector<int> (test_observers.size (), 0);
+
+ /* Attach all observers in the given order, specifying dependencies. */
+ for (int i : insertion_order)
+ {
+ const dependency_observer_data &o = test_observers[i];
+
+ /* Get tokens for dependencies using their indices. */
+ std::vector<const gdb::observers::token *> dependency_tokens;
+ for (int index : o.all_dependencies)
+ dependency_tokens.emplace_back (test_observers[index].token);
+
+ if (o.token != nullptr)
+ dependency_test_notification.attach
+ (o.callback, *o.token, o.name, dependency_tokens);
+ else
+ dependency_test_notification.attach (o.callback, o.name,
+ dependency_tokens);
+ }
+
+ /* Notify observers, they check that their dependencies were notified. */
+ dependency_test_notification.notify (1);
+}
+
+static void
+test_dependency ()
+{
+ /* Run dependency tests with different insertion orders. */
+ run_dependency_test ({0, 1, 2, 3, 4, 5, 6, 7});
+ run_dependency_test ({7, 6, 5, 4, 3, 2, 1, 0});
+ run_dependency_test ({0, 3, 2, 1, 7, 6, 4, 5});
+}
+
static void
run_tests ()
{
{
selftests::register_test ("gdb::observers",
selftests::observers::run_tests);
+ selftests::register_test ("gdb::observers dependency",
+ selftests::observers::test_dependency);
}
+2021-04-27 Michael Weghorn <m.weghorn@posteo.de>
+ Simon Marchi <simon.marchi@polymtl.ca>
+
+ * observable.h (class observable): Extend to allow specifying
+ dependencies between observers, keep vector holding observers
+ sorted so that dependencies are notified before observers
+ depending on them.
+
2021-04-24 Simon Marchi <simon.marchi@polymtl.ca>
* observable.h (observer_debug_printf,
private:
struct observer
{
- observer (const struct token *token, func_type func, const char *name)
- : token (token), func (func), name (name)
+ observer (const struct token *token, func_type func, const char *name,
+ const std::vector<const struct token *> &dependencies)
+ : token (token), func (func), name (name), dependencies (dependencies)
{}
const struct token *token;
func_type func;
const char *name;
+ std::vector<const struct token *> dependencies;
};
public:
DISABLE_COPY_AND_ASSIGN (observable);
- /* Attach F as an observer to this observable. F cannot be
- detached.
+ /* Attach F as an observer to this observable. F cannot be detached or
+ specified as a dependency.
+
+ DEPENDENCIES is a list of tokens of observers to be notified before this
+ one.
NAME is the name of the observer, used for debug output purposes. Its
lifetime must be at least as long as the observer is attached. */
- void attach (const func_type &f, const char *name)
+ void attach (const func_type &f, const char *name,
+ const std::vector<const struct token *> &dependencies = {})
{
- observer_debug_printf ("Attaching observable %s to observer %s",
- name, m_name);
-
- m_observers.emplace_back (nullptr, f, name);
+ attach (f, nullptr, name, dependencies);
}
- /* Attach F as an observer to this observable. T is a reference to
- a token that can be used to later remove F.
+ /* Attach F as an observer to this observable.
+
+ T is a reference to a token that can be used to later remove F or specify F
+ as a dependency of another observer.
+
+ DEPENDENCIES is a list of tokens of observers to be notified before this
+ one.
NAME is the name of the observer, used for debug output purposes. Its
lifetime must be at least as long as the observer is attached. */
- void attach (const func_type &f, const token &t, const char *name)
+ void attach (const func_type &f, const token &t, const char *name,
+ const std::vector<const struct token *> &dependencies = {})
{
- observer_debug_printf ("Attaching observable %s to observer %s",
- name, m_name);
-
- m_observers.emplace_back (&t, f, name);
+ attach (f, &t, name, dependencies);
}
/* Remove observers associated with T from this observable. T is
std::vector<observer> m_observers;
const char *m_name;
+
+ /* Use for sorting algorithm, to indicate which observer we have visited. */
+ enum class visit_state
+ {
+ NOT_VISITED,
+ VISITING,
+ VISITED,
+ };
+
+ /* Helper method for topological sort using depth-first search algorithm.
+
+ Visit all dependencies of observer at INDEX in M_OBSERVERS (later referred
+ to as "the observer"). Then append the observer to SORTED_OBSERVERS.
+
+ If the observer is already visited, do nothing. */
+ void visit_for_sorting (std::vector<observer> &sorted_observers,
+ std::vector<visit_state> &visit_states, int index)
+ {
+ if (visit_states[index] == visit_state::VISITED)
+ return;
+
+ /* If we are already visiting this observer, it means there's a cycle. */
+ gdb_assert (visit_states[index] != visit_state::VISITING);
+
+ visit_states[index] = visit_state::VISITING;
+
+ /* For each dependency of this observer... */
+ for (const token *dep : m_observers[index].dependencies)
+ {
+ /* ... find the observer that has token DEP. If found, visit it. */
+ auto it_dep
+ = std::find_if (m_observers.begin (), m_observers.end (),
+ [&] (observer o) { return o.token == dep; });
+ if (it_dep != m_observers.end ())
+ {
+ int i = std::distance (m_observers.begin (), it_dep);
+ visit_for_sorting (sorted_observers, visit_states, i);
+ }
+ }
+
+ visit_states[index] = visit_state::VISITED;
+ sorted_observers.push_back (m_observers[index]);
+ }
+
+ /* Sort the observers, so that dependencies come before observers
+ depending on them.
+
+ Uses depth-first search algorithm for topological sorting, see
+ https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search . */
+ void sort_observers ()
+ {
+ std::vector<observer> sorted_observers;
+ std::vector<visit_state> visit_states (m_observers.size (),
+ visit_state::NOT_VISITED);
+
+ for (size_t i = 0; i < m_observers.size (); i++)
+ visit_for_sorting (sorted_observers, visit_states, i);
+
+ m_observers = std::move (sorted_observers);
+ }
+
+ void attach (const func_type &f, const token *t, const char *name,
+ const std::vector<const struct token *> &dependencies)
+ {
+
+ observer_debug_printf ("Attaching observable %s to observer %s",
+ name, m_name);
+
+ m_observers.emplace_back (t, f, name, dependencies);
+
+ /* The observer has been inserted at the end of the vector, so it will be
+ after any of its potential dependencies attached earlier. If the
+ observer has a token, it means that other observers can specify it as
+ a dependency, so sorting is necessary to ensure those will be after the
+ newly inserted observer afterwards. */
+ if (t != nullptr)
+ sort_observers ();
+ };
};
} /* namespace observers */