mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-10-10 18:43:29 +02:00
Merge pull request #9361 from starius/optimize-context-guard
fn: optimize context guard
This commit is contained in:
@@ -51,6 +51,10 @@ func (g *ContextGuard) Quit() {
|
|||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear cancelFns. It is safe to use nil, because no write
|
||||||
|
// operations to it can happen after g.quit is closed.
|
||||||
|
g.cancelFns = nil
|
||||||
|
|
||||||
close(g.quit)
|
close(g.quit)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -149,7 +153,7 @@ func (g *ContextGuard) Create(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.blocking {
|
if opts.blocking {
|
||||||
g.ctxBlocking(ctx, cancel)
|
g.ctxBlocking(ctx)
|
||||||
|
|
||||||
return ctx, cancel
|
return ctx, cancel
|
||||||
}
|
}
|
||||||
@@ -169,9 +173,10 @@ func (g *ContextGuard) Create(ctx context.Context,
|
|||||||
return ctx, cancel
|
return ctx, cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
// ctxQuitUnsafe spins off a goroutine that will block until the passed context
|
// ctxQuitUnsafe increases the wait group counter, waits until the context is
|
||||||
// is cancelled or until the quit channel has been signaled after which it will
|
// cancelled and decreases the wait group counter. It stores the passed cancel
|
||||||
// call the passed cancel function and decrement the wait group.
|
// function and returns a wrapped version, which removed the stored one and
|
||||||
|
// calls it. The Quit method calls all the stored cancel functions.
|
||||||
//
|
//
|
||||||
// NOTE: the caller must hold the ContextGuard's mutex before calling this
|
// NOTE: the caller must hold the ContextGuard's mutex before calling this
|
||||||
// function.
|
// function.
|
||||||
@@ -181,35 +186,27 @@ func (g *ContextGuard) ctxQuitUnsafe(ctx context.Context,
|
|||||||
cancel = g.addCancelFnUnsafe(cancel)
|
cancel = g.addCancelFnUnsafe(cancel)
|
||||||
|
|
||||||
g.wg.Add(1)
|
g.wg.Add(1)
|
||||||
go func() {
|
|
||||||
defer cancel()
|
|
||||||
defer g.wg.Done()
|
|
||||||
|
|
||||||
select {
|
// We don't have to wait on g.quit here: g.quit can be closed only in
|
||||||
case <-g.quit:
|
// the Quit method, which also closes the context we are waiting for.
|
||||||
|
context.AfterFunc(ctx, func() {
|
||||||
case <-ctx.Done():
|
g.wg.Done()
|
||||||
}
|
})
|
||||||
}()
|
|
||||||
|
|
||||||
return cancel
|
return cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
// ctxBlocking spins off a goroutine that will block until the passed context
|
// ctxBlocking increases the wait group counter, waits until the context is
|
||||||
// is cancelled after which it will call the passed cancel function and
|
// cancelled and decreases the wait group counter.
|
||||||
// decrement the wait group.
|
//
|
||||||
func (g *ContextGuard) ctxBlocking(ctx context.Context,
|
// NOTE: the caller must hold the ContextGuard's mutex before calling this
|
||||||
cancel context.CancelFunc) {
|
// function.
|
||||||
|
func (g *ContextGuard) ctxBlocking(ctx context.Context) {
|
||||||
g.wg.Add(1)
|
g.wg.Add(1)
|
||||||
go func() {
|
|
||||||
defer cancel()
|
|
||||||
defer g.wg.Done()
|
|
||||||
|
|
||||||
select {
|
context.AfterFunc(ctx, func() {
|
||||||
case <-ctx.Done():
|
g.wg.Done()
|
||||||
}
|
})
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addCancelFnUnsafe adds a context cancel function to the manager and returns a
|
// addCancelFnUnsafe adds a context cancel function to the manager and returns a
|
||||||
|
@@ -2,8 +2,11 @@ package fn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestContextGuard tests the behaviour of the ContextGuard.
|
// TestContextGuard tests the behaviour of the ContextGuard.
|
||||||
@@ -298,6 +301,12 @@ func TestContextGuard(t *testing.T) {
|
|||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
t.Fatalf("timeout")
|
t.Fatalf("timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cancel the context.
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
// Make sure wg's counter gets to 0 eventually.
|
||||||
|
g.WgWait()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test that if we add the CustomTimeoutCGOpt option, then the context
|
// Test that if we add the CustomTimeoutCGOpt option, then the context
|
||||||
@@ -433,3 +442,36 @@ func TestContextGuard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContextGuardCountGoroutines makes sure that ContextGuard doesn't create
|
||||||
|
// any goroutines while waiting for contexts.
|
||||||
|
func TestContextGuardCountGoroutines(t *testing.T) {
|
||||||
|
// NOTE: t.Parallel() is not called in this test because it relies on an
|
||||||
|
// accurate count of active goroutines. Running other tests in parallel
|
||||||
|
// would introduce additional goroutines, leading to unreliable results.
|
||||||
|
|
||||||
|
g := NewContextGuard()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// Count goroutines before contexts are created.
|
||||||
|
count1 := runtime.NumGoroutine()
|
||||||
|
|
||||||
|
// Create 1000 contexts of each type.
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
_, _ = g.Create(ctx)
|
||||||
|
_, _ = g.Create(ctx, WithBlockingCG())
|
||||||
|
_, _ = g.Create(ctx, WithTimeoutCG())
|
||||||
|
_, _ = g.Create(ctx, WithBlockingCG(), WithTimeoutCG())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure no new goroutine was launched.
|
||||||
|
count2 := runtime.NumGoroutine()
|
||||||
|
require.LessOrEqual(t, count2, count1)
|
||||||
|
|
||||||
|
// Cancel root context.
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
// Make sure wg's counter gets to 0 eventually.
|
||||||
|
g.WgWait()
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user