lib/events: Introduce per-subscription event IDs (fixes #3335)

Events API consumers rely on being able to detect that events were skipped
by the fact that the event ID has increased by more than 1. This is
documented, and is absolutely necessary when trying to maintain a local
model of Syncthing's state.

With the introduction of LocalChangeDetected, which is not exposed to the
Events API, this contract was broken.

This commit introduces separate concepts of a "Global ID" and a
"Subscription ID". The Global ID of an event is unique across all
subscriptions. The Subscription ID is local to a particular subscription,
and always increments by 1. They are both exposed over the Events API, but
the Subscription ID uses the key "id" for backwards compatibility, and
the "?since=xx" parameter refers to the Subscription ID (making the Global
ID for information only).

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3351
LGTM: calmh
This commit is contained in:
Antony Male
2016-06-27 21:18:58 +00:00
committed by Jakob Borg
parent a165838cbd
commit 7ef2743964
3 changed files with 108 additions and 29 deletions

View File

@@ -111,16 +111,20 @@ func (t EventType) MarshalText() ([]byte, error) {
const BufferSize = 64
type Logger struct {
subs []*Subscription
nextID int
mutex sync.Mutex
subs []*Subscription
nextSubscriptionIDs []int
nextGlobalID int
mutex sync.Mutex
}
type Event struct {
ID int `json:"id"`
Time time.Time `json:"time"`
Type EventType `json:"type"`
Data interface{} `json:"data"`
// Per-subscription sequential event ID. Named "id" for backwards compatibility with the REST API
SubscriptionID int `json:"id"`
// Global ID of the event across all subscriptions
GlobalID int `json:"globalID"`
Time time.Time `json:"time"`
Type EventType `json:"type"`
Data interface{} `json:"data"`
}
type Subscription struct {
@@ -144,16 +148,21 @@ func NewLogger() *Logger {
func (l *Logger) Log(t EventType, data interface{}) {
l.mutex.Lock()
dl.Debugln("log", l.nextID, t, data)
l.nextID++
dl.Debugln("log", l.nextGlobalID, t, data)
l.nextGlobalID++
e := Event{
ID: l.nextID,
Time: time.Now(),
Type: t,
Data: data,
GlobalID: l.nextGlobalID,
Time: time.Now(),
Type: t,
Data: data,
}
for _, s := range l.subs {
for i, s := range l.subs {
if s.mask&t != 0 {
e.SubscriptionID = l.nextSubscriptionIDs[i]
l.nextSubscriptionIDs[i]++
select {
case s.events <- e:
default:
@@ -182,6 +191,7 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
}
l.subs = append(l.subs, s)
l.nextSubscriptionIDs = append(l.nextSubscriptionIDs, 1)
l.mutex.Unlock()
return s
}
@@ -192,9 +202,15 @@ func (l *Logger) Unsubscribe(s *Subscription) {
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.nextSubscriptionIDs[i] = l.nextSubscriptionIDs[last]
l.nextSubscriptionIDs[last] = 0
l.nextSubscriptionIDs = l.nextSubscriptionIDs[:last]
break
}
}
@@ -234,7 +250,7 @@ type bufferedSubscription struct {
sub *Subscription
buf []Event
next int
cur int
cur int // Current SubscriptionID
mut sync.Mutex
cond *stdsync.Cond
}
@@ -270,7 +286,7 @@ func (s *bufferedSubscription) pollingLoop() {
s.mut.Lock()
s.buf[s.next] = ev
s.next = (s.next + 1) % len(s.buf)
s.cur = ev.ID
s.cur = ev.SubscriptionID
s.cond.Broadcast()
s.mut.Unlock()
}
@@ -285,12 +301,12 @@ func (s *bufferedSubscription) Since(id int, into []Event) []Event {
}
for i := s.next; i < len(s.buf); i++ {
if s.buf[i].ID > id {
if s.buf[i].SubscriptionID > id {
into = append(into, s.buf[i])
}
}
for i := 0; i < s.next; i++ {
if s.buf[i].ID > id {
if s.buf[i].SubscriptionID > id {
into = append(into, s.buf[i])
}
}