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