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 (
"time"
)
const (
// Cache test results for one week.
CacheValidity = 7 * 24 * time.Hour
)
var cacheMutex sync.Mutex
var cache TestCache = make(TestCache)
var cache *TestCache
// Regular expression that captures the address:port part of a bridge line (for
// both IPv4 and IPv6 addresses).
......@@ -33,6 +27,14 @@ type CacheEntry struct {
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
// consisting of the bridge's addr:port (for both IPv4 and IPv6 addresses).
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
// across program restarts.
func (tc *TestCache) WriteToDisk(cacheFile string) error {
......@@ -59,13 +58,13 @@ func (tc *TestCache) WriteToDisk(cacheFile string) error {
defer fh.Close()
enc := gob.NewEncoder(fh)
cacheMutex.Lock()
tc.l.Lock()
err = enc.Encode(*tc)
if err == nil {
log.Printf("Wrote cache with %d elements to %q.",
len(*tc), cacheFile)
len((*tc).Entries), cacheFile)
}
cacheMutex.Unlock()
tc.l.Unlock()
return err
}
......@@ -80,39 +79,39 @@ func (tc *TestCache) ReadFromDisk(cacheFile string) error {
defer fh.Close()
dec := gob.NewDecoder(fh)
cacheMutex.Lock()
tc.l.Lock()
err = dec.Decode(tc)
if err == nil {
log.Printf("Read cache with %d elements from %q.",
len(*tc), cacheFile)
len((*tc).Entries), cacheFile)
}
cacheMutex.Unlock()
tc.l.Unlock()
return err
}
// 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 {
// First, prune expired cache entries.
now := time.Now().UTC()
cacheMutex.Lock()
for index, entry := range *tc {
if entry.Time.Before(now.Add(-CacheValidity)) {
delete(*tc, index)
tc.l.Lock()
for index, entry := range (*tc).Entries {
if entry.Time.Before(now.Add(-(*tc).EntryTimeout)) {
delete((*tc).Entries, index)
}
}
cacheMutex.Unlock()
tc.l.Unlock()
addrPort, err := bridgeLineToAddrPort(bridgeLine)
if err != nil {
return nil
}
cacheMutex.Lock()
var r *CacheEntry = (*tc)[addrPort]
cacheMutex.Unlock()
tc.l.Lock()
var r *CacheEntry = (*tc).Entries[addrPort]
tc.l.Unlock()
return r
}
......@@ -132,7 +131,7 @@ func (tc *TestCache) AddEntry(bridgeLine string, result error, lastTested time.T
} else {
errorStr = result.Error()
}
cacheMutex.Lock()
(*tc)[addrPort] = &CacheEntry{errorStr, lastTested}
cacheMutex.Unlock()
tc.l.Lock()
(*tc).Entries[addrPort] = &CacheEntry{errorStr, lastTested}
tc.l.Unlock()
}
......@@ -11,9 +11,16 @@ import (
"time"
)
func NewCache() *TestCache {
return &TestCache{
Entries: make(map[string]*CacheEntry),
EntryTimeout: 24 * time.Hour,
}
}
func TestCacheFunctions(t *testing.T) {
cache := make(TestCache)
cache := NewCache()
bridgeLine := "obfs4 127.0.0.1:1 cert=foo iat-mode=0"
e := cache.IsCached(bridgeLine)
......@@ -35,10 +42,10 @@ func TestCacheFunctions(t *testing.T) {
}
// A bogus bridge line shouldn't make it into the cache.
cache = make(TestCache)
cache = NewCache()
bogusBridgeLine := "bogus-bridge-line"
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.")
}
......@@ -50,15 +57,15 @@ func TestCacheFunctions(t *testing.T) {
func TestCacheExpiration(t *testing.T) {
cache := make(TestCache)
cache := NewCache()
const shortForm = "2006-Jan-02"
expiry, _ := time.Parse(shortForm, "2000-Jan-01")
bridgeLine1 := "1.1.1.1:1111"
cache[bridgeLine1] = &CacheEntry{"", expiry}
cache.Entries[bridgeLine1] = &CacheEntry{"", expiry}
bridgeLine2 := "2.2.2.2:2222"
cache[bridgeLine2] = &CacheEntry{"", time.Now().UTC()}
cache.Entries[bridgeLine2] = &CacheEntry{"", time.Now().UTC()}
e := cache.IsCached(bridgeLine1)
if e != nil {
......@@ -83,7 +90,7 @@ func BenchmarkIsCached(b *testing.B) {
}
numCacheEntries := 10000
cache := make(TestCache)
cache := NewCache()
for i := 0; i < numCacheEntries; i++ {
cache.AddEntry(getRandAddrPort(), getRandError(), time.Now().UTC())
}
......@@ -97,7 +104,7 @@ func BenchmarkIsCached(b *testing.B) {
func TestCacheSerialisation(t *testing.T) {
cache := make(TestCache)
cache := NewCache()
testError := fmt.Errorf("foo")
cache.AddEntry("1.1.1.1:1", testError, 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) {
t.Errorf("Failed to read cache from disk: %s", err)
}
if len(cache) != 2 {
t.Errorf("Cache supposed to contain but two elements but has %d.", len(cache))
if len(cache.Entries) != 2 {
t.Errorf("Cache supposed to contain but two elements but has %d.", len(cache.Entries))
}
e1 := cache.IsCached("1.1.1.1:1")
......@@ -140,7 +147,7 @@ func TestCacheSerialisation(t *testing.T) {
func TestCacheConcurrency(t *testing.T) {
cache := make(TestCache)
cache := NewCache()
max := 10000
doneReading := make(chan bool)
doneWriting := make(chan bool)
......
......@@ -94,7 +94,7 @@ func printPrettyCache() {
var shortError string
var numFunctional int
for bridgeLine, cacheEntry := range cache {
for bridgeLine, cacheEntry := range cache.Entries {
shortError = cacheEntry.Error
maxChars := 50
if len(cacheEntry.Error) > maxChars {
......@@ -105,9 +105,10 @@ func printPrettyCache() {
}
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,
float64(numFunctional)/float64(len(cache))*100.0, len(cache))
float64(numFunctional)/float64(cacheLen)*100.0, cacheLen)
}
}
......@@ -120,7 +121,7 @@ func main() {
var cacheFile string
var templatesDir string
var torBinary string
var testTimeout int
var testTimeout, cacheTimeout int
var logFile string
flag.StringVar(&addr, "addr", ":5000", "Address to listen on.")
......@@ -134,7 +135,8 @@ func main() {
flag.StringVar(&templatesDir, "templates", "templates", "Path to directory that contains our web templates.")
flag.StringVar(&torBinary, "tor", "tor", "Path to tor executable.")
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()
if showVersion {
......@@ -179,6 +181,12 @@ func main() {
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
log.Printf("Setting Tor test timeout to %s.", TorTestTimeout)
torCtx = &TorContext{TorBinary: torBinary}
......
......@@ -3,6 +3,7 @@ package main
import (
"bytes"
"testing"
"time"
)
func TestWriteConfigToTorrc(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"
bogusBridge := "127.0.0.1:1"
TorTestTimeout = time.Minute
torCtx = &TorContext{TorBinary: "tor"}
if err := torCtx.Start(); err != nil {
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