英文:
net/rpc server stay registered when running test more than once with the 'count' flag
问题
该程序创建了一个RPC服务器和客户端,并通过RPC接口公开了几种方法。
有几个测试函数,每个函数测试其中一种方法。
第一个测试函数注册了RPC服务器:
RPCServer := new(RPCServer)
rpc.Register(RPCServer)
rpc.HandleHTTP()
使用一个通道,每个函数等待服务器发出运行信号,当客户端完成后,向服务器发出关闭信号。
在测试函数中:
// 启动服务器
ch := make(chan bool, 1)
go RunRPCServer(ch)
// 等待服务器启动
<-ch
...做一些工作...
// 关闭服务器
ch <- true
close(ch)
在服务器函数中:
listener, err := net.Listen("tcp4", "")
if err != nil {
log.Fatal("无法打开监听端口:", err.Error())
}
wg := sync.WaitGroup{}
wg.Add(1)
// 启动服务器
rpcServer := http.Server{}
go func() {
go rpcServer.Serve(listener)
// 发出服务器正在运行的信号
ch <- true
// 等待关闭服务器的命令
<-ch
<-ch
// 关闭服务器并退出
if err := rpcServer.Shutdown(context.Background()); err != nil {
log.Printf("HTTP服务器关闭:%v\n", err)
}
listener.Close()
wg.Done()
}()
wg.Wait()
当只运行一次测试或者连续运行多次测试时,都能正常工作:
go test -count 1 ./src/rpcserver/
go test -count 1 ./src/rpcserver/
但是,当运行以下命令时:
go test -count 2 ./src/rpcserver/
第二次运行开始时会返回错误:
--- FAIL: TestRPCServer (0.00s)
panic: http: multiple registrations for /goRPC [recovered]
panic: http: multiple registrations for /goRPC
我想知道为什么运行以下命令:
go test -count 2 ./src/rpcserver/
的行为与以下命令不完全相同:
go test -count 1 ./src/rpcserver/
go test -count 1 ./src/rpcserver/
?并且在连续运行测试之间如何取消注册服务器?
英文:
The program creates a rpc server and a client and expose several methods via the rpc interface.<br>
Several test functions testing one of the methods each.<br>
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/<br>
> 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)<br>
panic: http: multiple registrations for /goRPC [recovered]<br>
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/<br>
> 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<br>
2022/05/11 10:59:49 Server running<br>
2022/05/11 10:59:49 network: tcp<br>
--- PASS: TestMethod_1 (0.00s)<br>
=== RUN TestMethod_2<br>
2022/05/11 10:59:49 Server running<br>
2022/05/11 10:59:49 network: tcp<br>
--- PASS: TestMethod_2 (0.00s)<br>
=== RUN TestMethod_3<br>
2022/05/11 10:59:49 Server running<br>
2022/05/11 10:59:49 network: tcp<br>
--- PASS: TestMethod_3 (0.00s)<br>
PASS<br>
ok lsfrpc/src/server 0.008s<br>
And when running
> go test -v -count 1 ./src/server/...<br>
> 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<br>
2022/05/11 10:59:52 Server running<br>
2022/05/11 10:59:52 network: tcp<br>
--- PASS: TestMethod_1 (0.00s)<br>
=== RUN TestMethod_2<br>
2022/05/11 10:59:52 Server running<br>
2022/05/11 10:59:52 network: tcp<br>
--- PASS: TestMethod_2 (0.00s)<br>
=== RUN TestMethod_3<br>
2022/05/11 10:59:52 Server running<br>
2022/05/11 10:59:52 network: tcp<br>
--- PASS: TestMethod_3 (0.00s)<br>
=== RUN TestMethod_1<br>
--- FAIL: TestMethod_1 (0.00s)<br>
panic: http: multiple registrations for /goRPC [recovered]<br>
panic: http: multiple registrations for /goRPC<br>
<br>
goroutine 63 [running]:<br>
testing.tRunner.func1.2({0x7b9f20, 0xc0002927f0})<br>
/home/sivsha01/go/src/testing/testing.go:1389 +0x24e<br>
testing.tRunner.func1()<br>
/home/sivsha01/go/src/testing/testing.go:1392 +0x39f<br>
panic({0x7b9f20, 0xc0002927f0})<br>
/home/sivsha01/go/src/runtime/panic.go:838 +0x207<br>
net/http.(*ServeMux).Handle(0xb2e160, {0x83449c, 0x8}, {0x8dc3c0?, 0xc000198000})<br>
Same as I described above. This seems as a wrong behavior to me.
答案1
得分: 1
一个用于复现问题的简化版本:
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) {})
}
当你使用go test -count 2 -v
运行测试时,你会发现两个测试在同一个进程中运行。正如你在评论中所说的:RegisterRPCMethods()
应该每个进程只调用一次。
你可以使用sync.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) {})
})
}
此外,你的测试将不再依赖于测试的执行顺序。所有的测试都可以单独运行。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论