mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-08 22:31:04 +00:00
This commit is contained in:
parent
0fe4c01a28
commit
ff2cde469e
@ -313,6 +313,7 @@
|
|||||||
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs">◼</span></span>
|
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs">◼</span></span>
|
||||||
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">◼</span></span>
|
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">◼</span></span>
|
||||||
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">◼</span></span>
|
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">◼</span></span>
|
||||||
|
<span ng-switch-when="scan-waiting"><span class="hidden-xs" translate>Waiting to scan</span><span class="visible-xs">◼</span></span>
|
||||||
<span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs">◼</span></span>
|
<span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs">◼</span></span>
|
||||||
<span ng-switch-when="scanning">
|
<span ng-switch-when="scanning">
|
||||||
<span class="hidden-xs" translate>Scanning</span>
|
<span class="hidden-xs" translate>Scanning</span>
|
||||||
|
@ -769,7 +769,7 @@ angular.module('syncthing.core')
|
|||||||
if (status === 'stopped' || status === 'outofsync' || status === 'error') {
|
if (status === 'stopped' || status === 'outofsync' || status === 'error') {
|
||||||
return 'danger';
|
return 'danger';
|
||||||
}
|
}
|
||||||
if (status === 'unshared') {
|
if (status === 'unshared' || status === 'scan-waiting') {
|
||||||
return 'warning';
|
return 'warning';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ type OptionsConfiguration struct {
|
|||||||
TrafficClass int `xml:"trafficClass" json:"trafficClass"`
|
TrafficClass int `xml:"trafficClass" json:"trafficClass"`
|
||||||
DefaultFolderPath string `xml:"defaultFolderPath" json:"defaultFolderPath" default:"~"`
|
DefaultFolderPath string `xml:"defaultFolderPath" json:"defaultFolderPath" default:"~"`
|
||||||
SetLowPriority bool `xml:"setLowPriority" json:"setLowPriority" default:"true"`
|
SetLowPriority bool `xml:"setLowPriority" json:"setLowPriority" default:"true"`
|
||||||
|
MaxConcurrentScans int `xml:"maxConcurrentScans" json:"maxConcurrentScans"`
|
||||||
|
|
||||||
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
|
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
|
||||||
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`
|
DeprecatedUPnPLeaseM int `xml:"upnpLeaseMinutes,omitempty" json:"-"`
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
type byteSemaphore struct {
|
type byteSemaphore struct {
|
||||||
max int
|
max int
|
||||||
@ -25,26 +27,44 @@ func newByteSemaphore(max int) *byteSemaphore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *byteSemaphore) take(bytes int) {
|
func (s *byteSemaphore) take(bytes int) {
|
||||||
|
s.mut.Lock()
|
||||||
if bytes > s.max {
|
if bytes > s.max {
|
||||||
bytes = s.max
|
bytes = s.max
|
||||||
}
|
}
|
||||||
s.mut.Lock()
|
|
||||||
for bytes > s.available {
|
for bytes > s.available {
|
||||||
s.cond.Wait()
|
s.cond.Wait()
|
||||||
|
if bytes > s.max {
|
||||||
|
bytes = s.max
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.available -= bytes
|
s.available -= bytes
|
||||||
s.mut.Unlock()
|
s.mut.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *byteSemaphore) give(bytes int) {
|
func (s *byteSemaphore) give(bytes int) {
|
||||||
|
s.mut.Lock()
|
||||||
if bytes > s.max {
|
if bytes > s.max {
|
||||||
bytes = s.max
|
bytes = s.max
|
||||||
}
|
}
|
||||||
s.mut.Lock()
|
|
||||||
if s.available+bytes > s.max {
|
if s.available+bytes > s.max {
|
||||||
panic("bug: can never give more than max")
|
s.available = s.max
|
||||||
|
} else {
|
||||||
|
s.available += bytes
|
||||||
|
}
|
||||||
|
s.cond.Broadcast()
|
||||||
|
s.mut.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *byteSemaphore) setCapacity(cap int) {
|
||||||
|
s.mut.Lock()
|
||||||
|
diff := cap - s.max
|
||||||
|
s.max = cap
|
||||||
|
s.available += diff
|
||||||
|
if s.available < 0 {
|
||||||
|
s.available = 0
|
||||||
|
} else if s.available > s.max {
|
||||||
|
s.available = s.max
|
||||||
}
|
}
|
||||||
s.available += bytes
|
|
||||||
s.cond.Broadcast()
|
s.cond.Broadcast()
|
||||||
s.mut.Unlock()
|
s.mut.Unlock()
|
||||||
}
|
}
|
||||||
|
113
lib/model/bytesemaphore_test.go
Normal file
113
lib/model/bytesemaphore_test.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (C) 2018 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 https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestZeroByteSempahore(t *testing.T) {
|
||||||
|
// A semaphore with zero capacity is just a no-op.
|
||||||
|
|
||||||
|
s := newByteSemaphore(0)
|
||||||
|
|
||||||
|
// None of these should block or panic
|
||||||
|
s.take(123)
|
||||||
|
s.take(456)
|
||||||
|
s.give(1 << 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteSempahoreCapChangeUp(t *testing.T) {
|
||||||
|
// Waiting takes should unblock when the capacity increases
|
||||||
|
|
||||||
|
s := newByteSemaphore(100)
|
||||||
|
|
||||||
|
s.take(75)
|
||||||
|
if s.available != 25 {
|
||||||
|
t.Error("bad state after take")
|
||||||
|
}
|
||||||
|
|
||||||
|
gotit := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
s.take(75)
|
||||||
|
close(gotit)
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.setCapacity(155)
|
||||||
|
<-gotit
|
||||||
|
if s.available != 5 {
|
||||||
|
t.Error("bad state after both takes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteSempahoreCapChangeDown1(t *testing.T) {
|
||||||
|
// Things should make sense when capacity is adjusted down
|
||||||
|
|
||||||
|
s := newByteSemaphore(100)
|
||||||
|
|
||||||
|
s.take(75)
|
||||||
|
if s.available != 25 {
|
||||||
|
t.Error("bad state after take")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.setCapacity(90)
|
||||||
|
if s.available != 15 {
|
||||||
|
t.Error("bad state after adjust")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.give(75)
|
||||||
|
if s.available != 90 {
|
||||||
|
t.Error("bad state after give")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteSempahoreCapChangeDown2(t *testing.T) {
|
||||||
|
// Things should make sense when capacity is adjusted down, different case
|
||||||
|
|
||||||
|
s := newByteSemaphore(100)
|
||||||
|
|
||||||
|
s.take(75)
|
||||||
|
if s.available != 25 {
|
||||||
|
t.Error("bad state after take")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.setCapacity(10)
|
||||||
|
if s.available != 0 {
|
||||||
|
t.Error("bad state after adjust")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.give(75)
|
||||||
|
if s.available != 10 {
|
||||||
|
t.Error("bad state after give")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteSempahoreGiveMore(t *testing.T) {
|
||||||
|
// We shouldn't end up with more available than we have capacity...
|
||||||
|
|
||||||
|
s := newByteSemaphore(100)
|
||||||
|
|
||||||
|
s.take(150)
|
||||||
|
if s.available != 0 {
|
||||||
|
t.Errorf("bad state after large take")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.give(150)
|
||||||
|
if s.available != 100 {
|
||||||
|
t.Errorf("bad state after large take + give")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.take(150)
|
||||||
|
s.setCapacity(125)
|
||||||
|
// available was zero before, we're increasing capacity by 25
|
||||||
|
if s.available != 25 {
|
||||||
|
t.Errorf("bad state after setcap")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.give(150)
|
||||||
|
if s.available != 125 {
|
||||||
|
t.Errorf("bad state after large take + give with adjustment")
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,9 @@ import (
|
|||||||
"github.com/syncthing/syncthing/lib/watchaggregator"
|
"github.com/syncthing/syncthing/lib/watchaggregator"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// scanLimiter limits the number of concurrent scans. A limit of zero means no limit.
|
||||||
|
var scanLimiter = newByteSemaphore(0)
|
||||||
|
|
||||||
var errWatchNotStarted = errors.New("not started")
|
var errWatchNotStarted = errors.New("not started")
|
||||||
|
|
||||||
type folder struct {
|
type folder struct {
|
||||||
@ -284,6 +287,10 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
|||||||
f.model.fmut.RUnlock()
|
f.model.fmut.RUnlock()
|
||||||
mtimefs := fset.MtimeFS()
|
mtimefs := fset.MtimeFS()
|
||||||
|
|
||||||
|
f.setState(FolderScanWaiting)
|
||||||
|
scanLimiter.take(1)
|
||||||
|
defer scanLimiter.give(1)
|
||||||
|
|
||||||
for i := range subDirs {
|
for i := range subDirs {
|
||||||
sub := osutil.NativeFilename(subDirs[i])
|
sub := osutil.NativeFilename(subDirs[i])
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ type folderState int
|
|||||||
const (
|
const (
|
||||||
FolderIdle folderState = iota
|
FolderIdle folderState = iota
|
||||||
FolderScanning
|
FolderScanning
|
||||||
|
FolderScanWaiting
|
||||||
FolderSyncing
|
FolderSyncing
|
||||||
FolderError
|
FolderError
|
||||||
)
|
)
|
||||||
@ -28,6 +29,8 @@ func (s folderState) String() string {
|
|||||||
return "idle"
|
return "idle"
|
||||||
case FolderScanning:
|
case FolderScanning:
|
||||||
return "scanning"
|
return "scanning"
|
||||||
|
case FolderScanWaiting:
|
||||||
|
return "scan-waiting"
|
||||||
case FolderSyncing:
|
case FolderSyncing:
|
||||||
return "syncing"
|
return "syncing"
|
||||||
case FolderError:
|
case FolderError:
|
||||||
|
@ -170,6 +170,7 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, clientName, clientVersi
|
|||||||
if cfg.Options().ProgressUpdateIntervalS > -1 {
|
if cfg.Options().ProgressUpdateIntervalS > -1 {
|
||||||
go m.progressEmitter.Serve()
|
go m.progressEmitter.Serve()
|
||||||
}
|
}
|
||||||
|
scanLimiter.setCapacity(cfg.Options().MaxConcurrentScans)
|
||||||
cfg.Subscribe(m)
|
cfg.Subscribe(m)
|
||||||
|
|
||||||
return m
|
return m
|
||||||
@ -2586,6 +2587,8 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scanLimiter.setCapacity(to.Options.MaxConcurrentScans)
|
||||||
|
|
||||||
// Some options don't require restart as those components handle it fine
|
// Some options don't require restart as those components handle it fine
|
||||||
// by themselves. Compare the options structs containing only the
|
// by themselves. Compare the options structs containing only the
|
||||||
// attributes that require restart and act apprioriately.
|
// attributes that require restart and act apprioriately.
|
||||||
|
Loading…
Reference in New Issue
Block a user