diff --git a/changes/ticket26063 b/changes/ticket26063 new file mode 100644 index 0000000000..c11eb2d15c --- /dev/null +++ b/changes/ticket26063 @@ -0,0 +1,5 @@ + o Major features (CPU usage, mobile): + - When Tor is disabled (via DisableNetwork or via hibernation), it + no longer needs to run any per-second events. This change should + make it easier for mobile applications to disable Tor while the + device is sleeping, or Tor is not running. Closes ticket 26063. diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index fa00fb836b..56ee682fde 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -253,10 +253,39 @@ periodic_timer_new(struct event_base *base, } timer->cb = cb; timer->data = data; - event_add(timer->ev, (struct timeval *)tv); /*drop const for old libevent*/ + periodic_timer_launch(timer, tv); return timer; } +/** + * Launch the timer timer to run at tv from now, and every + * tv thereafter. + * + * If the timer is already enabled, this function does nothing. + */ +void +periodic_timer_launch(periodic_timer_t *timer, const struct timeval *tv) +{ + tor_assert(timer); + if (event_pending(timer->ev, EV_TIMEOUT, NULL)) + return; + event_add(timer->ev, tv); +} + +/** + * Disable the provided timer, but do not free it. + * + * You can reenable the same timer later with periodic_timer_launch. + * + * If the timer is already disabled, this function does nothing. + */ +void +periodic_timer_disable(periodic_timer_t *timer) +{ + tor_assert(timer); + event_del(timer->ev); +} + /** Stop and free a periodic timer */ void periodic_timer_free_(periodic_timer_t *timer) diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index e2747860a9..286a268122 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -31,6 +31,8 @@ periodic_timer_t *periodic_timer_new(struct event_base *base, void (*cb)(periodic_timer_t *timer, void *data), void *data); void periodic_timer_free_(periodic_timer_t *); +void periodic_timer_launch(periodic_timer_t *, const struct timeval *tv); +void periodic_timer_disable(periodic_timer_t *); #define periodic_timer_free(t) \ FREE_AND_NULL(periodic_timer_t, periodic_timer_free_, (t)) diff --git a/src/or/config.c b/src/or/config.c index 5e84ec1d61..94a58f3488 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1449,9 +1449,9 @@ options_act_reversible(const or_options_t *old_options, char **msg) consider_hibernation(time(NULL)); /* Launch the listeners. (We do this before we setuid, so we can bind to - * ports under 1024.) We don't want to rebind if we're hibernating. If - * networking is disabled, this will close all but the control listeners, - * but disable those. */ + * ports under 1024.) We don't want to rebind if we're hibernating or + * shutting down. If networking is disabled, this will close all but the + * control listeners, but disable those. */ if (!we_are_hibernating()) { if (retry_all_listeners(replaced_listeners, new_listeners, options->DisableNetwork) < 0) { @@ -2001,6 +2001,9 @@ options_act(const or_options_t *old_options) finish_daemon(options->DataDirectory); } + /* See whether we need to enable/disable our once-a-second timer. */ + reschedule_per_second_timer(); + /* We want to reinit keys as needed before we do much of anything else: keys are important, and other things can depend on them. */ if (transition_affects_workers || diff --git a/src/or/connection.c b/src/or/connection.c index 1b15260801..3462dbeac2 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1762,13 +1762,13 @@ connection_connect_sockaddr,(connection_t *conn, tor_assert(sa); tor_assert(socket_error); - if (get_options()->DisableNetwork) { - /* We should never even try to connect anyplace if DisableNetwork is set. - * Warn if we do, and refuse to make the connection. + if (net_is_completely_disabled()) { + /* We should never even try to connect anyplace if the network is + * completely shut off. * - * We only check DisableNetwork here, not we_are_hibernating(), since - * we'll still try to fulfill client requests sometimes in the latter case - * (if it is soft hibernation) */ + * (We don't check net_is_disabled() here, since we still sometimes + * want to open connections when we're in soft hibernation.) + */ static ratelim_t disablenet_violated = RATELIM_INIT(30*60); *socket_error = SOCK_ERRNO(ENETUNREACH); log_fn_ratelim(&disablenet_violated, LOG_WARN, LD_BUG, diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 5ae1538bfe..28e18aa853 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3537,6 +3537,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) n_stream->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; /* default to failed, change in dns_resolve if it turns out not to fail */ + /* If we're hibernating or shutting down, we refuse to open new streams. */ if (we_are_hibernating()) { relay_send_end_cell_from_edge(rh.stream_id, circ, END_STREAM_REASON_HIBERNATING, NULL); diff --git a/src/or/control.c b/src/or/control.c index 93b204f9a3..237a8572d4 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -1,3 +1,4 @@ + /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ @@ -112,6 +113,10 @@ static int disable_log_messages = 0; #define EVENT_IS_INTERESTING(e) \ (!! (global_event_mask & EVENT_MASK_(e))) +/** Macro: true if any event from the bitfield 'e' is interesting. */ +#define ANY_EVENT_IS_INTERESTING(e) \ + EVENT_IS_INTERESTING(e) + /** If we're using cookie-type authentication, how long should our cookies be? */ #define AUTHENTICATION_COOKIE_LEN 32 @@ -219,6 +224,7 @@ static void set_cached_network_liveness(int liveness); static void flush_queued_events_cb(mainloop_event_t *event, void *arg); static char * download_status_to_string(const download_status_t *dl); +static void control_get_bytes_rw_last_sec(uint64_t *r, uint64_t *w); /** Given a control event code for a message event, return the corresponding * log severity. */ @@ -271,6 +277,7 @@ control_update_global_event_mask(void) smartlist_t *conns = get_connection_array(); event_mask_t old_mask, new_mask; old_mask = global_event_mask; + int any_old_per_sec_events = control_any_per_second_event_enabled(); global_event_mask = 0; SMARTLIST_FOREACH(conns, connection_t *, _conn, @@ -288,10 +295,13 @@ control_update_global_event_mask(void) * we want to hear...*/ control_adjust_event_log_severity(); + /* Macro: true if ev was false before and is true now. */ +#define NEWLY_ENABLED(ev) \ + (! (old_mask & (ev)) && (new_mask & (ev))) + /* ...then, if we've started logging stream or circ bw, clear the * appropriate fields. */ - if (! (old_mask & EVENT_STREAM_BANDWIDTH_USED) && - (new_mask & EVENT_STREAM_BANDWIDTH_USED)) { + if (NEWLY_ENABLED(EVENT_STREAM_BANDWIDTH_USED)) { SMARTLIST_FOREACH(conns, connection_t *, conn, { if (conn->type == CONN_TYPE_AP) { @@ -300,10 +310,18 @@ control_update_global_event_mask(void) } }); } - if (! (old_mask & EVENT_CIRC_BANDWIDTH_USED) && - (new_mask & EVENT_CIRC_BANDWIDTH_USED)) { + if (NEWLY_ENABLED(EVENT_CIRC_BANDWIDTH_USED)) { clear_circ_bw_fields(); } + if (NEWLY_ENABLED(EVENT_BANDWIDTH_USED)) { + uint64_t r, w; + control_get_bytes_rw_last_sec(&r, &w); + } + if (any_old_per_sec_events != control_any_per_second_event_enabled()) { + reschedule_per_second_timer(); + } + +#undef NEWLY_ENABLED } /** Adjust the log severities that result in control_event_logmsg being called @@ -352,6 +370,65 @@ control_event_is_interesting(int event) return EVENT_IS_INTERESTING(event); } +/** Return true if any event that needs to fire once a second is enabled. */ +int +control_any_per_second_event_enabled(void) +{ + return ANY_EVENT_IS_INTERESTING( + EVENT_BANDWIDTH_USED | + EVENT_CELL_STATS | + EVENT_CIRC_BANDWIDTH_USED | + EVENT_CONN_BW | + EVENT_STREAM_BANDWIDTH_USED + ); +} + +/* The value of 'get_bytes_read()' the previous time that + * control_get_bytes_rw_last_sec() as called. */ +static uint64_t stats_prev_n_read = 0; +/* The value of 'get_bytes_written()' the previous time that + * control_get_bytes_rw_last_sec() as called. */ +static uint64_t stats_prev_n_written = 0; + +/** + * Set n_read and n_written to the total number of bytes read + * and written by Tor since the last call to this function. + * + * Call this only from the main thread. + */ +static void +control_get_bytes_rw_last_sec(uint64_t *n_read, + uint64_t *n_written) +{ + const uint64_t stats_n_bytes_read = get_bytes_read(); + const uint64_t stats_n_bytes_written = get_bytes_written(); + + *n_read = stats_n_bytes_read - stats_prev_n_read; + *n_written = stats_n_bytes_written - stats_prev_n_written; + stats_prev_n_read = stats_n_bytes_read; + stats_prev_n_written = stats_n_bytes_written; +} + +/** + * Run all the controller events (if any) that are scheduled to trigger once + * per second. + */ +void +control_per_second_events(void) +{ + if (!control_any_per_second_event_enabled()) + return; + + uint64_t bytes_read, bytes_written; + control_get_bytes_rw_last_sec(&bytes_read, &bytes_written); + control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written); + + control_event_stream_bandwidth_used(); + control_event_conn_bandwidth_used(); + control_event_circ_bandwidth_used(); + control_event_circuit_cell_stats(); +} + /** Append a NUL-terminated string s to the end of * conn-\>outbuf. */ @@ -7035,6 +7112,8 @@ control_event_bootstrap_problem(const char *warn, const char *reason, if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD) dowarn = 1; + /* Don't warn about our bootstrapping status if we are hibernating or + * shutting down. */ if (we_are_hibernating()) dowarn = 0; @@ -7606,6 +7685,8 @@ control_free_all(void) { smartlist_t *queued_events = NULL; + stats_prev_n_read = stats_prev_n_written = 0; + if (authentication_cookie) /* Free the auth cookie */ tor_free(authentication_cookie); if (detached_onion_services) { /* Free the detached onion services */ diff --git a/src/or/control.h b/src/or/control.h index 7f8a0bdb5f..92cbf866dd 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -40,6 +40,9 @@ int connection_control_process_inbuf(control_connection_t *conn); #define EVENT_NS 0x000F int control_event_is_interesting(int event); +void control_per_second_events(void); +int control_any_per_second_event_enabled(void); + int control_event_circuit_status(origin_circuit_t *circ, circuit_status_event_t e, int reason); int control_event_circuit_purpose_changed(origin_circuit_t *circ, diff --git a/src/or/dirserv.c b/src/or/dirserv.c index bfcec6e105..c01234e0b9 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -955,7 +955,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) tor_assert(node); if (router_is_me(router)) { - /* We always know if we are down ourselves. */ + /* We always know if we are shutting down or hibernating ourselves. */ answer = ! we_are_hibernating(); } else if (router->is_hibernating && (router->cache_info.published_on + diff --git a/src/or/hibernate.c b/src/or/hibernate.c index b83167d20c..d7d259470f 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -883,13 +883,26 @@ hibernate_begin_shutdown(void) hibernate_begin(HIBERNATE_STATE_EXITING, time(NULL)); } -/** Return true iff we are currently hibernating. */ +/** + * Return true iff we are currently hibernating -- that is, if we are in + * any non-live state. + */ MOCK_IMPL(int, we_are_hibernating,(void)) { return hibernate_state != HIBERNATE_STATE_LIVE; } +/** + * Return true iff we are currently _fully_ hibernating -- that is, if we are + * in a state where we expect to handle no network activity at all. + */ +MOCK_IMPL(int, +we_are_fully_hibernating,(void)) +{ + return hibernate_state == HIBERNATE_STATE_DORMANT; +} + /** If we aren't currently dormant, close all connections and become * dormant. */ static void @@ -1187,6 +1200,8 @@ on_hibernate_state_change(hibernate_state_t prev_state) if (prev_state != HIBERNATE_STATE_INITIAL) { rescan_periodic_events(get_options()); } + + reschedule_per_second_timer(); } /** Free all resources held by the accounting module */ diff --git a/src/or/hibernate.h b/src/or/hibernate.h index 4bbda66940..453969d052 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -25,6 +25,7 @@ void accounting_add_bytes(size_t n_read, size_t n_written, int seconds); int accounting_record_bandwidth_usage(time_t now, or_state_t *state); void hibernate_begin_shutdown(void); MOCK_DECL(int, we_are_hibernating, (void)); +MOCK_DECL(int, we_are_fully_hibernating,(void)); void consider_hibernation(time_t now); int getinfo_helper_accounting(control_connection_t *conn, const char *question, char **answer, diff --git a/src/or/main.c b/src/or/main.c index 69e7f05b14..6cb9b62496 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -163,11 +163,6 @@ token_bucket_rw_t global_bucket; /* Token bucket for relayed traffic. */ token_bucket_rw_t global_relayed_bucket; -/* DOCDOC stats_prev_n_read */ -static uint64_t stats_prev_n_read = 0; -/* DOCDOC stats_prev_n_written */ -static uint64_t stats_prev_n_written = 0; - /* XXX we might want to keep stats about global_relayed_*_bucket too. Or not.*/ /** How many bytes have we read since we started the process? */ static uint64_t stats_n_bytes_read = 0; @@ -1258,7 +1253,8 @@ run_connection_housekeeping(int i, time_t now) } else if (we_are_hibernating() && ! have_any_circuits && !connection_get_outbuf_len(conn)) { - /* We're hibernating, there's no circuits, and nothing to flush.*/ + /* We're hibernating or shutting down, there's no circuits, and nothing to + * flush.*/ log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) " "[Hibernating or exiting].", (int)conn->s,conn->address, conn->port); @@ -2495,6 +2491,36 @@ hs_service_callback(time_t now, const or_options_t *options) /** Timer: used to invoke second_elapsed_callback() once per second. */ static periodic_timer_t *second_timer = NULL; + +/** + * Enable or disable the per-second timer as appropriate, creating it if + * necessary. + */ +void +reschedule_per_second_timer(void) +{ + struct timeval one_second; + one_second.tv_sec = 1; + one_second.tv_usec = 0; + + if (! second_timer) { + second_timer = periodic_timer_new(tor_libevent_get_base(), + &one_second, + second_elapsed_callback, + NULL); + tor_assert(second_timer); + } + + const bool run_per_second_events = + control_any_per_second_event_enabled() || ! net_is_completely_disabled(); + + if (run_per_second_events) { + periodic_timer_launch(second_timer, &one_second); + } else { + periodic_timer_disable(second_timer); + } +} + /** Last time that update_current_time was called. */ static time_t current_second = 0; /** Last time that update_current_time updated current_second. */ @@ -2568,8 +2594,6 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) * could use Libevent's timers for this rather than checking the current * time against a bunch of timeouts every second. */ time_t now; - size_t bytes_written; - size_t bytes_read; (void)timer; (void)arg; @@ -2581,18 +2605,8 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) */ update_current_time(now); - /* the second has rolled over. check more stuff. */ - // remove this once it's unneeded - bytes_read = (size_t)(stats_n_bytes_read - stats_prev_n_read); - bytes_written = (size_t)(stats_n_bytes_written - stats_prev_n_written); - stats_prev_n_read = stats_n_bytes_read; - stats_prev_n_written = stats_n_bytes_written; - - control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written); - control_event_stream_bandwidth_used(); - control_event_conn_bandwidth_used(); - control_event_circ_bandwidth_used(); - control_event_circuit_cell_stats(); + /* Maybe some controller events are ready to fire */ + control_per_second_events(); run_scheduled_events(now); } @@ -2872,17 +2886,7 @@ do_main_loop(void) } /* set up once-a-second callback. */ - if (! second_timer) { - struct timeval one_second; - one_second.tv_sec = 1; - one_second.tv_usec = 0; - - second_timer = periodic_timer_new(tor_libevent_get_base(), - &one_second, - second_elapsed_callback, - NULL); - tor_assert(second_timer); - } + reschedule_per_second_timer(); #ifdef HAVE_SYSTEMD_209 uint64_t watchdog_delay; @@ -3697,7 +3701,6 @@ tor_free_all(int postfork) memset(&global_bucket, 0, sizeof(global_bucket)); memset(&global_relayed_bucket, 0, sizeof(global_relayed_bucket)); - stats_prev_n_read = stats_prev_n_written = 0; stats_n_bytes_read = stats_n_bytes_written = 0; time_of_process_start = 0; time_of_last_signewnym = 0; diff --git a/src/or/main.h b/src/or/main.h index 45d6b4478f..9dbbc6e5ee 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -94,6 +94,7 @@ uint64_t get_main_loop_error_count(void); uint64_t get_main_loop_idle_count(void); void periodic_events_on_new_options(const or_options_t *options); +void reschedule_per_second_timer(void); extern time_t time_of_process_start; extern int quiet_level; diff --git a/src/or/router.c b/src/or/router.c index 996a28a91f..07abf1f8d5 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -1599,15 +1599,24 @@ router_perform_bandwidth_test(int num_circs, time_t now) } } -/** Return true iff our network is in some sense disabled: either we're - * hibernating, entering hibernation, or the network is turned off with - * DisableNetwork. */ +/** Return true iff our network is in some sense disabled or shutting down: + * either we're hibernating, entering hibernation, or the network is turned + * off with DisableNetwork. */ int net_is_disabled(void) { return get_options()->DisableNetwork || we_are_hibernating(); } +/** Return true iff our network is in some sense "completely disabled" either + * we're fully hibernating or the network is turned off with + * DisableNetwork. */ +int +net_is_completely_disabled(void) +{ + return get_options()->DisableNetwork || we_are_fully_hibernating(); +} + /** Return true iff we believe ourselves to be an authoritative * directory server. */ @@ -2268,6 +2277,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) /* and compute ri->bandwidthburst similarly */ ri->bandwidthburst = get_effective_bwburst(options); + /* Report bandwidth, unless we're hibernating or shutting down */ ri->bandwidthcapacity = hibernating ? 0 : rep_hist_bandwidth_assess(); if (dns_seems_to_be_broken() || has_dns_init_failed()) { @@ -2538,6 +2548,8 @@ check_descriptor_bandwidth_changed(time_t now) return; prev = router_get_my_routerinfo()->bandwidthcapacity; + /* Consider ourselves to have zero bandwidth if we're hibernating or + * shutting down. */ cur = we_are_hibernating() ? 0 : rep_hist_bandwidth_assess(); if ((prev != cur && (!prev || !cur)) || cur > prev*2 || diff --git a/src/or/router.h b/src/or/router.h index 03eca9c65d..0db2c1cfb2 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -53,6 +53,7 @@ void router_dirport_found_reachable(void); void router_perform_bandwidth_test(int num_circs, time_t now); int net_is_disabled(void); +int net_is_completely_disabled(void); int authdir_mode(const or_options_t *options); int authdir_mode_handles_descs(const or_options_t *options, int purpose);