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 }