2010-12-03 05:34:57 +01:00
|
|
|
// Copyright 2009 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package rpc
|
|
|
|
|
|
|
|
import (
|
2011-12-03 03:17:34 +01:00
|
|
|
"errors"
|
2010-12-03 05:34:57 +01:00
|
|
|
"fmt"
|
2011-09-16 17:47:21 +02:00
|
|
|
"io"
|
2010-12-03 05:34:57 +01:00
|
|
|
"log"
|
|
|
|
"net"
|
2011-12-07 02:11:29 +01:00
|
|
|
"net/http/httptest"
|
2011-03-25 00:46:17 +01:00
|
|
|
"runtime"
|
2010-12-03 05:34:57 +01:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2011-10-27 01:57:58 +02:00
|
|
|
"sync/atomic"
|
2010-12-03 05:34:57 +01:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2011-09-16 17:47:21 +02:00
|
|
|
newServer *Server
|
2010-12-03 05:34:57 +01:00
|
|
|
serverAddr, newServerAddr string
|
|
|
|
httpServerAddr string
|
|
|
|
once, newOnce, httpOnce sync.Once
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
newHttpPath = "/foo"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Args struct {
|
|
|
|
A, B int
|
|
|
|
}
|
|
|
|
|
|
|
|
type Reply struct {
|
|
|
|
C int
|
|
|
|
}
|
|
|
|
|
|
|
|
type Arith int
|
|
|
|
|
2011-05-20 02:18:15 +02:00
|
|
|
// Some of Arith's methods have value args, some have pointer args. That's deliberate.
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *Arith) Add(args Args, reply *Reply) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
reply.C = args.A + args.B
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *Arith) Mul(args *Args, reply *Reply) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
reply.C = args.A * args.B
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *Arith) Div(args Args, reply *Reply) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
if args.B == 0 {
|
2011-12-03 03:17:34 +01:00
|
|
|
return errors.New("divide by zero")
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
reply.C = args.A / args.B
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *Arith) String(args *Args, reply *string) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
*reply = fmt.Sprintf("%d+%d=%d", args.A, args.B, args.A+args.B)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *Arith) Scan(args string, reply *Reply) (err error) {
|
2011-05-20 02:18:15 +02:00
|
|
|
_, err = fmt.Sscan(args, &reply.C)
|
2010-12-03 05:34:57 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *Arith) Error(args *Args, reply *Reply) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
panic("ERROR")
|
|
|
|
}
|
|
|
|
|
|
|
|
func listenTCP() (net.Listener, string) {
|
|
|
|
l, e := net.Listen("tcp", "127.0.0.1:0") // any available address
|
|
|
|
if e != nil {
|
2011-03-17 00:05:44 +01:00
|
|
|
log.Fatalf("net.Listen tcp :0: %v", e)
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
return l, l.Addr().String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func startServer() {
|
|
|
|
Register(new(Arith))
|
|
|
|
|
|
|
|
var l net.Listener
|
|
|
|
l, serverAddr = listenTCP()
|
|
|
|
log.Println("Test RPC server listening on", serverAddr)
|
|
|
|
go Accept(l)
|
|
|
|
|
|
|
|
HandleHTTP()
|
|
|
|
httpOnce.Do(startHttpServer)
|
|
|
|
}
|
|
|
|
|
|
|
|
func startNewServer() {
|
2011-09-16 17:47:21 +02:00
|
|
|
newServer = NewServer()
|
|
|
|
newServer.Register(new(Arith))
|
2010-12-03 05:34:57 +01:00
|
|
|
|
|
|
|
var l net.Listener
|
|
|
|
l, newServerAddr = listenTCP()
|
|
|
|
log.Println("NewServer test RPC server listening on", newServerAddr)
|
|
|
|
go Accept(l)
|
|
|
|
|
2011-09-16 17:47:21 +02:00
|
|
|
newServer.HandleHTTP(newHttpPath, "/bar")
|
2010-12-03 05:34:57 +01:00
|
|
|
httpOnce.Do(startHttpServer)
|
|
|
|
}
|
|
|
|
|
|
|
|
func startHttpServer() {
|
2011-03-25 00:46:17 +01:00
|
|
|
server := httptest.NewServer(nil)
|
|
|
|
httpServerAddr = server.Listener.Addr().String()
|
2010-12-03 05:34:57 +01:00
|
|
|
log.Println("Test HTTP RPC server listening on", httpServerAddr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRPC(t *testing.T) {
|
|
|
|
once.Do(startServer)
|
|
|
|
testRPC(t, serverAddr)
|
|
|
|
newOnce.Do(startNewServer)
|
|
|
|
testRPC(t, newServerAddr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRPC(t *testing.T, addr string) {
|
|
|
|
client, err := Dial("tcp", addr)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("dialing", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Synchronous calls
|
|
|
|
args := &Args{7, 8}
|
|
|
|
reply := new(Reply)
|
|
|
|
err = client.Call("Arith.Add", args, reply)
|
|
|
|
if err != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("Add: expected no error but got string %q", err.Error())
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
if reply.C != args.A+args.B {
|
|
|
|
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
|
|
|
|
}
|
|
|
|
|
2011-03-17 00:05:44 +01:00
|
|
|
// Nonexistent method
|
|
|
|
args = &Args{7, 0}
|
2010-12-03 05:34:57 +01:00
|
|
|
reply = new(Reply)
|
2011-03-17 00:05:44 +01:00
|
|
|
err = client.Call("Arith.BadOperation", args, reply)
|
|
|
|
// expect an error
|
|
|
|
if err == nil {
|
|
|
|
t.Error("BadOperation: expected error")
|
2011-12-03 03:17:34 +01:00
|
|
|
} else if !strings.HasPrefix(err.Error(), "rpc: can't find method ") {
|
2011-03-17 00:05:44 +01:00
|
|
|
t.Errorf("BadOperation: expected can't find method error; got %q", err)
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
2011-03-17 00:05:44 +01:00
|
|
|
|
|
|
|
// Unknown service
|
|
|
|
args = &Args{7, 8}
|
|
|
|
reply = new(Reply)
|
|
|
|
err = client.Call("Arith.Unknown", args, reply)
|
|
|
|
if err == nil {
|
|
|
|
t.Error("expected error calling unknown service")
|
2011-12-03 03:17:34 +01:00
|
|
|
} else if strings.Index(err.Error(), "method") < 0 {
|
2011-03-17 00:05:44 +01:00
|
|
|
t.Error("expected error about method; got", err)
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Out of order.
|
|
|
|
args = &Args{7, 8}
|
|
|
|
mulReply := new(Reply)
|
|
|
|
mulCall := client.Go("Arith.Mul", args, mulReply, nil)
|
|
|
|
addReply := new(Reply)
|
|
|
|
addCall := client.Go("Arith.Add", args, addReply, nil)
|
|
|
|
|
|
|
|
addCall = <-addCall.Done
|
|
|
|
if addCall.Error != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("Add: expected no error but got string %q", addCall.Error.Error())
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
if addReply.C != args.A+args.B {
|
|
|
|
t.Errorf("Add: expected %d got %d", addReply.C, args.A+args.B)
|
|
|
|
}
|
|
|
|
|
|
|
|
mulCall = <-mulCall.Done
|
|
|
|
if mulCall.Error != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("Mul: expected no error but got string %q", mulCall.Error.Error())
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
if mulReply.C != args.A*args.B {
|
|
|
|
t.Errorf("Mul: expected %d got %d", mulReply.C, args.A*args.B)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error test
|
|
|
|
args = &Args{7, 0}
|
|
|
|
reply = new(Reply)
|
|
|
|
err = client.Call("Arith.Div", args, reply)
|
|
|
|
// expect an error: zero divide
|
|
|
|
if err == nil {
|
|
|
|
t.Error("Div: expected error")
|
2011-12-03 03:17:34 +01:00
|
|
|
} else if err.Error() != "divide by zero" {
|
2010-12-03 05:34:57 +01:00
|
|
|
t.Error("Div: expected divide by zero error; got", err)
|
|
|
|
}
|
|
|
|
|
2011-03-17 00:05:44 +01:00
|
|
|
// Bad type.
|
|
|
|
reply = new(Reply)
|
|
|
|
err = client.Call("Arith.Add", reply, reply) // args, reply would be the correct thing to use
|
|
|
|
if err == nil {
|
|
|
|
t.Error("expected error calling Arith.Add with wrong arg type")
|
2011-12-03 03:17:34 +01:00
|
|
|
} else if strings.Index(err.Error(), "type") < 0 {
|
2011-03-17 00:05:44 +01:00
|
|
|
t.Error("expected error about type; got", err)
|
|
|
|
}
|
|
|
|
|
2010-12-03 05:34:57 +01:00
|
|
|
// Non-struct argument
|
|
|
|
const Val = 12345
|
|
|
|
str := fmt.Sprint(Val)
|
|
|
|
reply = new(Reply)
|
|
|
|
err = client.Call("Arith.Scan", &str, reply)
|
|
|
|
if err != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("Scan: expected no error but got string %q", err.Error())
|
2010-12-03 05:34:57 +01:00
|
|
|
} else if reply.C != Val {
|
|
|
|
t.Errorf("Scan: expected %d got %d", Val, reply.C)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Non-struct reply
|
|
|
|
args = &Args{27, 35}
|
|
|
|
str = ""
|
|
|
|
err = client.Call("Arith.String", args, &str)
|
|
|
|
if err != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("String: expected no error but got string %q", err.Error())
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
expect := fmt.Sprintf("%d+%d=%d", args.A, args.B, args.A+args.B)
|
|
|
|
if str != expect {
|
|
|
|
t.Errorf("String: expected %s got %s", expect, str)
|
|
|
|
}
|
2011-03-17 00:05:44 +01:00
|
|
|
|
|
|
|
args = &Args{7, 8}
|
|
|
|
reply = new(Reply)
|
|
|
|
err = client.Call("Arith.Mul", args, reply)
|
|
|
|
if err != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("Mul: expected no error but got string %q", err.Error())
|
2011-03-17 00:05:44 +01:00
|
|
|
}
|
|
|
|
if reply.C != args.A*args.B {
|
|
|
|
t.Errorf("Mul: expected %d got %d", reply.C, args.A*args.B)
|
|
|
|
}
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
|
2011-03-17 00:05:44 +01:00
|
|
|
func TestHTTP(t *testing.T) {
|
2010-12-03 05:34:57 +01:00
|
|
|
once.Do(startServer)
|
|
|
|
testHTTPRPC(t, "")
|
|
|
|
newOnce.Do(startNewServer)
|
|
|
|
testHTTPRPC(t, newHttpPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testHTTPRPC(t *testing.T, path string) {
|
|
|
|
var client *Client
|
2011-12-03 03:17:34 +01:00
|
|
|
var err error
|
2010-12-03 05:34:57 +01:00
|
|
|
if path == "" {
|
|
|
|
client, err = DialHTTP("tcp", httpServerAddr)
|
|
|
|
} else {
|
|
|
|
client, err = DialHTTPPath("tcp", httpServerAddr, path)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("dialing", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Synchronous calls
|
|
|
|
args := &Args{7, 8}
|
|
|
|
reply := new(Reply)
|
|
|
|
err = client.Call("Arith.Add", args, reply)
|
|
|
|
if err != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("Add: expected no error but got string %q", err.Error())
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
if reply.C != args.A+args.B {
|
|
|
|
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-16 17:47:21 +02:00
|
|
|
// CodecEmulator provides a client-like api and a ServerCodec interface.
|
|
|
|
// Can be used to test ServeRequest.
|
|
|
|
type CodecEmulator struct {
|
|
|
|
server *Server
|
|
|
|
serviceMethod string
|
|
|
|
args *Args
|
|
|
|
reply *Reply
|
2011-12-03 03:17:34 +01:00
|
|
|
err error
|
2011-09-16 17:47:21 +02:00
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (codec *CodecEmulator) Call(serviceMethod string, args *Args, reply *Reply) error {
|
2011-09-16 17:47:21 +02:00
|
|
|
codec.serviceMethod = serviceMethod
|
|
|
|
codec.args = args
|
|
|
|
codec.reply = reply
|
|
|
|
codec.err = nil
|
2011-12-03 03:17:34 +01:00
|
|
|
var serverError error
|
2011-09-16 17:47:21 +02:00
|
|
|
if codec.server == nil {
|
|
|
|
serverError = ServeRequest(codec)
|
|
|
|
} else {
|
|
|
|
serverError = codec.server.ServeRequest(codec)
|
|
|
|
}
|
|
|
|
if codec.err == nil && serverError != nil {
|
|
|
|
codec.err = serverError
|
|
|
|
}
|
|
|
|
return codec.err
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (codec *CodecEmulator) ReadRequestHeader(req *Request) error {
|
2011-09-16 17:47:21 +02:00
|
|
|
req.ServiceMethod = codec.serviceMethod
|
|
|
|
req.Seq = 0
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (codec *CodecEmulator) ReadRequestBody(argv interface{}) error {
|
2011-09-16 17:47:21 +02:00
|
|
|
if codec.args == nil {
|
|
|
|
return io.ErrUnexpectedEOF
|
|
|
|
}
|
|
|
|
*(argv.(*Args)) = *codec.args
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (codec *CodecEmulator) WriteResponse(resp *Response, reply interface{}) error {
|
2011-09-16 17:47:21 +02:00
|
|
|
if resp.Error != "" {
|
2011-12-03 03:17:34 +01:00
|
|
|
codec.err = errors.New(resp.Error)
|
2011-12-02 20:34:41 +01:00
|
|
|
} else {
|
|
|
|
*codec.reply = *(reply.(*Reply))
|
2011-09-16 17:47:21 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (codec *CodecEmulator) Close() error {
|
2011-09-16 17:47:21 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServeRequest(t *testing.T) {
|
|
|
|
once.Do(startServer)
|
|
|
|
testServeRequest(t, nil)
|
|
|
|
newOnce.Do(startNewServer)
|
|
|
|
testServeRequest(t, newServer)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testServeRequest(t *testing.T, server *Server) {
|
|
|
|
client := CodecEmulator{server: server}
|
|
|
|
|
|
|
|
args := &Args{7, 8}
|
|
|
|
reply := new(Reply)
|
|
|
|
err := client.Call("Arith.Add", args, reply)
|
|
|
|
if err != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("Add: expected no error but got string %q", err.Error())
|
2011-09-16 17:47:21 +02:00
|
|
|
}
|
|
|
|
if reply.C != args.A+args.B {
|
|
|
|
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = client.Call("Arith.Add", nil, reply)
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("expected error calling Arith.Add with nil arg")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-03 05:34:57 +01:00
|
|
|
type ReplyNotPointer int
|
|
|
|
type ArgNotPublic int
|
|
|
|
type ReplyNotPublic int
|
2012-11-21 08:03:38 +01:00
|
|
|
type NeedsPtrType int
|
2010-12-03 05:34:57 +01:00
|
|
|
type local struct{}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *ReplyNotPointer) ReplyNotPointer(args *Args, reply Reply) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *ArgNotPublic) ArgNotPublic(args *local, reply *Reply) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (t *ReplyNotPublic) ReplyNotPublic(args *Args, reply *local) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2012-11-21 08:03:38 +01:00
|
|
|
func (t *NeedsPtrType) NeedsPtrType(args *Args, reply *Reply) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2010-12-03 05:34:57 +01:00
|
|
|
// Check that registration handles lots of bad methods and a type with no suitable methods.
|
|
|
|
func TestRegistrationError(t *testing.T) {
|
2011-05-20 02:18:15 +02:00
|
|
|
err := Register(new(ReplyNotPointer))
|
2010-12-03 05:34:57 +01:00
|
|
|
if err == nil {
|
2012-11-21 08:03:38 +01:00
|
|
|
t.Error("expected error registering ReplyNotPointer")
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
err = Register(new(ArgNotPublic))
|
|
|
|
if err == nil {
|
2012-11-21 08:03:38 +01:00
|
|
|
t.Error("expected error registering ArgNotPublic")
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
err = Register(new(ReplyNotPublic))
|
|
|
|
if err == nil {
|
2012-11-21 08:03:38 +01:00
|
|
|
t.Error("expected error registering ReplyNotPublic")
|
|
|
|
}
|
|
|
|
err = Register(NeedsPtrType(0))
|
|
|
|
if err == nil {
|
|
|
|
t.Error("expected error registering NeedsPtrType")
|
|
|
|
} else if !strings.Contains(err.Error(), "pointer") {
|
|
|
|
t.Error("expected hint when registering NeedsPtrType")
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type WriteFailCodec int
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (WriteFailCodec) WriteRequest(*Request, interface{}) error {
|
2010-12-03 05:34:57 +01:00
|
|
|
// the panic caused by this error used to not unlock a lock.
|
2011-12-03 03:17:34 +01:00
|
|
|
return errors.New("fail")
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (WriteFailCodec) ReadResponseHeader(*Response) error {
|
2012-03-02 21:01:37 +01:00
|
|
|
select {}
|
2010-12-03 05:34:57 +01:00
|
|
|
panic("unreachable")
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (WriteFailCodec) ReadResponseBody(interface{}) error {
|
2012-03-02 21:01:37 +01:00
|
|
|
select {}
|
2010-12-03 05:34:57 +01:00
|
|
|
panic("unreachable")
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (WriteFailCodec) Close() error {
|
2010-12-03 05:34:57 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSendDeadlock(t *testing.T) {
|
|
|
|
client := NewClientWithCodec(WriteFailCodec(0))
|
|
|
|
|
|
|
|
done := make(chan bool)
|
|
|
|
go func() {
|
|
|
|
testSendDeadlock(client)
|
|
|
|
testSendDeadlock(client)
|
|
|
|
done <- true
|
|
|
|
}()
|
2011-03-17 00:05:44 +01:00
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
return
|
2012-01-12 02:31:45 +01:00
|
|
|
case <-time.After(5 * time.Second):
|
2011-03-17 00:05:44 +01:00
|
|
|
t.Fatal("deadlock")
|
2010-12-03 05:34:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testSendDeadlock(client *Client) {
|
|
|
|
defer func() {
|
|
|
|
recover()
|
|
|
|
}()
|
|
|
|
args := &Args{7, 8}
|
|
|
|
reply := new(Reply)
|
|
|
|
client.Call("Arith.Add", args, reply)
|
|
|
|
}
|
2011-03-25 00:46:17 +01:00
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func dialDirect() (*Client, error) {
|
2011-05-20 02:18:15 +02:00
|
|
|
return Dial("tcp", serverAddr)
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func dialHTTP() (*Client, error) {
|
2011-05-20 02:18:15 +02:00
|
|
|
return DialHTTP("tcp", httpServerAddr)
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func countMallocs(dial func() (*Client, error), t *testing.T) uint64 {
|
2012-12-13 00:13:29 +01:00
|
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
|
2011-03-25 00:46:17 +01:00
|
|
|
once.Do(startServer)
|
2011-05-20 02:18:15 +02:00
|
|
|
client, err := dial()
|
2011-03-25 00:46:17 +01:00
|
|
|
if err != nil {
|
2011-05-20 02:18:15 +02:00
|
|
|
t.Fatal("error dialing", err)
|
2011-03-25 00:46:17 +01:00
|
|
|
}
|
|
|
|
args := &Args{7, 8}
|
|
|
|
reply := new(Reply)
|
2012-02-09 09:19:58 +01:00
|
|
|
memstats := new(runtime.MemStats)
|
|
|
|
runtime.ReadMemStats(memstats)
|
|
|
|
mallocs := 0 - memstats.Mallocs
|
2011-03-25 00:46:17 +01:00
|
|
|
const count = 100
|
|
|
|
for i := 0; i < count; i++ {
|
2011-05-20 02:18:15 +02:00
|
|
|
err := client.Call("Arith.Add", args, reply)
|
2011-03-25 00:46:17 +01:00
|
|
|
if err != nil {
|
2011-12-03 03:17:34 +01:00
|
|
|
t.Errorf("Add: expected no error but got string %q", err.Error())
|
2011-03-25 00:46:17 +01:00
|
|
|
}
|
|
|
|
if reply.C != args.A+args.B {
|
|
|
|
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
|
|
|
|
}
|
|
|
|
}
|
2012-02-09 09:19:58 +01:00
|
|
|
runtime.ReadMemStats(memstats)
|
|
|
|
mallocs += memstats.Mallocs
|
2011-05-20 02:18:15 +02:00
|
|
|
return mallocs / count
|
2011-03-25 00:46:17 +01:00
|
|
|
}
|
|
|
|
|
2011-05-20 02:18:15 +02:00
|
|
|
func TestCountMallocs(t *testing.T) {
|
|
|
|
fmt.Printf("mallocs per rpc round trip: %d\n", countMallocs(dialDirect, t))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCountMallocsOverHTTP(t *testing.T) {
|
|
|
|
fmt.Printf("mallocs per HTTP rpc round trip: %d\n", countMallocs(dialHTTP, t))
|
|
|
|
}
|
|
|
|
|
2012-02-01 20:26:59 +01:00
|
|
|
type writeCrasher struct {
|
|
|
|
done chan bool
|
|
|
|
}
|
2011-10-27 01:57:58 +02:00
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (writeCrasher) Close() error {
|
2011-10-27 01:57:58 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2012-02-01 20:26:59 +01:00
|
|
|
func (w *writeCrasher) Read(p []byte) (int, error) {
|
|
|
|
<-w.done
|
2011-12-03 03:17:34 +01:00
|
|
|
return 0, io.EOF
|
2011-10-27 01:57:58 +02:00
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func (writeCrasher) Write(p []byte) (int, error) {
|
|
|
|
return 0, errors.New("fake write failure")
|
2011-10-27 01:57:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestClientWriteError(t *testing.T) {
|
2012-02-01 20:26:59 +01:00
|
|
|
w := &writeCrasher{done: make(chan bool)}
|
|
|
|
c := NewClient(w)
|
2011-10-27 01:57:58 +02:00
|
|
|
res := false
|
|
|
|
err := c.Call("foo", 1, &res)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error")
|
|
|
|
}
|
2011-12-03 03:17:34 +01:00
|
|
|
if err.Error() != "fake write failure" {
|
2011-10-27 01:57:58 +02:00
|
|
|
t.Error("unexpected value of error:", err)
|
|
|
|
}
|
2012-02-01 20:26:59 +01:00
|
|
|
w.done <- true
|
2011-10-27 01:57:58 +02:00
|
|
|
}
|
|
|
|
|
2012-11-21 08:03:38 +01:00
|
|
|
func TestTCPClose(t *testing.T) {
|
|
|
|
once.Do(startServer)
|
|
|
|
|
|
|
|
client, err := dialHTTP()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("dialing: %v", err)
|
|
|
|
}
|
|
|
|
defer client.Close()
|
|
|
|
|
|
|
|
args := Args{17, 8}
|
|
|
|
var reply Reply
|
|
|
|
err = client.Call("Arith.Mul", args, &reply)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("arith error:", err)
|
|
|
|
}
|
|
|
|
t.Logf("Arith: %d*%d=%d\n", args.A, args.B, reply)
|
|
|
|
if reply.C != args.A*args.B {
|
|
|
|
t.Errorf("Add: expected %d got %d", reply.C, args.A*args.B)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func benchmarkEndToEnd(dial func() (*Client, error), b *testing.B) {
|
2011-03-25 00:46:17 +01:00
|
|
|
b.StopTimer()
|
|
|
|
once.Do(startServer)
|
2011-05-20 02:18:15 +02:00
|
|
|
client, err := dial()
|
2011-03-25 00:46:17 +01:00
|
|
|
if err != nil {
|
2012-01-25 21:56:26 +01:00
|
|
|
b.Fatal("error dialing:", err)
|
2011-03-25 00:46:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Synchronous calls
|
|
|
|
args := &Args{7, 8}
|
2011-10-27 01:57:58 +02:00
|
|
|
procs := runtime.GOMAXPROCS(-1)
|
|
|
|
N := int32(b.N)
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(procs)
|
2011-03-25 00:46:17 +01:00
|
|
|
b.StartTimer()
|
2011-10-27 01:57:58 +02:00
|
|
|
|
|
|
|
for p := 0; p < procs; p++ {
|
|
|
|
go func() {
|
|
|
|
reply := new(Reply)
|
|
|
|
for atomic.AddInt32(&N, -1) >= 0 {
|
2012-02-01 20:26:59 +01:00
|
|
|
err := client.Call("Arith.Add", args, reply)
|
2011-10-27 01:57:58 +02:00
|
|
|
if err != nil {
|
2012-01-13 06:11:45 +01:00
|
|
|
b.Fatalf("rpc error: Add: expected no error but got string %q", err.Error())
|
2011-10-27 01:57:58 +02:00
|
|
|
}
|
|
|
|
if reply.C != args.A+args.B {
|
2012-01-13 06:11:45 +01:00
|
|
|
b.Fatalf("rpc error: Add: expected %d got %d", reply.C, args.A+args.B)
|
2011-10-27 01:57:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
2011-12-03 03:17:34 +01:00
|
|
|
func benchmarkEndToEndAsync(dial func() (*Client, error), b *testing.B) {
|
2011-10-27 01:57:58 +02:00
|
|
|
const MaxConcurrentCalls = 100
|
|
|
|
b.StopTimer()
|
|
|
|
once.Do(startServer)
|
|
|
|
client, err := dial()
|
|
|
|
if err != nil {
|
2012-01-25 21:56:26 +01:00
|
|
|
b.Fatal("error dialing:", err)
|
2011-03-25 00:46:17 +01:00
|
|
|
}
|
2011-10-27 01:57:58 +02:00
|
|
|
|
|
|
|
// Asynchronous calls
|
|
|
|
args := &Args{7, 8}
|
|
|
|
procs := 4 * runtime.GOMAXPROCS(-1)
|
|
|
|
send := int32(b.N)
|
|
|
|
recv := int32(b.N)
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(procs)
|
|
|
|
gate := make(chan bool, MaxConcurrentCalls)
|
|
|
|
res := make(chan *Call, MaxConcurrentCalls)
|
|
|
|
b.StartTimer()
|
|
|
|
|
|
|
|
for p := 0; p < procs; p++ {
|
|
|
|
go func() {
|
|
|
|
for atomic.AddInt32(&send, -1) >= 0 {
|
|
|
|
gate <- true
|
|
|
|
reply := new(Reply)
|
|
|
|
client.Go("Arith.Add", args, reply, res)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
for call := range res {
|
2012-01-13 06:11:45 +01:00
|
|
|
A := call.Args.(*Args).A
|
|
|
|
B := call.Args.(*Args).B
|
|
|
|
C := call.Reply.(*Reply).C
|
|
|
|
if A+B != C {
|
|
|
|
b.Fatalf("incorrect reply: Add: expected %d got %d", A+B, C)
|
2011-10-27 01:57:58 +02:00
|
|
|
}
|
|
|
|
<-gate
|
|
|
|
if atomic.AddInt32(&recv, -1) == 0 {
|
|
|
|
close(res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
2011-03-25 00:46:17 +01:00
|
|
|
}
|
2011-05-20 02:18:15 +02:00
|
|
|
|
|
|
|
func BenchmarkEndToEnd(b *testing.B) {
|
|
|
|
benchmarkEndToEnd(dialDirect, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkEndToEndHTTP(b *testing.B) {
|
|
|
|
benchmarkEndToEnd(dialHTTP, b)
|
|
|
|
}
|
2011-10-27 01:57:58 +02:00
|
|
|
|
|
|
|
func BenchmarkEndToEndAsync(b *testing.B) {
|
|
|
|
benchmarkEndToEndAsync(dialDirect, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkEndToEndAsyncHTTP(b *testing.B) {
|
|
|
|
benchmarkEndToEndAsync(dialHTTP, b)
|
|
|
|
}
|