package rpc import ( "errors" "io" "net/rpc" "sync/atomic" "time" "mongo.games.com/goserver/core/utils" ) var ( ErrRPClientNoConn = errors.New("RPClient no connect.") ErrRPClientShutdown = errors.New("RPClient is shutdown.") ErrRPClientCallTimeout = errors.New("RPClient call timeout.") ) type RPClient struct { client *rpc.Client reconnInterv time.Duration net string addr string connecting int32 ready bool shutdown bool } func NewRPClient(net, addr string, d time.Duration) *RPClient { if d < time.Second { d = time.Second } c := &RPClient{ net: net, addr: addr, ready: false, shutdown: false, reconnInterv: d, } c.init() return c } func (this *RPClient) IsReady() bool { return this.ready } func (this *RPClient) IsConnecting() bool { return atomic.LoadInt32(&this.connecting) == 1 } func (this *RPClient) IsShutdown() bool { return this.shutdown } func (this *RPClient) init() { } func (this *RPClient) Start() error { if atomic.CompareAndSwapInt32(&this.connecting, 0, 1) { this.dialRoutine() } return nil } func (this *RPClient) Stop() error { if !this.shutdown { this.shutdown = true if this.client != nil { return this.client.Close() } } return nil } func (this *RPClient) Call(serviceMethod string, args interface{}, reply interface{}) error { return this.CallWithTimeout(serviceMethod, args, reply, time.Hour*24) } func (this *RPClient) CallWithTimeout(serviceMethod string, args interface{}, reply interface{}, d time.Duration) error { if this.client == nil { return ErrRPClientNoConn } if d <= time.Second { d = time.Second } start := time.Now() var err error call := this.client.Go(serviceMethod, args, reply, make(chan *rpc.Call, 1)) select { case <-time.After(d): return ErrRPClientCallTimeout case call = <-call.Done: err = call.Error } if err != nil && (err == rpc.ErrShutdown || err == io.ErrUnexpectedEOF) { var dailed chan struct{} if atomic.CompareAndSwapInt32(&this.connecting, 0, 1) { dailed = make(chan struct{}) _ = this.client.Close() go utils.CatchPanic(func() { this.dialRoutine() dailed <- struct{}{} }) } dd := d - time.Now().Sub(start) if dd <= 0 { return ErrRPClientCallTimeout } select { case <-time.After(dd): return ErrRPClientCallTimeout case <-dailed: dd = d - time.Now().Sub(start) if dd <= 0 { return ErrRPClientCallTimeout } call = this.client.Go(serviceMethod, args, reply, make(chan *rpc.Call, 1)) dd = d - time.Now().Sub(start) if dd <= 0 { return ErrRPClientCallTimeout } select { case <-time.After(dd): return ErrRPClientCallTimeout case call = <-call.Done: err = call.Error } return err } } return err } func (this *RPClient) dialRoutine() { defer func() { atomic.StoreInt32(&this.connecting, 0) }() dial := func() error { client, err := rpc.DialHTTP(this.net, this.addr) if err == nil { if this.shutdown { client.Close() return ErrRPClientShutdown } this.client = client this.ready = true return nil } return err } err := dial() if err != nil { for !this.shutdown { select { case <-time.After(this.reconnInterv): err = dial() if err == nil { return } } } } }