mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
staticaddr/deposit: finalize deposits asynchronously
Final deposit states should not stall while deposit locks are held, because a blocked manager receive loop can otherwise hold up the deposit FSM; if shutdown happens before notification delivery, startup recovery can still resume from the final state. Send finalization notifications from a goroutine so final states are recorded without waiting on the manager receive loop, and add tests for blocked manager delivery and shutdown races.
This commit is contained in:
parent
44a6c7a144
commit
bbeb813bfa
2 changed files with 153 additions and 7 deletions
|
|
@ -161,14 +161,25 @@ func (f *FSM) WaitForExpirySweepAction(ctx context.Context,
|
|||
|
||||
// FinalizeDepositAction is the final action after a withdrawal. It signals to
|
||||
// the manager that the deposit has been swept and the FSM can be removed.
|
||||
func (f *FSM) FinalizeDepositAction(ctx context.Context,
|
||||
func (f *FSM) FinalizeDepositAction(_ context.Context,
|
||||
_ fsm.EventContext) fsm.EventType {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fsm.OnError
|
||||
outpoint := f.deposit.OutPoint
|
||||
|
||||
case f.finalizedDepositChan <- f.deposit.OutPoint:
|
||||
return fsm.NoOp
|
||||
}
|
||||
// The finalization notification only tells the manager to remove the
|
||||
// deposit from its active set. Send it asynchronously so a busy manager
|
||||
// loop can't stall withdrawal confirmation while deposit locks are held.
|
||||
go func() {
|
||||
select {
|
||||
case <-f.quitChan:
|
||||
// The deposit is already in a final state. If shutdown wins
|
||||
// this race, startup recovery will skip it instead of
|
||||
// re-adding it to the active set.
|
||||
return
|
||||
|
||||
case f.finalizedDepositChan <- outpoint:
|
||||
}
|
||||
}()
|
||||
|
||||
return fsm.NoOp
|
||||
}
|
||||
|
|
|
|||
135
staticaddr/deposit/actions_test.go
Normal file
135
staticaddr/deposit/actions_test.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package deposit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestFinalizeDepositActionDoesNotBlock ensures the final cleanup notification
|
||||
// does not block the withdrawal completion path while the manager loop is busy.
|
||||
func TestFinalizeDepositActionDoesNotBlock(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
outpoint := wire.OutPoint{
|
||||
Hash: chainhash.Hash{1},
|
||||
Index: 1,
|
||||
}
|
||||
|
||||
depositFSM := &FSM{
|
||||
deposit: &Deposit{
|
||||
OutPoint: outpoint,
|
||||
},
|
||||
quitChan: make(chan struct{}),
|
||||
finalizedDepositChan: make(chan wire.OutPoint),
|
||||
}
|
||||
|
||||
resultChan := make(chan fsm.EventType, 1)
|
||||
go func() {
|
||||
resultChan <- depositFSM.FinalizeDepositAction(ctx, nil)
|
||||
}()
|
||||
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
require.Equal(t, fsm.NoOp, result)
|
||||
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("FinalizeDepositAction blocked on manager cleanup")
|
||||
}
|
||||
|
||||
select {
|
||||
case gotOutpoint := <-depositFSM.finalizedDepositChan:
|
||||
require.Equal(t, outpoint, gotOutpoint)
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("finalization cleanup notification was not delivered")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFinalizeDepositActionIgnoresRequestCancellation ensures the cleanup
|
||||
// notification is tied to the FSM lifetime, not the caller's request context.
|
||||
func TestFinalizeDepositActionIgnoresRequestCancellation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
quitChan := make(chan struct{})
|
||||
defer close(quitChan)
|
||||
|
||||
outpoint := wire.OutPoint{
|
||||
Hash: chainhash.Hash{2},
|
||||
Index: 2,
|
||||
}
|
||||
|
||||
depositFSM := &FSM{
|
||||
deposit: &Deposit{
|
||||
OutPoint: outpoint,
|
||||
},
|
||||
quitChan: quitChan,
|
||||
finalizedDepositChan: make(chan wire.OutPoint),
|
||||
}
|
||||
|
||||
resultChan := make(chan fsm.EventType, 1)
|
||||
go func() {
|
||||
resultChan <- depositFSM.FinalizeDepositAction(ctx, nil)
|
||||
}()
|
||||
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
require.Equal(t, fsm.NoOp, result)
|
||||
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("FinalizeDepositAction blocked on manager cleanup")
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
select {
|
||||
case gotOutpoint := <-depositFSM.finalizedDepositChan:
|
||||
require.Equal(t, outpoint, gotOutpoint)
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("finalization cleanup notification was dropped after " +
|
||||
"request cancellation")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFinalizeDepositActionIgnoresCanceledContext ensures the final cleanup
|
||||
// notification is still queued even if the caller's context is already done.
|
||||
func TestFinalizeDepositActionIgnoresCanceledContext(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
quitChan := make(chan struct{})
|
||||
defer close(quitChan)
|
||||
|
||||
outpoint := wire.OutPoint{
|
||||
Hash: chainhash.Hash{3},
|
||||
Index: 3,
|
||||
}
|
||||
|
||||
depositFSM := &FSM{
|
||||
deposit: &Deposit{
|
||||
OutPoint: outpoint,
|
||||
},
|
||||
quitChan: quitChan,
|
||||
finalizedDepositChan: make(chan wire.OutPoint),
|
||||
}
|
||||
|
||||
result := depositFSM.FinalizeDepositAction(ctx, nil)
|
||||
require.Equal(t, fsm.NoOp, result)
|
||||
|
||||
select {
|
||||
case gotOutpoint := <-depositFSM.finalizedDepositChan:
|
||||
require.Equal(t, outpoint, gotOutpoint)
|
||||
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("finalization cleanup notification was dropped for " +
|
||||
"an already-canceled request context")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue