lib: Replace done channel with contexts in and add names to util services (#6166)

This commit is contained in:
Simon Frei 2019-11-21 08:41:15 +01:00 committed by GitHub
parent 552ea68672
commit 90d85fd0a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 240 additions and 218 deletions

View File

@ -8,6 +8,7 @@ package api
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
@ -136,7 +137,7 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam
configChanged: make(chan struct{}), configChanged: make(chan struct{}),
startedOnce: make(chan struct{}), startedOnce: make(chan struct{}),
} }
s.Service = util.AsService(s.serve) s.Service = util.AsService(s.serve, s.String())
return s return s
} }
@ -207,7 +208,7 @@ func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
fmt.Fprintf(w, "%s\n", bs) fmt.Fprintf(w, "%s\n", bs)
} }
func (s *service) serve(stop chan struct{}) { func (s *service) serve(ctx context.Context) {
listener, err := s.getListener(s.cfg.GUI()) listener, err := s.getListener(s.cfg.GUI())
if err != nil { if err != nil {
select { select {
@ -381,7 +382,7 @@ func (s *service) serve(stop chan struct{}) {
// Wait for stop, restart or error signals // Wait for stop, restart or error signals
select { select {
case <-stop: case <-ctx.Done():
// Shutting down permanently // Shutting down permanently
l.Debugln("shutting down (stop)") l.Debugln("shutting down (stop)")
case <-s.configChanged: case <-s.configChanged:

View File

@ -7,6 +7,7 @@
package beacon package beacon
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"time" "time"
@ -63,23 +64,23 @@ func newCast(name string) *cast {
} }
} }
func (c *cast) addReader(svc func(chan struct{}) error) { func (c *cast) addReader(svc func(context.Context) error) {
c.reader = c.createService(svc, "reader") c.reader = c.createService(svc, "reader")
c.Add(c.reader) c.Add(c.reader)
} }
func (c *cast) addWriter(svc func(stop chan struct{}) error) { func (c *cast) addWriter(svc func(ctx context.Context) error) {
c.writer = c.createService(svc, "writer") c.writer = c.createService(svc, "writer")
c.Add(c.writer) c.Add(c.writer)
} }
func (c *cast) createService(svc func(chan struct{}) error, suffix string) util.ServiceWithError { func (c *cast) createService(svc func(context.Context) error, suffix string) util.ServiceWithError {
return util.AsServiceWithError(func(stop chan struct{}) error { return util.AsServiceWithError(func(ctx context.Context) error {
l.Debugln("Starting", c.name, suffix) l.Debugln("Starting", c.name, suffix)
err := svc(stop) err := svc(ctx)
l.Debugf("Stopped %v %v: %v", c.name, suffix, err) l.Debugf("Stopped %v %v: %v", c.name, suffix, err)
return err return err
}) }, fmt.Sprintf("%s/%s", c, suffix))
} }
func (c *cast) Stop() { func (c *cast) Stop() {

View File

@ -7,34 +7,32 @@
package beacon package beacon
import ( import (
"context"
"net" "net"
"time" "time"
) )
func NewBroadcast(port int) Interface { func NewBroadcast(port int) Interface {
c := newCast("broadcastBeacon") c := newCast("broadcastBeacon")
c.addReader(func(stop chan struct{}) error { c.addReader(func(ctx context.Context) error {
return readBroadcasts(c.outbox, port, stop) return readBroadcasts(ctx, c.outbox, port)
}) })
c.addWriter(func(stop chan struct{}) error { c.addWriter(func(ctx context.Context) error {
return writeBroadcasts(c.inbox, port, stop) return writeBroadcasts(ctx, c.inbox, port)
}) })
return c return c
} }
func writeBroadcasts(inbox <-chan []byte, port int, stop chan struct{}) error { func writeBroadcasts(ctx context.Context, inbox <-chan []byte, port int) error {
conn, err := net.ListenUDP("udp4", nil) conn, err := net.ListenUDP("udp4", nil)
if err != nil { if err != nil {
l.Debugln(err) l.Debugln(err)
return err return err
} }
done := make(chan struct{}) doneCtx, cancel := context.WithCancel(ctx)
defer close(done) defer cancel()
go func() { go func() {
select { <-doneCtx.Done()
case <-stop:
case <-done:
}
conn.Close() conn.Close()
}() }()
@ -42,7 +40,7 @@ func writeBroadcasts(inbox <-chan []byte, port int, stop chan struct{}) error {
var bs []byte var bs []byte
select { select {
case bs = <-inbox: case bs = <-inbox:
case <-stop: case <-doneCtx.Done():
return nil return nil
} }
@ -99,19 +97,17 @@ func writeBroadcasts(inbox <-chan []byte, port int, stop chan struct{}) error {
} }
} }
func readBroadcasts(outbox chan<- recv, port int, stop chan struct{}) error { func readBroadcasts(ctx context.Context, outbox chan<- recv, port int) error {
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: port}) conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: port})
if err != nil { if err != nil {
l.Debugln(err) l.Debugln(err)
return err return err
} }
done := make(chan struct{})
defer close(done) doneCtx, cancel := context.WithCancel(ctx)
defer cancel()
go func() { go func() {
select { <-doneCtx.Done()
case <-stop:
case <-done:
}
conn.Close() conn.Close()
}() }()
@ -129,7 +125,7 @@ func readBroadcasts(outbox chan<- recv, port int, stop chan struct{}) error {
copy(c, bs) copy(c, bs)
select { select {
case outbox <- recv{c, addr}: case outbox <- recv{c, addr}:
case <-stop: case <-doneCtx.Done():
return nil return nil
default: default:
l.Debugln("dropping message") l.Debugln("dropping message")

View File

@ -7,6 +7,7 @@
package beacon package beacon
import ( import (
"context"
"errors" "errors"
"net" "net"
"time" "time"
@ -16,16 +17,16 @@ import (
func NewMulticast(addr string) Interface { func NewMulticast(addr string) Interface {
c := newCast("multicastBeacon") c := newCast("multicastBeacon")
c.addReader(func(stop chan struct{}) error { c.addReader(func(ctx context.Context) error {
return readMulticasts(c.outbox, addr, stop) return readMulticasts(ctx, c.outbox, addr)
}) })
c.addWriter(func(stop chan struct{}) error { c.addWriter(func(ctx context.Context) error {
return writeMulticasts(c.inbox, addr, stop) return writeMulticasts(ctx, c.inbox, addr)
}) })
return c return c
} }
func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error { func writeMulticasts(ctx context.Context, inbox <-chan []byte, addr string) error {
gaddr, err := net.ResolveUDPAddr("udp6", addr) gaddr, err := net.ResolveUDPAddr("udp6", addr)
if err != nil { if err != nil {
l.Debugln(err) l.Debugln(err)
@ -37,13 +38,10 @@ func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error
l.Debugln(err) l.Debugln(err)
return err return err
} }
done := make(chan struct{}) doneCtx, cancel := context.WithCancel(ctx)
defer close(done) defer cancel()
go func() { go func() {
select { <-doneCtx.Done()
case <-stop:
case <-done:
}
conn.Close() conn.Close()
}() }()
@ -57,7 +55,7 @@ func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error
var bs []byte var bs []byte
select { select {
case bs = <-inbox: case bs = <-inbox:
case <-stop: case <-doneCtx.Done():
return nil return nil
} }
@ -84,7 +82,7 @@ func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error
success++ success++
select { select {
case <-stop: case <-doneCtx.Done():
return nil return nil
default: default:
} }
@ -96,7 +94,7 @@ func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error
} }
} }
func readMulticasts(outbox chan<- recv, addr string, stop chan struct{}) error { func readMulticasts(ctx context.Context, outbox chan<- recv, addr string) error {
gaddr, err := net.ResolveUDPAddr("udp6", addr) gaddr, err := net.ResolveUDPAddr("udp6", addr)
if err != nil { if err != nil {
l.Debugln(err) l.Debugln(err)
@ -108,13 +106,10 @@ func readMulticasts(outbox chan<- recv, addr string, stop chan struct{}) error {
l.Debugln(err) l.Debugln(err)
return err return err
} }
done := make(chan struct{}) doneCtx, cancel := context.WithCancel(ctx)
defer close(done) defer cancel()
go func() { go func() {
select { <-doneCtx.Done()
case <-stop:
case <-done:
}
conn.Close() conn.Close()
}() }()
@ -144,7 +139,7 @@ func readMulticasts(outbox chan<- recv, addr string, stop chan struct{}) error {
bs := make([]byte, 65536) bs := make([]byte, 65536)
for { for {
select { select {
case <-stop: case <-doneCtx.Done():
return nil return nil
default: default:
} }

View File

@ -78,13 +78,9 @@ func (t *quicListener) OnExternalAddressChanged(address *stun.Host, via string)
} }
} }
func (t *quicListener) serve(stop chan struct{}) error { func (t *quicListener) serve(ctx context.Context) error {
network := strings.Replace(t.uri.Scheme, "quic", "udp", -1) network := strings.Replace(t.uri.Scheme, "quic", "udp", -1)
// Convert the stop channel into a context
ctx, cancel := context.WithCancel(context.Background())
go func() { <-stop; cancel() }()
packetConn, err := net.ListenPacket(network, t.uri.Host) packetConn, err := net.ListenPacket(network, t.uri.Host)
if err != nil { if err != nil {
l.Infoln("Listen (BEP/quic):", err) l.Infoln("Listen (BEP/quic):", err)
@ -205,7 +201,7 @@ func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.
conns: conns, conns: conns,
factory: f, factory: f,
} }
l.ServiceWithError = util.AsServiceWithError(l.serve) l.ServiceWithError = util.AsServiceWithError(l.serve, l.String())
l.nat.Store(stun.NATUnknown) l.nat.Store(stun.NATUnknown)
return l return l
} }

View File

@ -7,6 +7,7 @@
package connections package connections
import ( import (
"context"
"crypto/tls" "crypto/tls"
"net/url" "net/url"
"sync" "sync"
@ -40,7 +41,7 @@ type relayListener struct {
mut sync.RWMutex mut sync.RWMutex
} }
func (t *relayListener) serve(stop chan struct{}) error { func (t *relayListener) serve(ctx context.Context) error {
clnt, err := client.NewClient(t.uri, t.tlsCfg.Certificates, nil, 10*time.Second) clnt, err := client.NewClient(t.uri, t.tlsCfg.Certificates, nil, 10*time.Second)
if err != nil { if err != nil {
l.Infoln("Listen (BEP/relay):", err) l.Infoln("Listen (BEP/relay):", err)
@ -112,7 +113,7 @@ func (t *relayListener) serve(stop chan struct{}) error {
t.notifyAddressesChanged(t) t.notifyAddressesChanged(t)
} }
case <-stop: case <-ctx.Done():
return nil return nil
} }
} }
@ -178,7 +179,7 @@ func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls
conns: conns, conns: conns,
factory: f, factory: f,
} }
t.ServiceWithError = util.AsServiceWithError(t.serve) t.ServiceWithError = util.AsServiceWithError(t.serve, t.String())
return t return t
} }

View File

@ -7,6 +7,7 @@
package connections package connections
import ( import (
"context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@ -185,18 +186,18 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
// the common handling regardless of whether the connection was // the common handling regardless of whether the connection was
// incoming or outgoing. // incoming or outgoing.
service.Add(util.AsService(service.connect)) service.Add(util.AsService(service.connect, fmt.Sprintf("%s/connect", service)))
service.Add(util.AsService(service.handle)) service.Add(util.AsService(service.handle, fmt.Sprintf("%s/handle", service)))
service.Add(service.listenerSupervisor) service.Add(service.listenerSupervisor)
return service return service
} }
func (s *service) handle(stop chan struct{}) { func (s *service) handle(ctx context.Context) {
var c internalConn var c internalConn
for { for {
select { select {
case <-stop: case <-ctx.Done():
return return
case c = <-s.conns: case c = <-s.conns:
} }
@ -324,7 +325,7 @@ func (s *service) handle(stop chan struct{}) {
} }
} }
func (s *service) connect(stop chan struct{}) { func (s *service) connect(ctx context.Context) {
nextDial := make(map[string]time.Time) nextDial := make(map[string]time.Time)
// Used as delay for the first few connection attempts, increases // Used as delay for the first few connection attempts, increases
@ -480,7 +481,7 @@ func (s *service) connect(stop chan struct{}) {
select { select {
case <-time.After(sleep): case <-time.After(sleep):
case <-stop: case <-ctx.Done():
return return
} }
} }

View File

@ -7,6 +7,7 @@
package connections package connections
import ( import (
"context"
"crypto/tls" "crypto/tls"
"net" "net"
"net/url" "net/url"
@ -42,7 +43,7 @@ type tcpListener struct {
mut sync.RWMutex mut sync.RWMutex
} }
func (t *tcpListener) serve(stop chan struct{}) error { func (t *tcpListener) serve(ctx context.Context) error {
tcaddr, err := net.ResolveTCPAddr(t.uri.Scheme, t.uri.Host) tcaddr, err := net.ResolveTCPAddr(t.uri.Scheme, t.uri.Host)
if err != nil { if err != nil {
l.Infoln("Listen (BEP/tcp):", err) l.Infoln("Listen (BEP/tcp):", err)
@ -76,7 +77,7 @@ func (t *tcpListener) serve(stop chan struct{}) error {
listener.SetDeadline(time.Now().Add(time.Second)) listener.SetDeadline(time.Now().Add(time.Second))
conn, err := listener.Accept() conn, err := listener.Accept()
select { select {
case <-stop: case <-ctx.Done():
if err == nil { if err == nil {
conn.Close() conn.Close()
} }
@ -183,7 +184,7 @@ func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.C
natService: natService, natService: natService,
factory: f, factory: f,
} }
l.ServiceWithError = util.AsServiceWithError(l.serve) l.ServiceWithError = util.AsServiceWithError(l.serve, l.String())
return l return l
} }

View File

@ -8,6 +8,7 @@ package discover
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
@ -128,7 +129,7 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, evLo
noLookup: opts.noLookup, noLookup: opts.noLookup,
evLogger: evLogger, evLogger: evLogger,
} }
cl.Service = util.AsService(cl.serve) cl.Service = util.AsService(cl.serve, cl.String())
if !opts.noAnnounce { if !opts.noAnnounce {
// If we are supposed to annonce, it's an error until we've done so. // If we are supposed to annonce, it's an error until we've done so.
cl.setError(errors.New("not announced")) cl.setError(errors.New("not announced"))
@ -188,11 +189,11 @@ func (c *globalClient) String() string {
return "global@" + c.server return "global@" + c.server
} }
func (c *globalClient) serve(stop chan struct{}) { func (c *globalClient) serve(ctx context.Context) {
if c.noAnnounce { if c.noAnnounce {
// We're configured to not do announcements, only lookups. To maintain // We're configured to not do announcements, only lookups. To maintain
// the same interface, we just pause here if Serve() is run. // the same interface, we just pause here if Serve() is run.
<-stop <-ctx.Done()
return return
} }
@ -212,7 +213,7 @@ func (c *globalClient) serve(stop chan struct{}) {
case <-timer.C: case <-timer.C:
c.sendAnnouncement(timer) c.sendAnnouncement(timer)
case <-stop: case <-ctx.Done():
return return
} }
} }

View File

@ -10,8 +10,10 @@
package discover package discover
import ( import (
"context"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"fmt"
"io" "io"
"net" "net"
"net/url" "net/url"
@ -81,9 +83,9 @@ func NewLocal(id protocol.DeviceID, addr string, addrList AddressLister, evLogge
c.beacon = beacon.NewMulticast(addr) c.beacon = beacon.NewMulticast(addr)
} }
c.Add(c.beacon) c.Add(c.beacon)
c.Add(util.AsService(c.recvAnnouncements)) c.Add(util.AsService(c.recvAnnouncements, fmt.Sprintf("%s/recv", c)))
c.Add(util.AsService(c.sendLocalAnnouncements)) c.Add(util.AsService(c.sendLocalAnnouncements, fmt.Sprintf("%s/sendLocal", c)))
return c, nil return c, nil
} }
@ -135,7 +137,7 @@ func (c *localClient) announcementPkt(instanceID int64, msg []byte) ([]byte, boo
return msg, true return msg, true
} }
func (c *localClient) sendLocalAnnouncements(stop chan struct{}) { func (c *localClient) sendLocalAnnouncements(ctx context.Context) {
var msg []byte var msg []byte
var ok bool var ok bool
instanceID := rand.Int63() instanceID := rand.Int63()
@ -147,18 +149,18 @@ func (c *localClient) sendLocalAnnouncements(stop chan struct{}) {
select { select {
case <-c.localBcastTick: case <-c.localBcastTick:
case <-c.forcedBcastTick: case <-c.forcedBcastTick:
case <-stop: case <-ctx.Done():
return return
} }
} }
} }
func (c *localClient) recvAnnouncements(stop chan struct{}) { func (c *localClient) recvAnnouncements(ctx context.Context) {
b := c.beacon b := c.beacon
warnedAbout := make(map[string]bool) warnedAbout := make(map[string]bool)
for { for {
select { select {
case <-stop: case <-ctx.Done():
return return
default: default:
} }

View File

@ -8,8 +8,10 @@
package events package events
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"runtime" "runtime"
"time" "time"
@ -258,7 +260,7 @@ func NewLogger() Logger {
funcs: make(chan func()), funcs: make(chan func()),
toUnsubscribe: make(chan *subscription), toUnsubscribe: make(chan *subscription),
} }
l.Service = util.AsService(l.serve) l.Service = util.AsService(l.serve, l.String())
// Make sure the timer is in the stopped state and hasn't fired anything // Make sure the timer is in the stopped state and hasn't fired anything
// into the channel. // into the channel.
if !l.timeout.Stop() { if !l.timeout.Stop() {
@ -267,7 +269,7 @@ func NewLogger() Logger {
return l return l
} }
func (l *logger) serve(stop chan struct{}) { func (l *logger) serve(ctx context.Context) {
loop: loop:
for { for {
select { select {
@ -282,7 +284,7 @@ loop:
case s := <-l.toUnsubscribe: case s := <-l.toUnsubscribe:
l.unsubscribe(s) l.unsubscribe(s)
case <-stop: case <-ctx.Done():
break loop break loop
} }
} }
@ -388,6 +390,10 @@ func (l *logger) unsubscribe(s *subscription) {
close(s.events) close(s.events)
} }
func (l *logger) String() string {
return fmt.Sprintf("events.Logger/@%p", l)
}
// Poll returns an event from the subscription or an error if the poll times // 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 // out of the event channel is closed. Poll should not be called concurrently
// from multiple goroutines for a single subscription. // from multiple goroutines for a single subscription.

View File

@ -55,12 +55,12 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
} }
errChan := make(chan error) errChan := make(chan error)
go f.watchLoop(name, roots, backendChan, outChan, errChan, ignore, ctx) go f.watchLoop(ctx, name, roots, backendChan, outChan, errChan, ignore)
return outChan, errChan, nil return outChan, errChan, nil
} }
func (f *BasicFilesystem) watchLoop(name string, roots []string, backendChan chan notify.EventInfo, outChan chan<- Event, errChan chan<- error, ignore Matcher, ctx context.Context) { func (f *BasicFilesystem) watchLoop(ctx context.Context, name string, roots []string, backendChan chan notify.EventInfo, outChan chan<- Event, errChan chan<- error, ignore Matcher) {
for { for {
// Detect channel overflow // Detect channel overflow
if len(backendChan) == backendBuffer { if len(backendChan) == backendBuffer {

View File

@ -178,7 +178,7 @@ func TestWatchWinRoot(t *testing.T) {
} }
cancel() cancel()
}() }()
fs.watchLoop(".", roots, backendChan, outChan, errChan, fakeMatcher{}, ctx) fs.watchLoop(ctx, ".", roots, backendChan, outChan, errChan, fakeMatcher{})
}() }()
// filepath.Dir as watch has a /... suffix // filepath.Dir as watch has a /... suffix
@ -219,7 +219,7 @@ func expectErrorForPath(t *testing.T, path string) {
// testFs is Filesystem, but we need BasicFilesystem here // testFs is Filesystem, but we need BasicFilesystem here
fs := newBasicFilesystem(testDirAbs) fs := newBasicFilesystem(testDirAbs)
go fs.watchLoop(".", []string{testDirAbs}, backendChan, outChan, errChan, fakeMatcher{}, ctx) go fs.watchLoop(ctx, ".", []string{testDirAbs}, backendChan, outChan, errChan, fakeMatcher{})
backendChan <- fakeEventInfo(path) backendChan <- fakeEventInfo(path)
@ -244,7 +244,7 @@ func TestWatchSubpath(t *testing.T) {
fs := newBasicFilesystem(testDirAbs) fs := newBasicFilesystem(testDirAbs)
abs, _ := fs.rooted("sub") abs, _ := fs.rooted("sub")
go fs.watchLoop("sub", []string{testDirAbs}, backendChan, outChan, errChan, fakeMatcher{}, ctx) go fs.watchLoop(ctx, "sub", []string{testDirAbs}, backendChan, outChan, errChan, fakeMatcher{})
backendChan <- fakeEventInfo(filepath.Join(abs, "file")) backendChan <- fakeEventInfo(filepath.Join(abs, "file"))

View File

@ -49,7 +49,6 @@ type folder struct {
fset *db.FileSet fset *db.FileSet
ignores *ignore.Matcher ignores *ignore.Matcher
ctx context.Context ctx context.Context
cancel context.CancelFunc
scanInterval time.Duration scanInterval time.Duration
scanTimer *time.Timer scanTimer *time.Timer
@ -80,8 +79,6 @@ type puller interface {
} }
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger) folder { func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger) folder {
ctx, cancel := context.WithCancel(context.Background())
return folder{ return folder{
stateTracker: newStateTracker(cfg.ID, evLogger), stateTracker: newStateTracker(cfg.ID, evLogger),
FolderConfiguration: cfg, FolderConfiguration: cfg,
@ -91,8 +88,6 @@ func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg conf
shortID: model.shortID, shortID: model.shortID,
fset: fset, fset: fset,
ignores: ignores, ignores: ignores,
ctx: ctx,
cancel: cancel,
scanInterval: time.Duration(cfg.RescanIntervalS) * time.Second, scanInterval: time.Duration(cfg.RescanIntervalS) * time.Second,
scanTimer: time.NewTimer(time.Millisecond), // The first scan should be done immediately. scanTimer: time.NewTimer(time.Millisecond), // The first scan should be done immediately.
@ -109,10 +104,12 @@ func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg conf
} }
} }
func (f *folder) serve(_ chan struct{}) { func (f *folder) serve(ctx context.Context) {
atomic.AddInt32(&f.model.foldersRunning, 1) atomic.AddInt32(&f.model.foldersRunning, 1)
defer atomic.AddInt32(&f.model.foldersRunning, -1) defer atomic.AddInt32(&f.model.foldersRunning, -1)
f.ctx = ctx
l.Debugln(f, "starting") l.Debugln(f, "starting")
defer l.Debugln(f, "exiting") defer l.Debugln(f, "exiting")
@ -256,11 +253,6 @@ func (f *folder) Delay(next time.Duration) {
f.scanDelay <- next f.scanDelay <- next
} }
func (f *folder) Stop() {
f.cancel()
f.Service.Stop()
}
// CheckHealth checks the folder for common errors, updates the folder state // CheckHealth checks the folder for common errors, updates the folder state
// and returns the current folder error, or nil if the folder is healthy. // and returns the current folder error, or nil if the folder is healthy.
func (f *folder) CheckHealth() error { func (f *folder) CheckHealth() error {
@ -643,7 +635,7 @@ func (f *folder) monitorWatch(ctx context.Context) {
failTimer.Reset(time.Minute) failTimer.Reset(time.Minute)
continue continue
} }
watchaggregator.Aggregate(eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, f.evLogger, aggrCtx) watchaggregator.Aggregate(aggrCtx, eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, f.evLogger)
l.Debugln("Started filesystem watcher for folder", f.Description()) l.Debugln("Started filesystem watcher for folder", f.Description())
case err = <-errChan: case err = <-errChan:
f.setWatchError(err) f.setWatchError(err)

View File

@ -30,7 +30,7 @@ func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher,
folder: newFolder(model, fset, ignores, cfg, evLogger), folder: newFolder(model, fset, ignores, cfg, evLogger),
} }
f.folder.puller = f f.folder.puller = f
f.folder.Service = util.AsService(f.serve) f.folder.Service = util.AsService(f.serve, f.String())
return f return f
} }

View File

@ -118,7 +118,7 @@ func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matche
pullErrorsMut: sync.NewMutex(), pullErrorsMut: sync.NewMutex(),
} }
f.folder.puller = f f.folder.puller = f
f.folder.Service = util.AsService(f.serve) f.folder.Service = util.AsService(f.serve, f.String())
if f.Copiers == 0 { if f.Copiers == 0 {
f.Copiers = defaultCopiers f.Copiers = defaultCopiers

View File

@ -7,6 +7,7 @@
package model package model
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -63,8 +64,8 @@ func NewFolderSummaryService(cfg config.Wrapper, m Model, id protocol.DeviceID,
lastEventReqMut: sync.NewMutex(), lastEventReqMut: sync.NewMutex(),
} }
service.Add(util.AsService(service.listenForUpdates)) service.Add(util.AsService(service.listenForUpdates, fmt.Sprintf("%s/listenForUpdates", service)))
service.Add(util.AsService(service.calculateSummaries)) service.Add(util.AsService(service.calculateSummaries, fmt.Sprintf("%s/calculateSummaries", service)))
return service return service
} }
@ -145,7 +146,7 @@ func (c *folderSummaryService) OnEventRequest() {
// listenForUpdates subscribes to the event bus and makes note of folders that // listenForUpdates subscribes to the event bus and makes note of folders that
// need their data recalculated. // need their data recalculated.
func (c *folderSummaryService) listenForUpdates(stop chan struct{}) { func (c *folderSummaryService) listenForUpdates(ctx context.Context) {
sub := c.evLogger.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress | events.DeviceConnected | events.FolderWatchStateChanged | events.DownloadProgress) sub := c.evLogger.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress | events.DeviceConnected | events.FolderWatchStateChanged | events.DownloadProgress)
defer sub.Unsubscribe() defer sub.Unsubscribe()
@ -155,7 +156,7 @@ func (c *folderSummaryService) listenForUpdates(stop chan struct{}) {
select { select {
case ev := <-sub.C(): case ev := <-sub.C():
c.processUpdate(ev) c.processUpdate(ev)
case <-stop: case <-ctx.Done():
return return
} }
} }
@ -234,7 +235,7 @@ func (c *folderSummaryService) processUpdate(ev events.Event) {
// calculateSummaries periodically recalculates folder summaries and // calculateSummaries periodically recalculates folder summaries and
// completion percentage, and sends the results on the event bus. // completion percentage, and sends the results on the event bus.
func (c *folderSummaryService) calculateSummaries(stop chan struct{}) { func (c *folderSummaryService) calculateSummaries(ctx context.Context) {
const pumpInterval = 2 * time.Second const pumpInterval = 2 * time.Second
pump := time.NewTimer(pumpInterval) pump := time.NewTimer(pumpInterval)
@ -255,7 +256,7 @@ func (c *folderSummaryService) calculateSummaries(stop chan struct{}) {
case folder := <-c.immediate: case folder := <-c.immediate:
c.sendSummary(folder) c.sendSummary(folder)
case <-stop: case <-ctx.Done():
return return
} }
} }

View File

@ -1227,7 +1227,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
dropSymlinks: dropSymlinks, dropSymlinks: dropSymlinks,
evLogger: m.evLogger, evLogger: m.evLogger,
} }
is.Service = util.AsService(is.serve) is.Service = util.AsService(is.serve, is.String())
// The token isn't tracked as the service stops when the connection // The token isn't tracked as the service stops when the connection
// terminates and is automatically removed from supervisor (by // terminates and is automatically removed from supervisor (by
// implementing suture.IsCompletable). // implementing suture.IsCompletable).
@ -1970,7 +1970,7 @@ type indexSender struct {
connClosed chan struct{} connClosed chan struct{}
} }
func (s *indexSender) serve(stop chan struct{}) { func (s *indexSender) serve(ctx context.Context) {
var err error var err error
l.Debugf("Starting indexSender for %s to %s at %s (slv=%d)", s.folder, s.dev, s.conn, s.prevSequence) l.Debugf("Starting indexSender for %s to %s at %s (slv=%d)", s.folder, s.dev, s.conn, s.prevSequence)
@ -1991,7 +1991,7 @@ func (s *indexSender) serve(stop chan struct{}) {
for err == nil { for err == nil {
select { select {
case <-stop: case <-ctx.Done():
return return
case <-s.connClosed: case <-s.connClosed:
return return
@ -2004,7 +2004,7 @@ func (s *indexSender) serve(stop chan struct{}) {
// sending for. // sending for.
if s.fset.Sequence(protocol.LocalDeviceID) <= s.prevSequence { if s.fset.Sequence(protocol.LocalDeviceID) <= s.prevSequence {
select { select {
case <-stop: case <-ctx.Done():
return return
case <-s.connClosed: case <-s.connClosed:
return return
@ -2037,7 +2037,7 @@ func (s *indexSender) sendIndexTo() error {
initial := s.prevSequence == 0 initial := s.prevSequence == 0
batch := newFileInfoBatch(nil) batch := newFileInfoBatch(nil)
batch.flushFn = func(fs []protocol.FileInfo) error { batch.flushFn = func(fs []protocol.FileInfo) error {
l.Debugf("Sending indexes for %s to %s at %s: %d files (<%d bytes)", s.folder, s.dev, s.conn, len(batch.infos), batch.size) l.Debugf("%v: Sending %d files (<%d bytes)", s, len(batch.infos), batch.size)
if initial { if initial {
initial = false initial = false
return s.conn.Index(s.folder, fs) return s.conn.Index(s.folder, fs)
@ -2099,6 +2099,10 @@ func (s *indexSender) sendIndexTo() error {
return err return err
} }
func (s *indexSender) String() string {
return fmt.Sprintf("indexSender@%p for %s to %s at %s", s, s.folder, s.dev, s.conn)
}
func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) { func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
m.pmut.RLock() m.pmut.RLock()
nc, ok := m.conn[deviceID] nc, ok := m.conn[deviceID]

View File

@ -7,6 +7,7 @@
package model package model
import ( import (
"context"
"fmt" "fmt"
"time" "time"
@ -47,7 +48,7 @@ func NewProgressEmitter(cfg config.Wrapper, evLogger events.Logger) *ProgressEmi
evLogger: evLogger, evLogger: evLogger,
mut: sync.NewMutex(), mut: sync.NewMutex(),
} }
t.Service = util.AsService(t.serve) t.Service = util.AsService(t.serve, t.String())
t.CommitConfiguration(config.Configuration{}, cfg.RawCopy()) t.CommitConfiguration(config.Configuration{}, cfg.RawCopy())
cfg.Subscribe(t) cfg.Subscribe(t)
@ -57,12 +58,12 @@ func NewProgressEmitter(cfg config.Wrapper, evLogger events.Logger) *ProgressEmi
// serve starts the progress emitter which starts emitting DownloadProgress // serve starts the progress emitter which starts emitting DownloadProgress
// events as the progress happens. // events as the progress happens.
func (t *ProgressEmitter) serve(stop chan struct{}) { func (t *ProgressEmitter) serve(ctx context.Context) {
var lastUpdate time.Time var lastUpdate time.Time
var lastCount, newCount int var lastCount, newCount int
for { for {
select { select {
case <-stop: case <-ctx.Done():
l.Debugln("progress emitter: stopping") l.Debugln("progress emitter: stopping")
return return
case <-t.timer.C: case <-t.timer.C:

View File

@ -7,6 +7,7 @@
package nat package nat
import ( import (
"context"
"sync" "sync"
"time" "time"
) )
@ -19,7 +20,7 @@ func Register(provider DiscoverFunc) {
providers = append(providers, provider) providers = append(providers, provider)
} }
func discoverAll(renewal, timeout time.Duration, stop chan struct{}) map[string]Device { func discoverAll(ctx context.Context, renewal, timeout time.Duration) map[string]Device {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(providers)) wg.Add(len(providers))
@ -32,7 +33,7 @@ func discoverAll(renewal, timeout time.Duration, stop chan struct{}) map[string]
for _, dev := range f(renewal, timeout) { for _, dev := range f(renewal, timeout) {
select { select {
case c <- dev: case c <- dev:
case <-stop: case <-ctx.Done():
return return
} }
} }
@ -50,7 +51,7 @@ func discoverAll(renewal, timeout time.Duration, stop chan struct{}) map[string]
return return
} }
nats[dev.ID()] = dev nats[dev.ID()] = dev
case <-stop: case <-ctx.Done():
return return
} }
} }

View File

@ -7,6 +7,7 @@
package nat package nat
import ( import (
"context"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"math/rand" "math/rand"
@ -43,11 +44,11 @@ func NewService(id protocol.DeviceID, cfg config.Wrapper) *Service {
timer: time.NewTimer(0), timer: time.NewTimer(0),
mut: sync.NewRWMutex(), mut: sync.NewRWMutex(),
} }
s.Service = util.AsService(s.serve) s.Service = util.AsService(s.serve, s.String())
return s return s
} }
func (s *Service) serve(stop chan struct{}) { func (s *Service) serve(ctx context.Context) {
announce := stdsync.Once{} announce := stdsync.Once{}
s.mut.Lock() s.mut.Lock()
@ -57,7 +58,7 @@ func (s *Service) serve(stop chan struct{}) {
for { for {
select { select {
case <-s.timer.C: case <-s.timer.C:
if found := s.process(stop); found != -1 { if found := s.process(ctx); found != -1 {
announce.Do(func() { announce.Do(func() {
suffix := "s" suffix := "s"
if found == 1 { if found == 1 {
@ -66,7 +67,7 @@ func (s *Service) serve(stop chan struct{}) {
l.Infoln("Detected", found, "NAT service"+suffix) l.Infoln("Detected", found, "NAT service"+suffix)
}) })
} }
case <-stop: case <-ctx.Done():
s.timer.Stop() s.timer.Stop()
s.mut.RLock() s.mut.RLock()
for _, mapping := range s.mappings { for _, mapping := range s.mappings {
@ -78,7 +79,7 @@ func (s *Service) serve(stop chan struct{}) {
} }
} }
func (s *Service) process(stop chan struct{}) int { func (s *Service) process(ctx context.Context) int {
// toRenew are mappings which are due for renewal // toRenew are mappings which are due for renewal
// toUpdate are the remaining mappings, which will only be updated if one of // toUpdate are the remaining mappings, which will only be updated if one of
// the old IGDs has gone away, or a new IGD has appeared, but only if we // the old IGDs has gone away, or a new IGD has appeared, but only if we
@ -120,14 +121,14 @@ func (s *Service) process(stop chan struct{}) int {
return -1 return -1
} }
nats := discoverAll(time.Duration(s.cfg.Options().NATRenewalM)*time.Minute, time.Duration(s.cfg.Options().NATTimeoutS)*time.Second, stop) nats := discoverAll(ctx, time.Duration(s.cfg.Options().NATRenewalM)*time.Minute, time.Duration(s.cfg.Options().NATTimeoutS)*time.Second)
for _, mapping := range toRenew { for _, mapping := range toRenew {
s.updateMapping(mapping, nats, true, stop) s.updateMapping(ctx, mapping, nats, true)
} }
for _, mapping := range toUpdate { for _, mapping := range toUpdate {
s.updateMapping(mapping, nats, false, stop) s.updateMapping(ctx, mapping, nats, false)
} }
return len(nats) return len(nats)
@ -177,17 +178,17 @@ func (s *Service) RemoveMapping(mapping *Mapping) {
// acquire mappings for natds which the mapping was unaware of before. // acquire mappings for natds which the mapping was unaware of before.
// Optionally takes renew flag which indicates whether or not we should renew // Optionally takes renew flag which indicates whether or not we should renew
// mappings with existing natds // mappings with existing natds
func (s *Service) updateMapping(mapping *Mapping, nats map[string]Device, renew bool, stop chan struct{}) { func (s *Service) updateMapping(ctx context.Context, mapping *Mapping, nats map[string]Device, renew bool) {
var added, removed []Address var added, removed []Address
renewalTime := time.Duration(s.cfg.Options().NATRenewalM) * time.Minute renewalTime := time.Duration(s.cfg.Options().NATRenewalM) * time.Minute
mapping.expires = time.Now().Add(renewalTime) mapping.expires = time.Now().Add(renewalTime)
newAdded, newRemoved := s.verifyExistingMappings(mapping, nats, renew, stop) newAdded, newRemoved := s.verifyExistingMappings(ctx, mapping, nats, renew)
added = append(added, newAdded...) added = append(added, newAdded...)
removed = append(removed, newRemoved...) removed = append(removed, newRemoved...)
newAdded, newRemoved = s.acquireNewMappings(mapping, nats, stop) newAdded, newRemoved = s.acquireNewMappings(ctx, mapping, nats)
added = append(added, newAdded...) added = append(added, newAdded...)
removed = append(removed, newRemoved...) removed = append(removed, newRemoved...)
@ -196,14 +197,14 @@ func (s *Service) updateMapping(mapping *Mapping, nats map[string]Device, renew
} }
} }
func (s *Service) verifyExistingMappings(mapping *Mapping, nats map[string]Device, renew bool, stop chan struct{}) ([]Address, []Address) { func (s *Service) verifyExistingMappings(ctx context.Context, mapping *Mapping, nats map[string]Device, renew bool) ([]Address, []Address) {
var added, removed []Address var added, removed []Address
leaseTime := time.Duration(s.cfg.Options().NATLeaseM) * time.Minute leaseTime := time.Duration(s.cfg.Options().NATLeaseM) * time.Minute
for id, address := range mapping.addressMap() { for id, address := range mapping.addressMap() {
select { select {
case <-stop: case <-ctx.Done():
return nil, nil return nil, nil
default: default:
} }
@ -225,7 +226,7 @@ func (s *Service) verifyExistingMappings(mapping *Mapping, nats map[string]Devic
l.Debugf("Renewing %s -> %s mapping on %s", mapping, address, id) l.Debugf("Renewing %s -> %s mapping on %s", mapping, address, id)
addr, err := s.tryNATDevice(nat, mapping.address.Port, address.Port, leaseTime, stop) addr, err := s.tryNATDevice(ctx, nat, mapping.address.Port, address.Port, leaseTime)
if err != nil { if err != nil {
l.Debugf("Failed to renew %s -> mapping on %s", mapping, address, id) l.Debugf("Failed to renew %s -> mapping on %s", mapping, address, id)
mapping.removeAddress(id) mapping.removeAddress(id)
@ -247,7 +248,7 @@ func (s *Service) verifyExistingMappings(mapping *Mapping, nats map[string]Devic
return added, removed return added, removed
} }
func (s *Service) acquireNewMappings(mapping *Mapping, nats map[string]Device, stop chan struct{}) ([]Address, []Address) { func (s *Service) acquireNewMappings(ctx context.Context, mapping *Mapping, nats map[string]Device) ([]Address, []Address) {
var added, removed []Address var added, removed []Address
leaseTime := time.Duration(s.cfg.Options().NATLeaseM) * time.Minute leaseTime := time.Duration(s.cfg.Options().NATLeaseM) * time.Minute
@ -255,7 +256,7 @@ func (s *Service) acquireNewMappings(mapping *Mapping, nats map[string]Device, s
for id, nat := range nats { for id, nat := range nats {
select { select {
case <-stop: case <-ctx.Done():
return nil, nil return nil, nil
default: default:
} }
@ -274,7 +275,7 @@ func (s *Service) acquireNewMappings(mapping *Mapping, nats map[string]Device, s
l.Debugf("Acquiring %s mapping on %s", mapping, id) l.Debugf("Acquiring %s mapping on %s", mapping, id)
addr, err := s.tryNATDevice(nat, mapping.address.Port, 0, leaseTime, stop) addr, err := s.tryNATDevice(ctx, nat, mapping.address.Port, 0, leaseTime)
if err != nil { if err != nil {
l.Debugf("Failed to acquire %s mapping on %s", mapping, id) l.Debugf("Failed to acquire %s mapping on %s", mapping, id)
continue continue
@ -291,7 +292,7 @@ func (s *Service) acquireNewMappings(mapping *Mapping, nats map[string]Device, s
// tryNATDevice tries to acquire a port mapping for the given internal address to // tryNATDevice tries to acquire a port mapping for the given internal address to
// the given external port. If external port is 0, picks a pseudo-random port. // the given external port. If external port is 0, picks a pseudo-random port.
func (s *Service) tryNATDevice(natd Device, intPort, extPort int, leaseTime time.Duration, stop chan struct{}) (Address, error) { func (s *Service) tryNATDevice(ctx context.Context, natd Device, intPort, extPort int, leaseTime time.Duration) (Address, error) {
var err error var err error
var port int var port int
@ -313,7 +314,7 @@ func (s *Service) tryNATDevice(natd Device, intPort, extPort int, leaseTime time
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
select { select {
case <-stop: case <-ctx.Done():
return Address{}, nil return Address{}, nil
default: default:
} }
@ -343,6 +344,10 @@ findIP:
}, nil }, nil
} }
func (s *Service) String() string {
return fmt.Sprintf("nat.Service@%p", s)
}
func hash(input string) int64 { func hash(input string) int64 {
h := fnv.New64a() h := fnv.New64a()
h.Write([]byte(input)) h.Write([]byte(input))

View File

@ -3,6 +3,7 @@
package client package client
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/url" "net/url"
@ -51,16 +52,16 @@ type commonClient struct {
mut sync.RWMutex mut sync.RWMutex
} }
func newCommonClient(invitations chan protocol.SessionInvitation, serve func(chan struct{}) error) commonClient { func newCommonClient(invitations chan protocol.SessionInvitation, serve func(context.Context) error, creator string) commonClient {
c := commonClient{ c := commonClient{
invitations: invitations, invitations: invitations,
mut: sync.NewRWMutex(), mut: sync.NewRWMutex(),
} }
newServe := func(stop chan struct{}) error { newServe := func(ctx context.Context) error {
defer c.cleanup() defer c.cleanup()
return serve(stop) return serve(ctx)
} }
c.ServiceWithError = util.AsServiceWithError(newServe) c.ServiceWithError = util.AsServiceWithError(newServe, creator)
if c.invitations == nil { if c.invitations == nil {
c.closeInvitationsOnFinish = true c.closeInvitationsOnFinish = true
c.invitations = make(chan protocol.SessionInvitation) c.invitations = make(chan protocol.SessionInvitation)

View File

@ -3,6 +3,7 @@
package client package client
import ( import (
"context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -32,11 +33,11 @@ func newDynamicClient(uri *url.URL, certs []tls.Certificate, invitations chan pr
certs: certs, certs: certs,
timeout: timeout, timeout: timeout,
} }
c.commonClient = newCommonClient(invitations, c.serve) c.commonClient = newCommonClient(invitations, c.serve, c.String())
return c return c
} }
func (c *dynamicClient) serve(stop chan struct{}) error { func (c *dynamicClient) serve(ctx context.Context) error {
uri := *c.pooladdr uri := *c.pooladdr
// Trim off the `dynamic+` prefix // Trim off the `dynamic+` prefix
@ -69,9 +70,9 @@ func (c *dynamicClient) serve(stop chan struct{}) error {
addrs = append(addrs, ruri.String()) addrs = append(addrs, ruri.String())
} }
for _, addr := range relayAddressesOrder(addrs, stop) { for _, addr := range relayAddressesOrder(ctx, addrs) {
select { select {
case <-stop: case <-ctx.Done():
l.Debugln(c, "stopping") l.Debugln(c, "stopping")
return nil return nil
default: default:
@ -148,7 +149,7 @@ type dynamicAnnouncement struct {
// the closest 50ms, and puts them in buckets of 50ms latency ranges. Then // the closest 50ms, and puts them in buckets of 50ms latency ranges. Then
// shuffles each bucket, and returns all addresses starting with the ones from // shuffles each bucket, and returns all addresses starting with the ones from
// the lowest latency bucket, ending with the highest latency buceket. // the lowest latency bucket, ending with the highest latency buceket.
func relayAddressesOrder(input []string, stop chan struct{}) []string { func relayAddressesOrder(ctx context.Context, input []string) []string {
buckets := make(map[int][]string) buckets := make(map[int][]string)
for _, relay := range input { for _, relay := range input {
@ -162,7 +163,7 @@ func relayAddressesOrder(input []string, stop chan struct{}) []string {
buckets[id] = append(buckets[id], relay) buckets[id] = append(buckets[id], relay)
select { select {
case <-stop: case <-ctx.Done():
return nil return nil
default: default:
} }

View File

@ -3,6 +3,7 @@
package client package client
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net" "net"
@ -39,11 +40,11 @@ func newStaticClient(uri *url.URL, certs []tls.Certificate, invitations chan pro
messageTimeout: time.Minute * 2, messageTimeout: time.Minute * 2,
connectTimeout: timeout, connectTimeout: timeout,
} }
c.commonClient = newCommonClient(invitations, c.serve) c.commonClient = newCommonClient(invitations, c.serve, c.String())
return c return c
} }
func (c *staticClient) serve(stop chan struct{}) error { func (c *staticClient) serve(ctx context.Context) error {
if err := c.connect(); err != nil { if err := c.connect(); err != nil {
l.Infof("Could not connect to relay %s: %s", c.uri, err) l.Infof("Could not connect to relay %s: %s", c.uri, err)
return err return err
@ -72,7 +73,7 @@ func (c *staticClient) serve(stop chan struct{}) error {
messages := make(chan interface{}) messages := make(chan interface{})
errors := make(chan error, 1) errors := make(chan error, 1)
go messageReader(c.conn, messages, errors, stop) go messageReader(ctx, c.conn, messages, errors)
timeout := time.NewTimer(c.messageTimeout) timeout := time.NewTimer(c.messageTimeout)
@ -106,7 +107,7 @@ func (c *staticClient) serve(stop chan struct{}) error {
return fmt.Errorf("protocol error: unexpected message %v", msg) return fmt.Errorf("protocol error: unexpected message %v", msg)
} }
case <-stop: case <-ctx.Done():
l.Debugln(c, "stopping") l.Debugln(c, "stopping")
return nil return nil
@ -241,7 +242,7 @@ func performHandshakeAndValidation(conn *tls.Conn, uri *url.URL) error {
return nil return nil
} }
func messageReader(conn net.Conn, messages chan<- interface{}, errors chan<- error, stop chan struct{}) { func messageReader(ctx context.Context, conn net.Conn, messages chan<- interface{}, errors chan<- error) {
for { for {
msg, err := protocol.ReadMessage(conn) msg, err := protocol.ReadMessage(conn)
if err != nil { if err != nil {
@ -250,7 +251,7 @@ func messageReader(conn net.Conn, messages chan<- interface{}, errors chan<- err
} }
select { select {
case messages <- msg: case messages <- msg:
case <-stop: case <-ctx.Done():
return return
} }
} }

View File

@ -7,6 +7,7 @@
package stun package stun
import ( import (
"context"
"net" "net"
"sync/atomic" "sync/atomic"
"time" "time"
@ -104,7 +105,7 @@ func New(cfg config.Wrapper, subscriber Subscriber, conn net.PacketConn) (*Servi
natType: NATUnknown, natType: NATUnknown,
addr: nil, addr: nil,
} }
s.Service = util.AsService(s.serve) s.Service = util.AsService(s.serve, s.String())
return s, otherDataConn return s, otherDataConn
} }
@ -113,7 +114,7 @@ func (s *Service) Stop() {
s.Service.Stop() s.Service.Stop()
} }
func (s *Service) serve(stop chan struct{}) { func (s *Service) serve(ctx context.Context) {
for { for {
disabled: disabled:
s.setNATType(NATUnknown) s.setNATType(NATUnknown)
@ -121,7 +122,7 @@ func (s *Service) serve(stop chan struct{}) {
if s.cfg.Options().IsStunDisabled() { if s.cfg.Options().IsStunDisabled() {
select { select {
case <-stop: case <-ctx.Done():
return return
case <-time.After(time.Second): case <-time.After(time.Second):
continue continue
@ -134,12 +135,12 @@ func (s *Service) serve(stop chan struct{}) {
// This blocks until we hit an exit condition or there are issues with the STUN server. // This blocks until we hit an exit condition or there are issues with the STUN server.
// This returns a boolean signifying if a different STUN server should be tried (oppose to the whole thing // This returns a boolean signifying if a different STUN server should be tried (oppose to the whole thing
// shutting down and this winding itself down. // shutting down and this winding itself down.
if !s.runStunForServer(addr, stop) { if !s.runStunForServer(ctx, addr) {
// Check exit conditions. // Check exit conditions.
// Have we been asked to stop? // Have we been asked to stop?
select { select {
case <-stop: case <-ctx.Done():
return return
default: default:
} }
@ -165,13 +166,13 @@ func (s *Service) serve(stop chan struct{}) {
// Chillout for a while. // Chillout for a while.
select { select {
case <-time.After(stunRetryInterval): case <-time.After(stunRetryInterval):
case <-stop: case <-ctx.Done():
return return
} }
} }
} }
func (s *Service) runStunForServer(addr string, stop chan struct{}) (tryNext bool) { func (s *Service) runStunForServer(ctx context.Context, addr string) (tryNext bool) {
l.Debugf("Running stun for %s via %s", s, addr) l.Debugf("Running stun for %s via %s", s, addr)
// Resolve the address, so that in case the server advertises two // Resolve the address, so that in case the server advertises two
@ -209,10 +210,10 @@ func (s *Service) runStunForServer(addr string, stop chan struct{}) (tryNext boo
return false return false
} }
return s.stunKeepAlive(addr, extAddr, stop) return s.stunKeepAlive(ctx, addr, extAddr)
} }
func (s *Service) stunKeepAlive(addr string, extAddr *Host, stop chan struct{}) (tryNext bool) { func (s *Service) stunKeepAlive(ctx context.Context, addr string, extAddr *Host) (tryNext bool) {
var err error var err error
nextSleep := time.Duration(s.cfg.Options().StunKeepaliveStartS) * time.Second nextSleep := time.Duration(s.cfg.Options().StunKeepaliveStartS) * time.Second
@ -255,7 +256,7 @@ func (s *Service) stunKeepAlive(addr string, extAddr *Host, stop chan struct{})
select { select {
case <-time.After(sleepFor): case <-time.After(sleepFor):
case <-stop: case <-ctx.Done():
l.Debugf("%s stopping, aborting stun", s) l.Debugf("%s stopping, aborting stun", s)
return false return false
} }

View File

@ -7,7 +7,9 @@
package syncthing package syncthing
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"github.com/thejerf/suture" "github.com/thejerf/suture"
@ -29,19 +31,19 @@ func newAuditService(w io.Writer, evLogger events.Logger) *auditService {
w: w, w: w,
sub: evLogger.Subscribe(events.AllEvents), sub: evLogger.Subscribe(events.AllEvents),
} }
s.Service = util.AsService(s.serve) s.Service = util.AsService(s.serve, s.String())
return s return s
} }
// serve runs the audit service. // serve runs the audit service.
func (s *auditService) serve(stop chan struct{}) { func (s *auditService) serve(ctx context.Context) {
enc := json.NewEncoder(s.w) enc := json.NewEncoder(s.w)
for { for {
select { select {
case ev := <-s.sub.C(): case ev := <-s.sub.C():
enc.Encode(ev) enc.Encode(ev)
case <-stop: case <-ctx.Done():
return return
} }
} }
@ -52,3 +54,7 @@ func (s *auditService) Stop() {
s.Service.Stop() s.Service.Stop()
s.sub.Unsubscribe() s.sub.Unsubscribe()
} }
func (s *auditService) String() string {
return fmt.Sprintf("auditService@%p", s)
}

View File

@ -7,6 +7,7 @@
package syncthing package syncthing
import ( import (
"context"
"fmt" "fmt"
"github.com/thejerf/suture" "github.com/thejerf/suture"
@ -26,12 +27,12 @@ func newVerboseService(evLogger events.Logger) *verboseService {
s := &verboseService{ s := &verboseService{
sub: evLogger.Subscribe(events.AllEvents), sub: evLogger.Subscribe(events.AllEvents),
} }
s.Service = util.AsService(s.serve) s.Service = util.AsService(s.serve, s.String())
return s return s
} }
// serve runs the verbose logging service. // serve runs the verbose logging service.
func (s *verboseService) serve(stop chan struct{}) { func (s *verboseService) serve(ctx context.Context) {
for { for {
select { select {
case ev := <-s.sub.C(): case ev := <-s.sub.C():
@ -39,7 +40,7 @@ func (s *verboseService) serve(stop chan struct{}) {
if formatted != "" { if formatted != "" {
l.Verboseln(formatted) l.Verboseln(formatted)
} }
case <-stop: case <-ctx.Done():
return return
} }
} }
@ -187,3 +188,7 @@ func (s *verboseService) formatEvent(ev events.Event) string {
return fmt.Sprintf("%s %#v", ev.Type, ev) return fmt.Sprintf("%s %#v", ev.Type, ev)
} }
func (s *verboseService) String() string {
return fmt.Sprintf("verboseService@%p", s)
}

View File

@ -57,7 +57,7 @@ func New(cfg config.Wrapper, m model.Model, connectionsService connections.Servi
noUpgrade: noUpgrade, noUpgrade: noUpgrade,
forceRun: make(chan struct{}, 1), // Buffered to prevent locking forceRun: make(chan struct{}, 1), // Buffered to prevent locking
} }
svc.Service = util.AsService(svc.serve) svc.Service = util.AsService(svc.serve, svc.String())
cfg.Subscribe(svc) cfg.Subscribe(svc)
return svc return svc
} }
@ -384,11 +384,11 @@ func (s *Service) sendUsageReport() error {
return err return err
} }
func (s *Service) serve(stop chan struct{}) { func (s *Service) serve(ctx context.Context) {
t := time.NewTimer(time.Duration(s.cfg.Options().URInitialDelayS) * time.Second) t := time.NewTimer(time.Duration(s.cfg.Options().URInitialDelayS) * time.Second)
for { for {
select { select {
case <-stop: case <-ctx.Done():
return return
case <-s.forceRun: case <-s.forceRun:
t.Reset(0) t.Reset(0)

View File

@ -7,10 +7,10 @@
package util package util
import ( import (
"context"
"fmt" "fmt"
"net/url" "net/url"
"reflect" "reflect"
"runtime"
"strconv" "strconv"
"strings" "strings"
@ -178,11 +178,11 @@ func Address(network, host string) string {
// AsService wraps the given function to implement suture.Service by calling // AsService wraps the given function to implement suture.Service by calling
// that function on serve and closing the passed channel when Stop is called. // that function on serve and closing the passed channel when Stop is called.
func AsService(fn func(stop chan struct{})) suture.Service { func AsService(fn func(ctx context.Context), creator string) suture.Service {
return asServiceWithError(func(stop chan struct{}) error { return asServiceWithError(func(ctx context.Context) error {
fn(stop) fn(ctx)
return nil return nil
}) }, creator)
} }
type ServiceWithError interface { type ServiceWithError interface {
@ -194,25 +194,18 @@ type ServiceWithError interface {
// AsServiceWithError does the same as AsService, except that it keeps track // AsServiceWithError does the same as AsService, except that it keeps track
// of an error returned by the given function. // of an error returned by the given function.
func AsServiceWithError(fn func(stop chan struct{}) error) ServiceWithError { func AsServiceWithError(fn func(ctx context.Context) error, creator string) ServiceWithError {
return asServiceWithError(fn) return asServiceWithError(fn, creator)
} }
// caller retrieves information about the creator of the service, i.e. the stack func asServiceWithError(fn func(ctx context.Context) error, creator string) ServiceWithError {
// two levels up from itself. ctx, cancel := context.WithCancel(context.Background())
func caller() string {
pc := make([]uintptr, 1)
_ = runtime.Callers(4, pc)
f, _ := runtime.CallersFrames(pc).Next()
return f.Function
}
func asServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
s := &service{ s := &service{
caller: caller(),
serve: fn, serve: fn,
stop: make(chan struct{}), ctx: ctx,
cancel: cancel,
stopped: make(chan struct{}), stopped: make(chan struct{}),
creator: creator,
mut: sync.NewMutex(), mut: sync.NewMutex(),
} }
close(s.stopped) // not yet started, don't block on Stop() close(s.stopped) // not yet started, don't block on Stop()
@ -220,9 +213,10 @@ func asServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
} }
type service struct { type service struct {
caller string creator string
serve func(stop chan struct{}) error serve func(ctx context.Context) error
stop chan struct{} ctx context.Context
cancel context.CancelFunc
stopped chan struct{} stopped chan struct{}
err error err error
mut sync.Mutex mut sync.Mutex
@ -231,7 +225,7 @@ type service struct {
func (s *service) Serve() { func (s *service) Serve() {
s.mut.Lock() s.mut.Lock()
select { select {
case <-s.stop: case <-s.ctx.Done():
s.mut.Unlock() s.mut.Unlock()
return return
default: default:
@ -247,16 +241,16 @@ func (s *service) Serve() {
close(s.stopped) close(s.stopped)
s.mut.Unlock() s.mut.Unlock()
}() }()
err = s.serve(s.stop) err = s.serve(s.ctx)
} }
func (s *service) Stop() { func (s *service) Stop() {
s.mut.Lock() s.mut.Lock()
select { select {
case <-s.stop: case <-s.ctx.Done():
panic(fmt.Sprintf("Stop called more than once on %v", s)) panic(fmt.Sprintf("Stop called more than once on %v", s))
default: default:
close(s.stop) s.cancel()
} }
s.mut.Unlock() s.mut.Unlock()
<-s.stopped <-s.stopped
@ -275,5 +269,5 @@ func (s *service) SetError(err error) {
} }
func (s *service) String() string { func (s *service) String() string {
return fmt.Sprintf("Service@%p created by %v", s, s.caller) return fmt.Sprintf("Service@%p created by %v", s, s.creator)
} }

View File

@ -7,6 +7,7 @@
package util package util
import ( import (
"context"
"strings" "strings"
"testing" "testing"
) )
@ -227,17 +228,17 @@ func TestCopyMatching(t *testing.T) {
} }
func TestUtilStopTwicePanic(t *testing.T) { func TestUtilStopTwicePanic(t *testing.T) {
s := AsService(func(stop chan struct{}) { name := "foo"
<-stop s := AsService(func(ctx context.Context) {
}) <-ctx.Done()
}, name)
go s.Serve() go s.Serve()
s.Stop() s.Stop()
defer func() { defer func() {
expected := "lib/util.TestUtilStopTwicePanic" if r := recover(); r == nil || !strings.Contains(r.(string), name) {
if r := recover(); r == nil || !strings.Contains(r.(string), expected) { t.Fatalf(`expected panic containing "%v", got "%v"`, name, r)
t.Fatalf(`expected panic containing "%v", got "%v"`, expected, r)
} }
}() }()
s.Stop() s.Stop()

View File

@ -7,6 +7,8 @@
package versioner package versioner
import ( import (
"context"
"fmt"
"sort" "sort"
"strconv" "strconv"
"time" "time"
@ -65,13 +67,13 @@ func NewStaggered(folderID string, folderFs fs.Filesystem, params map[string]str
}, },
mutex: sync.NewMutex(), mutex: sync.NewMutex(),
} }
s.Service = util.AsService(s.serve) s.Service = util.AsService(s.serve, s.String())
l.Debugf("instantiated %#v", s) l.Debugf("instantiated %#v", s)
return s return s
} }
func (v *Staggered) serve(stop chan struct{}) { func (v *Staggered) serve(ctx context.Context) {
v.clean() v.clean()
if v.testCleanDone != nil { if v.testCleanDone != nil {
close(v.testCleanDone) close(v.testCleanDone)
@ -83,7 +85,7 @@ func (v *Staggered) serve(stop chan struct{}) {
select { select {
case <-tck.C: case <-tck.C:
v.clean() v.clean()
case <-stop: case <-ctx.Done():
return return
} }
} }
@ -230,3 +232,7 @@ func (v *Staggered) GetVersions() (map[string][]FileVersion, error) {
func (v *Staggered) Restore(filepath string, versionTime time.Time) error { func (v *Staggered) Restore(filepath string, versionTime time.Time) error {
return restoreFile(v.versionsFs, v.folderFs, filepath, versionTime, TagFilename) return restoreFile(v.versionsFs, v.folderFs, filepath, versionTime, TagFilename)
} }
func (v *Staggered) String() string {
return fmt.Sprintf("Staggered/@%p", v)
}

View File

@ -7,6 +7,7 @@
package versioner package versioner
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
@ -38,7 +39,7 @@ func NewTrashcan(folderID string, folderFs fs.Filesystem, params map[string]stri
versionsFs: fsFromParams(folderFs, params), versionsFs: fsFromParams(folderFs, params),
cleanoutDays: cleanoutDays, cleanoutDays: cleanoutDays,
} }
s.Service = util.AsService(s.serve) s.Service = util.AsService(s.serve, s.String())
l.Debugf("instantiated %#v", s) l.Debugf("instantiated %#v", s)
return s return s
@ -52,7 +53,7 @@ func (t *Trashcan) Archive(filePath string) error {
}) })
} }
func (t *Trashcan) serve(stop chan struct{}) { func (t *Trashcan) serve(ctx context.Context) {
l.Debugln(t, "starting") l.Debugln(t, "starting")
defer l.Debugln(t, "stopping") defer l.Debugln(t, "stopping")
@ -62,7 +63,7 @@ func (t *Trashcan) serve(stop chan struct{}) {
for { for {
select { select {
case <-stop: case <-ctx.Done():
return return
case <-timer.C: case <-timer.C:

View File

@ -109,7 +109,7 @@ type aggregator struct {
ctx context.Context ctx context.Context
} }
func newAggregator(folderCfg config.FolderConfiguration, ctx context.Context) *aggregator { func newAggregator(ctx context.Context, folderCfg config.FolderConfiguration) *aggregator {
a := &aggregator{ a := &aggregator{
folderID: folderCfg.ID, folderID: folderCfg.ID,
folderCfgUpdate: make(chan config.FolderConfiguration), folderCfgUpdate: make(chan config.FolderConfiguration),
@ -125,8 +125,8 @@ func newAggregator(folderCfg config.FolderConfiguration, ctx context.Context) *a
return a return a
} }
func Aggregate(in <-chan fs.Event, out chan<- []string, folderCfg config.FolderConfiguration, cfg config.Wrapper, evLogger events.Logger, ctx context.Context) { func Aggregate(ctx context.Context, in <-chan fs.Event, out chan<- []string, folderCfg config.FolderConfiguration, cfg config.Wrapper, evLogger events.Logger) {
a := newAggregator(folderCfg, ctx) a := newAggregator(ctx, folderCfg)
// Necessary for unit tests where the backend is mocked // Necessary for unit tests where the backend is mocked
go a.mainLoop(in, out, cfg, evLogger) go a.mainLoop(in, out, cfg, evLogger)

View File

@ -67,7 +67,7 @@ func TestAggregate(t *testing.T) {
folderCfg.ID = "Aggregate" folderCfg.ID = "Aggregate"
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
a := newAggregator(folderCfg, ctx) a := newAggregator(ctx, folderCfg)
// checks whether maxFilesPerDir events in one dir are kept as is // checks whether maxFilesPerDir events in one dir are kept as is
for i := 0; i < maxFilesPerDir; i++ { for i := 0; i < maxFilesPerDir; i++ {
@ -95,7 +95,7 @@ func TestAggregate(t *testing.T) {
compareBatchToExpectedDirect(t, getEventPaths(a.root, ".", a), []string{"parent"}) compareBatchToExpectedDirect(t, getEventPaths(a.root, ".", a), []string{"parent"})
// again test aggregation in "parent" but with event in subdirs // again test aggregation in "parent" but with event in subdirs
a = newAggregator(folderCfg, ctx) a = newAggregator(ctx, folderCfg)
for i := 0; i < maxFilesPerDir; i++ { for i := 0; i < maxFilesPerDir; i++ {
a.newEvent(fs.Event{ a.newEvent(fs.Event{
Name: filepath.Join("parent", strconv.Itoa(i)), Name: filepath.Join("parent", strconv.Itoa(i)),
@ -109,7 +109,7 @@ func TestAggregate(t *testing.T) {
compareBatchToExpectedDirect(t, getEventPaths(a.root, ".", a), []string{"parent"}) compareBatchToExpectedDirect(t, getEventPaths(a.root, ".", a), []string{"parent"})
// test aggregation in root // test aggregation in root
a = newAggregator(folderCfg, ctx) a = newAggregator(ctx, folderCfg)
for i := 0; i < maxFiles; i++ { for i := 0; i < maxFiles; i++ {
a.newEvent(fs.Event{ a.newEvent(fs.Event{
Name: strconv.Itoa(i), Name: strconv.Itoa(i),
@ -132,7 +132,7 @@ func TestAggregate(t *testing.T) {
}, inProgress) }, inProgress)
compareBatchToExpectedDirect(t, getEventPaths(a.root, ".", a), []string{"."}) compareBatchToExpectedDirect(t, getEventPaths(a.root, ".", a), []string{"."})
a = newAggregator(folderCfg, ctx) a = newAggregator(ctx, folderCfg)
filesPerDir := maxFilesPerDir / 2 filesPerDir := maxFilesPerDir / 2
dirs := make([]string, maxFiles/filesPerDir+1) dirs := make([]string, maxFiles/filesPerDir+1)
for i := 0; i < maxFiles/filesPerDir+1; i++ { for i := 0; i < maxFiles/filesPerDir+1; i++ {
@ -293,7 +293,7 @@ func testScenario(t *testing.T, name string, testCase func(c chan<- fs.Event), e
folderCfg := defaultFolderCfg.Copy() folderCfg := defaultFolderCfg.Copy()
folderCfg.ID = name folderCfg.ID = name
a := newAggregator(folderCfg, ctx) a := newAggregator(ctx, folderCfg)
a.notifyTimeout = testNotifyTimeout a.notifyTimeout = testNotifyTimeout
startTime := time.Now() startTime := time.Now()