@@ -10,11 +10,11 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
liblogger "github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
dl = logger.DefaultLogger.NewFacility("events", "Event generation and logging")
|
||||
dl = liblogger.DefaultLogger.NewFacility("events", "Event generation and logging")
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -13,7 +13,10 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
type EventType int
|
||||
@@ -51,7 +54,10 @@ const (
|
||||
AllEvents = (1 << iota) - 1
|
||||
)
|
||||
|
||||
var runningTests = false
|
||||
var (
|
||||
runningTests = false
|
||||
errNoop = errors.New("method of a noop object called")
|
||||
)
|
||||
|
||||
const eventLogTimeout = 15 * time.Millisecond
|
||||
|
||||
@@ -199,13 +205,21 @@ func UnmarshalEventType(s string) EventType {
|
||||
|
||||
const BufferSize = 64
|
||||
|
||||
type Logger struct {
|
||||
subs []*Subscription
|
||||
type Logger interface {
|
||||
suture.Service
|
||||
Log(t EventType, data interface{})
|
||||
Subscribe(mask EventType) Subscription
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
suture.Service
|
||||
subs []*subscription
|
||||
nextSubscriptionIDs []int
|
||||
nextGlobalID int
|
||||
timeout *time.Timer
|
||||
events chan Event
|
||||
funcs chan func()
|
||||
toUnsubscribe chan *subscription
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
@@ -219,19 +233,17 @@ type Event struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
mask EventType
|
||||
events chan Event
|
||||
timeout *time.Timer
|
||||
type Subscription interface {
|
||||
C() <-chan Event
|
||||
Poll(timeout time.Duration) (Event, error)
|
||||
Unsubscribe()
|
||||
}
|
||||
|
||||
var Default = NewLogger()
|
||||
|
||||
func init() {
|
||||
// The default logger never stops. To ensure this we nil out the stop
|
||||
// channel so any attempt to stop it will panic.
|
||||
Default.stop = nil
|
||||
go Default.Serve()
|
||||
type subscription struct {
|
||||
mask EventType
|
||||
events chan Event
|
||||
toUnsubscribe chan *subscription
|
||||
timeout *time.Timer
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -239,13 +251,14 @@ var (
|
||||
ErrClosed = errors.New("closed")
|
||||
)
|
||||
|
||||
func NewLogger() *Logger {
|
||||
l := &Logger{
|
||||
timeout: time.NewTimer(time.Second),
|
||||
events: make(chan Event, BufferSize),
|
||||
funcs: make(chan func()),
|
||||
stop: make(chan struct{}),
|
||||
func NewLogger() Logger {
|
||||
l := &logger{
|
||||
timeout: time.NewTimer(time.Second),
|
||||
events: make(chan Event, BufferSize),
|
||||
funcs: make(chan func()),
|
||||
toUnsubscribe: make(chan *subscription),
|
||||
}
|
||||
l.Service = util.AsService(l.serve)
|
||||
// Make sure the timer is in the stopped state and hasn't fired anything
|
||||
// into the channel.
|
||||
if !l.timeout.Stop() {
|
||||
@@ -254,7 +267,7 @@ func NewLogger() *Logger {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Logger) Serve() {
|
||||
func (l *logger) serve(stop chan struct{}) {
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
@@ -263,10 +276,13 @@ loop:
|
||||
l.sendEvent(e)
|
||||
|
||||
case fn := <-l.funcs:
|
||||
// Subscriptions etc are handled here.
|
||||
// Subscriptions are handled here.
|
||||
fn()
|
||||
|
||||
case <-l.stop:
|
||||
case s := <-l.toUnsubscribe:
|
||||
l.unsubscribe(s)
|
||||
|
||||
case <-stop:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
@@ -279,11 +295,7 @@ loop:
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Stop() {
|
||||
close(l.stop)
|
||||
}
|
||||
|
||||
func (l *Logger) Log(t EventType, data interface{}) {
|
||||
func (l *logger) Log(t EventType, data interface{}) {
|
||||
l.events <- Event{
|
||||
Time: time.Now(),
|
||||
Type: t,
|
||||
@@ -292,7 +304,7 @@ func (l *Logger) Log(t EventType, data interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) sendEvent(e Event) {
|
||||
func (l *logger) sendEvent(e Event) {
|
||||
l.nextGlobalID++
|
||||
dl.Debugln("log", l.nextGlobalID, e.Type, e.Data)
|
||||
|
||||
@@ -323,15 +335,16 @@ func (l *Logger) sendEvent(e Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
res := make(chan *Subscription)
|
||||
func (l *logger) Subscribe(mask EventType) Subscription {
|
||||
res := make(chan Subscription)
|
||||
l.funcs <- func() {
|
||||
dl.Debugln("subscribe", mask)
|
||||
|
||||
s := &Subscription{
|
||||
mask: mask,
|
||||
events: make(chan Event, BufferSize),
|
||||
timeout: time.NewTimer(0),
|
||||
s := &subscription{
|
||||
mask: mask,
|
||||
events: make(chan Event, BufferSize),
|
||||
toUnsubscribe: l.toUnsubscribe,
|
||||
timeout: time.NewTimer(0),
|
||||
}
|
||||
|
||||
// We need to create the timeout timer in the stopped, non-fired state so
|
||||
@@ -355,32 +368,30 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
return <-res
|
||||
}
|
||||
|
||||
func (l *Logger) Unsubscribe(s *Subscription) {
|
||||
l.funcs <- func() {
|
||||
dl.Debugln("unsubscribe")
|
||||
for i, ss := range l.subs {
|
||||
if s == ss {
|
||||
last := len(l.subs) - 1
|
||||
func (l *logger) unsubscribe(s *subscription) {
|
||||
dl.Debugln("unsubscribe", s.mask)
|
||||
for i, ss := range l.subs {
|
||||
if s == ss {
|
||||
last := len(l.subs) - 1
|
||||
|
||||
l.subs[i] = l.subs[last]
|
||||
l.subs[last] = nil
|
||||
l.subs = l.subs[:last]
|
||||
l.subs[i] = l.subs[last]
|
||||
l.subs[last] = nil
|
||||
l.subs = l.subs[:last]
|
||||
|
||||
l.nextSubscriptionIDs[i] = l.nextSubscriptionIDs[last]
|
||||
l.nextSubscriptionIDs[last] = 0
|
||||
l.nextSubscriptionIDs = l.nextSubscriptionIDs[:last]
|
||||
l.nextSubscriptionIDs[i] = l.nextSubscriptionIDs[last]
|
||||
l.nextSubscriptionIDs[last] = 0
|
||||
l.nextSubscriptionIDs = l.nextSubscriptionIDs[:last]
|
||||
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
close(s.events)
|
||||
}
|
||||
close(s.events)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (s *subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
dl.Debugln("poll", timeout)
|
||||
|
||||
s.timeout.Reset(timeout)
|
||||
@@ -409,12 +420,16 @@ func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscription) C() <-chan Event {
|
||||
func (s *subscription) C() <-chan Event {
|
||||
return s.events
|
||||
}
|
||||
|
||||
func (s *subscription) Unsubscribe() {
|
||||
s.toUnsubscribe <- s
|
||||
}
|
||||
|
||||
type bufferedSubscription struct {
|
||||
sub *Subscription
|
||||
sub Subscription
|
||||
buf []Event
|
||||
next int
|
||||
cur int // Current SubscriptionID
|
||||
@@ -426,7 +441,7 @@ type BufferedSubscription interface {
|
||||
Since(id int, into []Event, timeout time.Duration) []Event
|
||||
}
|
||||
|
||||
func NewBufferedSubscription(s *Subscription, size int) BufferedSubscription {
|
||||
func NewBufferedSubscription(s Subscription, size int) BufferedSubscription {
|
||||
bs := &bufferedSubscription{
|
||||
sub: s,
|
||||
buf: make([]Event, size),
|
||||
@@ -489,3 +504,29 @@ func Error(err error) *string {
|
||||
str := err.Error()
|
||||
return &str
|
||||
}
|
||||
|
||||
type noopLogger struct{}
|
||||
|
||||
var NoopLogger Logger = &noopLogger{}
|
||||
|
||||
func (*noopLogger) Serve() {}
|
||||
|
||||
func (*noopLogger) Stop() {}
|
||||
|
||||
func (*noopLogger) Log(t EventType, data interface{}) {}
|
||||
|
||||
func (*noopLogger) Subscribe(mask EventType) Subscription {
|
||||
return &noopSubscription{}
|
||||
}
|
||||
|
||||
type noopSubscription struct{}
|
||||
|
||||
func (*noopSubscription) C() <-chan Event {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*noopSubscription) Poll(timeout time.Duration) (Event, error) {
|
||||
return Event{}, errNoop
|
||||
}
|
||||
|
||||
func (*noopSubscription) Unsubscribe() {}
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestSubscriber(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(0)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
if s == nil {
|
||||
t.Fatal("Unexpected nil Subscription")
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func TestTimeout(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(0)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
_, err := s.Poll(timeout)
|
||||
if err != ErrTimeout {
|
||||
t.Fatal("Unexpected non-Timeout error:", err)
|
||||
@@ -59,7 +59,7 @@ func TestEventBeforeSubscribe(t *testing.T) {
|
||||
|
||||
l.Log(DeviceConnected, "foo")
|
||||
s := l.Subscribe(0)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
|
||||
_, err := s.Poll(timeout)
|
||||
if err != ErrTimeout {
|
||||
@@ -73,7 +73,7 @@ func TestEventAfterSubscribe(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
l.Log(DeviceConnected, "foo")
|
||||
|
||||
ev, err := s.Poll(timeout)
|
||||
@@ -100,7 +100,7 @@ func TestEventAfterSubscribeIgnoreMask(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(DeviceDisconnected)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
l.Log(DeviceConnected, "foo")
|
||||
|
||||
_, err := s.Poll(timeout)
|
||||
@@ -115,7 +115,7 @@ func TestBufferOverflow(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
|
||||
// The first BufferSize events will be logged pretty much
|
||||
// instantaneously. The next BufferSize events will each block for up to
|
||||
@@ -147,7 +147,7 @@ func TestUnsubscribe(t *testing.T) {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
}
|
||||
|
||||
l.Unsubscribe(s)
|
||||
s.Unsubscribe()
|
||||
l.Log(DeviceConnected, "foo")
|
||||
|
||||
_, err = s.Poll(timeout)
|
||||
@@ -162,7 +162,7 @@ func TestGlobalIDs(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
l.Log(DeviceConnected, "foo")
|
||||
l.Subscribe(AllEvents)
|
||||
l.Log(DeviceConnected, "bar")
|
||||
@@ -194,7 +194,7 @@ func TestSubscriptionIDs(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(DeviceConnected)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
|
||||
l.Log(DeviceDisconnected, "a")
|
||||
l.Log(DeviceConnected, "b")
|
||||
@@ -236,7 +236,7 @@ func TestBufferedSub(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
bs := NewBufferedSubscription(s, 10*BufferSize)
|
||||
|
||||
go func() {
|
||||
@@ -267,7 +267,7 @@ func BenchmarkBufferedSub(b *testing.B) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
bufferSize := BufferSize
|
||||
bs := NewBufferedSubscription(s, bufferSize)
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestSinceUsesSubscriptionId(t *testing.T) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(DeviceConnected)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
bs := NewBufferedSubscription(s, 10*BufferSize)
|
||||
|
||||
l.Log(DeviceConnected, "a") // SubscriptionID = 1
|
||||
@@ -390,7 +390,7 @@ func TestUnsubscribeContention(t *testing.T) {
|
||||
defer listenerWg.Done()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -449,7 +449,7 @@ func BenchmarkLogEvent(b *testing.B) {
|
||||
go l.Serve()
|
||||
|
||||
s := l.Subscribe(AllEvents)
|
||||
defer l.Unsubscribe(s)
|
||||
defer s.Unsubscribe()
|
||||
NewBufferedSubscription(s, 1) // runs in the background
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
Reference in New Issue
Block a user