Does Rpc Have a Timeout Mechanism?

If RPC does not have a timeout mechanism, how do I "kill" an RPC call if it is trying to call an RPC method of a server that is closed?

5 Answers

You can use channels to implement a timeout pattern:

import "time"

c := make(chan error, 1)
go func() { c <- client.Call("Service", args, &result) } ()
select {
  case err := <-c:
    // use err and result
  case <-time.After(timeoutNanoseconds):
    // call timed out
}

The select will block until either client.Call returns or timeoutNanoseconds elapsed.

5

if you want to implement a timeout (to prevent a call from taking too long), then you'll want to change rpc.Dial for net.DialTimeout (notice they're separate packages: rpc vs net). Also be aware that the returned type isn't a client any more (as it is in the previous example); instead it is a 'connection'.

  conn, err := net.DialTimeout("tcp", "localhost:8080", time.Minute)
  if err != nil {
    log.Fatal("dialing:", err)
  }

  client := rpc.NewClient(conn)

It seems the only solution for net/rpc is to close the underlying connection when you notice stuck requests. Then the client should finish pending requests with "connection broken" errors.

An alternative way is to use , which supports timeout RPC calls out of the box.

1
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error

Call method may block goroutine forever

Change use Go method:

func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call

Client example:

call := rpcClient.Go(method, args, reply, make(chan *rpc.Call, 1))
    select {
    case <-time.After(timeout):
        log.Printf("[WARN] rpc call timeout(%v) %v => %v", timeout, rpcClient, s.RpcServer)
        rpcClient.Close()
        return errors.New("timeout")
    case resp := <-call.Done:
        if resp != nil && resp.Error != nil {
            rpcClient.Close()
            return resp.Error
        }

Or, anno now, someone might prefer to use context instead. This also takes care of returning a proper error when timed out. (context.DeadlineExceeded)

import (
    "context"
    "log"
    "net/rpc"
)

type Client struct {
    *rpc.Client
}

// CallEx is a context aware wrapper around rpc's Client.Call()
func (c *client) CallEx(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {
    ec := make(chan error, 1)
    go func() {
        ec <- c.Call(serviceMethod, args, reply)
    }()
    select {
    case err := <-ec:
        return err
    case <-ctx.Done():
        return ctx.Err()
    }
}

Invoke this with a Deadlined context:

type Args struct {
   A, B int
}

func main(){
    rpc, err := rpc.DialHTTP("tcp", "host")
    if err != nil {
        t.Fatal(err)
    }
    c := client{rpc}

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    var i int
    if err := c.CallEx(ctx, "Calc.Multiply", Args{2, 2}, &i); err != nil {
        log.Fatal(err)
    }
}
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Marcus Vance

Marcus Vance

Cybersecurity & Digital Privacy Researcher

Marcus Vance is a cybersecurity auditor and technology writer dedicated to educating the public about online safety, data privacy regulations, enterprise security, and emerging cyber threats.

Share this article
Twitter Facebook Pinterest