mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
routing_plugin: release with a live context
`ReleaseRoutingPlugin` called `Done` with the caller context. In the loop-out payment flow this cleanup is deferred, so the context may already be canceled by the time teardown runs. That can prevent mission-control restoration in `Done`, leaving plugin-induced routing state behind after a swap exits. Fix this by detaching cancellation in `ReleaseRoutingPlugin` and passing `context.WithoutCancel(ctx)` to `Done`, so teardown still executes during caller cancellation. Also add a regression test that cancels the caller context before release and asserts the plugin `Done` call still receives a live context.
This commit is contained in:
parent
3e526faf82
commit
f191775773
2 changed files with 60 additions and 3 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btclog/v2"
|
||||
|
|
@ -124,9 +125,17 @@ func ReleaseRoutingPlugin(ctx context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := routingPluginInstance.Done(ctx); err != nil {
|
||||
log.Errorf("Error while releasing routing plugin: %v",
|
||||
err)
|
||||
// Use a timeout so a hanging Done call does not block the mutex
|
||||
// indefinitely, which would prevent other loop-outs from acquiring
|
||||
// the routing plugin.
|
||||
releaseCtx, cancel := context.WithTimeout(
|
||||
context.WithoutCancel(ctx), 30*time.Second,
|
||||
)
|
||||
defer cancel()
|
||||
|
||||
err := routingPluginInstance.Done(releaseCtx)
|
||||
if err != nil {
|
||||
log.Errorf("Error while releasing routing plugin: %v", err)
|
||||
}
|
||||
|
||||
routingPluginInstance = nil
|
||||
|
|
|
|||
|
|
@ -692,3 +692,51 @@ func TestRoutingPluginAcquireRelease(t *testing.T) {
|
|||
require.NotNil(t, plugin2)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// mockRoutingPlugin is a minimal RoutingPlugin used to capture the context
|
||||
// passed to Done.
|
||||
type mockRoutingPlugin struct {
|
||||
doneCtxErr error
|
||||
}
|
||||
|
||||
// Init is a no-op initializer for the mock plugin.
|
||||
func (m *mockRoutingPlugin) Init(_ context.Context, _ route.Vertex,
|
||||
_ [][]zpay32.HopHint, _ btcutil.Amount) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Done records ctx.Err() so tests can assert whether teardown ran with a live
|
||||
// context.
|
||||
func (m *mockRoutingPlugin) Done(ctx context.Context) error {
|
||||
m.doneCtxErr = ctx.Err()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforePayment is a no-op hook for the mock plugin.
|
||||
func (m *mockRoutingPlugin) BeforePayment(_ context.Context, _, _ int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestReleaseRoutingPluginUsesLiveContext checks that ReleaseRoutingPlugin does
|
||||
// not propagate caller cancellation to plugin teardown. The test cancels the
|
||||
// caller context before release and verifies mock Done still sees nil ctx.Err.
|
||||
func TestReleaseRoutingPluginUsesLiveContext(t *testing.T) {
|
||||
ReleaseRoutingPlugin(context.Background())
|
||||
t.Cleanup(func() {
|
||||
ReleaseRoutingPlugin(context.Background())
|
||||
})
|
||||
|
||||
mockPlugin := &mockRoutingPlugin{}
|
||||
|
||||
routingPluginMx.Lock()
|
||||
routingPluginInstance = mockPlugin
|
||||
routingPluginMx.Unlock()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
ReleaseRoutingPlugin(ctx)
|
||||
|
||||
require.NoError(t, mockPlugin.doneCtxErr)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue