Compare commits

..

25 Commits

Author SHA1 Message Date
Audrius Butkevicius
3a203b8d83 Merge pull request #1861 from calmh/fix-1860
Be more lenient against errors when deleting (fixes #1860)
2015-05-24 00:58:46 +01:00
Audrius Butkevicius
feecdcc7a4 Merge pull request #1854 from calmh/eventmemallocs
Reuse a timer instead of allocating a new one in subscription.Poll
2015-05-23 23:23:46 +01:00
Audrius Butkevicius
51ad533be6 Merge pull request #1859 from calmh/fix-1858
UPnP discovery results must not be collected in a background goroutine (fixes #1858)
2015-05-23 22:58:09 +01:00
Jakob Borg
29da0bc8f5 Be more lenient against errors when deleting (fixes #1860) 2015-05-23 23:57:41 +02:00
Jakob Borg
bccf7fc2a8 Merge pull request #1857 from calmh/testhax
Refactor integration tests to be a little cleaner and more stable, I hope
2015-05-23 23:57:21 +02:00
Jakob Borg
7b6b5981c4 UPnP discovery results must not be collected in a background goroutine (fixes #1858) 2015-05-23 23:27:02 +02:00
Jakob Borg
9463192224 Refactor integration tests to be a little cleaner and more stable, I hope 2015-05-23 23:26:23 +02:00
Audrius Butkevicius
d1689f0012 Merge pull request #1855 from calmh/dboptsagain
Reduce db write cache to (2*) 4 MiB
2015-05-23 20:38:41 +01:00
Jakob Borg
8ed67fe349 Reduce db write cache to (2*) 4 MiB
I haven't been able to reproduce any performance advantage of having it
set higher and it reduces the memory footprint a bit.
2015-05-23 21:05:52 +02:00
Jakob Borg
90a1d99785 Reuse a timer instead of allocating a new one in subscription.Poll
This is surprisingly memory expensive when Poll gets called a lot, such
as when syncing lots of small files generating itemstarted/itemfinished
events. It's line number three in this heap profile on the
TestBenchmarkManyFiles test:

	jb@syno:~/src/github.com/syncthing/syncthing/test (master) $ go tool pprof ../bin/syncthing heap-13194.pprof
	Entering interactive mode (type "help" for commands)
	(pprof) top
	80.91MB of 83.05MB total (97.42%)
	Dropped 1024 nodes (cum <= 0.42MB)
	Showing top 10 nodes out of 85 (cum >= 1.75MB)
	      flat  flat%   sum%        cum   cum%
	      32MB 38.53% 38.53%    32.01MB 38.54%  github.com/syndtr/goleveldb/leveldb/memdb.New
	   22.16MB 26.68% 65.21%    22.16MB 26.68%  github.com/syndtr/goleveldb/leveldb/util.(*BufferPool).Get
	   13.02MB 15.68% 80.89%    13.02MB 15.68%  time.NewTimer
	    6.94MB  8.35% 89.24%     6.94MB  8.35%  github.com/syndtr/goleveldb/leveldb/memdb.(*DB).Put
	    3.18MB  3.82% 93.06%     3.18MB  3.82%  github.com/calmh/xdr.(*Reader).ReadBytesMaxInto

With this change the allocation is removed entirely.
2015-05-23 20:38:41 +02:00
Jakob Borg
e215cf6fb8 Add some REST API benchmarks 2015-05-23 20:15:54 +02:00
Jakob Borg
e827c0bd94 Run benchmarks in docker-all instead 2015-05-23 15:17:19 +02:00
Jakob Borg
8dd7e4e6b5 Run benchmarks when running tests 2015-05-23 15:08:17 +02:00
Jakob Borg
a2b94f4e06 Build Debian armhf and armel 2015-05-23 13:10:33 +02:00
Jakob Borg
8b0037ffab Make check-contribs a little more generous in recognizing a copyright header 2015-05-21 21:42:46 +02:00
Jakob Borg
4ab03f3bef Update CONTRIBUTING to not encourage changing AUTHORS 2015-05-21 20:58:17 +02:00
Jakob Borg
3c3db52f49 Merge pull request #1842 from Zillode/fix-1822
Support the creation of top-level folders on Windows (fixes #1822)
2015-05-21 20:50:52 +02:00
Jakob Borg
5b1e884659 Merge pull request #1847 from Zillode/fix-1833
Show date and time for web GUI notification (fixes #1833)
2015-05-21 20:49:04 +02:00
Lode Hoste
7ec6740e26 Show date and time for web GUI notification (fixes #1833) 2015-05-21 19:44:45 +02:00
Lode Hoste
f12b8c19be Support the creation of top-level folders on Windows (fixes #1822) 2015-05-21 19:21:19 +02:00
Jakob Borg
76174d31ce Merge pull request #1843 from Zillode/fix-1831
Set permanent UPnP lease when required
2015-05-21 12:50:48 +02:00
Lode Hoste
5042248260 Set permanent UPnP lease when required (fixes #1831) 2015-05-21 10:48:40 +02:00
Lode Hoste
1df6589533 Add status code to SOAP response 2015-05-21 09:54:39 +02:00
Jakob Borg
12f76b448c Merge pull request #1826 from AudriusButkevicius/screwflags
Don't check interface flags on Windows
2015-05-19 14:59:16 +02:00
Audrius Butkevicius
f112ef34f6 Don't check interface flags on Windows 2015-05-17 16:42:26 +01:00
31 changed files with 579 additions and 487 deletions

View File

@@ -100,8 +100,8 @@ International License. You retain the copyright to code you have
written.
When accepting your first contribution, the maintainer of the project
will ensure that you are added to the AUTHORS file. You are welcome to
add yourself as a separate commit in your first pull request.
will ensure that you are added to the AUTHORS file, the NICKS file and
the list of authors in the about box.
## Building

2
Godeps/Godeps.json generated
View File

@@ -11,7 +11,7 @@
},
{
"ImportPath": "github.com/calmh/logger",
"Rev": "4d4e2801954c5581e4c2a80a3d3beb3b3645fd04"
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
},
{
"ImportPath": "github.com/calmh/luhn",

View File

@@ -6,6 +6,7 @@ package logger
import (
"fmt"
"io/ioutil"
"log"
"os"
"strings"
@@ -37,6 +38,13 @@ type Logger struct {
var DefaultLogger = New()
func New() *Logger {
if os.Getenv("LOGGER_DISCARD") != "" {
// Hack to completely disable logging, for example when running benchmarks.
return &Logger{
logger: log.New(ioutil.Discard, "", 0),
}
}
return &Logger{
logger: log.New(os.Stdout, "", log.Ltime),
}

50
benchfilter.go Normal file
View File

@@ -0,0 +1,50 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build ignore
// Neatly format benchmarking output which otherwise looks like crap.
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"regexp"
"text/tabwriter"
)
var (
benchRe = regexp.MustCompile(`^Bench`)
spacesRe = regexp.MustCompile(`\s+`)
numbersRe = regexp.MustCompile(`\b[\d\.]+\b`)
)
func main() {
tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
br := bufio.NewScanner(os.Stdin)
n := 0
for br.Scan() {
line := br.Bytes()
if benchRe.Match(line) {
n++
line = spacesRe.ReplaceAllLiteral(line, []byte("\t"))
line = numbersRe.ReplaceAllFunc(line, func(n []byte) []byte {
return []byte(fmt.Sprintf("%12s", n))
})
tw.Write(line)
tw.Write([]byte("\n"))
} else if n > 0 && bytes.HasPrefix(line, []byte("ok")) {
n = 0
tw.Flush()
fmt.Printf("%s\n\n", line)
}
}
tw.Flush()
}

View File

@@ -110,6 +110,9 @@ func main() {
case "test":
test("./...")
case "bench":
bench("./...")
case "assets":
assets()
@@ -182,6 +185,11 @@ func test(pkg string) {
runPrint("go", "test", "-short", "-timeout", "60s", pkg)
}
func bench(pkg string) {
setBuildEnv()
runPrint("go", "test", "-run", "NONE", "-bench", ".", pkg)
}
func install(pkg string, tags []string) {
os.Setenv("GOBIN", "./bin")
args := []string{"install", "-v", "-ldflags", ldflags()}
@@ -278,6 +286,17 @@ func buildZip() {
func buildDeb() {
os.RemoveAll("deb")
// "goarch" here is set to whatever the Debian packages expect. We correct
// "it to what we actually know how to build and keep the Debian variant
// "name in "debarch".
debarch := goarch
switch goarch {
case "i386":
goarch = "386"
case "armel", "armhf":
goarch = "arm"
}
build("./cmd/syncthing", []string{"noupgrade"})
files := []archiveFile{
@@ -297,11 +316,6 @@ func buildDeb() {
}
}
debarch := goarch
if debarch == "386" {
debarch = "i386"
}
control := `Package: syncthing
Architecture: {{arch}}
Depends: libc6

View File

@@ -17,8 +17,11 @@ case "${1:-default}" in
ulimit -t 60 &>/dev/null || true
ulimit -d 512000 &>/dev/null || true
ulimit -m 512000 &>/dev/null || true
go run build.go test
;;
go run build.go "$1"
bench)
LOGGER_DISCARD=1 go run build.go bench | go run benchfilter.go
;;
tar)
@@ -118,8 +121,9 @@ case "${1:-default}" in
-e "STTRACE=$STTRACE" \
syncthing/build:latest \
sh -c './build.sh clean \
&& ./build.sh all \
&& STTRACE=all ./build.sh test-cov'
&& ./build.sh test-cov \
&& ./build.sh bench \
&& ./build.sh all'
;;
docker-test)

View File

@@ -29,7 +29,7 @@ print-missing-authors() {
}
print-missing-copyright() {
find . -name \*.go | xargs egrep -L 'Copyright \(C\)|automatically generated' | grep -v Godeps | grep -v internal/auto/
find . -name \*.go | xargs egrep -L 'Copyright|automatically generated' | grep -v Godeps | grep -v internal/auto/
}
authors=$(print-missing-authors)

View File

@@ -279,7 +279,7 @@ func main() {
l.Fatalln(dir, "is not a directory")
}
if err != nil && os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
err = osutil.MkdirAll(dir, 0700)
if err != nil {
l.Fatalln("generate:", err)
}
@@ -714,9 +714,7 @@ func syncthingMain() {
func dbOpts() *opt.Options {
// Calculate a suitable database block cache capacity.
// Default is 8 MiB. In reality, the database will use twice the amount we
// calculate here, as it also has two write buffers each sized at half the
// block cache.
// Default is 8 MiB.
blockCacheCapacity := 8 << 20
// Increase block cache up to this maximum:
const maxCapacity = 64 << 20
@@ -743,7 +741,7 @@ func dbOpts() *opt.Options {
return &opt.Options{
OpenFilesCacheCapacity: 100,
BlockCacheCapacity: blockCacheCapacity,
WriteBuffer: blockCacheCapacity / 2,
WriteBuffer: 4 << 20,
}
}
@@ -882,7 +880,7 @@ func discovery(extPort int) *discover.Discoverer {
func ensureDir(dir string, mode int) {
fi, err := os.Stat(dir)
if os.IsNotExist(err) {
err := os.MkdirAll(dir, 0700)
err := osutil.MkdirAll(dir, 0700)
if err != nil {
l.Fatalln(err)
}

View File

@@ -92,7 +92,7 @@
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<small>{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}:</small>
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
Device {%device%} ({%address%}) wants to connect. Add new device?
<span>
@@ -122,7 +122,7 @@
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<small>{{ event.time | date:"yyyy-MM-dd HH:mm:ss" }}:</small>
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
{%device%} wants to share folder "{%folder%}".
</span>
@@ -154,7 +154,7 @@
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
<p ng-repeat="err in errorList()"><small>{{err.time | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>OK</span></button>
@@ -265,7 +265,7 @@
<tr ng-if="!folder.readOnly && folderStats[folder.id].lastFile">
<th><span class="glyphicon glyphicon-transfer"></span>&emsp;<span translate>Last File Received</span></th>
<td class="text-right">
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm'}}">
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
{{folderStats[folder.id].lastFile.filename | basename}}
</span>
</td>
@@ -403,7 +403,7 @@
<tr ng-if="!connections[deviceCfg.deviceID]">
<th><span class="glyphicon glyphicon-eye-open"></span>&emsp;<span translate>Last seen</span></th>
<td translate ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays || deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" class="text-right">Never</td>
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm"}}</td>
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm:ss"}}</td>
</tr>
<tr ng-if="deviceFolders(deviceCfg).length > 0">
<th><span class="glyphicon glyphicon-hdd"></span>&emsp;<span translate>Folders</span></th>

View File

@@ -12,6 +12,7 @@ import (
"errors"
"io"
"net"
"runtime"
"strconv"
"time"
@@ -110,7 +111,8 @@ func (d *Discoverer) startLocalIPv6Multicasts(localMCAddr string) {
v6Intfs := 0
for _, intf := range intfs {
if intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0 {
// Interface flags seem to always be 0 on Windows
if runtime.GOOS != "windows" && (intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0) {
continue
}

View File

@@ -100,9 +100,10 @@ type Event struct {
}
type Subscription struct {
mask EventType
id int
events chan Event
mask EventType
id int
events chan Event
timeout *time.Timer
}
var Default = NewLogger()
@@ -149,9 +150,10 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
dl.Debugln("subscribe", mask)
}
s := &Subscription{
mask: mask,
id: l.nextID,
events: make(chan Event, BufferSize),
mask: mask,
id: l.nextID,
events: make(chan Event, BufferSize),
timeout: time.NewTimer(0),
}
l.nextID++
l.subs[s.id] = s
@@ -169,19 +171,22 @@ func (l *Logger) Unsubscribe(s *Subscription) {
l.mutex.Unlock()
}
// Poll returns an event from the subscription or an error if the poll times
// out of the event channel is closed. Poll should not be called concurrently
// from multiple goroutines for a single subscription.
func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
if debug {
dl.Debugln("poll", timeout)
}
to := time.After(timeout)
s.timeout.Reset(timeout)
select {
case e, ok := <-s.events:
if !ok {
return e, ErrClosed
}
return e, nil
case <-to:
case <-s.timeout.C:
return Event{}, ErrTimeout
}
}

View File

@@ -1611,7 +1611,7 @@ func (m *Model) CheckFolderHealth(id string) error {
} else if os.IsNotExist(err) {
// If we don't have any files in the index, and the directory
// doesn't exist, try creating it.
err = os.MkdirAll(folder.Path(), 0700)
err = osutil.MkdirAll(folder.Path(), 0700)
if err == nil {
err = folder.CreateMarker()
}

View File

@@ -176,74 +176,58 @@ func genFiles(n int) []protocol.FileInfo {
return files
}
func BenchmarkIndex10000(b *testing.B) {
func BenchmarkIndex_10000(b *testing.B) {
benchmarkIndex(b, 10000)
}
func BenchmarkIndex_100(b *testing.B) {
benchmarkIndex(b, 100)
}
func benchmarkIndex(b *testing.B, nfiles int) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(10000)
m.StartFolderRO("default")
files := genFiles(nfiles)
m.Index(device1, "default", files, 0, nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Index(device1, "default", files, 0, nil)
}
b.ReportAllocs()
}
func BenchmarkIndex00100(b *testing.B) {
func BenchmarkIndexUpdate_10000_10000(b *testing.B) {
benchmarkIndexUpdate(b, 10000, 10000)
}
func BenchmarkIndexUpdate_10000_100(b *testing.B) {
benchmarkIndexUpdate(b, 10000, 100)
}
func BenchmarkIndexUpdate_10000_1(b *testing.B) {
benchmarkIndexUpdate(b, 10000, 1)
}
func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(100)
m.StartFolderRO("default")
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Index(device1, "default", files, 0, nil)
}
}
files := genFiles(nfiles)
ufiles := genFiles(nufiles)
func BenchmarkIndexUpdate10000f10000(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(10000)
m.Index(device1, "default", files, 0, nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.IndexUpdate(device1, "default", files, 0, nil)
}
}
func BenchmarkIndexUpdate10000f00100(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(10000)
m.Index(device1, "default", files, 0, nil)
ufiles := genFiles(100)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.IndexUpdate(device1, "default", ufiles, 0, nil)
}
}
func BenchmarkIndexUpdate10000f00001(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(10000)
m.Index(device1, "default", files, 0, nil)
ufiles := genFiles(1)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.IndexUpdate(device1, "default", ufiles, 0, nil)
}
b.ReportAllocs()
}
type FakeConnection struct {
@@ -1166,56 +1150,23 @@ func genDeepFiles(n, d int) []protocol.FileInfo {
}
func BenchmarkTree_10000_50(b *testing.B) {
benchmarkTree(b, 10000, 50)
}
func BenchmarkTree_100_50(b *testing.B) {
benchmarkTree(b, 100, 50)
}
func BenchmarkTree_100_10(b *testing.B) {
benchmarkTree(b, 100, 10)
}
func benchmarkTree(b *testing.B, n1, n2 int) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genDeepFiles(10000, 50)
m.Index(device1, "default", files, 0, nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.GlobalDirectoryTree("default", "", -1, false)
}
}
func BenchmarkTree_10000_10(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genDeepFiles(10000, 10)
m.Index(device1, "default", files, 0, nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.GlobalDirectoryTree("default", "", -1, false)
}
}
func BenchmarkTree_00100_50(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genDeepFiles(100, 50)
m.Index(device1, "default", files, 0, nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.GlobalDirectoryTree("default", "", -1, false)
}
}
func BenchmarkTree_00100_10(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genDeepFiles(100, 10)
files := genDeepFiles(n1, n2)
m.Index(device1, "default", files, 0, nil)
@@ -1223,4 +1174,5 @@ func BenchmarkTree_00100_10(b *testing.B) {
for i := 0; i < b.N; i++ {
m.GlobalDirectoryTree("default", "", -1, false)
}
b.ReportAllocs()
}

View File

@@ -631,8 +631,16 @@ func (p *rwFolder) deleteDir(file protocol.FileInfo) {
}
}
}
err = osutil.InWritableDir(osutil.Remove, realName)
if err == nil || os.IsNotExist(err) {
// It was removed or it doesn't exist to start with
p.dbUpdates <- file
} else if _, err = os.Lstat(realName); err != nil && !os.IsPermission(err) {
// We get an error just looking at the directory, and it's not a
// permission problem. Lets assume the error is in fact some variant
// of "file does not exist" (possibly expressed as some parent being a
// file and not a directory etc) and that the delete is handled.
p.dbUpdates <- file
} else {
l.Infof("Puller (folder %q, dir %q): delete: %v", p.folder, file.Name, err)
@@ -673,10 +681,17 @@ func (p *rwFolder) deleteFile(file protocol.FileInfo) {
err = osutil.InWritableDir(osutil.Remove, realName)
}
if err != nil && !os.IsNotExist(err) {
l.Infof("Puller (folder %q, file %q): delete: %v", p.folder, file.Name, err)
} else {
if err == nil || os.IsNotExist(err) {
// It was removed or it doesn't exist to start with
p.dbUpdates <- file
} else if _, err := os.Lstat(realName); err != nil && !os.IsPermission(err) {
// We get an error just looking at the file, and it's not a permission
// problem. Lets assume the error is in fact some variant of "file
// does not exist" (possibly expressed as some parent being a file and
// not a directory etc) and that the delete is handled.
p.dbUpdates <- file
} else {
l.Infof("Puller (folder %q, file %q): delete: %v", p.folder, file.Name, err)
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build !windows
package osutil
import (
"os"
)
func MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}

View File

@@ -0,0 +1,65 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Modified by Zillode to fix https://github.com/syncthing/syncthing/issues/1822
// Sync with https://github.com/golang/go/blob/master/src/os/path.go
// See https://github.com/golang/go/issues/10900
package osutil
import (
"os"
"path/filepath"
"syscall"
)
// MkdirAll creates a directory named path, along with any necessary parents,
// and returns nil, or else returns an error.
// The permission bits perm are used for all directories that MkdirAll creates.
// If path is already a directory, MkdirAll does nothing and returns nil.
func MkdirAll(path string, perm os.FileMode) error {
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
dir, err := os.Stat(path)
if err == nil {
if dir.IsDir() {
return nil
}
return &os.PathError{"mkdir", path, syscall.ENOTDIR}
}
// Slow path: make sure parent exists and then call Mkdir for path.
i := len(path)
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
i--
}
j := i
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
j--
}
if j > 1 {
// Create parent
parent := path[0 : j-1]
if parent != filepath.VolumeName(parent) {
err = MkdirAll(parent, perm)
if err != nil {
return err
}
}
}
// Parent now exists; invoke Mkdir and use its result.
err = os.Mkdir(path, perm)
if err != nil {
// Handle arguments like "foo/." by
// double-checking that directory doesn't exist.
dir, err1 := os.Lstat(path)
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
return nil
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/ignore"
"github.com/syncthing/syncthing/internal/osutil"
"golang.org/x/text/unicode/norm"
)
@@ -218,7 +219,7 @@ func TestNormalization(t *testing.T) {
for _, s1 := range tests {
// Create a directory for each of the interesting strings above
if err := os.MkdirAll(filepath.Join("testdata/normalization", s1), 0755); err != nil {
if err := osutil.MkdirAll(filepath.Join("testdata/normalization", s1), 0755); err != nil {
t.Fatal(err)
}

View File

@@ -21,6 +21,7 @@ import (
"net/http"
"net/url"
"regexp"
"runtime"
"strings"
"time"
@@ -102,37 +103,11 @@ func Discover(timeout time.Duration) []IGD {
resultChan := make(chan IGD)
// Aggregator
go func() {
next:
for result := range resultChan {
for _, existingResult := range results {
if existingResult.uuid == result.uuid {
if debug {
l.Debugf("Skipping duplicate result %s with services:", result.uuid)
for _, svc := range result.services {
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
}
}
goto next
}
}
results = append(results, result)
if debug {
l.Debugf("UPnP discovery result %s with services:", result.uuid)
for _, svc := range result.services {
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
}
}
}
}()
wg := sync.NewWaitGroup()
for _, intf := range interfaces {
if intf.Flags&net.FlagUp == 0 {
continue
}
if intf.Flags&net.FlagMulticast == 0 {
// Interface flags seem to always be 0 on Windows
if runtime.GOOS != "windows" && (intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagMulticast == 0) {
continue
}
@@ -145,8 +120,33 @@ func Discover(timeout time.Duration) []IGD {
}
}
wg.Wait()
close(resultChan)
go func() {
wg.Wait()
close(resultChan)
}()
nextResult:
for result := range resultChan {
for _, existingResult := range results {
if existingResult.uuid == result.uuid {
if debug {
l.Debugf("Skipping duplicate result %s with services:", result.uuid)
for _, svc := range result.services {
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
}
}
continue nextResult
}
}
results = append(results, result)
if debug {
l.Debugf("UPnP discovery result %s with services:", result.uuid)
for _, svc := range result.services {
l.Debugf("* [%s] %s", svc.serviceID, svc.serviceURL)
}
}
}
return results
}
@@ -472,7 +472,7 @@ func soapRequest(url, service, function, message string) ([]byte, error) {
resp, _ = ioutil.ReadAll(r.Body)
if debug {
l.Debugln("SOAP Response:\n\n" + string(resp) + "\n")
l.Debugf("SOAP Response: %v\n\n%v\n\n", r.StatusCode, string(resp))
}
r.Body.Close()
@@ -528,6 +528,11 @@ type getExternalIPAddressResponse struct {
NewExternalIPAddress string `xml:"NewExternalIPAddress"`
}
type soapErrorResponse struct {
ErrorCode int `xml:"Body>Fault>detail>UPnPError>errorCode"`
ErrorDescription string `xml:"Body>Fault>detail>UPnPError>errorDescription"`
}
// AddPortMapping adds a port mapping to the specified IGD service.
func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
tpl := `<u:AddPortMapping xmlns:u="%s">
@@ -542,12 +547,20 @@ func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, ex
</u:AddPortMapping>`
body := fmt.Sprintf(tpl, s.serviceURN, externalPort, protocol, internalPort, localIPAddress, description, timeout)
_, err := soapRequest(s.serviceURL, s.serviceURN, "AddPortMapping", body)
if err != nil {
return err
response, err := soapRequest(s.serviceURL, s.serviceURN, "AddPortMapping", body)
if err != nil && timeout > 0 {
// Try to repair error code 725 - OnlyPermanentLeasesSupported
envelope := &soapErrorResponse{}
err = xml.Unmarshal(response, envelope)
if err != nil {
return err
}
if envelope.ErrorCode == 725 {
return s.AddPortMapping(localIPAddress, protocol, externalPort, internalPort, description, 0)
}
}
return nil
return err
}
// DeletePortMapping deletes a port mapping from the specified IGD service.

View File

@@ -33,6 +33,33 @@ func TestExternalIPParsing(t *testing.T) {
}
}
func TestSoapFaultParsing(t *testing.T) {
soapResponse :=
[]byte(`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<s:Fault>
<faultcode>s:Client</faultcode>
<faultstring>UPnPError</faultstring>
<detail>
<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
<errorCode>725</errorCode>
<errorDescription>OnlyPermanentLeasesSupported</errorDescription></UPnPError>
</detail>
</s:Fault>
</s:Body>
</s:Envelope>`)
envelope := &soapErrorResponse{}
err := xml.Unmarshal(soapResponse, envelope)
if err != nil {
t.Error(err)
}
if envelope.ErrorCode != 725 {
t.Error("Parse of SOAP request failed.", envelope)
}
}
func TestControlURLParsing(t *testing.T) {
rootURL := "http://192.168.243.1:80/igd.xml"

View File

@@ -61,7 +61,7 @@ func (v Simple) Archive(filePath string) error {
if debug {
l.Debugln("creating versions dir", versionsDir)
}
os.MkdirAll(versionsDir, 0755)
osutil.MkdirAll(versionsDir, 0755)
osutil.HideFile(versionsDir)
} else {
return err
@@ -79,7 +79,7 @@ func (v Simple) Archive(filePath string) error {
}
dir := filepath.Join(versionsDir, inFolderPath)
err = os.MkdirAll(dir, 0755)
err = osutil.MkdirAll(dir, 0755)
if err != nil && !os.IsExist(err) {
return err
}

View File

@@ -257,7 +257,7 @@ func (v Staggered) Archive(filePath string) error {
if debug {
l.Debugln("creating versions dir", v.versionsPath)
}
os.MkdirAll(v.versionsPath, 0755)
osutil.MkdirAll(v.versionsPath, 0755)
osutil.HideFile(v.versionsPath)
} else {
return err
@@ -275,7 +275,7 @@ func (v Staggered) Archive(filePath string) error {
}
dir := filepath.Join(v.versionsPath, inFolderPath)
err = os.MkdirAll(dir, 0755)
err = osutil.MkdirAll(dir, 0755)
if err != nil && !os.IsExist(err) {
return err
}

View File

@@ -53,7 +53,7 @@ func TestConflict(t *testing.T) {
defer sender.stop()
defer receiver.stop()
if err = coCompletion(sender, receiver); err != nil {
if err = awaitCompletion("default", sender, receiver); err != nil {
t.Fatal(err)
}
@@ -111,7 +111,7 @@ func TestConflict(t *testing.T) {
t.Fatal(err)
}
if err = coCompletion(sender, receiver); err != nil {
if err = awaitCompletion("default", sender, receiver); err != nil {
t.Fatal(err)
}
@@ -161,7 +161,7 @@ func TestConflict(t *testing.T) {
t.Fatal(err)
}
if err = coCompletion(sender, receiver); err != nil {
if err = awaitCompletion("default", sender, receiver); err != nil {
t.Fatal(err)
}
@@ -230,7 +230,7 @@ func TestInitialMergeConflicts(t *testing.T) {
log.Println("Syncing...")
if err = coCompletion(sender, receiver); err != nil {
if err = awaitCompletion("default", sender, receiver); err != nil {
t.Fatal(err)
}
@@ -309,7 +309,7 @@ func TestResetConflicts(t *testing.T) {
log.Println("Syncing...")
if err = coCompletion(sender, receiver); err != nil {
if err = awaitCompletion("default", sender, receiver); err != nil {
t.Fatal(err)
}
@@ -369,7 +369,7 @@ func TestResetConflicts(t *testing.T) {
}
time.Sleep(time.Second)
if err = coCompletion(sender, receiver); err != nil {
if err = awaitCompletion("default", sender, receiver); err != nil {
t.Fatal(err)
}
@@ -404,7 +404,7 @@ func TestResetConflicts(t *testing.T) {
log.Println("Syncing...")
if err = coCompletion(sender, receiver); err != nil {
if err = awaitCompletion("default", sender, receiver); err != nil {
t.Fatal(err)
}
@@ -467,31 +467,3 @@ func coSenderReceiver(t *testing.T) (syncthingProcess, syncthingProcess) {
return sender, receiver
}
func coCompletion(p ...syncthingProcess) error {
mainLoop:
for {
time.Sleep(2500 * time.Millisecond)
tot := 0
for i := range p {
comp, err := p[i].peerCompletion()
if err != nil {
if isTimeout(err) {
continue mainLoop
}
return err
}
for _, pct := range comp {
tot += pct
}
}
if tot == 100*(len(p)) {
return nil
}
log.Printf("%d / %d...", tot, 100*(len(p)))
}
}

View File

@@ -12,7 +12,6 @@ import (
"log"
"os"
"testing"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
@@ -113,6 +112,7 @@ func testFileTypeChange(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer sender.stop()
receiver := syncthingProcess{ // id2
instance: "2",
@@ -125,28 +125,11 @@ func testFileTypeChange(t *testing.T) {
sender.stop()
t.Fatal(err)
}
defer receiver.stop()
for {
comp, err := sender.peerCompletion()
if err != nil {
if isTimeout(err) {
time.Sleep(time.Second)
continue
}
sender.stop()
receiver.stop()
t.Fatal(err)
}
curComp := comp[id2]
if curComp == 100 {
sender.stop()
receiver.stop()
break
}
time.Sleep(time.Second)
err = awaitCompletion("default", sender, receiver)
if err != nil {
t.Fatal(err)
}
_, err = sender.stop()
@@ -212,36 +195,10 @@ func testFileTypeChange(t *testing.T) {
err = receiver.start()
if err != nil {
sender.stop()
t.Fatal(err)
}
for {
comp, err := sender.peerCompletion()
if err != nil {
if isTimeout(err) {
time.Sleep(time.Second)
continue
}
sender.stop()
receiver.stop()
t.Fatal(err)
}
curComp := comp[id2]
if curComp == 100 {
break
}
time.Sleep(time.Second)
}
_, err = sender.stop()
if err != nil {
t.Fatal(err)
}
_, err = receiver.stop()
err = awaitCompletion("default", sender, receiver)
if err != nil {
t.Fatal(err)
}

View File

@@ -14,7 +14,11 @@ import (
"io/ioutil"
"net/http"
"strings"
"sync"
"testing"
"time"
"github.com/syncthing/protocol"
)
var jsonEndpoints = []string{
@@ -172,11 +176,13 @@ func TestGetJSON(t *testing.T) {
for _, path := range jsonEndpoints {
res, err := st.get(path)
if err != nil {
t.Error(err)
t.Error(path, err)
continue
}
if ct := res.Header.Get("Content-Type"); ct != "application/json; charset=utf-8" {
t.Errorf("Incorrect Content-Type %q for %q", ct, path)
continue
}
var intf interface{}
@@ -184,7 +190,7 @@ func TestGetJSON(t *testing.T) {
res.Body.Close()
if err != nil {
t.Error(err)
t.Error(path, err)
}
}
}
@@ -264,3 +270,92 @@ func TestPOSTWithoutCSRF(t *testing.T) {
t.Fatalf("Status %d != 403 for POST", res.StatusCode)
}
}
var (
initOnce sync.Once
proc syncthingProcess
)
func setupAPIBench() {
err := removeAll("s1", "s2", "h1/index*", "h2/index*")
if err != nil {
panic(err)
}
err = generateFiles("s1", 25000, 20, "../LICENSE")
if err != nil {
panic(err)
}
err = ioutil.WriteFile("s1/knownfile", []byte("somedatahere"), 0644)
if err != nil {
panic(err)
}
proc = syncthingProcess{ // id1
instance: "1",
argv: []string{"-home", "h1"},
port: 8081,
apiKey: apiKey,
}
err = proc.start()
if err != nil {
panic(err)
}
// Wait for one scan to succeed, or up to 20 seconds... This is to let
// startup, UPnP etc complete and make sure the sender has the full index
// before they connect.
for i := 0; i < 20; i++ {
resp, err := proc.post("/rest/scan?folder=default", nil)
if err != nil {
time.Sleep(time.Second)
continue
}
if resp.StatusCode != 200 {
resp.Body.Close()
time.Sleep(time.Second)
continue
}
break
}
}
func benchmarkURL(b *testing.B, url string) {
initOnce.Do(setupAPIBench)
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := proc.get(url)
if err != nil {
b.Fatal(err)
}
if resp.StatusCode != 200 {
b.Fatal(resp.Status)
}
resp.Body.Close()
}
}
func BenchmarkAPI_db_completion(b *testing.B) {
benchmarkURL(b, "/rest/db/completion?folder=default&device="+protocol.LocalDeviceID.String())
}
func BenchmarkAPI_db_file(b *testing.B) {
benchmarkURL(b, "/rest/db/file?folder=default&file=knownfile")
}
func BenchmarkAPI_db_ignores(b *testing.B) {
benchmarkURL(b, "/rest/db/ignores?folder=default")
}
func BenchmarkAPI_db_need(b *testing.B) {
benchmarkURL(b, "/rest/db/need?folder=default")
}
func BenchmarkAPI_db_status(b *testing.B) {
benchmarkURL(b, "/rest/db/status?folder=default")
}
func BenchmarkAPI_db_browse_dirsonly(b *testing.B) {
benchmarkURL(b, "/rest/db/browse?folder=default&dirsonly=true")
}

View File

@@ -13,7 +13,6 @@ import (
"encoding/json"
"log"
"testing"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
@@ -92,19 +91,9 @@ func TestManyPeers(t *testing.T) {
}
defer sender.stop()
for {
comp, err := sender.peerCompletion()
if err != nil {
if isTimeout(err) {
time.Sleep(250 * time.Millisecond)
continue
}
t.Fatal(err)
}
if comp[id2] == 100 {
return
}
time.Sleep(2 * time.Second)
err = awaitCompletion("default", sender, receiver)
if err != nil {
t.Fatal(err)
}
log.Println("Comparing directories...")

View File

@@ -102,7 +102,8 @@ func TestOverride(t *testing.T) {
log.Println("Syncing...")
if err = ovCompletion(100, master, slave); err != nil {
err = awaitCompletion("default", master, slave)
if err != nil {
t.Fatal(err)
}
@@ -137,14 +138,9 @@ func TestOverride(t *testing.T) {
t.Fatal(err)
}
log.Println("Syncing...")
log.Println("Waiting for index to send...")
time.Sleep(5 * time.Second)
// Expect ~99% completion since the change will be rejected by the master side
if err = ovCompletion(99, master, slave); err != nil {
t.Fatal(err)
}
time.Sleep(10 * time.Second)
log.Println("Hitting Override on master...")
@@ -158,7 +154,8 @@ func TestOverride(t *testing.T) {
log.Println("Syncing...")
if err = ovCompletion(100, master, slave); err != nil {
err = awaitCompletion("default", master, slave)
if err != nil {
t.Fatal(err)
}
@@ -193,6 +190,9 @@ func TestOverride(t *testing.T) {
}
}
/* This doesn't currently work with detection completion, as we don't actually
get to completion when in master/slave mode. Needs fixing.
func TestOverrideIgnores(t *testing.T) {
// Enable "Master" on s1/default
id, _ := protocol.DeviceIDFromString(id1)
@@ -247,18 +247,6 @@ func TestOverrideIgnores(t *testing.T) {
}
defer master.stop()
// Wait for one scan to succeed, or up to 20 seconds... This is to let
// startup, UPnP etc complete and make sure the master has the full index
// before they connect.
for i := 0; i < 20; i++ {
err := master.rescan("default")
if err != nil {
time.Sleep(time.Second)
continue
}
break
}
log.Println("Starting slave...")
slave := syncthingProcess{ // id2
instance: "2",
@@ -275,7 +263,8 @@ func TestOverrideIgnores(t *testing.T) {
log.Println("Syncing...")
if err = ovCompletion(100, master, slave); err != nil {
err = awaitCompletion("default", master, slave)
if err != nil {
t.Fatal(err)
}
@@ -335,12 +324,8 @@ func TestOverrideIgnores(t *testing.T) {
err = master.rescan("default")
log.Println("Syncing...")
// Expect 100% completion since the change should be invisible to the master side
if err = ovCompletion(100, master, slave); err != nil {
t.Fatal(err)
}
log.Println("Waiting for sync...")
time.Sleep(10 * time.Second)
// Verify that sync worked
@@ -381,12 +366,8 @@ func TestOverrideIgnores(t *testing.T) {
t.Fatal(err)
}
log.Println("Syncing...")
// Expect 100% completion since the change should be invisible to the master side
if err = ovCompletion(100, master, slave); err != nil {
t.Fatal(err)
}
log.Println("Waiting for sync...")
time.Sleep(10 * time.Second)
// Verify that nothing changed
@@ -434,12 +415,8 @@ func TestOverrideIgnores(t *testing.T) {
t.Fatal(resp.Status)
}
log.Println("Syncing...")
// Expect ~99% completion since the change will be rejected by the master side
if err = ovCompletion(99, master, slave); err != nil {
t.Fatal(err)
}
log.Println("Waiting for sync...")
time.Sleep(10 * time.Second)
fd, err = os.Open("s2/testfile.txt")
if err == nil {
@@ -473,12 +450,8 @@ func TestOverrideIgnores(t *testing.T) {
t.Fatal(resp.Status)
}
log.Println("Syncing...")
// Expect ~99% completion since the change will be rejected by the master side
if err = ovCompletion(99, master, slave); err != nil {
t.Fatal(err)
}
log.Println("Waiting for sync...")
time.Sleep(10 * time.Second)
fd, err = os.Open("s2/testfile.txt")
if err == nil {
@@ -503,31 +476,4 @@ func TestOverrideIgnores(t *testing.T) {
fd.Close()
}
func ovCompletion(expected int, p ...syncthingProcess) error {
mainLoop:
for {
time.Sleep(2500 * time.Millisecond)
tot := 0
for i := range p {
comp, err := p[i].peerCompletion()
if err != nil {
if isTimeout(err) {
continue mainLoop
}
return err
}
for _, pct := range comp {
tot += pct
}
}
if tot >= expected*len(p) {
return nil
}
log.Printf("%d / %d...", tot, expected*len(p))
}
}
*/

View File

@@ -54,6 +54,9 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
if err != nil {
t.Fatal(err)
}
defer sender.stop()
waitForScan(sender)
receiver := syncthingProcess{ // id2
instance: "2",
@@ -63,38 +66,24 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
}
err = receiver.start()
if err != nil {
sender.stop()
t.Fatal(err)
}
defer receiver.stop()
var prevComp int
var prevBytes int
for {
comp, err := sender.peerCompletion()
recv, err := receiver.dbStatus("default")
if err != nil {
if isTimeout(err) {
time.Sleep(250 * time.Millisecond)
continue
}
sender.stop()
receiver.stop()
t.Fatal(err)
}
curComp := comp[id2]
if curComp == 100 {
_, err = sender.stop()
if err != nil {
t.Fatal(err)
}
_, err = receiver.stop()
if err != nil {
t.Fatal(err)
}
if recv.InSyncBytes > 0 && recv.InSyncBytes == recv.GlobalBytes {
// Receiver is done
break
}
} else if recv.InSyncBytes > prevBytes+recv.GlobalBytes/10 {
// Receiver has made progress
prevBytes = recv.InSyncBytes
if curComp > prevComp {
if restartReceiver {
log.Printf("Stopping receiver...")
_, err = receiver.stop()
@@ -134,8 +123,6 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
}
wg.Wait()
prevComp = curComp
}
time.Sleep(time.Second)

View File

@@ -13,7 +13,6 @@ import (
"os"
"path/filepath"
"testing"
"time"
)
func TestReset(t *testing.T) {
@@ -41,13 +40,13 @@ func TestReset(t *testing.T) {
// startup, UPnP etc complete and make sure that we've performed folder
// error checking which creates the folder path if it's missing.
log.Println("Starting...")
waitForScan(t, &p)
waitForScan(p)
log.Println("Creating files...")
size := createFiles(t)
log.Println("Scanning files...")
waitForScan(t, &p)
waitForScan(p)
m, err := p.model("default")
if err != nil {
@@ -90,7 +89,7 @@ func TestReset(t *testing.T) {
// Wait for ST and scan
p.start()
waitForScan(t, &p)
waitForScan(p)
// Verify that we see them
m, err = p.model("default")
@@ -105,7 +104,7 @@ func TestReset(t *testing.T) {
// Recreate the files and scan
log.Println("Creating files...")
size = createFiles(t)
waitForScan(t, &p)
waitForScan(p)
// Verify that we see them
m, err = p.model("default")
@@ -126,7 +125,7 @@ func TestReset(t *testing.T) {
// Wait for ST and scan
p.start()
waitForScan(t, &p)
waitForScan(p)
m, err = p.model("default")
if err != nil {
@@ -138,18 +137,6 @@ func TestReset(t *testing.T) {
}
}
func waitForScan(t *testing.T, p *syncthingProcess) {
// Wait for one scan to succeed, or up to 20 seconds...
for i := 0; i < 20; i++ {
err := p.rescan("default")
if err != nil {
time.Sleep(time.Second)
continue
}
break
}
}
func createFiles(t *testing.T) int {
// Create eight empty files and directories
files := []string{"f1", "f2", "f3", "f4", "f11", "f12", "f13", "f14"}

View File

@@ -14,7 +14,6 @@ import (
"os"
"path/filepath"
"testing"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
@@ -177,6 +176,7 @@ func testSymlinks(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer sender.stop()
receiver := syncthingProcess{ // id2
instance: "2",
@@ -186,29 +186,13 @@ func testSymlinks(t *testing.T) {
}
err = receiver.start()
if err != nil {
sender.stop()
t.Fatal(err)
}
defer receiver.stop()
for {
comp, err := sender.peerCompletion()
if err != nil {
if isTimeout(err) {
time.Sleep(time.Second)
continue
}
sender.stop()
receiver.stop()
t.Fatal(err)
}
curComp := comp[id2]
if curComp == 100 {
break
}
time.Sleep(time.Second)
err = awaitCompletion("default", sender, receiver)
if err != nil {
t.Fatal(err)
}
_, err = sender.stop()
@@ -311,29 +295,12 @@ func testSymlinks(t *testing.T) {
err = receiver.start()
if err != nil {
sender.stop()
t.Fatal(err)
}
for {
comp, err := sender.peerCompletion()
if err != nil {
if isTimeout(err) {
time.Sleep(time.Second)
continue
}
sender.stop()
receiver.stop()
t.Fatal(err)
}
curComp := comp[id2]
if curComp == 100 {
break
}
time.Sleep(time.Second)
err = awaitCompletion("default", sender, receiver)
if err != nil {
t.Fatal(err)
}
_, err = sender.stop()

View File

@@ -163,17 +163,8 @@ func testSyncCluster(t *testing.T) {
}()
log.Println("Waiting for startup...")
// Wait for one scan to succeed, or up to 20 seconds...
// This is to let startup, UPnP etc complete.
for _, device := range p {
for i := 0; i < 20; i++ {
err := device.rescan("default")
if err != nil {
time.Sleep(time.Second)
continue
}
break
}
for _, dev := range p {
waitForScan(dev)
}
for count := 0; count < iterations; count++ {
@@ -310,15 +301,15 @@ func scStartProcesses() ([]syncthingProcess, error) {
func scSyncAndCompare(p []syncthingProcess, expected [][]fileInfo) error {
log.Println("Syncing...")
for {
time.Sleep(2 * time.Second)
if err := allDevicesInSync(p); err != nil {
log.Println(err)
continue
}
break
// Special handling because we know which devices share which folders...
if err := awaitCompletion("default", p...); err != nil {
return err
}
if err := awaitCompletion("s12", p[0], p[1]); err != nil {
return err
}
if err := awaitCompletion("s23", p[1], p[2]); err != nil {
return err
}
// This is necessary, or all files won't be in place even when everything

View File

@@ -66,7 +66,7 @@ func (p *syncthingProcess) start() error {
binary = binary + "-" + p.instance + ".exe"
}
argv := append(p.argv, "-no-browser")
argv := append(p.argv, "-no-browser", "-verbose")
cmd := exec.Command(binary, argv...)
cmd.Stdout = p.logfd
cmd.Stderr = p.logfd
@@ -203,40 +203,6 @@ func (p *syncthingProcess) post(path string, data io.Reader) (*http.Response, er
return resp, nil
}
func (p *syncthingProcess) peerCompletion() (map[string]int, error) {
resp, err := p.get("/rest/debug/peerCompletion")
if err != nil {
return nil, err
}
defer resp.Body.Close()
comp := map[string]int{}
err = json.NewDecoder(resp.Body).Decode(&comp)
// Remove ourselves from the set. In the remaining map, all peers should
// be att 100% if we're in sync.
for id := range comp {
if id == p.id.String() {
delete(comp, id)
}
}
return comp, err
}
func (p *syncthingProcess) allPeersInSync() error {
comp, err := p.peerCompletion()
if err != nil {
return err
}
for id, val := range comp {
if val != 100 {
return fmt.Errorf("%.7s at %d%%", id, val)
}
}
return nil
}
type model struct {
GlobalBytes int
GlobalDeleted int
@@ -311,6 +277,35 @@ func (p *syncthingProcess) version() (string, error) {
return v.Version, nil
}
type statusResp struct {
GlobalBytes int
InSyncBytes int
Version int
}
func (p *syncthingProcess) dbStatus(folder string) (statusResp, error) {
resp, err := p.get("/rest/db/status?folder=" + folder)
if err != nil {
return statusResp{}, err
}
defer resp.Body.Close()
var s statusResp
err = json.NewDecoder(resp.Body).Decode(&s)
if err != nil {
return statusResp{}, err
}
return s, nil
}
func (p *syncthingProcess) insync(folder string) (bool, int, error) {
s, err := p.dbStatus(folder)
if err != nil {
return false, 0, err
}
return s.GlobalBytes == s.InSyncBytes, s.Version, nil
}
func (p *syncthingProcess) rescan(folder string) error {
resp, err := p.post("/rest/db/scan?folder="+folder, nil)
if err != nil {
@@ -350,11 +345,46 @@ func (p *syncthingProcess) reset(folder string) error {
return nil
}
func allDevicesInSync(p []syncthingProcess) error {
for _, device := range p {
if err := device.allPeersInSync(); err != nil {
return fmt.Errorf("%.7s: %v", device.id.String(), err)
func awaitCompletion(folder string, ps ...syncthingProcess) error {
mainLoop:
for {
time.Sleep(2500 * time.Millisecond)
expectedVersion := 0
for _, p := range ps {
insync, version, err := p.insync(folder)
if err != nil {
if isTimeout(err) {
continue mainLoop
}
return err
}
if !insync {
continue mainLoop
}
if expectedVersion == 0 {
expectedVersion = version
} else if version != expectedVersion {
// Version number mismatch between devices, so not in sync.
continue mainLoop
}
}
return nil
}
}
func waitForScan(p syncthingProcess) {
// Wait for one scan to succeed, or up to 20 seconds...
for i := 0; i < 20; i++ {
err := p.rescan("default")
if err != nil {
time.Sleep(time.Second)
continue
}
break
}
return nil
}