feat(wallet) process all the events and debounce updates

Process missing events

Throttle down downloader's events to avoid overloading the CPU with
change detection.

Updates status-desktop #12120
This commit is contained in:
Stefan 2024-02-16 23:41:44 -03:00 committed by Stefan Dunca
parent dc726007b0
commit a63d33e04a
3 changed files with 74 additions and 57 deletions

View File

@ -68,7 +68,8 @@ type Service struct {
subscriptions event.Subscription subscriptions event.Subscription
ch chan walletevent.Event ch chan walletevent.Event
// sessionsRWMutex is used to protect all sessions related members // sessionsRWMutex is used to protect all sessions related members
sessionsRWMutex sync.RWMutex sessionsRWMutex sync.RWMutex
debounceDuration time.Duration
pendingTracker *transactions.PendingTxTracker pendingTracker *transactions.PendingTxTracker
} }
@ -86,6 +87,8 @@ func NewService(db *sql.DB, tokenManager token.ManagerInterface, collectibles co
scheduler: async.NewMultiClientScheduler(), scheduler: async.NewMultiClientScheduler(),
sessions: make(map[SessionID]*Session), sessions: make(map[SessionID]*Session),
// here to be overwritten by tests
debounceDuration: 1 * time.Second,
pendingTracker: pendingTracker, pendingTracker: pendingTracker,
} }

View File

@ -84,6 +84,7 @@ func setupTestService(tb testing.TB) (state testState) {
state.pendingTracker = transactions.NewPendingTxTracker(db, state.chainClient, nil, state.eventFeed, pendingCheckInterval) state.pendingTracker = transactions.NewPendingTxTracker(db, state.chainClient, nil, state.eventFeed, pendingCheckInterval)
state.service = NewService(db, state.tokenMock, state.collectiblesMock, state.eventFeed, state.pendingTracker) state.service = NewService(db, state.tokenMock, state.collectiblesMock, state.eventFeed, state.pendingTracker)
state.service.debounceDuration = 0
state.close = func() { state.close = func() {
require.NoError(tb, state.pendingTracker.Stop()) require.NoError(tb, state.pendingTracker.Stop())
require.NoError(tb, db.Close()) require.NoError(tb, db.Close())

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"strconv" "strconv"
"time"
eth "github.com/ethereum/go-ethereum/common" eth "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
@ -331,66 +332,78 @@ func (s *Service) subscribeToEvents() {
// processEvents runs only if more than one session is active // processEvents runs only if more than one session is active
func (s *Service) processEvents() { func (s *Service) processEvents() {
eventCount := 0
lastUpdate := time.Now().UnixMilli()
for event := range s.ch { for event := range s.ch {
// TODO #12120: process rest of the events transactions.EventPendingTransactionStatusChanged, transfer.EventNewTransfers if event.Type == transactions.EventPendingTransactionUpdate ||
// TODO #12120: debounce for 1s and sum all events as extraCount to be sure we don't miss any change event.Type == transactions.EventPendingTransactionStatusChanged ||
if event.Type == transactions.EventPendingTransactionUpdate { event.Type == transfer.EventNewTransfers {
for sessionID := range s.sessions { eventCount++
session := s.sessions[sessionID] }
// debounce events updates
if eventCount > 0 &&
(time.Duration(time.Now().UnixMilli()-lastUpdate)*time.Millisecond) >= s.debounceDuration {
s.detectNew(eventCount)
eventCount = 0
lastUpdate = time.Now().UnixMilli()
}
}
}
extraCount := 1 func (s *Service) detectNew(changeCount int) {
fetchLen := len(session.model) + extraCount for sessionID := range s.sessions {
activities, err := getActivityEntries(context.Background(), s.getDeps(), session.addresses, session.allAddresses, session.chainIDs, session.filter, 0, fetchLen) session := s.sessions[sessionID]
if err != nil {
log.Error("Error getting activity entries", "error", err) fetchLen := len(session.model) + changeCount
continue activities, err := getActivityEntries(context.Background(), s.getDeps(), session.addresses, session.allAddresses, session.chainIDs, session.filter, 0, fetchLen)
} if err != nil {
log.Error("Error getting activity entries", "error", err)
s.sessionsRWMutex.RLock() continue
allData := append(session.new, session.model...) }
new, _ /*removed*/ := findUpdates(allData, activities)
s.sessionsRWMutex.RUnlock() s.sessionsRWMutex.RLock()
allData := append(session.new, session.model...)
s.sessionsRWMutex.Lock() new, _ /*removed*/ := findUpdates(allData, activities)
lastProcessed := -1 s.sessionsRWMutex.RUnlock()
onTop := true
var mixed []*EntryUpdate s.sessionsRWMutex.Lock()
for i, idRes := range new { lastProcessed := -1
// Detect on top onTop := true
if onTop { var mixed []*EntryUpdate
// mixedIdentityResult.newPos includes session.new, therefore compensate for it for i, idRes := range new {
if ((idRes.newPos - len(session.new)) - lastProcessed) > 1 { // Detect on top
// From now on the events are not on top and continuous but mixed between existing entries if onTop {
onTop = false // mixedIdentityResult.newPos includes session.new, therefore compensate for it
mixed = make([]*EntryUpdate, 0, len(new)-i) if ((idRes.newPos - len(session.new)) - lastProcessed) > 1 {
} // From now on the events are not on top and continuous but mixed between existing entries
lastProcessed = idRes.newPos onTop = false
} mixed = make([]*EntryUpdate, 0, len(new)-i)
if onTop {
if session.new == nil {
session.new = make([]EntryIdentity, 0, len(new))
}
session.new = append(session.new, idRes.id)
} else {
modelPos := idRes.newPos - len(session.new)
entry := activities[idRes.newPos]
entry.isNew = true
mixed = append(mixed, &EntryUpdate{
Pos: modelPos,
Entry: &entry,
})
// Insert in session model at modelPos index
session.model = append(session.model[:modelPos], append([]EntryIdentity{{payloadType: entry.payloadType, transaction: entry.transaction, id: entry.id}}, session.model[modelPos:]...)...)
}
}
s.sessionsRWMutex.Unlock()
if len(session.new) > 0 || len(mixed) > 0 {
go notify(s.eventFeed, sessionID, len(session.new) > 0, mixed)
} }
lastProcessed = idRes.newPos
} }
if onTop {
if session.new == nil {
session.new = make([]EntryIdentity, 0, len(new))
}
session.new = append(session.new, idRes.id)
} else {
modelPos := idRes.newPos - len(session.new)
entry := activities[idRes.newPos]
entry.isNew = true
mixed = append(mixed, &EntryUpdate{
Pos: modelPos,
Entry: &entry,
})
// Insert in session model at modelPos index
session.model = append(session.model[:modelPos], append([]EntryIdentity{{payloadType: entry.payloadType, transaction: entry.transaction, id: entry.id}}, session.model[modelPos:]...)...)
}
}
s.sessionsRWMutex.Unlock()
if len(session.new) > 0 || len(mixed) > 0 {
go notify(s.eventFeed, sessionID, len(session.new) > 0, mixed)
} }
} }
} }