github.com/codingfuture/orig-energi3@v0.8.4/rpc/client_test.go (about)

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