'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