Skip to content

Commit cecb24c

Browse files
authored
Merge pull request #902 from hieblmi/fix-deadlock
staticaddr: lock mutex for active deposits access
2 parents 75ad9b4 + 679ba13 commit cecb24c

File tree

4 files changed

+39
-21
lines changed

4 files changed

+39
-21
lines changed

looprpc/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module github.com/lightninglabs/loop/looprpc
22

3-
go 1.22.3
3+
go 1.23.6
4+
45
toolchain go1.23.7
56

67
require (

staticaddr/deposit/actions.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,9 @@ func (f *FSM) SweptExpiredDepositAction(ctx context.Context,
147147
case <-ctx.Done():
148148
return fsm.OnError
149149

150-
default:
151-
f.finalizedDepositChan <- f.deposit.OutPoint
152-
ctx.Done()
150+
case f.finalizedDepositChan <- f.deposit.OutPoint:
151+
return fsm.NoOp
153152
}
154-
155-
return fsm.NoOp
156153
}
157154

158155
// FinalizeDepositAction is the final action after a withdrawal. It signals to
@@ -164,9 +161,7 @@ func (f *FSM) FinalizeDepositAction(ctx context.Context,
164161
case <-ctx.Done():
165162
return fsm.OnError
166163

167-
default:
168-
f.finalizedDepositChan <- f.deposit.OutPoint
164+
case f.finalizedDepositChan <- f.deposit.OutPoint:
165+
return fsm.NoOp
169166
}
170-
171-
return fsm.NoOp
172167
}

staticaddr/deposit/fsm.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ type FSM struct {
141141

142142
blockNtfnChan chan uint32
143143

144+
// quitChan stops after the FSM stops consuming blockNtfnChan.
145+
quitChan chan struct{}
146+
147+
// finalizedDepositChan is used to signal that the deposit has been
148+
// finalized and the FSM can be removed from the manager's memory.
144149
finalizedDepositChan chan wire.OutPoint
145150
}
146151

@@ -167,6 +172,7 @@ func NewFSM(ctx context.Context, deposit *Deposit, cfg *ManagerConfig,
167172
params: params,
168173
address: address,
169174
blockNtfnChan: make(chan uint32),
175+
quitChan: make(chan struct{}),
170176
finalizedDepositChan: finalizedDepositChan,
171177
}
172178

@@ -191,10 +197,12 @@ func NewFSM(ctx context.Context, deposit *Deposit, cfg *ManagerConfig,
191197

192198
depoFsm.ActionEntryFunc = depoFsm.updateDeposit
193199

194-
go func() {
200+
go func(fsm *FSM) {
201+
defer close(fsm.quitChan)
202+
195203
for {
196204
select {
197-
case currentHeight := <-depoFsm.blockNtfnChan:
205+
case currentHeight := <-fsm.blockNtfnChan:
198206
depoFsm.handleBlockNotification(
199207
ctx, currentHeight,
200208
)
@@ -203,7 +211,7 @@ func NewFSM(ctx context.Context, deposit *Deposit, cfg *ManagerConfig,
203211
return
204212
}
205213
}
206-
}()
214+
}(depoFsm)
207215

208216
return depoFsm, nil
209217
}

staticaddr/deposit/manager.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,31 @@ func (m *Manager) Run(ctx context.Context) error {
131131
select {
132132
case height := <-newBlockChan:
133133
// Inform all active deposits about a new block arrival.
134+
m.mu.Lock()
135+
activeDeposits := make([]*FSM, 0, len(m.activeDeposits))
134136
for _, fsm := range m.activeDeposits {
137+
activeDeposits = append(activeDeposits, fsm)
138+
}
139+
m.mu.Unlock()
140+
141+
for _, fsm := range activeDeposits {
135142
select {
136143
case fsm.blockNtfnChan <- uint32(height):
137144

145+
case <-fsm.quitChan:
146+
continue
147+
138148
case <-ctx.Done():
139149
return ctx.Err()
140150
}
141151
}
152+
142153
case outpoint := <-m.finalizedDepositChan:
143-
// If deposits notify us about their finalization, we
144-
// update the manager's internal state and flush the
145-
// finalized deposit from memory.
154+
// If deposits notify us about their finalization, flush
155+
// the finalized deposit from memory.
156+
m.mu.Lock()
146157
delete(m.activeDeposits, outpoint)
158+
m.mu.Unlock()
147159

148160
case err = <-newBlockErrChan:
149161
return err
@@ -192,7 +204,9 @@ func (m *Manager) recoverDeposits(ctx context.Context) error {
192204
}
193205
}()
194206

207+
m.mu.Lock()
195208
m.activeDeposits[d.OutPoint] = fsm
209+
m.mu.Unlock()
196210
}
197211

198212
return nil
@@ -488,9 +502,9 @@ func (m *Manager) AllStringOutpointsActiveDeposits(outpoints []string,
488502
// TransitionDeposits allows a caller to transition a set of deposits to a new
489503
// state.
490504
// Caveat: The action triggered by the state transitions should not compute
491-
// heavy things or call external endpoints that can block for a long time.
492-
// Deposits will be released if a transition takes longer than
493-
// DefaultTransitionTimeout which is set to 5 seconds.
505+
// heavy things or call external endpoints that can block for a long time as
506+
// this function blocks until the expectedFinalState is reached. The default
507+
// timeout for the transition is set to DefaultTransitionTimeout.
494508
func (m *Manager) TransitionDeposits(ctx context.Context, deposits []*Deposit,
495509
event fsm.EventType, expectedFinalState fsm.StateType) error {
496510

@@ -500,9 +514,9 @@ func (m *Manager) TransitionDeposits(ctx context.Context, deposits []*Deposit,
500514
}
501515

502516
m.mu.Lock()
503-
defer m.mu.Unlock()
504-
505517
stateMachines, _ := m.toActiveDeposits(&outpoints)
518+
m.mu.Unlock()
519+
506520
if stateMachines == nil {
507521
return fmt.Errorf("deposits not found in active deposits")
508522
}

0 commit comments

Comments
 (0)