perf(login)!: Set-up messenger filters outside login flow (#5229)

Fixes the slow login in mobile devices when users have joined large communities,
such as the Status one. A user would get stuck for almost 20s in some devices.

We identified that the step to set-up filters in the messenger is potentially
expensive and that it is not critical to happen before the node.login signal is
emitted. The solution presented in this PR is to set-up filters inside
messenger.Start(), which the client already calls immediately after login.

With this change, users of the mobile app can login pretty fast even when they
joined large communities. They can immediately interact with other parts of the
app even if filter initialization is running in the background, like Wallet,
Activity Center, Settings, and Profile.

Breaking changes: in the mobile repository, we had to change where the endpoint
wakuext_startMessenger was called and the order of a few events to process
chats. So essentially ordering, but no data changes.

- Root issue https://github.com/status-im/status-mobile/issues/20059
- Related mobile PR https://github.com/status-im/status-mobile/pull/20173
This commit is contained in:
Icaro Motta 2024-06-10 12:02:42 -03:00 committed by GitHub
parent 39a7d41135
commit dbed69d155
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 75 additions and 27 deletions

View File

@ -287,9 +287,15 @@ func main() {
return return
} }
err = messenger.Init() err = messenger.InitInstallations()
if err != nil { if err != nil {
logger.Error("failed to init messenger", "error", err) logger.Error("failed to init messenger installations", "error", err)
return
}
err = messenger.InitFilters()
if err != nil {
logger.Error("failed to init messenger filters", "error", err)
return return
} }

View File

@ -726,6 +726,11 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
} }
m.started = true m.started = true
err := m.InitFilters()
if err != nil {
return nil, err
}
now := time.Now().UnixMilli() now := time.Now().UnixMilli()
if err := m.settings.CheckAndDeleteExpiredKeypairsAndAccounts(uint64(now)); err != nil { if err := m.settings.CheckAndDeleteExpiredKeypairsAndAccounts(uint64(now)); err != nil {
return nil, err return nil, err
@ -1663,9 +1668,27 @@ func (m *Messenger) handlePushNotificationClientRegistrations(c chan struct{}) {
}() }()
} }
// Init analyzes chats and contacts in order to setup filters func (m *Messenger) InitInstallations() error {
installations, err := m.encryptor.GetOurInstallations(&m.identity.PublicKey)
if err != nil {
return err
}
for _, installation := range installations {
m.allInstallations.Store(installation.ID, installation)
}
err = m.setInstallationHostname()
if err != nil {
return err
}
return nil
}
// InitFilters analyzes chats and contacts in order to setup filters
// which are responsible for retrieving messages. // which are responsible for retrieving messages.
func (m *Messenger) Init() error { func (m *Messenger) InitFilters() error {
// Seed the for color generation // Seed the for color generation
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
@ -1852,20 +1875,6 @@ func (m *Messenger) Init() error {
publicKeys = append(publicKeys, publicKey) publicKeys = append(publicKeys, publicKey)
} }
installations, err := m.encryptor.GetOurInstallations(&m.identity.PublicKey)
if err != nil {
return err
}
for _, installation := range installations {
m.allInstallations.Store(installation.ID, installation)
}
err = m.setInstallationHostname()
if err != nil {
return err
}
_, err = m.transport.InitFilters(filtersToInit, publicKeys) _, err = m.transport.InitFilters(filtersToInit, publicKeys)
if err != nil { if err != nil {
return err return err

View File

@ -106,7 +106,12 @@ func newTestMessenger(waku types.Waku, config testMessengerConfig) (*Messenger,
m.retrievedMessagesIteratorFactory = config.messagesOrderController.newMessagesIterator m.retrievedMessagesIteratorFactory = config.messagesOrderController.newMessagesIterator
} }
err = m.Init() err = m.InitInstallations()
if err != nil {
return nil, err
}
err = m.InitFilters()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -150,6 +150,8 @@ func (m *Messenger) setInstallationHostname() error {
if err != nil { if err != nil {
return err return err
} }
// NOTE: imd.Name is always empty in this else branch, which leads to the
// result of Sprintf having a trailing whitespace.
imd.Name = fmt.Sprintf("%s %s", hn, imd.Name) imd.Name = fmt.Sprintf("%s %s", hn, imd.Name)
} }
} }

View File

@ -6,9 +6,11 @@ import (
"image" "image"
"image/png" "image/png"
"os" "os"
"runtime"
"testing" "testing"
userimage "github.com/status-im/status-go/images" userimage "github.com/status-im/status-go/images"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/services/browsers" "github.com/status-im/status-go/services/browsers"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -403,3 +405,23 @@ func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() {
s.Require().NoError(bob2.Shutdown()) s.Require().NoError(bob2.Shutdown())
s.Require().NoError(alice.Shutdown()) s.Require().NoError(alice.Shutdown())
} }
func (s *MessengerInstallationSuite) TestInitInstallations() {
m, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
s.Require().NoError(err)
// m.InitInstallations is already called when we set-up the messenger for
// testing, thus this test has no act phase.
// err = m.InitInstallations()
// We get one installation when the messenger initializes installations
// correctly.
s.Require().Equal(1, m.allInstallations.Len())
deviceName, err := server.GetDeviceName()
s.Require().NoError(err)
installation, ok := m.allInstallations.Load(m.installationID)
s.Require().True(ok)
s.Require().Equal(deviceName+" ", installation.InstallationMetadata.Name)
s.Require().Equal(runtime.GOOS, installation.InstallationMetadata.DeviceType)
}

View File

@ -81,7 +81,7 @@ func (n *testNode) PeersCount() int {
return 1 return 1
} }
func (s *MessengerSuite) TestInit() { func (s *MessengerSuite) TestInitFilters() {
testCases := []struct { testCases := []struct {
Name string Name string
Prep func() Prep func()
@ -174,7 +174,7 @@ func (s *MessengerSuite) TestInit() {
for _, tc := range testCases { for _, tc := range testCases {
s.Run(tc.Name, func() { s.Run(tc.Name, func() {
tc.Prep() tc.Prep()
err := s.m.Init() err := s.m.InitFilters()
s.Require().NoError(err) s.Require().NoError(err)
filters := s.m.transport.Filters() filters := s.m.transport.Filters()
expectedFilters += tc.AddedFilters expectedFilters += tc.AddedFilters

View File

@ -184,7 +184,11 @@ func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, appD
if s.config.ProcessBackedupMessages { if s.config.ProcessBackedupMessages {
s.messenger.EnableBackedupMessagesProcessing() s.messenger.EnableBackedupMessagesProcessing()
} }
return messenger.Init()
// Be mindful of adding more initialization code, as it can easily
// impact login times for mobile users. For example, we avoid calling
// messenger.InitFilters here.
return s.messenger.InitInstallations()
} }
func (s *Service) StartMessenger() (*protocol.MessengerResponse, error) { func (s *Service) StartMessenger() (*protocol.MessengerResponse, error) {