@@ -34,74 +34,78 @@ import (
3434
3535const (
3636 dialTimeout = 5 * time .Second
37+ retryPeriod = 10 * time .Second
3738)
3839
3940var (
4041 ErrInvalidTypeCast = errors .New ("invalid type cast" )
4142 ErrNoEthClient = errors .New ("no eth client" )
42-
43- retryPeriod = 10 * time .Second
4443)
4544
4645type Client struct {
46+ ctx context.Context
47+ cancel context.CancelFunc
4748 rpcClientMap * Map
49+ retrydialWg sync.WaitGroup
4850}
4951
5052func New (ctx context.Context , opts ... Option ) (* Client , error ) {
53+ // create client own context to control the internal go routines
54+ myCtx , myCancel := context .WithCancel (context .Background ())
5155 mc := & Client {
56+ ctx : myCtx ,
57+ cancel : myCancel ,
5258 rpcClientMap : NewMap (),
5359 }
60+
61+ var newErr error
62+ defer func () {
63+ if newErr != nil {
64+ // cancel go routines
65+ mc .Close ()
66+ }
67+ }()
68+
5469 for _ , opt := range opts {
55- if err : = opt (mc ); err != nil {
56- return nil , err
70+ if newErr = opt (mc ); newErr != nil {
71+ return nil , newErr
5772 }
5873 }
5974
60- urls := mc .rpcClientMap .Keys ()
61- lens := len (urls )
62- if lens == 0 {
63- return nil , ErrNoEthClient
64- }
75+ // Dial each eth client
76+ mc .DialClients (ctx )
6577
66- log .Debug ("Create multiclient" , "urls" , lens )
67- errCh := make (chan error , lens )
68- for _ , rawURL := range urls {
69- go func (ctx context.Context , rawURL string ) {
70- ctx , cancel := context .WithTimeout (ctx , dialTimeout )
71- defer cancel ()
72- c , err := rpc .DialContext (ctx , rawURL )
73- if err == nil {
74- log .Info ("Connect to ethclient successfully" , "url" , rawURL )
75- mc .rpcClientMap .Set (rawURL , c )
76- } else {
77- log .Error ("Failed to dial eth client" , "rawURL" , rawURL , "err" , err )
78- }
79- errCh <- err
80- }(ctx , rawURL )
78+ // Return error if have no available eth client.
79+ if len (mc .rpcClientMap .List ()) == 0 {
80+ newErr = ErrNoEthClient
81+ return nil , newErr
8182 }
8283
83- var dialErr error
84- for i := 0 ; i < lens ; i ++ {
85- err := <- errCh
86- if err != nil {
87- dialErr = err
88- }
89- }
90- if dialErr != nil {
91- mc .Close ()
92- return nil , dialErr
93- }
84+ mc .retrydialWg .Add (1 )
85+ go mc .retrydial ()
86+
9487 return mc , nil
9588}
9689
9790// Close closes an existing RPC connection.
9891func (mc * Client ) Close () {
92+ // stop go routines
93+ mc .cancel ()
94+ mc .retrydialWg .Wait ()
9995 clients := mc .rpcClientMap .List ()
10096 for _ , c := range clients {
10197 c .Close ()
10298 }
10399}
104100
101+ func (mc * Client ) Context () context.Context {
102+ return mc .ctx
103+ }
104+
105+ func (mc * Client ) ClientMap () * Map {
106+ return mc .rpcClientMap
107+ }
108+
105109func (mc * Client ) EthClients () []* ethclient.Client {
106110 clients := mc .rpcClientMap .List ()
107111 ethClients := make ([]* ethclient.Client , len (clients ))
@@ -727,3 +731,50 @@ func postToAll(ctx context.Context, fns []postFn) error {
727731 }
728732 return lastError
729733}
734+
735+ type dialedClient struct {
736+ url string
737+ client * rpc.Client
738+ }
739+
740+ func (mc * Client ) DialClients (ctx context.Context ) {
741+ urls := mc .rpcClientMap .NilClients ()
742+ dialCh := make (chan * dialedClient , len (urls ))
743+ for _ , rawURL := range urls {
744+ go func (rawURL string ) {
745+ dialCtx , cancel := context .WithTimeout (ctx , dialTimeout )
746+ defer cancel ()
747+ c , err := rpc .DialContext (dialCtx , rawURL )
748+ if err == nil {
749+ log .Info ("Connect to eth client successfully" , "url" , rawURL )
750+ } else {
751+ log .Warn ("Failed to dial eth client" , "url" , rawURL , "err" , err )
752+ }
753+ dialCh <- & dialedClient {url : rawURL , client : c }
754+ }(rawURL )
755+ }
756+
757+ for i := 0 ; i < len (urls ); i ++ {
758+ dialed := <- dialCh
759+ if dialed .client != nil {
760+ mc .rpcClientMap .Replace (dialed .url , dialed .client )
761+ }
762+ }
763+ }
764+
765+ func (mc * Client ) retrydial () {
766+ defer mc .retrydialWg .Done ()
767+
768+ ticker := time .NewTicker (retryPeriod )
769+ defer ticker .Stop ()
770+
771+ for {
772+ select {
773+ case <- ticker .C :
774+ mc .DialClients (mc .ctx )
775+ case <- mc .ctx .Done ():
776+ // mc is closed, stop retry
777+ return
778+ }
779+ }
780+ }
0 commit comments