'net/rpc server stay registered when running test more than once with the 'count' flag
The program creates a rpc server and a client and expose several methods via the rpc interface.
Several test functions testing one of the methods each.
The first test function register the rpc server:
RPCServer := new(RPCServer)
rpc.Register(RPCServer)
rpc.HandleHTTP()
Using a channel, each function wait for the server to signal that it is running and when the client finish, signal the server to shut down.
In the test function:
// start the server
ch := make(chan bool, 1)
go RunRPCServer(ch)
// wait for the server to start
<-ch
...Do Some Work...
// close the server
ch <- true
close(ch)
and in the server function:
listener, err := net.Listen("tcp4", "")
if err != nil {
log.Fatal("Could not open a listener port: ", err.Error())
}
wg := sync.WaitGroup{}
wg.Add(1)
// Start the server
rpcServer := http.Server{}
go func() {
go rpcServer.Serve(listener)
// signal that the server is running
ch <- true
// wait for the command to close the server
<-ch
<-ch
// shutdown server and exit
if err := rpcServer.Shutdown(context.Background()); err != nil {
log.Printf("HTTP server Shutdown: %v\n", err)
}
listener.Close()
wg.Done()
}()
wg.Wait()
It all works perfectly fine when running the tests once, or even several times with:
go test -count 1 ./src/rpcserver/
go test -count 1 ./src/rpcserver/
However, when running
go test -count 2 ./src/rpcserver/
An error is returned when the second run starts:
--- FAIL: TestRPCServer (0.00s)
panic: http: multiple registrations for /goRPC [recovered]
panic: http: multiple registrations for /goRPC
I wonder why
go test -count 2 ./src/rpcserver/
does not behave exactly as:
go test -count 1 ./src/rpcserver/
go test -count 1 ./src/rpcserver/
?
And what can be done in order to de-register the server between consecutive run of the tests?
Edit: Here is a full minimal example that illustrates the issue:
server.go:
package rpcserver
import (
"context"
"log"
"net"
"net/http"
"net/rpc"
)
type RPCServer int
// RPC: only methods that comply with the following scheme are exported:
// func (t *T) MethodName(argType T1, replyType *T2) error
// Since RPC-exposed methods must takes an argument.
type EmptyArg struct{}
// Expose several methods via the RPC interface:
func (RpcServer *RPCServer) Method_1(emptyArg EmptyArg, reply *string) error {
*reply = "This is Method_1"
return nil
}
func (RpcServer *RPCServer) Method_2(emptyArg EmptyArg, reply *string) error {
*reply = "This is Method_2"
return nil
}
func (RpcServer *RPCServer) Method_3(emptyArg EmptyArg, reply *string) error {
*reply = "This is Method_3"
return nil
}
// should be called only once per process
func RegisterRPCMethods() {
// Register the rpc methods
RpcServer := new(RPCServer)
rpc.Register(RpcServer)
rpc.HandleHTTP()
}
func RunRPCServer(ch chan struct{}) {
// Open a listener port
listener, err := net.Listen("tcp4", "127.0.0.1:38659")
if err != nil {
log.Fatal("listen error:", err)
}
// Print some data
log.Println("Server running")
log.Println("network:", listener.Addr().Network())
// Start the server
rpcServer := http.Server{}
go func() {
go rpcServer.Serve(listener)
// signal that the server is running
ch <- struct{}{}
// wait for the command to close the server
<-ch
// shutdown server and exit
if err := rpcServer.Shutdown(context.Background()); err != nil {
log.Printf("HTTP server Shutdown: %v\n", err)
}
listener.Close()
// signal that the server is closed
ch <- struct{}{}
}()
}
server_test.go:
package rpcserver
import (
"net/rpc"
"testing"
)
func TestMethod_1(t *testing.T) {
// call once.
// (test functions are executed in-order)
RegisterRPCMethods()
// start the server
ch := make(chan struct{})
go RunRPCServer(ch)
// wait for the server to start
<-ch
// Dial to the rpc server
client, err := rpc.DialHTTP("tcp", "127.0.0.1:38659")
if err != nil {
t.Errorf("Could not dial %s: %s", "127.0.0.1:38659", err.Error())
}
// the called function allready asserting type.
reply := ""
err = client.Call("RPCServer.Method_1", EmptyArg{}, &reply)
if err != nil {
t.Error(err)
}
// close the server
ch <- struct{}{}
// wait for the server to close
<-ch
close(ch)
}
func TestMethod_2(t *testing.T) {
// start the server
ch := make(chan struct{})
go RunRPCServer(ch)
// wait for the server to start
<-ch
// Dial to the rpc server
client, err := rpc.DialHTTP("tcp", "127.0.0.1:38659")
if err != nil {
t.Errorf("Could not dial %s: %s", "127.0.0.1:38659", err.Error())
}
// the called function allready asserting type.
reply := ""
err = client.Call("RPCServer.Method_2", EmptyArg{}, &reply)
if err != nil {
t.Error(err)
}
// close the server
ch <- struct{}{}
// wait for the server to close
<-ch
close(ch)
}
func TestMethod_3(t *testing.T) {
// start the server
ch := make(chan struct{})
go RunRPCServer(ch)
// wait for the server to start
<-ch
// Dial to the rpc server
client, err := rpc.DialHTTP("tcp", "127.0.0.1:38659")
if err != nil {
t.Errorf("Could not dial %s: %s", "127.0.0.1:38659", err.Error())
}
// the called function allready asserting type.
reply := ""
err = client.Call("RPCServer.Method_3", EmptyArg{}, &reply)
if err != nil {
t.Error(err)
}
// close the server
ch <- struct{}{}
// wait for the server to close
<-ch
close(ch)
}
When running:
go test -v -count 1 ./src/server/...
The output is as expected:
=== RUN TestMethod_1
2022/05/11 10:59:49 Server running
2022/05/11 10:59:49 network: tcp
--- PASS: TestMethod_1 (0.00s)
=== RUN TestMethod_2
2022/05/11 10:59:49 Server running
2022/05/11 10:59:49 network: tcp
--- PASS: TestMethod_2 (0.00s)
=== RUN TestMethod_3
2022/05/11 10:59:49 Server running
2022/05/11 10:59:49 network: tcp
--- PASS: TestMethod_3 (0.00s)
PASS
ok lsfrpc/src/server 0.008s
And when running
go test -v -count 1 ./src/server/...
go test -v -count 1 ./src/server/...
Everything working fine (the output above, twice)
However, when running:
go test -v -count 2 ./src/server/...
There is an error at the beginning of the second run:
=== RUN TestMethod_1
2022/05/11 10:59:52 Server running
2022/05/11 10:59:52 network: tcp
--- PASS: TestMethod_1 (0.00s)
=== RUN TestMethod_2
2022/05/11 10:59:52 Server running
2022/05/11 10:59:52 network: tcp
--- PASS: TestMethod_2 (0.00s)
=== RUN TestMethod_3
2022/05/11 10:59:52 Server running
2022/05/11 10:59:52 network: tcp
--- PASS: TestMethod_3 (0.00s)
=== RUN TestMethod_1
--- FAIL: TestMethod_1 (0.00s)
panic: http: multiple registrations for /goRPC [recovered]
panic: http: multiple registrations for /goRPC
goroutine 63 [running]:
testing.tRunner.func1.2({0x7b9f20, 0xc0002927f0})
/home/sivsha01/go/src/testing/testing.go:1389 +0x24e
testing.tRunner.func1()
/home/sivsha01/go/src/testing/testing.go:1392 +0x39f
panic({0x7b9f20, 0xc0002927f0})
/home/sivsha01/go/src/runtime/panic.go:838 +0x207
net/http.(*ServeMux).Handle(0xb2e160, {0x83449c, 0x8}, {0x8dc3c0?, 0xc000198000})
Same as I described above. This seems as a wrong behavior to me.
Solution 1:[1]
A reduced version to reproduce the issue:
package rpcserver
import (
"net/http"
"os"
"testing"
)
func TestRegister(t *testing.T) {
t.Logf("pid: %d", os.Getpid())
register()
}
func register() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
}
When you run the test with: go test -count 2 -v
, you will find that the 2 tests running in the same process. As what is stated in your comment: RegisterRPCMethods()
should be called only once per process.
You can use sync.Once
to make sure it's just called once:
package rpcserver
import (
"net/http"
"os"
"sync"
"testing"
)
func TestRegister(t *testing.T) {
t.Logf("pid: %d", os.Getpid())
register()
}
var registerOnce sync.Once
func register() {
registerOnce.Do(func() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
})
}
What's more, your tests won't depend on the execution order of the tests any longer. And all the tests can be run alone.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |