diff --git a/changes/ticket26064 b/changes/ticket26064 new file mode 100644 index 000000000..6efb8a483 --- /dev/null +++ b/changes/ticket26064 @@ -0,0 +1,5 @@ + o Minor features (accounting): + - When we become dormant, use a scheduled event to wake up at the right + time. Previously, we would use the per-second timer to check whether + to wake up, but we no longer have any per-second timers enabled when + the network is disabled. Closes ticket 26064. diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 98f32adb1..b83167d20 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -52,6 +52,10 @@ static time_t hibernate_end_time = 0; * we aren't shutting down. */ static time_t shutdown_time = 0; +/** A timed event that we'll use when it's time to wake up from + * hibernation. */ +static mainloop_event_t *wakeup_event = NULL; + /** Possible accounting periods. */ typedef enum { UNIT_MONTH=1, UNIT_WEEK=2, UNIT_DAY=3, @@ -131,6 +135,8 @@ static time_t start_of_accounting_period_after(time_t now); static time_t start_of_accounting_period_containing(time_t now); static void accounting_set_wakeup_time(void); static void on_hibernate_state_change(hibernate_state_t prev_state); +static void hibernate_schedule_wakeup_event(time_t now, time_t end_time); +static void wakeup_event_callback(mainloop_event_t *ev, void *data); /** * Return the human-readable name for the hibernation state state @@ -936,6 +942,63 @@ hibernate_go_dormant(time_t now) or_state_mark_dirty(get_or_state(), get_options()->AvoidDiskWrites ? now+600 : 0); + + hibernate_schedule_wakeup_event(now, hibernate_end_time); +} + +/** + * Schedule a mainloop event at end_time to wake up from a dormant + * state. We can't rely on this happening from second_elapsed_callback, + * since second_elapsed_callback will be shut down when we're dormant. + * + * (Note that We might immediately go back to sleep after we set the next + * wakeup time.) + */ +static void +hibernate_schedule_wakeup_event(time_t now, time_t end_time) +{ + struct timeval delay = { 0, 0 }; + + if (now >= end_time) { + // In these cases we always wait at least a second, to avoid running + // the callback in a tight loop. + delay.tv_sec = 1; + } else { + delay.tv_sec = (end_time - now); + } + + if (!wakeup_event) { + wakeup_event = mainloop_event_postloop_new(wakeup_event_callback, NULL); + } + + mainloop_event_schedule(wakeup_event, &delay); +} + +/** + * Called at the end of the interval, or at the wakeup time of the current + * interval, to exit the dormant state. + **/ +static void +wakeup_event_callback(mainloop_event_t *ev, void *data) +{ + (void) ev; + (void) data; + + const time_t now = time(NULL); + accounting_run_housekeeping(now); + consider_hibernation(now); + if (hibernate_state != HIBERNATE_STATE_DORMANT) { + /* We woke up, so everything's great here */ + return; + } + + /* We're still dormant. */ + if (now < interval_wakeup_time) + hibernate_end_time = interval_wakeup_time; + else + hibernate_end_time = interval_end_time; + + hibernate_schedule_wakeup_event(now, hibernate_end_time); } /** Called when hibernate_end_time has arrived. */ @@ -1126,6 +1189,16 @@ on_hibernate_state_change(hibernate_state_t prev_state) } } +/** Free all resources held by the accounting module */ +void +accounting_free_all(void) +{ + mainloop_event_free(wakeup_event); + hibernate_state = HIBERNATE_STATE_INITIAL; + hibernate_end_time = 0; + shutdown_time = 0; +} + #ifdef TOR_UNIT_TESTS /** * Manually change the hibernation state. Private; used only by the unit diff --git a/src/or/hibernate.h b/src/or/hibernate.h index 85fb42864..4bbda6694 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -30,6 +30,7 @@ int getinfo_helper_accounting(control_connection_t *conn, const char *question, char **answer, const char **errmsg); uint64_t get_accounting_max_total(void); +void accounting_free_all(void); #ifdef HIBERNATE_PRIVATE /** Possible values of hibernate_state */ diff --git a/src/or/main.c b/src/or/main.c index e63843b63..69e7f05b1 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -3662,6 +3662,8 @@ tor_free_all(int postfork) hs_free_all(); dos_free_all(); circuitmux_ewma_free_all(); + accounting_free_all(); + if (!postfork) { config_free_all(); or_state_free_all();