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