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