fix: close chain head subscription when the reader is slow
The reader can just re-subscribe when they're ready to catch up. This prevents a slow reader from bogging down the entire system.
This commit is contained in:
parent
14754f1b18
commit
43bbde1e6b
@ -293,27 +293,36 @@ func (cs *ChainStore) SubHeadChanges(ctx context.Context) chan []*api.HeadChange
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(out)
|
defer func() {
|
||||||
var unsubOnce sync.Once
|
// Tell the caller we're done first, the following may block for a bit.
|
||||||
|
close(out)
|
||||||
|
|
||||||
|
// Unsubscribe.
|
||||||
|
cs.bestTips.Unsub(subch)
|
||||||
|
|
||||||
|
// Drain the channel.
|
||||||
|
for range subch {
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case val, ok := <-subch:
|
case val, ok := <-subch:
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warn("chain head sub exit loop")
|
// Shutting down.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case out <- val.([]*api.HeadChange):
|
||||||
|
default:
|
||||||
|
log.Errorf("closing head change subscription due to slow reader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(out) > 5 {
|
if len(out) > 5 {
|
||||||
log.Warnf("head change sub is slow, has %d buffered entries", len(out))
|
log.Warnf("head change sub is slow, has %d buffered entries", len(out))
|
||||||
}
|
}
|
||||||
select {
|
|
||||||
case out <- val.([]*api.HeadChange):
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
}
|
return
|
||||||
case <-ctx.Done():
|
|
||||||
unsubOnce.Do(func() {
|
|
||||||
go cs.bestTips.Unsub(subch)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/filecoin-project/lotus/build"
|
"github.com/filecoin-project/lotus/build"
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
"github.com/filecoin-project/lotus/itests/kit"
|
"github.com/filecoin-project/lotus/itests/kit"
|
||||||
|
logging "github.com/ipfs/go-log/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPI(t *testing.T) {
|
func TestAPI(t *testing.T) {
|
||||||
@ -39,6 +40,7 @@ func runAPITest(t *testing.T, opts ...interface{}) {
|
|||||||
t.Run("testConnectTwo", ts.testConnectTwo)
|
t.Run("testConnectTwo", ts.testConnectTwo)
|
||||||
t.Run("testMining", ts.testMining)
|
t.Run("testMining", ts.testMining)
|
||||||
t.Run("testMiningReal", ts.testMiningReal)
|
t.Run("testMiningReal", ts.testMiningReal)
|
||||||
|
t.Run("testSlowNotify", ts.testSlowNotify)
|
||||||
t.Run("testSearchMsg", ts.testSearchMsg)
|
t.Run("testSearchMsg", ts.testSearchMsg)
|
||||||
t.Run("testNonGenesisMiner", ts.testNonGenesisMiner)
|
t.Run("testNonGenesisMiner", ts.testNonGenesisMiner)
|
||||||
}
|
}
|
||||||
@ -169,6 +171,51 @@ func (ts *apiSuite) testMiningReal(t *testing.T) {
|
|||||||
ts.testMining(t)
|
ts.testMining(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ts *apiSuite) testSlowNotify(t *testing.T) {
|
||||||
|
_ = logging.SetLogLevel("rpc", "ERROR")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
full, miner, _ := kit.EnsembleMinimal(t, ts.opts...)
|
||||||
|
|
||||||
|
// Subscribe a bunch of times to make sure we fill up any RPC buffers.
|
||||||
|
var newHeadsChans []<-chan []*lapi.HeadChange
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
newHeads, err := full.ChainNotify(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
newHeadsChans = append(newHeadsChans, newHeads)
|
||||||
|
}
|
||||||
|
|
||||||
|
initHead := (<-newHeadsChans[0])[0]
|
||||||
|
baseHeight := initHead.Val.Height()
|
||||||
|
|
||||||
|
bm := kit.NewBlockMiner(t, miner)
|
||||||
|
bm.MineBlocks(ctx, time.Microsecond)
|
||||||
|
|
||||||
|
full.WaitTillChain(ctx, kit.HeightAtLeast(baseHeight+100))
|
||||||
|
|
||||||
|
// Make sure they were all closed.
|
||||||
|
for _, ch := range newHeadsChans {
|
||||||
|
var ok bool
|
||||||
|
for ok {
|
||||||
|
select {
|
||||||
|
case _, ok = <-ch:
|
||||||
|
default:
|
||||||
|
t.Fatal("expected new heads channel to be closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we can resubscribe and everything still works.
|
||||||
|
newHeads, err := full.ChainNotify(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
_, ok := <-newHeads
|
||||||
|
require.True(t, ok, "notify channel closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ts *apiSuite) testNonGenesisMiner(t *testing.T) {
|
func (ts *apiSuite) testNonGenesisMiner(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -139,7 +139,10 @@ func GetTips(ctx context.Context, api v0api.FullNode, lastHeight abi.ChainEpoch,
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case changes := <-notif:
|
case changes, ok := <-notif:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
log.Infow("Head event", "height", change.Val.Height(), "type", change.Type)
|
log.Infow("Head event", "height", change.Val.Height(), "type", change.Type)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user