Skip to content

Commit 2715c40

Browse files
committed
fix: Avoid locking on app lifecycle change
1 parent a9c169a commit 2715c40

File tree

2 files changed

+42
-16
lines changed

2 files changed

+42
-16
lines changed

pkg/backend/geth_backend.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ type LoginParams struct {
7979

8080
// StatusBackend implements the Status.im service over go-ethereum
8181
type StatusBackend struct {
82-
mu sync.Mutex
82+
mu sync.Mutex
83+
lifecycleMu sync.Mutex
8384
// rootDataDir is the same for all networks.
8485
rootDataDir string
8586
appDB *sql.DB
@@ -2048,19 +2049,27 @@ func (b *StatusBackend) getVerifiedWalletAccount(address, password string) (*gen
20482049
// AppStateChange handles app state changes (background/foreground).
20492050
// state values: see https://facebook.github.io/react-native/docs/appstate.html
20502051
func (b *StatusBackend) AppStateChange(state AppState) {
2051-
b.mu.Lock()
2052-
defer b.mu.Unlock()
2053-
20542052
if !state.IsValid() {
20552053
b.logger.Warn("invalid app state, not reporting app state change", zap.Any("state", state))
20562054
return
20572055
}
20582056

2057+
b.mu.Lock()
20592058
b.appState = state
2060-
20612059
if b.statusNode == nil {
20622060
b.logger.Warn("statusNode nil, applying app state change without running node")
20632061
}
2062+
b.mu.Unlock()
2063+
2064+
b.lifecycleMu.Lock()
2065+
defer b.lifecycleMu.Unlock()
2066+
2067+
if state == AppStateForeground && b.lifecycleState == AppLifecycleRunning {
2068+
return
2069+
}
2070+
if state != AppStateForeground && b.lifecycleState == AppLifecyclePausedBackground {
2071+
return
2072+
}
20642073

20652074
if state == AppStateForeground {
20662075
if err := b.resumeLocked(); err != nil {
@@ -2092,6 +2101,10 @@ func (b *StatusBackend) StartLocalNotifications() error {
20922101
func (b *StatusBackend) Logout() error {
20932102
b.mu.Lock()
20942103
defer b.mu.Unlock()
2104+
// Drain any in-progress AppStateChange transition before stopping the node.
2105+
// Lock order is always b.mu → lifecycleMu to prevent deadlock.
2106+
b.lifecycleMu.Lock()
2107+
defer b.lifecycleMu.Unlock()
20952108

20962109
b.logger.Debug("logging out")
20972110

pkg/backend/pause_play.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package backend
33
import (
44
"fmt"
55

6+
"github.com/status-im/status-go/pkg/backend/node"
67
"github.com/status-im/status-go/protocol"
78
)
89

@@ -18,9 +19,11 @@ const (
1819

1920
func (s AppLifecycleState) String() string { return string(s) }
2021

22+
// LifecycleState returns the current backend lifecycle state.
23+
// Requires lifecycleMu since lifecycleState is written by pause/resumeLocked.
2124
func (b *StatusBackend) LifecycleState() AppLifecycleState {
22-
b.mu.Lock()
23-
defer b.mu.Unlock()
25+
b.lifecycleMu.Lock()
26+
defer b.lifecycleMu.Unlock()
2427
return b.lifecycleState
2528
}
2629

@@ -31,14 +34,19 @@ func (b *StatusBackend) pauseLocked() error {
3134
if b.lifecycleState == AppLifecyclePausedBackground {
3235
return nil
3336
}
34-
if b.statusNode == nil || !b.statusNode.IsRunning() {
37+
// Snapshot statusNode pointer under b.mu to avoid data race with Logout.
38+
b.mu.Lock()
39+
sn := b.statusNode
40+
b.mu.Unlock()
41+
42+
if sn == nil || !sn.IsRunning() {
3543
b.lifecycleState = AppLifecycleStopped
3644
return nil
3745
}
38-
if messenger := b.currentMessengerLocked(); messenger != nil {
46+
if messenger := b.currentMessenger(sn); messenger != nil {
3947
messenger.ToBackground()
4048
}
41-
if err := b.statusNode.PauseBackground(); err != nil {
49+
if err := sn.PauseBackground(); err != nil {
4250
return fmt.Errorf("pause background: %w", err)
4351
}
4452
b.lifecycleState = AppLifecyclePausedBackground
@@ -52,25 +60,30 @@ func (b *StatusBackend) resumeLocked() error {
5260
if b.lifecycleState == AppLifecycleRunning {
5361
return nil
5462
}
55-
if b.statusNode == nil || !b.statusNode.IsRunning() {
63+
// Snapshot statusNode pointer under b.mu to avoid data race with Logout.
64+
b.mu.Lock()
65+
sn := b.statusNode
66+
b.mu.Unlock()
67+
68+
if sn == nil || !sn.IsRunning() {
5669
b.lifecycleState = AppLifecycleStopped
5770
return nil
5871
}
59-
if messenger := b.currentMessengerLocked(); messenger != nil {
72+
if messenger := b.currentMessenger(sn); messenger != nil {
6073
messenger.ToForeground()
6174
}
6275

6376
b.lifecycleState = AppLifecycleResumingForeground
64-
if err := b.statusNode.ResumeForeground(); err != nil {
77+
if err := sn.ResumeForeground(); err != nil {
6578
return fmt.Errorf("resume foreground: %w", err)
6679
}
6780
b.lifecycleState = AppLifecycleRunning
6881
return nil
6982
}
7083

71-
func (b *StatusBackend) currentMessengerLocked() *protocol.Messenger {
72-
if b.statusNode == nil || b.statusNode.WakuV2ExtService() == nil {
84+
func (b *StatusBackend) currentMessenger(sn *node.StatusNode) *protocol.Messenger {
85+
if sn == nil || sn.WakuV2ExtService() == nil {
7386
return nil
7487
}
75-
return b.statusNode.WakuV2ExtService().Messenger()
88+
return sn.WakuV2ExtService().Messenger()
7689
}

0 commit comments

Comments
 (0)