@ -8,7 +8,6 @@ import (
"fmt"
"fmt"
"io"
"io"
"net"
"net"
"reflect"
"sync"
"sync"
"time"
"time"
@ -34,12 +33,11 @@ type socketClient struct {
mustConnect bool
mustConnect bool
conn net . Conn
conn net . Conn
reqQueue chan * ReqRes
reqQueue chan * requestAndResponse
mtx sync . Mutex
mtx sync . Mutex
err error
err error
reqSent * list . List // list of requests sent, waiting for response
resCb func ( * types . Request , * types . Response ) // called on all requests, if set.
reqSent * list . List // list of requests sent, waiting for response
}
}
var _ Client = ( * socketClient ) ( nil )
var _ Client = ( * socketClient ) ( nil )
@ -50,11 +48,10 @@ var _ Client = (*socketClient)(nil)
func NewSocketClient ( logger log . Logger , addr string , mustConnect bool ) Client {
func NewSocketClient ( logger log . Logger , addr string , mustConnect bool ) Client {
cli := & socketClient {
cli := & socketClient {
logger : logger ,
logger : logger ,
reqQueue : make ( chan * ReqRes , reqQueueSize ) ,
reqQueue : make ( chan * requestAndResponse , reqQueueSize ) ,
mustConnect : mustConnect ,
mustConnect : mustConnect ,
addr : addr ,
addr : addr ,
reqSent : list . New ( ) ,
reqSent : list . New ( ) ,
resCb : nil ,
}
}
cli . BaseService = * service . NewBaseService ( logger , "socketClient" , cli )
cli . BaseService = * service . NewBaseService ( logger , "socketClient" , cli )
return cli
return cli
@ -126,6 +123,7 @@ func (cli *socketClient) sendRequestsRoutine(ctx context.Context, conn io.Writer
cli . stopForError ( fmt . Errorf ( "write to buffer: %w" , err ) )
cli . stopForError ( fmt . Errorf ( "write to buffer: %w" , err ) )
return
return
}
}
if err := bw . Flush ( ) ; err != nil {
if err := bw . Flush ( ) ; err != nil {
cli . stopForError ( fmt . Errorf ( "flush buffer: %w" , err ) )
cli . stopForError ( fmt . Errorf ( "flush buffer: %w" , err ) )
return
return
@ -140,23 +138,20 @@ func (cli *socketClient) recvResponseRoutine(ctx context.Context, conn io.Reader
if ctx . Err ( ) != nil {
if ctx . Err ( ) != nil {
return
return
}
}
var res = & types . Response { }
err := types . ReadMessage ( r , res )
if err != nil {
res : = & types . Response { }
if err := types . ReadMessage ( r , res ) ; err != nil {
cli . stopForError ( fmt . Errorf ( "read message: %w" , err ) )
cli . stopForError ( fmt . Errorf ( "read message: %w" , err ) )
return
return
}
}
// cli.logger.Debug("Received response", "responseType", reflect.TypeOf(res), "response", res)
switch r := res . Value . ( type ) {
switch r := res . Value . ( type ) {
case * types . Response_Exception : // app responded with error
case * types . Response_Exception : // app responded with error
// XXX After setting cli.err, release waiters (e.g. reqres.Done())
// XXX After setting cli.err, release waiters (e.g. reqres.Done())
cli . stopForError ( errors . New ( r . Exception . Error ) )
cli . stopForError ( errors . New ( r . Exception . Error ) )
return
return
default :
default :
err := cli . didRecvResponse ( res )
if err != nil {
if err := cli . didRecvResponse ( res ) ; err != nil {
cli . stopForError ( err )
cli . stopForError ( err )
return
return
}
}
@ -164,7 +159,7 @@ func (cli *socketClient) recvResponseRoutine(ctx context.Context, conn io.Reader
}
}
}
}
func ( cli * socketClient ) willSendReq ( reqres * ReqRes ) {
func ( cli * socketClient ) willSendReq ( reqres * requestAndResponse ) {
cli . mtx . Lock ( )
cli . mtx . Lock ( )
defer cli . mtx . Unlock ( )
defer cli . mtx . Unlock ( )
cli . reqSent . PushBack ( reqres )
cli . reqSent . PushBack ( reqres )
@ -177,258 +172,172 @@ func (cli *socketClient) didRecvResponse(res *types.Response) error {
// Get the first ReqRes.
// Get the first ReqRes.
next := cli . reqSent . Front ( )
next := cli . reqSent . Front ( )
if next == nil {
if next == nil {
return fmt . Errorf ( "unexpected %v when nothing expected" , reflect . TypeOf ( re s . Value ) )
return fmt . Errorf ( "unexpected %T when nothing expected" , res . Value )
}
}
reqres := next . Value . ( * ReqRes )
reqres := next . Value . ( * requestAndResponse )
if ! resMatchesReq ( reqres . Request , res ) {
if ! resMatchesReq ( reqres . Request , res ) {
return fmt . Errorf ( "unexpected %v when response to %v expected" ,
reflect . TypeOf ( res . Value ) , reflect . TypeOf ( reqres . Request . Value ) )
return fmt . Errorf ( "unexpected %T when response to %T expected" , res . Value , reqres . Request . Value )
}
}
reqres . Response = res
reqres . Response = res
reqres . Set Done( ) // release waiters
reqres . mark Done( ) // release waiters
cli . reqSent . Remove ( next ) // pop first item from linked list
cli . reqSent . Remove ( next ) // pop first item from linked list
// Notify client listener if set (global callback).
if cli . resCb != nil {
cli . resCb ( reqres . Request , res )
}
// Notify reqRes listener if set (request specific callback).
//
// NOTE: It is possible this callback isn't set on the reqres object. At this
// point, in which case it will be called after, when it is set.
reqres . InvokeCallback ( )
return nil
return nil
}
}
//----------------------------------------
//----------------------------------------
func ( cli * socketClient ) Flush ( ctx context . Context ) error {
func ( cli * socketClient ) Flush ( ctx context . Context ) error {
reqRes , err := cli . queue Request( ctx , types . ToRequestFlush ( ) )
_ , err := cli . doRequest ( ctx , types . ToRequestFlush ( ) )
if err != nil {
if err != nil {
return queueErr ( err )
}
if err := cli . Error ( ) ; err != nil {
return err
return err
}
}
select {
case <- reqRes . signal :
return cli . Error ( )
case <- ctx . Done ( ) :
return ctx . Err ( )
}
return nil
}
}
func ( cli * socketClient ) Echo ( ctx context . Context , msg string ) ( * types . ResponseEcho , error ) {
func ( cli * socketClient ) Echo ( ctx context . Context , msg string ) ( * types . ResponseEcho , error ) {
reqre s , err := cli . queueRequestAndFlush ( ctx , types . ToRequestEcho ( msg ) )
res , err := cli . doRequest ( ctx , types . ToRequestEcho ( msg ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetEcho ( ) , nil
return res . GetEcho ( ) , nil
}
}
func ( cli * socketClient ) Info (
ctx context . Context ,
req types . RequestInfo ,
) ( * types . ResponseInfo , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestInfo ( req ) )
func ( cli * socketClient ) Info ( ctx context . Context , req types . RequestInfo ) ( * types . ResponseInfo , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestInfo ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetInfo ( ) , nil
return res . GetInfo ( ) , nil
}
}
func ( cli * socketClient ) CheckTx (
ctx context . Context ,
req types . RequestCheckTx ,
) ( * types . ResponseCheckTx , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestCheckTx ( req ) )
func ( cli * socketClient ) CheckTx ( ctx context . Context , req types . RequestCheckTx ) ( * types . ResponseCheckTx , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestCheckTx ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetCheckTx ( ) , nil
return res . GetCheckTx ( ) , nil
}
}
func ( cli * socketClient ) Query (
ctx context . Context ,
req types . RequestQuery ,
) ( * types . ResponseQuery , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestQuery ( req ) )
func ( cli * socketClient ) Query ( ctx context . Context , req types . RequestQuery ) ( * types . ResponseQuery , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestQuery ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetQuery ( ) , nil
return res . GetQuery ( ) , nil
}
}
func ( cli * socketClient ) Commit ( ctx context . Context ) ( * types . ResponseCommit , error ) {
func ( cli * socketClient ) Commit ( ctx context . Context ) ( * types . ResponseCommit , error ) {
reqre s , err := cli . queueRequestAndFlush ( ctx , types . ToRequestCommit ( ) )
res , err := cli . doRequest ( ctx , types . ToRequestCommit ( ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetCommit ( ) , nil
return res . GetCommit ( ) , nil
}
}
func ( cli * socketClient ) InitChain (
ctx context . Context ,
req types . RequestInitChain ,
) ( * types . ResponseInitChain , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestInitChain ( req ) )
func ( cli * socketClient ) InitChain ( ctx context . Context , req types . RequestInitChain ) ( * types . ResponseInitChain , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestInitChain ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetInitChain ( ) , nil
return res . GetInitChain ( ) , nil
}
}
func ( cli * socketClient ) ListSnapshots (
ctx context . Context ,
req types . RequestListSnapshots ,
) ( * types . ResponseListSnapshots , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestListSnapshots ( req ) )
func ( cli * socketClient ) ListSnapshots ( ctx context . Context , req types . RequestListSnapshots ) ( * types . ResponseListSnapshots , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestListSnapshots ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetListSnapshots ( ) , nil
return res . GetListSnapshots ( ) , nil
}
}
func ( cli * socketClient ) OfferSnapshot (
ctx context . Context ,
req types . RequestOfferSnapshot ,
) ( * types . ResponseOfferSnapshot , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestOfferSnapshot ( req ) )
func ( cli * socketClient ) OfferSnapshot ( ctx context . Context , req types . RequestOfferSnapshot ) ( * types . ResponseOfferSnapshot , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestOfferSnapshot ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetOfferSnapshot ( ) , nil
return res . GetOfferSnapshot ( ) , nil
}
}
func ( cli * socketClient ) LoadSnapshotChunk (
ctx context . Context ,
req types . RequestLoadSnapshotChunk ) ( * types . ResponseLoadSnapshotChunk , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestLoadSnapshotChunk ( req ) )
func ( cli * socketClient ) LoadSnapshotChunk ( ctx context . Context , req types . RequestLoadSnapshotChunk ) ( * types . ResponseLoadSnapshotChunk , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestLoadSnapshotChunk ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetLoadSnapshotChunk ( ) , nil
return res . GetLoadSnapshotChunk ( ) , nil
}
}
func ( cli * socketClient ) ApplySnapshotChunk (
ctx context . Context ,
req types . RequestApplySnapshotChunk ) ( * types . ResponseApplySnapshotChunk , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestApplySnapshotChunk ( req ) )
func ( cli * socketClient ) ApplySnapshotChunk ( ctx context . Context , req types . RequestApplySnapshotChunk ) ( * types . ResponseApplySnapshotChunk , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestApplySnapshotChunk ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetApplySnapshotChunk ( ) , nil
return res . GetApplySnapshotChunk ( ) , nil
}
}
func ( cli * socketClient ) PrepareProposal (
ctx context . Context ,
req types . RequestPrepareProposal ) ( * types . ResponsePrepareProposal , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestPrepareProposal ( req ) )
func ( cli * socketClient ) PrepareProposal ( ctx context . Context , req types . RequestPrepareProposal ) ( * types . ResponsePrepareProposal , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestPrepareProposal ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetPrepareProposal ( ) , nil
return res . GetPrepareProposal ( ) , nil
}
}
func ( cli * socketClient ) ProcessProposal (
ctx context . Context ,
req types . RequestProcessProposal ,
) ( * types . ResponseProcessProposal , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestProcessProposal ( req ) )
func ( cli * socketClient ) ProcessProposal ( ctx context . Context , req types . RequestProcessProposal ) ( * types . ResponseProcessProposal , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestProcessProposal ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetProcessProposal ( ) , nil
return res . GetProcessProposal ( ) , nil
}
}
func ( cli * socketClient ) ExtendVote (
ctx context . Context ,
req types . RequestExtendVote ) ( * types . ResponseExtendVote , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestExtendVote ( req ) )
func ( cli * socketClient ) ExtendVote ( ctx context . Context , req types . RequestExtendVote ) ( * types . ResponseExtendVote , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestExtendVote ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetExtendVote ( ) , nil
return res . GetExtendVote ( ) , nil
}
}
func ( cli * socketClient ) VerifyVoteExtension (
ctx context . Context ,
req types . RequestVerifyVoteExtension ) ( * types . ResponseVerifyVoteExtension , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestVerifyVoteExtension ( req ) )
func ( cli * socketClient ) VerifyVoteExtension ( ctx context . Context , req types . RequestVerifyVoteExtension ) ( * types . ResponseVerifyVoteExtension , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestVerifyVoteExtension ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetVerifyVoteExtension ( ) , nil
return res . GetVerifyVoteExtension ( ) , nil
}
}
func ( cli * socketClient ) FinalizeBlock (
ctx context . Context ,
req types . RequestFinalizeBlock ) ( * types . ResponseFinalizeBlock , error ) {
reqres , err := cli . queueRequestAndFlush ( ctx , types . ToRequestFinalizeBlock ( req ) )
func ( cli * socketClient ) FinalizeBlock ( ctx context . Context , req types . RequestFinalizeBlock ) ( * types . ResponseFinalizeBlock , error ) {
res , err := cli . doRequest ( ctx , types . ToRequestFinalizeBlock ( req ) )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
return reqre s . Response . GetFinalizeBlock ( ) , nil
return res . GetFinalizeBlock ( ) , nil
}
}
//----------------------------------------
//----------------------------------------
// queueRequest enqueues req onto the queue. The request can break early if the
// the context is canceled. If the queue is full, this method blocks to allow
// the request to be placed onto the queue. This has the effect of creating an
// unbounded queue of goroutines waiting to write to this queue which is a bit
// antithetical to the purposes of a queue, however, undoing this behavior has
// dangerous upstream implications as a result of the usage of this behavior upstream.
// Remove at your peril.
//
// The caller is responsible for checking cli.Error.
func ( cli * socketClient ) queueRequest ( ctx context . Context , req * types . Request ) ( * ReqRes , error ) {
reqres := NewReqRes ( req )
func ( cli * socketClient ) doRequest ( ctx context . Context , req * types . Request ) ( * types . Response , error ) {
reqres := makeReqRes ( req )
select {
select {
case cli . reqQueue <- reqres :
case cli . reqQueue <- reqres :
case <- ctx . Done ( ) :
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
return nil , fmt . Errorf ( "can't queue req: %w" , ctx . Err ( ) )
}
}
return reqres , nil
}
func ( cli * socketClient ) queueRequestAndFlush (
ctx context . Context ,
req * types . Request ,
) ( * ReqRes , error ) {
reqres , err := cli . queueRequest ( ctx , req )
if err != nil {
return nil , queueErr ( err )
}
select {
case <- reqres . signal :
if err := cli . Error ( ) ; err != nil {
return nil , err
}
if err := cli . Flush ( ctx ) ; err != nil {
return nil , err
return reqres . Response , nil
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
}
}
return reqres , cli . Error ( )
}
func queueErr ( e error ) error {
return fmt . Errorf ( "can't queue req: %w" , e )
}
}
// drainQueue marks as complete and discards all remaining pending requests
// drainQueue marks as complete and discards all remaining pending requests
@ -439,8 +348,8 @@ func (cli *socketClient) drainQueue(ctx context.Context) {
// mark all in-flight messages as resolved (they will get cli.Error())
// mark all in-flight messages as resolved (they will get cli.Error())
for req := cli . reqSent . Front ( ) ; req != nil ; req = req . Next ( ) {
for req := cli . reqSent . Front ( ) ; req != nil ; req = req . Next ( ) {
reqres := req . Value . ( * ReqRes )
reqres . Set Done( )
reqres := req . Value . ( * requestAndResponse )
reqres . mark Done( )
}
}
// Mark all queued messages as resolved.
// Mark all queued messages as resolved.
@ -453,7 +362,7 @@ func (cli *socketClient) drainQueue(ctx context.Context) {
case <- ctx . Done ( ) :
case <- ctx . Done ( ) :
return
return
case reqres := <- cli . reqQueue :
case reqres := <- cli . reqQueue :
reqres . Set Done( )
reqres . mark Done( )
default :
default :
return
return
}
}