aboutsummaryrefslogtreecommitdiff
path: root/src/core/mainloop/periodic.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/mainloop/periodic.c')
-rw-r--r--src/core/mainloop/periodic.c223
1 files changed, 206 insertions, 17 deletions
diff --git a/src/core/mainloop/periodic.c b/src/core/mainloop/periodic.c
index 2651bbbc89..b5fd8fab61 100644
--- a/src/core/mainloop/periodic.c
+++ b/src/core/mainloop/periodic.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2019, The Tor Project, Inc. */
+/* Copyright (c) 2015-2020, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -6,9 +6,22 @@
*
* \brief Generic backend for handling periodic events.
*
- * The events in this module are used by main.c to track items that need
+ * The events in this module are used to track items that need
* to fire once every N seconds, possibly picking a new interval each time
- * that they fire. See periodic_events[] in main.c for examples.
+ * that they fire. See periodic_events[] in mainloop.c for examples.
+ *
+ * This module manages a global list of periodic_event_item_t objects,
+ * each corresponding to a single event. To register an event, pass it to
+ * periodic_events_register() when initializing your subsystem.
+ *
+ * Registering an event makes the periodic event subsystem know about it, but
+ * doesn't cause the event to get created immediately. Before the event can
+ * be started, periodic_event_connect_all() must be called by mainloop.c to
+ * connect all the events to Libevent.
+ *
+ * We expect that periodic_event_item_t objects will be statically allocated;
+ * we set them up and tear them down here, but we don't take ownership of
+ * them.
*/
#include "core/or/or.h"
@@ -16,7 +29,6 @@
#include "app/config/config.h"
#include "core/mainloop/mainloop.h"
#include "core/mainloop/periodic.h"
-#include "lib/evloop/compat_libevent.h"
/** We disable any interval greater than this number of seconds, on the
* grounds that it is probably an absolute time mistakenly passed in as a
@@ -24,6 +36,12 @@
*/
static const int MAX_INTERVAL = 10 * 365 * 86400;
+/**
+ * Global list of periodic events that have been registered with
+ * <b>periodic_event_register</b>.
+ **/
+static smartlist_t *the_periodic_events = NULL;
+
/** Set the event <b>event</b> to run in <b>next_interval</b> seconds from
* now. */
static void
@@ -45,10 +63,6 @@ periodic_event_dispatch(mainloop_event_t *ev, void *data)
periodic_event_item_t *event = data;
tor_assert(ev == event->ev);
- if (BUG(!periodic_event_is_enabled(event))) {
- return;
- }
-
time_t now = time(NULL);
update_current_time(now);
const or_options_t *options = get_options();
@@ -57,7 +71,7 @@ periodic_event_dispatch(mainloop_event_t *ev, void *data)
int next_interval = 0;
if (!periodic_event_is_enabled(event)) {
- /* The event got disabled from inside its callback; no need to
+ /* The event got disabled from inside its callback, or before: no need to
* reschedule. */
return;
}
@@ -91,15 +105,16 @@ periodic_event_dispatch(mainloop_event_t *ev, void *data)
void
periodic_event_reschedule(periodic_event_item_t *event)
{
- /* Don't reschedule a disabled event. */
- if (periodic_event_is_enabled(event)) {
+ /* Don't reschedule a disabled or uninitialized event. */
+ if (event->ev && periodic_event_is_enabled(event)) {
periodic_event_set_interval(event, 1);
}
}
-/** Initializes the libevent backend for a periodic event. */
+/** Connects a periodic event to the Libevent backend. Does not launch the
+ * event immediately. */
void
-periodic_event_setup(periodic_event_item_t *event)
+periodic_event_connect(periodic_event_item_t *event)
{
if (event->ev) { /* Already setup? This is a bug */
log_err(LD_BUG, "Initial dispatch should only be done once.");
@@ -117,7 +132,7 @@ void
periodic_event_launch(periodic_event_item_t *event)
{
if (! event->ev) { /* Not setup? This is a bug */
- log_err(LD_BUG, "periodic_event_launch without periodic_event_setup");
+ log_err(LD_BUG, "periodic_event_launch without periodic_event_connect");
tor_assert(0);
}
/* Event already enabled? This is a bug */
@@ -131,9 +146,9 @@ periodic_event_launch(periodic_event_item_t *event)
periodic_event_dispatch(event->ev, event);
}
-/** Release all storage associated with <b>event</b> */
-void
-periodic_event_destroy(periodic_event_item_t *event)
+/** Disconnect and unregister the periodic event in <b>event</b> */
+static void
+periodic_event_disconnect(periodic_event_item_t *event)
{
if (!event)
return;
@@ -177,3 +192,177 @@ periodic_event_disable(periodic_event_item_t *event)
mainloop_event_cancel(event->ev);
event->enabled = 0;
}
+
+/**
+ * Disable an event, then schedule it to run once.
+ * Do nothing if the event was already disabled.
+ */
+void
+periodic_event_schedule_and_disable(periodic_event_item_t *event)
+{
+ tor_assert(event);
+ if (!periodic_event_is_enabled(event))
+ return;
+
+ periodic_event_disable(event);
+
+ mainloop_event_activate(event->ev);
+}
+
+/**
+ * Add <b>item</b> to the list of periodic events.
+ *
+ * Note that <b>item</b> should be statically allocated: we do not
+ * take ownership of it.
+ **/
+void
+periodic_events_register(periodic_event_item_t *item)
+{
+ if (!the_periodic_events)
+ the_periodic_events = smartlist_new();
+
+ if (BUG(smartlist_contains(the_periodic_events, item)))
+ return;
+
+ smartlist_add(the_periodic_events, item);
+}
+
+/**
+ * Make all registered periodic events connect to the libevent backend.
+ */
+void
+periodic_events_connect_all(void)
+{
+ if (! the_periodic_events)
+ return;
+
+ SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) {
+ if (item->ev)
+ continue;
+ periodic_event_connect(item);
+ } SMARTLIST_FOREACH_END(item);
+}
+
+/**
+ * Reset all the registered periodic events so we'll do all our actions again
+ * as if we just started up.
+ *
+ * Useful if our clock just moved back a long time from the future,
+ * so we don't wait until that future arrives again before acting.
+ */
+void
+periodic_events_reset_all(void)
+{
+ if (! the_periodic_events)
+ return;
+
+ SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) {
+ if (!item->ev)
+ continue;
+
+ periodic_event_reschedule(item);
+ } SMARTLIST_FOREACH_END(item);
+}
+
+/**
+ * Return the registered periodic event whose name is <b>name</b>.
+ * Return NULL if no such event is found.
+ */
+periodic_event_item_t *
+periodic_events_find(const char *name)
+{
+ if (! the_periodic_events)
+ return NULL;
+
+ SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) {
+ if (strcmp(name, item->name) == 0)
+ return item;
+ } SMARTLIST_FOREACH_END(item);
+ return NULL;
+}
+
+/**
+ * Start or stop registered periodic events, depending on our current set of
+ * roles.
+ *
+ * Invoked when our list of roles, or the net_disabled flag has changed.
+ **/
+void
+periodic_events_rescan_by_roles(int roles, bool net_disabled)
+{
+ if (! the_periodic_events)
+ return;
+
+ SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) {
+ if (!item->ev)
+ continue;
+
+ int enable = !!(item->roles & roles);
+
+ /* Handle the event flags. */
+ if (net_disabled &&
+ (item->flags & PERIODIC_EVENT_FLAG_NEED_NET)) {
+ enable = 0;
+ }
+
+ /* Enable the event if needed. It is safe to enable an event that was
+ * already enabled. Same goes for disabling it. */
+ if (enable) {
+ log_debug(LD_GENERAL, "Launching periodic event %s", item->name);
+ periodic_event_enable(item);
+ } else {
+ log_debug(LD_GENERAL, "Disabling periodic event %s", item->name);
+ if (item->flags & PERIODIC_EVENT_FLAG_RUN_ON_DISABLE) {
+ periodic_event_schedule_and_disable(item);
+ } else {
+ periodic_event_disable(item);
+ }
+ }
+ } SMARTLIST_FOREACH_END(item);
+}
+
+/**
+ * Invoked at shutdown: disconnect and unregister all periodic events.
+ *
+ * Does not free the periodic_event_item_t object themselves, because we do
+ * not own them.
+ */
+void
+periodic_events_disconnect_all(void)
+{
+ if (! the_periodic_events)
+ return;
+
+ SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) {
+ periodic_event_disconnect(item);
+ } SMARTLIST_FOREACH_END(item);
+
+ smartlist_free(the_periodic_events);
+}
+
+#define LONGEST_TIMER_PERIOD (30 * 86400)
+/** Helper: Return the number of seconds between <b>now</b> and <b>next</b>,
+ * clipped to the range [1 second, LONGEST_TIMER_PERIOD].
+ *
+ * We use this to answer the question, "how many seconds is it from now until
+ * next" in periodic timer callbacks. Don't use it for other purposes
+ **/
+int
+safe_timer_diff(time_t now, time_t next)
+{
+ if (next > now) {
+ /* There were no computers at signed TIME_MIN (1902 on 32-bit systems),
+ * and nothing that could run Tor. It's a bug if 'next' is around then.
+ * On 64-bit systems with signed TIME_MIN, TIME_MIN is before the Big
+ * Bang. We cannot extrapolate past a singularity, but there was probably
+ * nothing that could run Tor then, either.
+ **/
+ tor_assert(next > TIME_MIN + LONGEST_TIMER_PERIOD);
+
+ if (next - LONGEST_TIMER_PERIOD > now)
+ return LONGEST_TIMER_PERIOD;
+ return (int)(next - now);
+ } else {
+ return 1;
+ }
+}