github.com/Debrief-BC/go-debrief@v0.0.0-20200420203408-0c26ca968123/rpc/client_test.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package rpc
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math/rand"
    23  	"net"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"os"
    27  	"reflect"
    28  	"runtime"
    29  	"sync"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/davecgh/go-spew/spew"
    34  	"github.com/Debrief-BC/go-debrief/log"
    35  )
    36  
    37  func TestClientRequest(t *testing.T) {
    38  	server := newTestServer()
    39  	defer server.Stop()
    40  	client := DialInProc(server)
    41  	defer client.Close()
    42  
    43  	var resp echoResult
    44  	if err := client.Call(&resp, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
    45  		t.Fatal(err)
    46  	}
    47  	if !reflect.DeepEqual(resp, echoResult{"hello", 10, &echoArgs{"world"}}) {
    48  		t.Errorf("incorrect result %#v", resp)
    49  	}
    50  }
    51  
    52  func TestClientResponseType(t *testing.T) {
    53  	server := newTestServer()
    54  	defer server.Stop()
    55  	client := DialInProc(server)
    56  	defer client.Close()
    57  
    58  	if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
    59  		t.Errorf("Passing nil as result should be fine, but got an error: %v", err)
    60  	}
    61  	var resultVar echoResult
    62  	// Note: passing the var, not a ref
    63  	err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"})
    64  	if err == nil {
    65  		t.Error("Passing a var as result should be an error")
    66  	}
    67  }
    68  
    69  func TestClientBatchRequest(t *testing.T) {
    70  	server := newTestServer()
    71  	defer server.Stop()
    72  	client := DialInProc(server)
    73  	defer client.Close()
    74  
    75  	batch := []BatchElem{
    76  		{
    77  			Method: "test_echo",
    78  			Args:   []interface{}{"hello", 10, &echoArgs{"world"}},
    79  			Result: new(echoResult),
    80  		},
    81  		{
    82  			Method: "test_echo",
    83  			Args:   []interface{}{"hello2", 11, &echoArgs{"world"}},
    84  			Result: new(echoResult),
    85  		},
    86  		{
    87  			Method: "no_such_method",
    88  			Args:   []interface{}{1, 2, 3},
    89  			Result: new(int),
    90  		},
    91  	}
    92  	if err := client.BatchCall(batch); err != nil {
    93  		t.Fatal(err)
    94  	}
    95  	wantResult := []BatchElem{
    96  		{
    97  			Method: "test_echo",
    98  			Args:   []interface{}{"hello", 10, &echoArgs{"world"}},
    99  			Result: &echoResult{"hello", 10, &echoArgs{"world"}},
   100  		},
   101  		{
   102  			Method: "test_echo",
   103  			Args:   []interface{}{"hello2", 11, &echoArgs{"world"}},
   104  			Result: &echoResult{"hello2", 11, &echoArgs{"world"}},
   105  		},
   106  		{
   107  			Method: "no_such_method",
   108  			Args:   []interface{}{1, 2, 3},
   109  			Result: new(int),
   110  			Error:  &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"},
   111  		},
   112  	}
   113  	if !reflect.DeepEqual(batch, wantResult) {
   114  		t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult))
   115  	}
   116  }
   117  
   118  func TestClientNotify(t *testing.T) {
   119  	server := newTestServer()
   120  	defer server.Stop()
   121  	client := DialInProc(server)
   122  	defer client.Close()
   123  
   124  	if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
   125  		t.Fatal(err)
   126  	}
   127  }
   128  
   129  // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) }
   130  func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) }
   131  func TestClientCancelHTTP(t *testing.T)      { testClientCancel("http", t) }
   132  func TestClientCancelIPC(t *testing.T)       { testClientCancel("ipc", t) }
   133  
   134  // This test checks that requests made through CallContext can be canceled by canceling
   135  // the context.
   136  func testClientCancel(transport string, t *testing.T) {
   137  	// These tests take a lot of time, run them all at once.
   138  	// You probably want to run with -parallel 1 or comment out
   139  	// the call to t.Parallel if you enable the logging.
   140  	t.Parallel()
   141  
   142  	server := newTestServer()
   143  	defer server.Stop()
   144  
   145  	// What we want to achieve is that the context gets canceled
   146  	// at various stages of request processing. The interesting cases
   147  	// are:
   148  	//  - cancel during dial
   149  	//  - cancel while performing a HTTP request
   150  	//  - cancel while waiting for a response
   151  	//
   152  	// To trigger those, the times are chosen such that connections
   153  	// are killed within the deadline for every other call (maxKillTimeout
   154  	// is 2x maxCancelTimeout).
   155  	//
   156  	// Once a connection is dead, there is a fair chance it won't connect
   157  	// successfully because the accept is delayed by 1s.
   158  	maxContextCancelTimeout := 300 * time.Millisecond
   159  	fl := &flakeyListener{
   160  		maxAcceptDelay: 1 * time.Second,
   161  		maxKillTimeout: 600 * time.Millisecond,
   162  	}
   163  
   164  	var client *Client
   165  	switch transport {
   166  	case "ws", "http":
   167  		c, hs := httpTestClient(server, transport, fl)
   168  		defer hs.Close()
   169  		client = c
   170  	case "ipc":
   171  		c, l := ipcTestClient(server, fl)
   172  		defer l.Close()
   173  		client = c
   174  	default:
   175  		panic("unknown transport: " + transport)
   176  	}
   177  
   178  	// The actual test starts here.
   179  	var (
   180  		wg       sync.WaitGroup
   181  		nreqs    = 10
   182  		ncallers = 10
   183  	)
   184  	caller := func(index int) {
   185  		defer wg.Done()
   186  		for i := 0; i < nreqs; i++ {
   187  			var (
   188  				ctx     context.Context
   189  				cancel  func()
   190  				timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout)))
   191  			)
   192  			if index < ncallers/2 {
   193  				// For half of the callers, create a context without deadline
   194  				// and cancel it later.
   195  				ctx, cancel = context.WithCancel(context.Background())
   196  				time.AfterFunc(timeout, cancel)
   197  			} else {
   198  				// For the other half, create a context with a deadline instead. This is
   199  				// different because the context deadline is used to set the socket write
   200  				// deadline.
   201  				ctx, cancel = context.WithTimeout(context.Background(), timeout)
   202  			}
   203  
   204  			// Now perform a call with the context.
   205  			// The key thing here is that no call will ever complete successfully.
   206  			err := client.CallContext(ctx, nil, "test_block")
   207  			switch {
   208  			case err == nil:
   209  				_, hasDeadline := ctx.Deadline()
   210  				t.Errorf("no error for call with %v wait time (deadline: %v)", timeout, hasDeadline)
   211  				// default:
   212  				// 	t.Logf("got expected error with %v wait time: %v", timeout, err)
   213  			}
   214  			cancel()
   215  		}
   216  	}
   217  	wg.Add(ncallers)
   218  	for i := 0; i < ncallers; i++ {
   219  		go caller(i)
   220  	}
   221  	wg.Wait()
   222  }
   223  
   224  func TestClientSubscribeInvalidArg(t *testing.T) {
   225  	server := newTestServer()
   226  	defer server.Stop()
   227  	client := DialInProc(server)
   228  	defer client.Close()
   229  
   230  	check := func(shouldPanic bool, arg interface{}) {
   231  		defer func() {
   232  			err := recover()
   233  			if shouldPanic && err == nil {
   234  				t.Errorf("EthSubscribe should've panicked for %#v", arg)
   235  			}
   236  			if !shouldPanic && err != nil {
   237  				t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg)
   238  				buf := make([]byte, 1024*1024)
   239  				buf = buf[:runtime.Stack(buf, false)]
   240  				t.Error(err)
   241  				t.Error(string(buf))
   242  			}
   243  		}()
   244  		client.EthSubscribe(context.Background(), arg, "foo_bar")
   245  	}
   246  	check(true, nil)
   247  	check(true, 1)
   248  	check(true, (chan int)(nil))
   249  	check(true, make(<-chan int))
   250  	check(false, make(chan int))
   251  	check(false, make(chan<- int))
   252  }
   253  
   254  func TestClientSubscribe(t *testing.T) {
   255  	server := newTestServer()
   256  	defer server.Stop()
   257  	client := DialInProc(server)
   258  	defer client.Close()
   259  
   260  	nc := make(chan int)
   261  	count := 10
   262  	sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0)
   263  	if err != nil {
   264  		t.Fatal("can't subscribe:", err)
   265  	}
   266  	for i := 0; i < count; i++ {
   267  		if val := <-nc; val != i {
   268  			t.Fatalf("value mismatch: got %d, want %d", val, i)
   269  		}
   270  	}
   271  
   272  	sub.Unsubscribe()
   273  	select {
   274  	case v := <-nc:
   275  		t.Fatal("received value after unsubscribe:", v)
   276  	case err := <-sub.Err():
   277  		if err != nil {
   278  			t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
   279  		}
   280  	case <-time.After(1 * time.Second):
   281  		t.Fatalf("subscription not closed within 1s after unsubscribe")
   282  	}
   283  }
   284  
   285  // In this test, the connection drops while Subscribe is waiting for a response.
   286  func TestClientSubscribeClose(t *testing.T) {
   287  	server := newTestServer()
   288  	service := &notificationTestService{
   289  		gotHangSubscriptionReq:  make(chan struct{}),
   290  		unblockHangSubscription: make(chan struct{}),
   291  	}
   292  	if err := server.RegisterName("nftest2", service); err != nil {
   293  		t.Fatal(err)
   294  	}
   295  
   296  	defer server.Stop()
   297  	client := DialInProc(server)
   298  	defer client.Close()
   299  
   300  	var (
   301  		nc   = make(chan int)
   302  		errc = make(chan error, 1)
   303  		sub  *ClientSubscription
   304  		err  error
   305  	)
   306  	go func() {
   307  		sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999)
   308  		errc <- err
   309  	}()
   310  
   311  	<-service.gotHangSubscriptionReq
   312  	client.Close()
   313  	service.unblockHangSubscription <- struct{}{}
   314  
   315  	select {
   316  	case err := <-errc:
   317  		if err == nil {
   318  			t.Errorf("Subscribe returned nil error after Close")
   319  		}
   320  		if sub != nil {
   321  			t.Error("Subscribe returned non-nil subscription after Close")
   322  		}
   323  	case <-time.After(1 * time.Second):
   324  		t.Fatalf("Subscribe did not return within 1s after Close")
   325  	}
   326  }
   327  
   328  // This test reproduces https://github.com/Debrief-BC/go-debrief/issues/17837 where the
   329  // client hangs during shutdown when Unsubscribe races with Client.Close.
   330  func TestClientCloseUnsubscribeRace(t *testing.T) {
   331  	server := newTestServer()
   332  	defer server.Stop()
   333  
   334  	for i := 0; i < 20; i++ {
   335  		client := DialInProc(server)
   336  		nc := make(chan int)
   337  		sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1)
   338  		if err != nil {
   339  			t.Fatal(err)
   340  		}
   341  		go client.Close()
   342  		go sub.Unsubscribe()
   343  		select {
   344  		case <-sub.Err():
   345  		case <-time.After(5 * time.Second):
   346  			t.Fatal("subscription not closed within timeout")
   347  		}
   348  	}
   349  }
   350  
   351  // This test checks that Client doesn't lock up when a single subscriber
   352  // doesn't read subscription events.
   353  func TestClientNotificationStorm(t *testing.T) {
   354  	server := newTestServer()
   355  	defer server.Stop()
   356  
   357  	doTest := func(count int, wantError bool) {
   358  		client := DialInProc(server)
   359  		defer client.Close()
   360  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   361  		defer cancel()
   362  
   363  		// Subscribe on the server. It will start sending many notifications
   364  		// very quickly.
   365  		nc := make(chan int)
   366  		sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0)
   367  		if err != nil {
   368  			t.Fatal("can't subscribe:", err)
   369  		}
   370  		defer sub.Unsubscribe()
   371  
   372  		// Process each notification, try to run a call in between each of them.
   373  		for i := 0; i < count; i++ {
   374  			select {
   375  			case val := <-nc:
   376  				if val != i {
   377  					t.Fatalf("(%d/%d) unexpected value %d", i, count, val)
   378  				}
   379  			case err := <-sub.Err():
   380  				if wantError && err != ErrSubscriptionQueueOverflow {
   381  					t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow)
   382  				} else if !wantError {
   383  					t.Fatalf("(%d/%d) got unexpected error %q", i, count, err)
   384  				}
   385  				return
   386  			}
   387  			var r int
   388  			err := client.CallContext(ctx, &r, "nftest_echo", i)
   389  			if err != nil {
   390  				if !wantError {
   391  					t.Fatalf("(%d/%d) call error: %v", i, count, err)
   392  				}
   393  				return
   394  			}
   395  		}
   396  		if wantError {
   397  			t.Fatalf("didn't get expected error")
   398  		}
   399  	}
   400  
   401  	doTest(8000, false)
   402  	doTest(23000, true)
   403  }
   404  
   405  func TestClientHTTP(t *testing.T) {
   406  	server := newTestServer()
   407  	defer server.Stop()
   408  
   409  	client, hs := httpTestClient(server, "http", nil)
   410  	defer hs.Close()
   411  	defer client.Close()
   412  
   413  	// Launch concurrent requests.
   414  	var (
   415  		results    = make([]echoResult, 100)
   416  		errc       = make(chan error, len(results))
   417  		wantResult = echoResult{"a", 1, new(echoArgs)}
   418  	)
   419  	defer client.Close()
   420  	for i := range results {
   421  		i := i
   422  		go func() {
   423  			errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args)
   424  		}()
   425  	}
   426  
   427  	// Wait for all of them to complete.
   428  	timeout := time.NewTimer(5 * time.Second)
   429  	defer timeout.Stop()
   430  	for i := range results {
   431  		select {
   432  		case err := <-errc:
   433  			if err != nil {
   434  				t.Fatal(err)
   435  			}
   436  		case <-timeout.C:
   437  			t.Fatalf("timeout (got %d/%d) results)", i+1, len(results))
   438  		}
   439  	}
   440  
   441  	// Check results.
   442  	for i := range results {
   443  		if !reflect.DeepEqual(results[i], wantResult) {
   444  			t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult)
   445  		}
   446  	}
   447  }
   448  
   449  func TestClientReconnect(t *testing.T) {
   450  	startServer := func(addr string) (*Server, net.Listener) {
   451  		srv := newTestServer()
   452  		l, err := net.Listen("tcp", addr)
   453  		if err != nil {
   454  			t.Fatal("can't listen:", err)
   455  		}
   456  		go http.Serve(l, srv.WebsocketHandler([]string{"*"}))
   457  		return srv, l
   458  	}
   459  
   460  	ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
   461  	defer cancel()
   462  
   463  	// Start a server and corresponding client.
   464  	s1, l1 := startServer("127.0.0.1:0")
   465  	client, err := DialContext(ctx, "ws://"+l1.Addr().String())
   466  	if err != nil {
   467  		t.Fatal("can't dial", err)
   468  	}
   469  
   470  	// Perform a call. This should work because the server is up.
   471  	var resp echoResult
   472  	if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil {
   473  		t.Fatal(err)
   474  	}
   475  
   476  	// Shut down the server and allow for some cool down time so we can listen on the same
   477  	// address again.
   478  	l1.Close()
   479  	s1.Stop()
   480  	time.Sleep(2 * time.Second)
   481  
   482  	// Try calling again. It shouldn't work.
   483  	if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil {
   484  		t.Error("successful call while the server is down")
   485  		t.Logf("resp: %#v", resp)
   486  	}
   487  
   488  	// Start it up again and call again. The connection should be reestablished.
   489  	// We spawn multiple calls here to check whether this hangs somehow.
   490  	s2, l2 := startServer(l1.Addr().String())
   491  	defer l2.Close()
   492  	defer s2.Stop()
   493  
   494  	start := make(chan struct{})
   495  	errors := make(chan error, 20)
   496  	for i := 0; i < cap(errors); i++ {
   497  		go func() {
   498  			<-start
   499  			var resp echoResult
   500  			errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil)
   501  		}()
   502  	}
   503  	close(start)
   504  	errcount := 0
   505  	for i := 0; i < cap(errors); i++ {
   506  		if err = <-errors; err != nil {
   507  			errcount++
   508  		}
   509  	}
   510  	t.Logf("%d errors, last error: %v", errcount, err)
   511  	if errcount > 1 {
   512  		t.Errorf("expected one error after disconnect, got %d", errcount)
   513  	}
   514  }
   515  
   516  func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) {
   517  	// Create the HTTP server.
   518  	var hs *httptest.Server
   519  	switch transport {
   520  	case "ws":
   521  		hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"}))
   522  	case "http":
   523  		hs = httptest.NewUnstartedServer(srv)
   524  	default:
   525  		panic("unknown HTTP transport: " + transport)
   526  	}
   527  	// Wrap the listener if required.
   528  	if fl != nil {
   529  		fl.Listener = hs.Listener
   530  		hs.Listener = fl
   531  	}
   532  	// Connect the client.
   533  	hs.Start()
   534  	client, err := Dial(transport + "://" + hs.Listener.Addr().String())
   535  	if err != nil {
   536  		panic(err)
   537  	}
   538  	return client, hs
   539  }
   540  
   541  func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) {
   542  	// Listen on a random endpoint.
   543  	endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63())
   544  	if runtime.GOOS == "windows" {
   545  		endpoint = `\\.\pipe\` + endpoint
   546  	} else {
   547  		endpoint = os.TempDir() + "/" + endpoint
   548  	}
   549  	l, err := ipcListen(endpoint)
   550  	if err != nil {
   551  		panic(err)
   552  	}
   553  	// Connect the listener to the server.
   554  	if fl != nil {
   555  		fl.Listener = l
   556  		l = fl
   557  	}
   558  	go srv.ServeListener(l)
   559  	// Connect the client.
   560  	client, err := Dial(endpoint)
   561  	if err != nil {
   562  		panic(err)
   563  	}
   564  	return client, l
   565  }
   566  
   567  // flakeyListener kills accepted connections after a random timeout.
   568  type flakeyListener struct {
   569  	net.Listener
   570  	maxKillTimeout time.Duration
   571  	maxAcceptDelay time.Duration
   572  }
   573  
   574  func (l *flakeyListener) Accept() (net.Conn, error) {
   575  	delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay)))
   576  	time.Sleep(delay)
   577  
   578  	c, err := l.Listener.Accept()
   579  	if err == nil {
   580  		timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout)))
   581  		time.AfterFunc(timeout, func() {
   582  			log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout))
   583  			c.Close()
   584  		})
   585  	}
   586  	return c, err
   587  }