diff --git a/ChangeLog b/ChangeLog
index c6d2d3c199..e84637cc7c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,9 @@ Changes in version 0.1.2.5-xxxx - 200?-??-??
- Start using the state file to store bandwidth accounting data:
the bw_accounting file is now obsolete. We'll keep generating it
for a while for people who are still using 0.1.2.4-alpha.
+ - Try to batch changes to the state so that we do as few disk writes
+ as possible while still storing important things in a timely
+ fashion.
o Minor bugfixes;
- Fix a bug when a PF socket is first used. (Patch from Fabian
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index 29a762e887..958894d724 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -2376,13 +2376,16 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg)
}
/** Our list of entry guards has changed, or some element of one
- * of our entry guards has changed. Write the changes to disk. */
+ * of our entry guards has changed. Write the changes to disk within
+ * the next 5 minutes.
+ */
static void
entry_guards_changed(void)
{
entry_guards_dirty = 1;
- or_state_save();
+ /* or_state_save() will call entry_guards_update_state(). */
+ or_state_mark_dirty(get_or_state(), time(NULL)+600);
}
/** If the entry guard info has not changed, do nothing and return.
@@ -2433,7 +2436,7 @@ entry_guards_update_state(or_state_t *state)
next = &(line->next);
}
});
- state->dirty = 1;
+ or_state_mark_dirty(get_or_state(), 0);
entry_guards_dirty = 0;
}
diff --git a/src/or/config.c b/src/or/config.c
index 9beb10a6c2..5da79db38e 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -3934,8 +3934,8 @@ or_state_load(void)
or_state_set(new_state);
new_state = NULL;
if (!contents) {
- global_state->dirty = 1;
- or_state_save();
+ global_state->next_write = 0;
+ or_state_save(time(NULL));
}
r = 0;
@@ -3950,7 +3950,7 @@ or_state_load(void)
/** Write the persistent state to disk. Return 0 for success, <0 on failure. */
int
-or_state_save(void)
+or_state_save(time_t now)
{
char *state, *contents;
char tbuf[ISO_TIME_LEN+1];
@@ -3959,11 +3959,14 @@ or_state_save(void)
tor_assert(global_state);
+ if (global_state->next_write > now)
+ return 0;
+
+ /* Call everything else that might dirty the state even more, in order
+ * to avoid redundant writes. */
entry_guards_update_state(global_state);
rep_hist_update_state(global_state);
-
- if (!global_state->dirty)
- return 0;
+ accounting_run_housekeeping(now);
global_state->LastWritten = time(NULL);
tor_free(global_state->TorVersion);
@@ -3988,7 +3991,7 @@ or_state_save(void)
tor_free(fname);
tor_free(contents);
- global_state->dirty = 0;
+ global_state->next_write = TIME_MAX;
return 0;
}
diff --git a/src/or/hibernate.c b/src/or/hibernate.c
index 79c51c7184..bd89240e47 100644
--- a/src/or/hibernate.c
+++ b/src/or/hibernate.c
@@ -391,8 +391,8 @@ reset_accounting(time_t now)
static INLINE int
time_to_record_bandwidth_usage(time_t now)
{
- /* Note every 60 sec */
-#define NOTE_INTERVAL (60)
+ /* Note every 600 sec */
+#define NOTE_INTERVAL (600)
/* Or every 20 megabytes */
#define NOTE_BYTES 20*(1024*1024)
static uint64_t last_read_bytes_noted = 0;
@@ -575,7 +575,7 @@ accounting_record_bandwidth_usage(time_t now, or_state_t *state)
state->AccountingBytesWrittenInInterval = n_bytes_written_in_interval;
state->AccountingSecondsActive = n_seconds_active_in_interval;
state->AccountingExpectedUsage = expected_bandwidth_usage;
- state->dirty = 1;
+ or_state_mark_dirty(state, 60);
return r;
}
@@ -760,6 +760,7 @@ hibernate_begin(int new_state, time_t now)
hibernate_state = new_state;
accounting_record_bandwidth_usage(now, get_or_state());
+ or_state_mark_dirty(get_or_state(), 0);
}
/** Called when we've been hibernating and our timeout is reached. */
@@ -827,6 +828,7 @@ hibernate_go_dormant(time_t now)
}
accounting_record_bandwidth_usage(now, get_or_state());
+ or_state_mark_dirty(get_or_state(), 0);
}
/** Called when hibernate_end_time has arrived. */
diff --git a/src/or/main.c b/src/or/main.c
index bfed55b2fe..163cf188da 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -830,9 +830,6 @@ run_scheduled_events(time_t now)
* and the rend cache. */
rep_history_clean(now - options->RephistTrackTime);
rend_cache_clean();
- /* And while we are at it, save the state with bandwidth history
- * and more. */
- or_state_save();
}
/* 2b. Once per minute, regenerate and upload the descriptor if the old
@@ -935,6 +932,10 @@ run_scheduled_events(time_t now)
*/
close_closeable_connections();
+ /** 8b. And if anything in our state is ready to get flushed to disk, we
+ * flush it. */
+ or_state_save(now);
+
/** 9. and if we're a server, check whether our DNS is telling stories to
* us. */
if (server_mode(options) && time_to_check_for_wildcarded_dns < now) {
@@ -1595,7 +1596,8 @@ tor_cleanup(void)
unlink(options->PidFile);
if (accounting_is_enabled(options))
accounting_record_bandwidth_usage(time(NULL), get_or_state());
- or_state_save();
+ or_state_mark_dirty(get_or_state(), 0); /* force an immediate save. */
+ or_state_save(time(NULL));
}
tor_free_all(0); /* move tor_free_all back into the ifdef below later. XXX*/
crypto_global_cleanup();
diff --git a/src/or/or.h b/src/or/or.h
index 9f25dfd59a..053c5a499d 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -1655,19 +1655,30 @@ typedef struct {
/** Persistent state for an onion router, as saved to disk. */
typedef struct {
uint32_t _magic;
- /** True iff this state has been changed since it was last read/written
- * to the disk. */
- int dirty;
+ /** The time at which we next plan to write the state to the disk. Equal to
+ * TIME_MAX if there are no saveable changes, 0 if there are changes that
+ * should be saved right away. */
+ time_t next_write;
+ /** When was the state last written to disk? */
time_t LastWritten;
+
+ /** Fields for */
time_t AccountingIntervalStart;
uint64_t AccountingBytesReadInInterval;
uint64_t AccountingBytesWrittenInInterval;
int AccountingSecondsActive;
uint64_t AccountingExpectedUsage;
+ /** A list of Entry Guard-related configuration lines. */
config_line_t *EntryGuards;
+ /** These fields hold information on the history of bandwidth usage for
+ * servers. The "Ends" fields hold the time when we last updated the
+ * bandwidth usage. The "Interval" fields hold the granularity, in seconds,
+ * of the entries of Values. The "Values" lists hold decimal string
+ * representations of the number of bytes read or written in each
+ * interval. */
time_t BWHistoryReadEnds;
int BWHistoryReadInterval;
smartlist_t *BWHistoryReadValues;
@@ -1675,11 +1686,24 @@ typedef struct {
int BWHistoryWriteInterval;
smartlist_t *BWHistoryWriteValues;
+ /** What version of Tor write this state file? */
char *TorVersion;
+ /** holds any unrecognized values we found in the state file, in the order
+ * in which we found them. */
config_line_t *ExtraLines;
} or_state_t;
+static void or_state_mark_dirty(or_state_t *state, time_t when);
+/** Change the next_write time of state to when, unless the
+ * state is already scheduled to be written to disk earlier than when.
+ */
+static INLINE void or_state_mark_dirty(or_state_t *state, time_t when)
+{
+ if (state->next_write > when)
+ state->next_write = when;
+}
+
#define MAX_SOCKS_REPLY_LEN 1024
#define MAX_SOCKS_ADDR_LEN 256
#define SOCKS_COMMAND_CONNECT 0x01
@@ -1897,7 +1921,7 @@ const char *get_torrc_fname(void);
or_state_t *get_or_state(void);
int or_state_load(void);
-int or_state_save(void);
+int or_state_save(time_t now);
int config_getinfo_helper(const char *question, char **answer);
diff --git a/src/or/rephist.c b/src/or/rephist.c
index 20b76679bd..af295facf3 100644
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@ -666,6 +666,8 @@ rep_hist_update_state(or_state_t *state)
if (! server_mode(get_options())) {
/* Clients don't need to store bandwidth history persistently;
* force these values to the defaults. */
+ if (*s_begins != 0 || *s_interval != 900)
+ or_state_mark_dirty(get_or_state(), time(NULL)+600);
*s_begins = 0;
*s_interval = 900;
*s_values = smartlist_create();
@@ -682,7 +684,8 @@ rep_hist_update_state(or_state_t *state)
smartlist_split_string(*s_values, buf, ",", SPLIT_SKIP_SPACE, 0);
}
tor_free(buf);
- state->dirty = 1;
+ if (server_mode(get_options()))
+ or_state_mark_dirty(get_or_state(), time(NULL)+(2*3600));
}
/** Set bandwidth history from our saved state. */