diff options
author | Nick Mathewson <nickm@torproject.org> | 2004-11-07 23:14:47 +0000 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2004-11-07 23:14:47 +0000 |
commit | 2640be28facc260e5dcd3d215ec368c85909bc32 (patch) | |
tree | 5f9204466932366fd7c1e8d304947494815bb08c /src | |
parent | fc9c6e7c95ea2c8e8583e5fbbf02b5618fdb54ce (diff) | |
download | tor-2640be28facc260e5dcd3d215ec368c85909bc32.tar.gz tor-2640be28facc260e5dcd3d215ec368c85909bc32.zip |
Document hibernate.c. Also fix handling of corner case where we hit the end of the interval while we are in HIBERNATE_STATE_LOWBANDWIDTH
svn:r2709
Diffstat (limited to 'src')
-rw-r--r-- | src/or/hibernate.c | 199 |
1 files changed, 151 insertions, 48 deletions
diff --git a/src/or/hibernate.c b/src/or/hibernate.c index c616066ed3..7a983d81ee 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -31,7 +31,37 @@ hibernating, phase 2: #define SHUTDOWN_WAIT_LENGTH 30 /* seconds */ static int hibernate_state = HIBERNATE_STATE_LIVE; -static time_t hibernate_timeout = 0; +/** If are hibernating, when do we plan to wake up? Set to 0 if we + * aren't hibernating. */ +static time_t hibernate_end_time = 0; + +/* Fields for accounting logic. Accounting overview: + * + * Accounting is designed to ensure that more than N bytes are sent in + * either direction over a given interval (currently, one month, + * starting at 0:00 GMT an arbitrary date within the month). We could + * try to do this by choking our bandwidth to a trickle, but that + * would make our streams useless. Instead, we estimate what our + * bandwidth usage will be, and guess how long we'll be able to + * provide that much bandwidth before hitting our limit. We then + * choose a random time within the accounting interval to come up (so + * that we don't get 50 Tors running on the 1st of the month and none + * on the 30th). + * + * Each interval runs as follows: + * + * 1. We guess our bandwidth usage, based on how much we used + * last time. We choose a "wakeup time" within the interval to come up. + * 2. Until the chosen wakeup time, we hibernate. + * 3. We come up at the wakeup time, and provide bandwidth until we are + * "very close" to running out. + * 4. Then we go into low-bandwidth mode, and stop accepting new + * connections, but provide bandwidth until we run out. + * 5. Then we hibernate until the end of the interval. + * + * If the interval ends before we run out of bandwdith, we go back to + * step one. + */ /** How many bytes have we read/written in this accounting interval? */ static uint64_t n_bytes_read_in_interval = 0; @@ -58,6 +88,9 @@ static void accounting_set_wakeup_time(void); * Functions for bandwidth accounting. * ************/ +/** Called from main.c to tell us that <b>seconds</b> seconds have + * passed, <b>n_read</b> bytes have been read, and <b>n_written</b> + * bytes have been written. */ void accounting_add_bytes(size_t n_read, size_t n_written, int seconds) { @@ -68,16 +101,20 @@ accounting_add_bytes(size_t n_read, size_t n_written, int seconds) n_seconds_active_in_interval += (seconds < 10) ? seconds : 0; } +/** Increment the month field of <b>tm</b> by <b>delta</b> months. */ static INLINE void incr_month(struct tm *tm, unsigned int delta) { tm->tm_mon += delta; + /* officially, we don't have to do this, but some platforms are rumored + * to have broken implementations. */ while (tm->tm_mon > 11) { ++tm->tm_year; tm->tm_mon -= 12; } } +/** Decrement the month field of <b>tm</b> by <b>delta</b> months. */ static INLINE void decr_month(struct tm *tm, unsigned int delta) { @@ -88,6 +125,8 @@ decr_month(struct tm *tm, unsigned int delta) } } +/** Return the start of the accounting period that contains the time + * <b>now</b> */ static time_t start_of_accounting_period_containing(time_t now) { @@ -106,6 +145,10 @@ start_of_accounting_period_containing(time_t now) tm->tm_sec = 0; return tor_timegm(tm); } + + +/** Return the start of the accounting period that comes after the one + * containing the time <b>now</b>. */ static time_t start_of_accounting_period_after(time_t now) { @@ -118,15 +161,19 @@ start_of_accounting_period_after(time_t now) return tor_timegm(tm); } + +/** Initialize the accounting subsystem. */ void configure_accounting(time_t now) { + /* Try to remember our recorded usage. */ if (!interval_start_time) read_bandwidth_usage(); /* If we fail, we'll leave values at zero, and * reset below.*/ if (!interval_start_time || start_of_accounting_period_after(interval_start_time) <= now) { - /* We start a new interval. */ + /* We didn't have recorded usage, or we don't have recorded usage + * for this interval. Start a new interval. */ log_fn(LOG_INFO, "Starting new accounting interval."); reset_accounting(now); } if (interval_start_time == @@ -140,6 +187,9 @@ configure_accounting(time_t now) accounting_set_wakeup_time(); } +/** Set expected_bandwidth_usage based on how much we sent/received + * per minute last interval (if we were up for at least 30 minutes), + * or based on our declared bandwidth otherwise. */ static void update_expected_bandwidth(void) { @@ -158,6 +208,10 @@ update_expected_bandwidth(void) } } +/** Called at the start of a new accounting interval: reset our + * expected bandwidth usage based on what happened last time, set up + * the start and end of the interval, and clear byte/time totals. + */ static void reset_accounting(time_t now) { log_fn(LOG_INFO, "Starting new accounting interval."); @@ -169,7 +223,9 @@ reset_accounting(time_t now) { n_seconds_active_in_interval = 0; } -static INLINE int time_to_record_bandwidth_usage(time_t now) +/** Return true iff we should save our bandwidth usage to disk. */ +static INLINE int +time_to_record_bandwidth_usage(time_t now) { /* Note every 5 minutes */ #define NOTE_INTERVAL (5*60) @@ -205,6 +261,8 @@ accounting_run_housekeeping(time_t now) } } +/** Based on our interval and our estimated bandwidth, choose a + * deterministic (but random-ish) time to wake up. */ static void accounting_set_wakeup_time(void) { @@ -225,8 +283,8 @@ accounting_set_wakeup_time(void) crypto_digest_get_digest(d, digest, DIGEST_LEN); crypto_free_digest_env(d); - n_days_to_exhaust_bw = (get_options()->AccountingMaxKB/expected_bandwidth_usage) - /(24*60); + n_days_to_exhaust_bw = + (get_options()->AccountingMaxKB/expected_bandwidth_usage)/(24*60); tm = gmtime(&interval_start_time); if (++tm->tm_mon > 11) { tm->tm_mon = 0; ++tm->tm_year; } @@ -245,7 +303,10 @@ accounting_set_wakeup_time(void) } #define BW_ACCOUNTING_VERSION 1 -static int record_bandwidth_usage(time_t now) +/** Save all our bandwidth tracking information to disk. Return 0 on + * success, -1 on failure*/ +static int +record_bandwidth_usage(time_t now) { char buf[128]; char fname[512]; @@ -272,7 +333,10 @@ static int record_bandwidth_usage(time_t now) return write_str_to_file(fname, buf, 0); } -static int read_bandwidth_usage(void) +/** Read stored accounting information from disk. Return 0 on success; + * return -1 and change nothing on failure. */ +static int +read_bandwidth_usage(void) { char *s = NULL; char fname[512]; @@ -349,7 +413,10 @@ static int read_bandwidth_usage(void) return -1; } -static int hibernate_hard_limit_reached(void) +/** Return true iff we have sent/received all the bytes we are willing + * to send/receive this interval. */ +static int +hibernate_hard_limit_reached(void) { uint64_t hard_limit = get_options()->AccountingMaxKB<<10; if (!hard_limit) @@ -358,6 +425,8 @@ static int hibernate_hard_limit_reached(void) || n_bytes_written_in_interval >= hard_limit; } +/** Return true iff we have sent/received almost all the bytes we are willing + * to send/receive this interval. */ static int hibernate_soft_limit_reached(void) { uint64_t soft_limit = (uint64_t) ((get_options()->AccountingMaxKB<<10) * .99); @@ -367,8 +436,9 @@ static int hibernate_soft_limit_reached(void) || n_bytes_written_in_interval >= soft_limit; } -/** Called when we get a SIGINT, or when bandwidth soft limit - * is reached. */ +/** Called when we get a SIGINT, or when bandwidth soft limit is + * reached. Puts us into "loose hibernation": we don't accept new + * connections, but we continue handling old ones. */ static void hibernate_begin(int new_state, time_t now) { connection_t *conn; @@ -394,17 +464,18 @@ static void hibernate_begin(int new_state, time_t now) { if(new_state == HIBERNATE_STATE_EXITING) { log(LOG_NOTICE,"Interrupt: will shut down in %d seconds. Interrupt again to exit now.", SHUTDOWN_WAIT_LENGTH); - hibernate_timeout = time(NULL) + SHUTDOWN_WAIT_LENGTH; + hibernate_end_time = time(NULL) + SHUTDOWN_WAIT_LENGTH; } else { /* soft limit reached */ log_fn(LOG_NOTICE,"Bandwidth limit reached; beginning hibernation."); - hibernate_timeout = interval_end_time; + hibernate_end_time = interval_end_time; } hibernate_state = new_state; } /** Called when we've been hibernating and our timeout is reached. */ -static void hibernate_end(int new_state) { +static void +hibernate_end(int new_state) { tor_assert(hibernate_state == HIBERNATE_STATE_LOWBANDWIDTH || hibernate_state == HIBERNATE_STATE_DORMANT); @@ -413,28 +484,74 @@ static void hibernate_end(int new_state) { log_fn(LOG_NOTICE,"Hibernation period ended. Resuming normal activity."); hibernate_state = new_state; - hibernate_timeout = 0; /* no longer hibernating */ + hibernate_end_time = 0; /* no longer hibernating */ } /** A wrapper around hibernate_begin, for when we get SIGINT. */ -void hibernate_begin_shutdown(void) { +void +hibernate_begin_shutdown(void) { hibernate_begin(HIBERNATE_STATE_EXITING, time(NULL)); } -/** A wrapper to expose whether we're hibernating. */ -int we_are_hibernating(void) { +/** Return true iff we are currently hibernating. */ +int +we_are_hibernating(void) { return hibernate_state != HIBERNATE_STATE_LIVE; } -/** The big function. Consider our environment and decide if it's - * time to start/stop hibernating. +/** If we aren't currently dormant, close all connections and become + * dormant. */ +static void +hibernate_go_dormant(void) { + connection_t *conn; + + if (hibernate_state == HIBERNATE_STATE_DORMANT) + return; + + hibernate_state = HIBERNATE_STATE_DORMANT; + log_fn(LOG_NOTICE,"Going dormant. Blowing away remaining connections."); + + /* Close all OR/AP/exit conns. Leave dir conns. */ + /* XXXX Why leave dir cons? -NM */ + while((conn = connection_get_by_type(CONN_TYPE_OR)) || + (conn = connection_get_by_type(CONN_TYPE_AP)) || + (conn = connection_get_by_type(CONN_TYPE_EXIT))) { + log_fn(LOG_INFO,"Closing conn type %d", conn->type); + connection_mark_for_close(conn); + } +} + +/** Called when hibernate_end_time has arrived. */ +static void +hibernate_end_time_elapsed(time_t now) +{ + /* The interval has ended, or it is wakeup time. Find out which. */ + accounting_run_housekeeping(now); + if (interval_wakeup_time <= now) { + /* The interval hasn't changed, but interval_wakeup_time has passed. + * It's time to wake up and start being a server. */ + hibernate_end(HIBERNATE_STATE_LIVE); + return; + } else { + /* The interval has changed, and it isn't time to wake up yet. */ + hibernate_end_time = interval_wakeup_time; + if (hibernate_state != HIBERNATE_STATE_DORMANT) + /* We weren't sleeping before; we should sleep now. */ + hibernate_go_dormant(); + } +} + +/** The big function. Consider our environment and decide if it's time + * to start/stop hibernating. */ void consider_hibernation(time_t now) { connection_t *conn; + /* If we're in 'exiting' mode, then we just shutdown after the interval + * elapses. */ if (hibernate_state == HIBERNATE_STATE_EXITING) { - tor_assert(hibernate_timeout); - if(hibernate_timeout <= now) { + tor_assert(hibernate_end_time); + if(hibernate_end_time <= now) { log(LOG_NOTICE,"Clean shutdown finished. Exiting."); tor_cleanup(); exit(0); @@ -442,46 +559,32 @@ void consider_hibernation(time_t now) { return; /* if exiting soon, don't worry about bandwidth limits */ } - if(hibernate_state != HIBERNATE_STATE_LIVE) { + if(hibernate_state == HIBERNATE_STATE_DORMANT) { /* We've been hibernating because of bandwidth accounting. */ - tor_assert(hibernate_timeout); - if (hibernate_timeout > now) { + tor_assert(hibernate_end_time); + if (hibernate_end_time > now) { /* If we're hibernating, don't wake up until it's time, regardless of - * whether we're in a new interval */ + * whether we're in a new interval. */ return ; } else { - /* The interval has ended, or it is wakeup time. Find out which */ - accounting_run_housekeeping(now); - if (interval_wakeup_time <= now) { - /* The interval hasn't changed, but interval_wakeup_time has passed. - * It's time to wake up. */ - hibernate_end(HIBERNATE_STATE_LIVE); - return; - } else { - /* The interval has changed, and it isn't time to wake up yet. */ - hibernate_timeout = interval_wakeup_time; - } + hibernate_end_time_elapsed(now); } } - /* Else, see if it's time to start hibernating, or to go dormant. */ + /* Else, we aren't hibernating. See if it's time to start hibernating, or to + * go dormant. */ if (hibernate_state == HIBERNATE_STATE_LIVE && hibernate_soft_limit_reached()) { log_fn(LOG_NOTICE,"Bandwidth soft limit reached; commencing hibernation."); hibernate_begin(HIBERNATE_STATE_LOWBANDWIDTH, now); } - if (hibernate_state == HIBERNATE_STATE_LOWBANDWIDTH && - hibernate_hard_limit_reached()) { - hibernate_state = HIBERNATE_STATE_DORMANT; - log_fn(LOG_NOTICE,"Going dormant. Blowing away remaining connections."); - - /* Close all OR/AP/exit conns. Leave dir conns. */ - while((conn = connection_get_by_type(CONN_TYPE_OR)) || - (conn = connection_get_by_type(CONN_TYPE_AP)) || - (conn = connection_get_by_type(CONN_TYPE_EXIT))) { - log_fn(LOG_INFO,"Closing conn type %d", conn->type); - connection_mark_for_close(conn); + if (hibernate_state == HIBERNATE_STATE_LOWBANDWIDTH) { + if (hibernate_hard_limit_reached()) { + hibernate_go_dormant(); + } else if (hibernate_end_time <= now) { + /* The hibernation period ended while we were still in lowbandwidth.*/ + hibernate_end_time_elapsed(now); } } } |