GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

Add -cache-timeout command line switch.

So far, our cache timeout was hard-coded to be one week.  Now that
bridgestrap is more efficient, we can afford to test resources more
often, so this patch changes the default timeout to 24 hours.  The patch
also makes this timeout configurable through the new -cache-timeout
switch.

While at it, the patch also refactored our cache mechanism a bit and
renamed -timeout to -test-timeout.

This patch fixes #8
parent df61aa13
...@@ -10,13 +10,7 @@ import ( ...@@ -10,13 +10,7 @@ import (
"time" "time"
) )
const ( var cache *TestCache
// Cache test results for one week.
CacheValidity = 7 * 24 * time.Hour
)
var cacheMutex sync.Mutex
var cache TestCache = make(TestCache)
// Regular expression that captures the address:port part of a bridge line (for // Regular expression that captures the address:port part of a bridge line (for
// both IPv4 and IPv6 addresses). // both IPv4 and IPv6 addresses).
...@@ -33,6 +27,14 @@ type CacheEntry struct { ...@@ -33,6 +27,14 @@ type CacheEntry struct {
Time time.Time Time time.Time
} }
type TestCache struct {
// Entries maps a bridge's addr:port tuple to a cache entry.
Entries map[string]*CacheEntry
// EntryTimeout determines how long a cache entry is valid for.
EntryTimeout time.Duration
l sync.Mutex
}
// bridgeLineToAddrPort takes a bridge line as input and returns a string // bridgeLineToAddrPort takes a bridge line as input and returns a string
// consisting of the bridge's addr:port (for both IPv4 and IPv6 addresses). // consisting of the bridge's addr:port (for both IPv4 and IPv6 addresses).
func bridgeLineToAddrPort(bridgeLine string) (string, error) { func bridgeLineToAddrPort(bridgeLine string) (string, error) {
...@@ -45,9 +47,6 @@ func bridgeLineToAddrPort(bridgeLine string) (string, error) { ...@@ -45,9 +47,6 @@ func bridgeLineToAddrPort(bridgeLine string) (string, error) {
} }
} }
// TestCache maps a bridge's addr:port tuple to a cache entry.
type TestCache map[string]*CacheEntry
// WriteToDisk writes our test result cache to disk, allowing it to persist // WriteToDisk writes our test result cache to disk, allowing it to persist
// across program restarts. // across program restarts.
func (tc *TestCache) WriteToDisk(cacheFile string) error { func (tc *TestCache) WriteToDisk(cacheFile string) error {
...@@ -59,13 +58,13 @@ func (tc *TestCache) WriteToDisk(cacheFile string) error { ...@@ -59,13 +58,13 @@ func (tc *TestCache) WriteToDisk(cacheFile string) error {
defer fh.Close() defer fh.Close()
enc := gob.NewEncoder(fh) enc := gob.NewEncoder(fh)
cacheMutex.Lock() tc.l.Lock()
err = enc.Encode(*tc) err = enc.Encode(*tc)
if err == nil { if err == nil {
log.Printf("Wrote cache with %d elements to %q.", log.Printf("Wrote cache with %d elements to %q.",
len(*tc), cacheFile) len((*tc).Entries), cacheFile)
} }
cacheMutex.Unlock() tc.l.Unlock()
return err return err
} }
...@@ -80,39 +79,39 @@ func (tc *TestCache) ReadFromDisk(cacheFile string) error { ...@@ -80,39 +79,39 @@ func (tc *TestCache) ReadFromDisk(cacheFile string) error {
defer fh.Close() defer fh.Close()
dec := gob.NewDecoder(fh) dec := gob.NewDecoder(fh)
cacheMutex.Lock() tc.l.Lock()
err = dec.Decode(tc) err = dec.Decode(tc)
if err == nil { if err == nil {
log.Printf("Read cache with %d elements from %q.", log.Printf("Read cache with %d elements from %q.",
len(*tc), cacheFile) len((*tc).Entries), cacheFile)
} }
cacheMutex.Unlock() tc.l.Unlock()
return err return err
} }
// IsCached returns a cache entry if the given bridge line has been tested // IsCached returns a cache entry if the given bridge line has been tested
// recently (as determined by CacheValidity), and nil otherwise. // recently (as determined by EntryTimeout), and nil otherwise.
func (tc *TestCache) IsCached(bridgeLine string) *CacheEntry { func (tc *TestCache) IsCached(bridgeLine string) *CacheEntry {
// First, prune expired cache entries. // First, prune expired cache entries.
now := time.Now().UTC() now := time.Now().UTC()
cacheMutex.Lock() tc.l.Lock()
for index, entry := range *tc { for index, entry := range (*tc).Entries {
if entry.Time.Before(now.Add(-CacheValidity)) { if entry.Time.Before(now.Add(-(*tc).EntryTimeout)) {
delete(*tc, index) delete((*tc).Entries, index)
} }
} }
cacheMutex.Unlock() tc.l.Unlock()
addrPort, err := bridgeLineToAddrPort(bridgeLine) addrPort, err := bridgeLineToAddrPort(bridgeLine)
if err != nil { if err != nil {
return nil return nil
} }
cacheMutex.Lock() tc.l.Lock()
var r *CacheEntry = (*tc)[addrPort] var r *CacheEntry = (*tc).Entries[addrPort]
cacheMutex.Unlock() tc.l.Unlock()
return r return r
} }
...@@ -132,7 +131,7 @@ func (tc *TestCache) AddEntry(bridgeLine string, result error, lastTested time.T ...@@ -132,7 +131,7 @@ func (tc *TestCache) AddEntry(bridgeLine string, result error, lastTested time.T
} else { } else {
errorStr = result.Error() errorStr = result.Error()
} }
cacheMutex.Lock() tc.l.Lock()
(*tc)[addrPort] = &CacheEntry{errorStr, lastTested} (*tc).Entries[addrPort] = &CacheEntry{errorStr, lastTested}
cacheMutex.Unlock() tc.l.Unlock()
} }
...@@ -11,9 +11,16 @@ import ( ...@@ -11,9 +11,16 @@ import (
"time" "time"
) )
func NewCache() *TestCache {
return &TestCache{
Entries: make(map[string]*CacheEntry),
EntryTimeout: 24 * time.Hour,
}
}
func TestCacheFunctions(t *testing.T) { func TestCacheFunctions(t *testing.T) {
cache := make(TestCache) cache := NewCache()
bridgeLine := "obfs4 127.0.0.1:1 cert=foo iat-mode=0" bridgeLine := "obfs4 127.0.0.1:1 cert=foo iat-mode=0"
e := cache.IsCached(bridgeLine) e := cache.IsCached(bridgeLine)
...@@ -35,10 +42,10 @@ func TestCacheFunctions(t *testing.T) { ...@@ -35,10 +42,10 @@ func TestCacheFunctions(t *testing.T) {
} }
// A bogus bridge line shouldn't make it into the cache. // A bogus bridge line shouldn't make it into the cache.
cache = make(TestCache) cache = NewCache()
bogusBridgeLine := "bogus-bridge-line" bogusBridgeLine := "bogus-bridge-line"
cache.AddEntry(bogusBridgeLine, errors.New("bogus-error"), time.Now().UTC()) cache.AddEntry(bogusBridgeLine, errors.New("bogus-error"), time.Now().UTC())
if len(cache) != 0 { if len(cache.Entries) != 0 {
t.Errorf("Bogus bridge line made it into cache.") t.Errorf("Bogus bridge line made it into cache.")
} }
...@@ -50,15 +57,15 @@ func TestCacheFunctions(t *testing.T) { ...@@ -50,15 +57,15 @@ func TestCacheFunctions(t *testing.T) {
func TestCacheExpiration(t *testing.T) { func TestCacheExpiration(t *testing.T) {
cache := make(TestCache) cache := NewCache()
const shortForm = "2006-Jan-02" const shortForm = "2006-Jan-02"
expiry, _ := time.Parse(shortForm, "2000-Jan-01") expiry, _ := time.Parse(shortForm, "2000-Jan-01")
bridgeLine1 := "1.1.1.1:1111" bridgeLine1 := "1.1.1.1:1111"
cache[bridgeLine1] = &CacheEntry{"", expiry} cache.Entries[bridgeLine1] = &CacheEntry{"", expiry}
bridgeLine2 := "2.2.2.2:2222" bridgeLine2 := "2.2.2.2:2222"
cache[bridgeLine2] = &CacheEntry{"", time.Now().UTC()} cache.Entries[bridgeLine2] = &CacheEntry{"", time.Now().UTC()}
e := cache.IsCached(bridgeLine1) e := cache.IsCached(bridgeLine1)
if e != nil { if e != nil {
...@@ -83,7 +90,7 @@ func BenchmarkIsCached(b *testing.B) { ...@@ -83,7 +90,7 @@ func BenchmarkIsCached(b *testing.B) {
} }
numCacheEntries := 10000 numCacheEntries := 10000
cache := make(TestCache) cache := NewCache()
for i := 0; i < numCacheEntries; i++ { for i := 0; i < numCacheEntries; i++ {
cache.AddEntry(getRandAddrPort(), getRandError(), time.Now().UTC()) cache.AddEntry(getRandAddrPort(), getRandError(), time.Now().UTC())
} }
...@@ -97,7 +104,7 @@ func BenchmarkIsCached(b *testing.B) { ...@@ -97,7 +104,7 @@ func BenchmarkIsCached(b *testing.B) {
func TestCacheSerialisation(t *testing.T) { func TestCacheSerialisation(t *testing.T) {
cache := make(TestCache) cache := NewCache()
testError := fmt.Errorf("foo") testError := fmt.Errorf("foo")
cache.AddEntry("1.1.1.1:1", testError, time.Now().UTC()) cache.AddEntry("1.1.1.1:1", testError, time.Now().UTC())
cache.AddEntry("2.2.2.2:2", fmt.Errorf("bar"), time.Now().UTC()) cache.AddEntry("2.2.2.2:2", fmt.Errorf("bar"), time.Now().UTC())
...@@ -117,8 +124,8 @@ func TestCacheSerialisation(t *testing.T) { ...@@ -117,8 +124,8 @@ func TestCacheSerialisation(t *testing.T) {
t.Errorf("Failed to read cache from disk: %s", err) t.Errorf("Failed to read cache from disk: %s", err)
} }
if len(cache) != 2 { if len(cache.Entries) != 2 {
t.Errorf("Cache supposed to contain but two elements but has %d.", len(cache)) t.Errorf("Cache supposed to contain but two elements but has %d.", len(cache.Entries))
} }
e1 := cache.IsCached("1.1.1.1:1") e1 := cache.IsCached("1.1.1.1:1")
...@@ -140,7 +147,7 @@ func TestCacheSerialisation(t *testing.T) { ...@@ -140,7 +147,7 @@ func TestCacheSerialisation(t *testing.T) {
func TestCacheConcurrency(t *testing.T) { func TestCacheConcurrency(t *testing.T) {
cache := make(TestCache) cache := NewCache()
max := 10000 max := 10000
doneReading := make(chan bool) doneReading := make(chan bool)
doneWriting := make(chan bool) doneWriting := make(chan bool)
......
...@@ -94,7 +94,7 @@ func printPrettyCache() { ...@@ -94,7 +94,7 @@ func printPrettyCache() {
var shortError string var shortError string
var numFunctional int var numFunctional int
for bridgeLine, cacheEntry := range cache { for bridgeLine, cacheEntry := range cache.Entries {
shortError = cacheEntry.Error shortError = cacheEntry.Error
maxChars := 50 maxChars := 50
if len(cacheEntry.Error) > maxChars { if len(cacheEntry.Error) > maxChars {
...@@ -105,9 +105,10 @@ func printPrettyCache() { ...@@ -105,9 +105,10 @@ func printPrettyCache() {
} }
fmt.Printf("%-22s %-50s %s\n", bridgeLine, shortError, cacheEntry.Time) fmt.Printf("%-22s %-50s %s\n", bridgeLine, shortError, cacheEntry.Time)
} }
if len(cache) > 0 { cacheLen := len(cache.Entries)
if len(cache.Entries) > 0 {
log.Printf("Found %d (%.2f%%) out of %d functional.\n", numFunctional, log.Printf("Found %d (%.2f%%) out of %d functional.\n", numFunctional,
float64(numFunctional)/float64(len(cache))*100.0, len(cache)) float64(numFunctional)/float64(cacheLen)*100.0, cacheLen)
} }
} }
...@@ -120,7 +121,7 @@ func main() { ...@@ -120,7 +121,7 @@ func main() {
var cacheFile string var cacheFile string
var templatesDir string var templatesDir string
var torBinary string var torBinary string
var testTimeout int var testTimeout, cacheTimeout int
var logFile string var logFile string
flag.StringVar(&addr, "addr", ":5000", "Address to listen on.") flag.StringVar(&addr, "addr", ":5000", "Address to listen on.")
...@@ -134,7 +135,8 @@ func main() { ...@@ -134,7 +135,8 @@ func main() {
flag.StringVar(&templatesDir, "templates", "templates", "Path to directory that contains our web templates.") flag.StringVar(&templatesDir, "templates", "templates", "Path to directory that contains our web templates.")
flag.StringVar(&torBinary, "tor", "tor", "Path to tor executable.") flag.StringVar(&torBinary, "tor", "tor", "Path to tor executable.")
flag.StringVar(&logFile, "log", "", "File to write logs to.") flag.StringVar(&logFile, "log", "", "File to write logs to.")
flag.IntVar(&testTimeout, "timeout", 60, "Test timeout in seconds.") flag.IntVar(&testTimeout, "test-timeout", 60, "Test timeout in seconds.")
flag.IntVar(&cacheTimeout, "cache-timeout", 24, "Cache timeout in hours.")
flag.Parse() flag.Parse()
if showVersion { if showVersion {
...@@ -179,6 +181,12 @@ func main() { ...@@ -179,6 +181,12 @@ func main() {
return return
} }
cache = &TestCache{
Entries: make(map[string]*CacheEntry),
EntryTimeout: time.Duration(cacheTimeout) * time.Hour,
}
log.Printf("Setting cache timeout to %s.", cache.EntryTimeout)
TorTestTimeout = time.Duration(testTimeout) * time.Second TorTestTimeout = time.Duration(testTimeout) * time.Second
log.Printf("Setting Tor test timeout to %s.", TorTestTimeout) log.Printf("Setting Tor test timeout to %s.", TorTestTimeout)
torCtx = &TorContext{TorBinary: torBinary} torCtx = &TorContext{TorBinary: torBinary}
......
...@@ -3,6 +3,7 @@ package main ...@@ -3,6 +3,7 @@ package main
import ( import (
"bytes" "bytes"
"testing" "testing"
"time"
) )
func TestWriteConfigToTorrc(t *testing.T) { func TestWriteConfigToTorrc(t *testing.T) {
...@@ -54,6 +55,7 @@ func TestBridgeTest(t *testing.T) { ...@@ -54,6 +55,7 @@ func TestBridgeTest(t *testing.T) {
defaultBridge2 := "obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0" defaultBridge2 := "obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0"
bogusBridge := "127.0.0.1:1" bogusBridge := "127.0.0.1:1"
TorTestTimeout = time.Minute
torCtx = &TorContext{TorBinary: "tor"} torCtx = &TorContext{TorBinary: "tor"}
if err := torCtx.Start(); err != nil { if err := torCtx.Start(); err != nil {
t.Fatalf("Failed to start tor: %s", err) t.Fatalf("Failed to start tor: %s", err)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment