ethclient/lightclient: requestMap.close()

This commit is contained in:
Zsolt Felfoldi 2024-06-09 15:47:51 +02:00
parent 238e47e78a
commit 2d485d5972
6 changed files with 65 additions and 18 deletions

View file

@ -173,7 +173,6 @@ loop:
break loop break loop
} }
} }
client.Close()
client.Stop()
return nil return nil
} }

View file

@ -39,7 +39,7 @@ const recentCanonicalLength = 256
type canonicalChainFields struct { type canonicalChainFields struct {
chainLock sync.Mutex chainLock sync.Mutex
head, finality *btypes.ExecutionHeader head, finality *btypes.ExecutionHeader
recent map[uint64]common.Hash // nil until initialized recent map[uint64]common.Hash // nil while head == nil
recentTail uint64 // if recent != nil then recent hashes are available from recentTail to head recentTail uint64 // if recent != nil then recent hashes are available from recentTail to head
tailFetchCh chan struct{} tailFetchCh chan struct{}
finalized *lru.Cache[uint64, common.Hash] // finalized but not recent hashes finalized *lru.Cache[uint64, common.Hash] // finalized but not recent hashes
@ -53,6 +53,10 @@ func (c *Client) initCanonicalChain() {
go c.tailFetcher() go c.tailFetcher()
} }
func (c *Client) closeCanonicalChain() {
c.requests.close()
}
// Process implements request.Module in order to get notified about new heads. // Process implements request.Module in order to get notified about new heads.
func (c *Client) Process(requester request.Requester, events []request.Event) { func (c *Client) Process(requester request.Requester, events []request.Event) {
if finality, ok := c.headTracker.ValidatedFinality(); ok { if finality, ok := c.headTracker.ValidatedFinality(); ok {
@ -268,6 +272,11 @@ func (c *Client) initBlocksAndHeaders() {
c.blockRequests = newRequestMap[common.Hash, *types.Block](c.requestBlock) c.blockRequests = newRequestMap[common.Hash, *types.Block](c.requestBlock)
} }
func (c *Client) closeBlocksAndHeaders() {
c.headerRequests.close()
c.blockRequests.close()
}
func (c *Client) requestHeader(ctx context.Context, hash common.Hash) (*types.Header, error) { func (c *Client) requestHeader(ctx context.Context, hash common.Hash) (*types.Header, error) {
var header *types.Header var header *types.Header
log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", false) log.Debug("Starting RPC request", "type", "eth_getBlockByHash", "hash", hash, "full", false)

View file

@ -92,7 +92,11 @@ func (c *Client) Start() {
} }
} }
func (c *Client) Stop() { func (c *Client) Close() {
c.closeBlocksAndHeaders()
c.closeCanonicalChain()
c.closeTxAndReceipts()
c.closeLightState()
c.scheduler.Stop() c.scheduler.Stop()
} }

View file

@ -18,15 +18,29 @@ package lightclient
import ( import (
"context" "context"
"errors"
"sync" "sync"
) )
type requestMap[K comparable, V any] struct { type requestMap[K comparable, V any] struct {
lock sync.Mutex lock sync.Mutex
closed bool
requestFn func(context.Context, K) (V, error) requestFn func(context.Context, K) (V, error)
requests map[K]*mappedRequest[K, V] requests map[K]*mappedRequest[K, V]
} }
type mappedRequest[K comparable, V any] struct {
lock sync.Mutex
rm *requestMap[K, V]
key K
refCount int
delivered, closed bool
deliveredCh chan struct{}
cancelFn func() // called when delivered || closed || refCount == 0 becomes true
result V
err error
}
func newRequestMap[K comparable, V any](requestFn func(context.Context, K) (V, error)) *requestMap[K, V] { func newRequestMap[K comparable, V any](requestFn func(context.Context, K) (V, error)) *requestMap[K, V] {
return &requestMap[K, V]{ return &requestMap[K, V]{
requestFn: requestFn, requestFn: requestFn,
@ -34,6 +48,22 @@ func newRequestMap[K comparable, V any](requestFn func(context.Context, K) (V, e
} }
} }
func (rm *requestMap[K, V]) close() {
rm.lock.Lock()
defer rm.lock.Unlock()
if rm.closed {
return
}
for _, req := range rm.requests {
if !req.delivered && req.refCount != 0 {
req.cancelFn()
req.closed = true
}
}
rm.closed = true
}
func (rm *requestMap[K, V]) request(key K) *mappedRequest[K, V] { func (rm *requestMap[K, V]) request(key K) *mappedRequest[K, V] {
rm.lock.Lock() rm.lock.Lock()
defer rm.lock.Unlock() defer rm.lock.Unlock()
@ -53,6 +83,14 @@ func (rm *requestMap[K, V]) request(key K) *mappedRequest[K, V] {
cancelFn: cancelFn, cancelFn: cancelFn,
} }
rm.requests[key] = r rm.requests[key] = r
if rm.closed {
// return a closed dummy request for simplicity
r.closed = true
r.cancelFn()
var null V
r.deliver(null, errors.New("request map is closed"))
return r
}
if rm.requestFn != nil { if rm.requestFn != nil {
go func() { go func() {
result, err := rm.requestFn(ctx, key) result, err := rm.requestFn(ctx, key)
@ -91,25 +129,13 @@ func (rm *requestMap[K, V]) tryDeliver(key K, result V) {
} }
} }
type mappedRequest[K comparable, V any] struct {
lock sync.Mutex
rm *requestMap[K, V]
key K
refCount int
delivered bool
deliveredCh chan struct{}
cancelFn func() // called when delivered || refCount == 0 becomes true
result V
err error
}
func (r *mappedRequest[K, V]) deliver(result V, err error) { func (r *mappedRequest[K, V]) deliver(result V, err error) {
r.lock.Lock() r.lock.Lock()
if !r.delivered { if !r.delivered {
r.result, r.err = result, err r.result, r.err = result, err
r.delivered = true r.delivered = true
close(r.deliveredCh) close(r.deliveredCh)
if r.refCount != 0 { if !r.closed && r.refCount != 0 {
r.cancelFn() r.cancelFn()
} }
} }
@ -133,7 +159,7 @@ func (r *mappedRequest[K, V]) release() {
r.refCount-- r.refCount--
if r.refCount == 0 { if r.refCount == 0 {
delete(r.rm.requests, r.key) delete(r.rm.requests, r.key)
if !r.delivered { if !r.delivered && !r.closed {
r.cancelFn() r.cancelFn()
} }
} }

View file

@ -60,6 +60,11 @@ func (c *Client) initLightState() {
c.codeRequests = newRequestMap[codeRequest, []byte](c.requestCode) c.codeRequests = newRequestMap[codeRequest, []byte](c.requestCode)
} }
func (c *Client) closeLightState() {
c.proofRequests.close()
c.codeRequests.close()
}
func (c *Client) fetchProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) { func (c *Client) fetchProof(ctx context.Context, req proofRequest) (*gethclient.AccountResult, error) {
if proof, ok := c.proofCache.Get(req); ok { if proof, ok := c.proofCache.Get(req); ok {
return proof, nil return proof, nil

View file

@ -70,6 +70,10 @@ func (c *Client) initTxAndReceipts() {
c.receiptsRequests = newRequestMap[common.Hash, types.Receipts](c.requestBlockReceipts) c.receiptsRequests = newRequestMap[common.Hash, types.Receipts](c.requestBlockReceipts)
} }
func (c *Client) closeTxAndReceipts() {
c.receiptsRequests.close()
}
func (c *Client) getTxByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) { func (c *Client) getTxByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) {
if pos, ok := c.txPosCache.Get(txHash); ok { if pos, ok := c.txPosCache.Get(txHash); ok {
if hash, ok := c.getCachedHash(pos.blockNumber); ok && hash == pos.blockHash { if hash, ok := c.getCachedHash(pos.blockNumber); ok && hash == pos.blockHash {