Tweak locking and integration test.

This commit is contained in:
Jakob Borg 2014-01-20 22:22:27 +01:00
parent b67443eb40
commit d9ffd359e2
7 changed files with 122 additions and 110 deletions

View File

@ -18,17 +18,22 @@ func name() string {
func main() { func main() {
var files int var files int
var maxsize int var maxexp int
flag.IntVar(&files, "files", 1000, "Number of files") flag.IntVar(&files, "files", 1000, "Number of files")
flag.IntVar(&maxsize, "maxsize", 1000, "Maximum file size (KB)") flag.IntVar(&maxexp, "maxexp", 20, "Maximum file size (max = 2^n + 128*1024 B)")
flag.Parse() flag.Parse()
for i := 0; i < files; i++ { for i := 0; i < files; i++ {
n := name() n := name()
p0 := path.Join(string(n[0]), n[0:2]) p0 := path.Join(string(n[0]), n[0:2])
os.MkdirAll(p0, 0755) os.MkdirAll(p0, 0755)
s := mr.Intn(maxsize * 1024) s := 1 << uint(mr.Intn(maxexp))
a := 128 * 1024
if a > s {
a = s
}
s += mr.Intn(a)
b := make([]byte, s) b := make([]byte, s)
rand.Reader.Read(b) rand.Reader.Read(b)
p1 := path.Join(p0, n) p1 := path.Join(p0, n)

View File

@ -2,6 +2,7 @@
rm -rf files-* conf-* md5-* rm -rf files-* conf-* md5-*
extraopts=""
p=$(pwd) p=$(pwd)
go build genfiles.go go build genfiles.go
@ -29,19 +30,22 @@ EOT
mkdir files-$i mkdir files-$i
pushd files-$i >/dev/null pushd files-$i >/dev/null
../genfiles -maxsize 780 -files 1500 ../genfiles -maxexp 21 -files 4000
../md5r > ../md5-$i ../md5r > ../md5-$i
popd >/dev/null popd >/dev/null
done done
echo "Starting..." echo "Starting..."
for i in 1 2 3 ; do for i in 1 2 3 ; do
syncthing -c conf-$i --no-gui -l :2200$i & sleep 1
syncthing -c conf-$i --no-gui -l :2200$i $extraopts &
done done
cat md5-* | sort > md5-tot cat md5-* | sort > md5-tot
while true ; do while true ; do
sleep 10 read
echo Verifying...
conv=0 conv=0
for i in 1 2 3 ; do for i in 1 2 3 ; do
pushd files-$i >/dev/null pushd files-$i >/dev/null

View File

@ -119,6 +119,8 @@ func main() {
myID = string(certId(cert.Certificate[0])) myID = string(certId(cert.Certificate[0]))
infoln("My ID:", myID) infoln("My ID:", myID)
log.SetPrefix("[" + myID[0:5] + "] ")
logger.SetPrefix("[" + myID[0:5] + "] ")
if opts.Debug.Profiler != "" { if opts.Debug.Profiler != "" {
go func() { go func() {
@ -223,7 +225,9 @@ func main() {
go func() { go func() {
for { for {
time.Sleep(opts.Advanced.ScanInterval) time.Sleep(opts.Advanced.ScanInterval)
updateLocalModel(m) if m.LocalAge() > opts.Advanced.ScanInterval.Seconds()/2 {
updateLocalModel(m)
}
} }
}() }()
@ -248,7 +252,7 @@ func printStatsLoop(m *model.Model) {
outbps := 8 * int(float64(stats.OutBytesTotal-lastStats[node].OutBytesTotal)/secs) outbps := 8 * int(float64(stats.OutBytesTotal-lastStats[node].OutBytesTotal)/secs)
if inbps+outbps > 0 { if inbps+outbps > 0 {
infof("%s: %sb/s in, %sb/s out", node, MetricPrefix(inbps), MetricPrefix(outbps)) infof("%s: %sb/s in, %sb/s out", node[0:5], MetricPrefix(inbps), MetricPrefix(outbps))
} }
lastStats[node] = stats lastStats[node] = stats

View File

@ -57,6 +57,12 @@ type queuedBlock struct {
index int index int
} }
func NewFileQueue() *FileQueue {
return &FileQueue{
availability: make(map[string][]string),
}
}
func (q *FileQueue) Add(name string, blocks []Block, monitor Monitor) { func (q *FileQueue) Add(name string, blocks []Block, monitor Monitor) {
q.fmut.Lock() q.fmut.Lock()
defer q.fmut.Unlock() defer q.fmut.Unlock()
@ -212,24 +218,11 @@ func (q *FileQueue) deleteFile(n string) {
} }
} }
func (q *FileQueue) SetAvailable(file, node string) { func (q *FileQueue) SetAvailable(file string, nodes []string) {
q.amut.Lock() q.amut.Lock()
defer q.amut.Unlock() defer q.amut.Unlock()
if q.availability == nil { q.availability[file] = nodes
q.availability = make(map[string][]string)
}
q.availability[file] = []string{node}
}
func (q *FileQueue) AddAvailable(file, node string) {
q.amut.Lock()
defer q.amut.Unlock()
if q.availability == nil {
q.availability = make(map[string][]string)
}
q.availability[file] = append(q.availability[file], node)
} }
func (q *FileQueue) RemoveAvailable(toRemove string) { func (q *FileQueue) RemoveAvailable(toRemove string) {

View File

@ -8,14 +8,14 @@ import (
) )
func TestFileQueueAdd(t *testing.T) { func TestFileQueueAdd(t *testing.T) {
q := FileQueue{} q := NewFileQueue()
q.Add("foo", nil, nil) q.Add("foo", nil, nil)
} }
func TestFileQueueAddSorting(t *testing.T) { func TestFileQueueAddSorting(t *testing.T) {
q := FileQueue{} q := NewFileQueue()
q.SetAvailable("zzz", "nodeID") q.SetAvailable("zzz", []string{"nodeID"})
q.SetAvailable("aaa", "nodeID") q.SetAvailable("aaa", []string{"nodeID"})
q.Add("zzz", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil) q.Add("zzz", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
q.Add("aaa", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil) q.Add("aaa", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
@ -24,9 +24,9 @@ func TestFileQueueAddSorting(t *testing.T) {
t.Errorf("Incorrectly sorted get: %+v", b) t.Errorf("Incorrectly sorted get: %+v", b)
} }
q = FileQueue{} q = NewFileQueue()
q.SetAvailable("zzz", "nodeID") q.SetAvailable("zzz", []string{"nodeID"})
q.SetAvailable("aaa", "nodeID") q.SetAvailable("aaa", []string{"nodeID"})
q.Add("zzz", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil) q.Add("zzz", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
b, _ = q.Get("nodeID") // Start on zzzz b, _ = q.Get("nodeID") // Start on zzzz
@ -42,7 +42,7 @@ func TestFileQueueAddSorting(t *testing.T) {
} }
func TestFileQueueLen(t *testing.T) { func TestFileQueueLen(t *testing.T) {
q := FileQueue{} q := NewFileQueue()
q.Add("foo", nil, nil) q.Add("foo", nil, nil)
q.Add("bar", nil, nil) q.Add("bar", nil, nil)
@ -52,9 +52,9 @@ func TestFileQueueLen(t *testing.T) {
} }
func TestFileQueueGet(t *testing.T) { func TestFileQueueGet(t *testing.T) {
q := FileQueue{} q := NewFileQueue()
q.SetAvailable("foo", "nodeID") q.SetAvailable("foo", []string{"nodeID"})
q.SetAvailable("bar", "nodeID") q.SetAvailable("bar", []string{"nodeID"})
q.Add("foo", []Block{ q.Add("foo", []Block{
{Offset: 0, Size: 128, Hash: []byte("some foo hash bytes")}, {Offset: 0, Size: 128, Hash: []byte("some foo hash bytes")},
@ -177,11 +177,9 @@ func TestFileQueueDone(t *testing.T) {
*/ */
func TestFileQueueGetNodeIDs(t *testing.T) { func TestFileQueueGetNodeIDs(t *testing.T) {
q := FileQueue{} q := NewFileQueue()
q.SetAvailable("a-foo", "nodeID") q.SetAvailable("a-foo", []string{"nodeID", "a"})
q.AddAvailable("a-foo", "a") q.SetAvailable("b-bar", []string{"nodeID", "b"})
q.SetAvailable("b-bar", "nodeID")
q.AddAvailable("b-bar", "b")
q.Add("a-foo", []Block{ q.Add("a-foo", []Block{
{Offset: 0, Size: 128, Hash: []byte("some foo hash bytes")}, {Offset: 0, Size: 128, Hash: []byte("some foo hash bytes")},
@ -254,9 +252,9 @@ func TestFileQueueThreadHandling(t *testing.T) {
total += i total += i
} }
q := FileQueue{} q := NewFileQueue()
q.Add("foo", blocks, nil) q.Add("foo", blocks, nil)
q.SetAvailable("foo", "nodeID") q.SetAvailable("foo", []string{"nodeID"})
var start = make(chan bool) var start = make(chan bool)
var gotTot uint32 var gotTot uint32

View File

@ -1,16 +1,5 @@
package model package model
/*
Locking
=======
The model has read and write locks. These must be acquired as appropriate by
public methods. To prevent deadlock situations, private methods should never
acquire locks, but document what locks they require.
*/
import ( import (
"crypto/sha1" "crypto/sha1"
"errors" "errors"
@ -40,7 +29,9 @@ type Model struct {
rawConn map[string]io.Closer rawConn map[string]io.Closer
pmut sync.RWMutex // protects protoConn and rawConn pmut sync.RWMutex // protects protoConn and rawConn
fq FileQueue // queue for files to fetch // Queue for files to fetch. fq can call back into the model, so we must ensure
// to hold no locks when calling methods on fq.
fq *FileQueue
dq chan File // queue for files to delete dq chan File // queue for files to delete
updatedLocal int64 // timestamp of last update to local updatedLocal int64 // timestamp of last update to local
@ -100,6 +91,7 @@ func NewModel(dir string) *Model {
trace: make(map[string]bool), trace: make(map[string]bool),
fileLastChanged: make(map[string]time.Time), fileLastChanged: make(map[string]time.Time),
fileWasSuppressed: make(map[string]int), fileWasSuppressed: make(map[string]int),
fq: NewFileQueue(),
dq: make(chan File), dq: make(chan File),
} }
@ -157,6 +149,13 @@ func (m *Model) Generation() int64 {
return m.updatedLocal + m.updateGlobal return m.updatedLocal + m.updateGlobal
} }
func (m *Model) LocalAge() float64 {
m.umut.RLock()
defer m.umut.RUnlock()
return time.Since(time.Unix(m.updatedLocal, 0)).Seconds()
}
type ConnectionInfo struct { type ConnectionInfo struct {
protocol.Statistics protocol.Statistics
Address string Address string
@ -241,9 +240,11 @@ func (m *Model) InSyncSize() (files, bytes int) {
// NeedFiles returns the list of currently needed files and the total size. // NeedFiles returns the list of currently needed files and the total size.
func (m *Model) NeedFiles() (files []File, bytes int) { func (m *Model) NeedFiles() (files []File, bytes int) {
qf := m.fq.QueuedFiles()
m.gmut.RLock() m.gmut.RLock()
for _, n := range m.fq.QueuedFiles() { for _, n := range qf {
f := m.global[n] f := m.global[n]
files = append(files, f) files = append(files, f)
bytes += f.Size() bytes += f.Size()
@ -320,9 +321,11 @@ func (m *Model) indexUpdate(repo map[string]File, f protocol.FileInfo) {
repo[f.Name] = fileFromFileInfo(f) repo[f.Name] = fileFromFileInfo(f)
} }
// Close removes the peer from the model and closes the underlyign connection if possible. // Close removes the peer from the model and closes the underlying connection if possible.
// Implements the protocol.Model interface. // Implements the protocol.Model interface.
func (m *Model) Close(node string, err error) { func (m *Model) Close(node string, err error) {
m.fq.RemoveAvailable(node)
m.pmut.Lock() m.pmut.Lock()
m.rmut.Lock() m.rmut.Lock()
@ -334,7 +337,6 @@ func (m *Model) Close(node string, err error) {
delete(m.remote, node) delete(m.remote, node)
delete(m.protoConn, node) delete(m.protoConn, node)
delete(m.rawConn, node) delete(m.rawConn, node)
m.fq.RemoveAvailable(node)
m.rmut.Unlock() m.rmut.Unlock()
m.pmut.Unlock() m.pmut.Unlock()
@ -471,37 +473,42 @@ func (m *Model) AddConnection(rawConn io.Closer, protoConn Connection) {
protoConn.Index(idx) protoConn.Index(idx)
}() }()
if m.rwRunning { m.initmut.Lock()
for i := 0; i < m.parallellRequests; i++ { rw := m.rwRunning
i := i m.initmut.Unlock()
go func() { if !rw {
if m.trace["pull"] { return
log.Println("PULL: Starting", nodeID, i) }
}
for { for i := 0; i < m.parallellRequests; i++ {
m.pmut.RLock() i := i
if _, ok := m.protoConn[nodeID]; !ok { go func() {
if m.trace["pull"] { if m.trace["pull"] {
log.Println("PULL: Exiting", nodeID, i) log.Println("PULL: Starting", nodeID, i)
} }
m.pmut.RUnlock() for {
return m.pmut.RLock()
if _, ok := m.protoConn[nodeID]; !ok {
if m.trace["pull"] {
log.Println("PULL: Exiting", nodeID, i)
} }
m.pmut.RUnlock() m.pmut.RUnlock()
return
qb, ok := m.fq.Get(nodeID)
if ok {
if m.trace["pull"] {
log.Println("PULL: Request", nodeID, i, qb.name, qb.block.Offset)
}
data, _ := protoConn.Request(qb.name, qb.block.Offset, qb.block.Size, qb.block.Hash)
m.fq.Done(qb.name, qb.block.Offset, data)
} else {
time.Sleep(1 * time.Second)
}
} }
}() m.pmut.RUnlock()
}
qb, ok := m.fq.Get(nodeID)
if ok {
if m.trace["pull"] {
log.Println("PULL: Request", nodeID, i, qb.name, qb.block.Offset)
}
data, _ := protoConn.Request(qb.name, qb.block.Offset, qb.block.Size, qb.block.Hash)
m.fq.Done(qb.name, qb.block.Offset, data)
} else {
time.Sleep(1 * time.Second)
}
}
}()
} }
} }
@ -606,7 +613,6 @@ func (m *Model) broadcastIndexLoop() {
} }
// markDeletedLocals sets the deleted flag on files that have gone missing locally. // markDeletedLocals sets the deleted flag on files that have gone missing locally.
// Must be called with the write lock held.
func (m *Model) markDeletedLocals(newLocal map[string]File) bool { func (m *Model) markDeletedLocals(newLocal map[string]File) bool {
// For every file in the existing local table, check if they are also // For every file in the existing local table, check if they are also
// present in the new local table. If they are not, check that we already // present in the new local table. If they are not, check that we already
@ -661,7 +667,6 @@ func (m *Model) updateLocal(f File) {
} }
} }
// Must be called with the write lock held.
func (m *Model) recomputeGlobal() { func (m *Model) recomputeGlobal() {
var newGlobal = make(map[string]File) var newGlobal = make(map[string]File)
@ -671,23 +676,29 @@ func (m *Model) recomputeGlobal() {
} }
m.lmut.RUnlock() m.lmut.RUnlock()
var available = make(map[string][]string)
m.rmut.RLock() m.rmut.RLock()
var highestMod int64 var highestMod int64
for nodeID, fs := range m.remote { for nodeID, fs := range m.remote {
for n, nf := range fs { for n, nf := range fs {
if lf, ok := newGlobal[n]; !ok || nf.NewerThan(lf) { if lf, ok := newGlobal[n]; !ok || nf.NewerThan(lf) {
newGlobal[n] = nf newGlobal[n] = nf
m.fq.SetAvailable(n, nodeID) available[n] = []string{nodeID}
if nf.Modified > highestMod { if nf.Modified > highestMod {
highestMod = nf.Modified highestMod = nf.Modified
} }
} else if lf.Equals(nf) { } else if lf.Equals(nf) {
m.fq.AddAvailable(n, nodeID) available[n] = append(available[n], nodeID)
} }
} }
} }
m.rmut.RUnlock() m.rmut.RUnlock()
for f, ns := range available {
m.fq.SetAvailable(f, ns)
}
// Figure out if anything actually changed // Figure out if anything actually changed
m.gmut.RLock() m.gmut.RLock()
@ -727,10 +738,6 @@ func (m *Model) recomputeNeed() {
m.gmut.RLock() m.gmut.RLock()
for n, gf := range m.global { for n, gf := range m.global {
if m.fq.Queued(n) {
continue
}
m.lmut.RLock() m.lmut.RLock()
lf, ok := m.local[n] lf, ok := m.local[n]
m.lmut.RUnlock() m.lmut.RUnlock()
@ -771,7 +778,9 @@ func (m *Model) recomputeNeed() {
m.gmut.RUnlock() m.gmut.RUnlock()
for _, ao := range toAdd { for _, ao := range toAdd {
m.fq.Add(ao.n, ao.remote, ao.fm) if !m.fq.Queued(ao.n) {
m.fq.Add(ao.n, ao.remote, ao.fm)
}
} }
for _, gf := range toDelete { for _, gf := range toDelete {
m.dq <- gf m.dq <- gf

View File

@ -85,6 +85,9 @@ func (m *Model) loadIgnoreFiles(ign map[string][]string) filepath.WalkFunc {
func (m *Model) walkAndHashFiles(res *[]File, ign map[string][]string) filepath.WalkFunc { func (m *Model) walkAndHashFiles(res *[]File, ign map[string][]string) filepath.WalkFunc {
return func(p string, info os.FileInfo, err error) error { return func(p string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
if m.trace["file"] {
log.Printf("FILE: %q: %v", p, err)
}
return nil return nil
} }
@ -110,22 +113,18 @@ func (m *Model) walkAndHashFiles(res *[]File, ign map[string][]string) filepath.
} }
if info.Mode()&os.ModeType == 0 { if info.Mode()&os.ModeType == 0 {
fi, err := os.Stat(p) modified := info.ModTime().Unix()
if err != nil {
return nil
}
modified := fi.ModTime().Unix()
m.lmut.RLock() m.lmut.RLock()
hf, ok := m.local[rn] lf, ok := m.local[rn]
m.lmut.RUnlock() m.lmut.RUnlock()
if ok && hf.Modified == modified { if ok && lf.Modified == modified {
if nf := uint32(info.Mode()); nf != hf.Flags { if nf := uint32(info.Mode()); nf != lf.Flags {
hf.Flags = nf lf.Flags = nf
hf.Version++ lf.Version++
} }
*res = append(*res, hf) *res = append(*res, lf)
} else { } else {
if m.shouldSuppressChange(rn) { if m.shouldSuppressChange(rn) {
if m.trace["file"] { if m.trace["file"] {
@ -133,9 +132,9 @@ func (m *Model) walkAndHashFiles(res *[]File, ign map[string][]string) filepath.
} }
if ok { if ok {
hf.Flags = protocol.FlagInvalid lf.Flags = protocol.FlagInvalid
hf.Version++ lf.Version++
*res = append(*res, hf) *res = append(*res, lf)
} }
return nil return nil
} }
@ -195,9 +194,9 @@ func (m *Model) Walk(followSymlinks bool) (files []File, ignore map[string][]str
return return
} }
for _, fi := range fis { for _, info := range fis {
if fi.Mode()&os.ModeSymlink != 0 { if info.Mode()&os.ModeSymlink != 0 {
dir := path.Join(m.dir, fi.Name()) + "/" dir := path.Join(m.dir, info.Name()) + "/"
filepath.Walk(dir, m.loadIgnoreFiles(ignore)) filepath.Walk(dir, m.loadIgnoreFiles(ignore))
filepath.Walk(dir, hashFiles) filepath.Walk(dir, hashFiles)
} }