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
}
err = messenger.Init()
err = messenger.InitInstallations()
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
}
@ -424,11 +430,11 @@ func printUsage() {
usage := `
Usage: statusd [options]
Examples:
statusd # run regular Whisper node that joins Status network
statusd -c ./default.json # run node with configuration specified in ./default.json file
statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file
statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call
statusd -c ./default.json -log DEBUG --seed-phrase="test test test test test test test test test test test junk" --password=password # run node with configuration specified in ./default.json file, and import account with seed phrase and password
statusd # run regular Whisper node that joins Status network
statusd -c ./default.json # run node with configuration specified in ./default.json file
statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file
statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call
statusd -c ./default.json -log DEBUG --seed-phrase="test test test test test test test test test test test junk" --password=password # run node with configuration specified in ./default.json file, and import account with seed phrase and password
Options:
`

View File

@ -726,6 +726,11 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
}
m.started = true
err := m.InitFilters()
if err != nil {
return nil, err
}
now := time.Now().UnixMilli()
if err := m.settings.CheckAndDeleteExpiredKeypairsAndAccounts(uint64(now)); err != nil {
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.
func (m *Messenger) Init() error {
func (m *Messenger) InitFilters() error {
// Seed the for color generation
rand.Seed(time.Now().Unix())
@ -1852,20 +1875,6 @@ func (m *Messenger) Init() error {
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)
if err != nil {
return err

View File

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

View File

@ -150,6 +150,8 @@ func (m *Messenger) setInstallationHostname() error {
if err != nil {
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)
}
}

View File

@ -6,9 +6,11 @@ import (
"image"
"image/png"
"os"
"runtime"
"testing"
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/stretchr/testify/suite"
@ -403,3 +405,23 @@ func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() {
s.Require().NoError(bob2.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
}
func (s *MessengerSuite) TestInit() {
func (s *MessengerSuite) TestInitFilters() {
testCases := []struct {
Name string
Prep func()
@ -174,7 +174,7 @@ func (s *MessengerSuite) TestInit() {
for _, tc := range testCases {
s.Run(tc.Name, func() {
tc.Prep()
err := s.m.Init()
err := s.m.InitFilters()
s.Require().NoError(err)
filters := s.m.transport.Filters()
expectedFilters += tc.AddedFilters

View File

@ -184,7 +184,11 @@ func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, appD
if s.config.ProcessBackedupMessages {
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) {