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 := ¬ificationTestService{ 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 }