当使用’count’标志多次运行测试时,net/rpc服务器保持注册状态。

huangapple go评论90阅读模式
英文:

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
	&lt;-ch

	...Do Some Work...

	// close the server
	ch &lt;- true
	close(ch)

and in the server function:

	listener, err := net.Listen(&quot;tcp4&quot;, &quot;&quot;)
	if err != nil {
		log.Fatal(&quot;Could not open a listener port: &quot;, 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 &lt;- true
			// wait for the command to close the server
			&lt;-ch
			&lt;-ch
			// shutdown server and exit
			if err := rpcServer.Shutdown(context.Background()); err != nil {
				log.Printf(&quot;HTTP server Shutdown: %v\n&quot;, 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 (
	&quot;context&quot;
	&quot;log&quot;
	&quot;net&quot;
	&quot;net/http&quot;
	&quot;net/rpc&quot;
)

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 = &quot;This is Method_1&quot;
	return nil
}

func (RpcServer *RPCServer) Method_2(emptyArg EmptyArg, reply *string) error {

	*reply = &quot;This is Method_2&quot;
	return nil
}

func (RpcServer *RPCServer) Method_3(emptyArg EmptyArg, reply *string) error {

	*reply = &quot;This is Method_3&quot;
	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(&quot;tcp4&quot;, &quot;127.0.0.1:38659&quot;)
	if err != nil {
		log.Fatal(&quot;listen error:&quot;, err)
	}

	// Print some data
	log.Println(&quot;Server running&quot;)
	log.Println(&quot;network:&quot;, listener.Addr().Network())

	// Start the server
	rpcServer := http.Server{}
	go func() {
		go rpcServer.Serve(listener)
		// signal that the server is running
		ch &lt;- struct{}{}
		// wait for the command to close the server
		&lt;-ch
		// shutdown server and exit
		if err := rpcServer.Shutdown(context.Background()); err != nil {
			log.Printf(&quot;HTTP server Shutdown: %v\n&quot;, err)
		}
		listener.Close()
		// signal that the server is closed
		ch &lt;- struct{}{}
	}()
}

server_test.go:


package rpcserver

import (
	&quot;net/rpc&quot;
	&quot;testing&quot;
)

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
	&lt;-ch

	// Dial to the rpc server
	client, err := rpc.DialHTTP(&quot;tcp&quot;, &quot;127.0.0.1:38659&quot;)
	if err != nil {
		t.Errorf(&quot;Could not dial %s: %s&quot;, &quot;127.0.0.1:38659&quot;, err.Error())
	}
	// the called function allready asserting type.
	reply := &quot;&quot;
	err = client.Call(&quot;RPCServer.Method_1&quot;, EmptyArg{}, &amp;reply)
	if err != nil {
		t.Error(err)
	}

	// close the server
	ch &lt;- struct{}{}
	// wait for the server to close
	&lt;-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
	&lt;-ch

	// Dial to the rpc server
	client, err := rpc.DialHTTP(&quot;tcp&quot;, &quot;127.0.0.1:38659&quot;)
	if err != nil {
		t.Errorf(&quot;Could not dial %s: %s&quot;, &quot;127.0.0.1:38659&quot;, err.Error())
	}
	// the called function allready asserting type.
	reply := &quot;&quot;
	err = client.Call(&quot;RPCServer.Method_2&quot;, EmptyArg{}, &amp;reply)
	if err != nil {
		t.Error(err)
	}

	// close the server
	ch &lt;- struct{}{}
	// wait for the server to close
	&lt;-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
	&lt;-ch

	// Dial to the rpc server
	client, err := rpc.DialHTTP(&quot;tcp&quot;, &quot;127.0.0.1:38659&quot;)
	if err != nil {
		t.Errorf(&quot;Could not dial %s: %s&quot;, &quot;127.0.0.1:38659&quot;, err.Error())
	}
	// the called function allready asserting type.
	reply := &quot;&quot;
	err = client.Call(&quot;RPCServer.Method_3&quot;, EmptyArg{}, &amp;reply)
	if err != nil {
		t.Error(err)
	}

	// close the server
	ch &lt;- struct{}{}
	// wait for the server to close
	&lt;-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 (
	&quot;net/http&quot;
	&quot;os&quot;
	&quot;testing&quot;
)

func TestRegister(t *testing.T) {
	t.Logf(&quot;pid: %d&quot;, os.Getpid())

	register()
}

func register() {
	http.HandleFunc(&quot;/&quot;, 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 (
	&quot;net/http&quot;
	&quot;os&quot;
	&quot;sync&quot;
	&quot;testing&quot;
)

func TestRegister(t *testing.T) {
	t.Logf(&quot;pid: %d&quot;, os.Getpid())

	register()
}

var registerOnce sync.Once

func register() {
	registerOnce.Do(func() {
		http.HandleFunc(&quot;/&quot;, 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.

huangapple
  • 本文由 发表于 2022年5月11日 02:49:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/72191616.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定