github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/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 "encoding/json" 22 "fmt" 23 "math/rand" 24 "net" 25 "net/http" 26 "net/http/httptest" 27 "os" 28 "reflect" 29 "runtime" 30 "strings" 31 "sync" 32 "testing" 33 "time" 34 35 "github.com/davecgh/go-spew/spew" 36 37 "github.com/ethereum/go-ethereum/log" 38 ) 39 40 func TestClientRequest(t *testing.T) { 41 server := newTestServer() 42 defer server.Stop() 43 44 client := DialInProc(server) 45 defer client.Close() 46 47 var resp echoResult 48 if err := client.Call(&resp, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil { 49 t.Fatal(err) 50 } 51 52 if !reflect.DeepEqual(resp, echoResult{"hello", 10, &echoArgs{"world"}}) { 53 t.Errorf("incorrect result %#v", resp) 54 } 55 } 56 57 func TestClientResponseType(t *testing.T) { 58 server := newTestServer() 59 defer server.Stop() 60 client := DialInProc(server) 61 defer client.Close() 62 63 if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil { 64 t.Errorf("Passing nil as result should be fine, but got an error: %v", err) 65 } 66 var resultVar echoResult 67 // Note: passing the var, not a ref 68 err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"}) 69 if err == nil { 70 t.Error("Passing a var as result should be an error") 71 } 72 } 73 74 // This test checks that server-returned errors with code and data come out of Client.Call. 75 func TestClientErrorData(t *testing.T) { 76 server := newTestServer() 77 defer server.Stop() 78 client := DialInProc(server) 79 defer client.Close() 80 81 var resp interface{} 82 err := client.Call(&resp, "test_returnError") 83 if err == nil { 84 t.Fatal("expected error") 85 } 86 87 // Check code. 88 if e, ok := err.(Error); !ok { 89 t.Fatalf("client did not return rpc.Error, got %#v", e) 90 } else if e.ErrorCode() != (testError{}.ErrorCode()) { 91 t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), testError{}.ErrorCode()) 92 } 93 // Check data. 94 if e, ok := err.(DataError); !ok { 95 t.Fatalf("client did not return rpc.DataError, got %#v", e) 96 } else if e.ErrorData() != (testError{}.ErrorData()) { 97 t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData()) 98 } 99 } 100 101 func TestClientBatchRequest(t *testing.T) { 102 server := newTestServer() 103 defer server.Stop() 104 client := DialInProc(server) 105 defer client.Close() 106 107 batch := []BatchElem{ 108 { 109 Method: "test_echo", 110 Args: []interface{}{"hello", 10, &echoArgs{"world"}}, 111 Result: new(echoResult), 112 }, 113 { 114 Method: "test_echo", 115 Args: []interface{}{"hello2", 11, &echoArgs{"world"}}, 116 Result: new(echoResult), 117 }, 118 { 119 Method: "no_such_method", 120 Args: []interface{}{1, 2, 3}, 121 Result: new(int), 122 }, 123 } 124 if err := client.BatchCall(batch); err != nil { 125 t.Fatal(err) 126 } 127 wantResult := []BatchElem{ 128 { 129 Method: "test_echo", 130 Args: []interface{}{"hello", 10, &echoArgs{"world"}}, 131 Result: &echoResult{"hello", 10, &echoArgs{"world"}}, 132 }, 133 { 134 Method: "test_echo", 135 Args: []interface{}{"hello2", 11, &echoArgs{"world"}}, 136 Result: &echoResult{"hello2", 11, &echoArgs{"world"}}, 137 }, 138 { 139 Method: "no_such_method", 140 Args: []interface{}{1, 2, 3}, 141 Result: new(int), 142 Error: &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"}, 143 }, 144 } 145 if !reflect.DeepEqual(batch, wantResult) { 146 t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult)) 147 } 148 } 149 150 func TestClientNotify(t *testing.T) { 151 server := newTestServer() 152 defer server.Stop() 153 client := DialInProc(server) 154 defer client.Close() 155 156 if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil { 157 t.Fatal(err) 158 } 159 } 160 161 // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) } 162 func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) } 163 func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) } 164 func TestClientCancelIPC(t *testing.T) { testClientCancel("ipc", t) } 165 166 // This test checks that requests made through CallContext can be canceled by canceling 167 // the context. 168 func testClientCancel(transport string, t *testing.T) { 169 // These tests take a lot of time, run them all at once. 170 // You probably want to run with -parallel 1 or comment out 171 // the call to t.Parallel if you enable the logging. 172 t.Parallel() 173 174 server := newTestServer() 175 defer server.Stop() 176 177 // What we want to achieve is that the context gets canceled 178 // at various stages of request processing. The interesting cases 179 // are: 180 // - cancel during dial 181 // - cancel while performing a HTTP request 182 // - cancel while waiting for a response 183 // 184 // To trigger those, the times are chosen such that connections 185 // are killed within the deadline for every other call (maxKillTimeout 186 // is 2x maxCancelTimeout). 187 // 188 // Once a connection is dead, there is a fair chance it won't connect 189 // successfully because the accept is delayed by 1s. 190 maxContextCancelTimeout := 300 * time.Millisecond 191 fl := &flakeyListener{ 192 maxAcceptDelay: 1 * time.Second, 193 maxKillTimeout: 600 * time.Millisecond, 194 } 195 196 var client *Client 197 switch transport { 198 case "ws", "http": 199 c, hs := httpTestClient(server, transport, fl) 200 defer hs.Close() 201 client = c 202 case "ipc": 203 c, l := ipcTestClient(server, fl) 204 defer l.Close() 205 client = c 206 default: 207 panic("unknown transport: " + transport) 208 } 209 210 // The actual test starts here. 211 var ( 212 wg sync.WaitGroup 213 nreqs = 10 214 ncallers = 10 215 ) 216 caller := func(index int) { 217 defer wg.Done() 218 for i := 0; i < nreqs; i++ { 219 var ( 220 ctx context.Context 221 cancel func() 222 timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout))) 223 ) 224 if index < ncallers/2 { 225 // For half of the callers, create a context without deadline 226 // and cancel it later. 227 ctx, cancel = context.WithCancel(context.Background()) 228 time.AfterFunc(timeout, cancel) 229 } else { 230 // For the other half, create a context with a deadline instead. This is 231 // different because the context deadline is used to set the socket write 232 // deadline. 233 ctx, cancel = context.WithTimeout(context.Background(), timeout) 234 } 235 236 // Now perform a call with the context. 237 // The key thing here is that no call will ever complete successfully. 238 err := client.CallContext(ctx, nil, "test_block") 239 switch { 240 case err == nil: 241 _, hasDeadline := ctx.Deadline() 242 t.Errorf("no error for call with %v wait time (deadline: %v)", timeout, hasDeadline) 243 // default: 244 // t.Logf("got expected error with %v wait time: %v", timeout, err) 245 } 246 cancel() 247 } 248 } 249 wg.Add(ncallers) 250 for i := 0; i < ncallers; i++ { 251 go caller(i) 252 } 253 wg.Wait() 254 } 255 256 func TestClientSubscribeInvalidArg(t *testing.T) { 257 server := newTestServer() 258 defer server.Stop() 259 client := DialInProc(server) 260 defer client.Close() 261 262 check := func(shouldPanic bool, arg interface{}) { 263 defer func() { 264 err := recover() 265 if shouldPanic && err == nil { 266 t.Errorf("EthSubscribe should've panicked for %#v", arg) 267 } 268 if !shouldPanic && err != nil { 269 t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg) 270 buf := make([]byte, 1024*1024) 271 buf = buf[:runtime.Stack(buf, false)] 272 t.Error(err) 273 t.Error(string(buf)) 274 } 275 }() 276 client.EthSubscribe(context.Background(), arg, "foo_bar") 277 } 278 check(true, nil) 279 check(true, 1) 280 check(true, (chan int)(nil)) 281 check(true, make(<-chan int)) 282 check(false, make(chan int)) 283 check(false, make(chan<- int)) 284 } 285 286 func TestClientSubscribe(t *testing.T) { 287 server := newTestServer() 288 defer server.Stop() 289 client := DialInProc(server) 290 defer client.Close() 291 292 nc := make(chan int) 293 count := 10 294 sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0) 295 if err != nil { 296 t.Fatal("can't subscribe:", err) 297 } 298 for i := 0; i < count; i++ { 299 if val := <-nc; val != i { 300 t.Fatalf("value mismatch: got %d, want %d", val, i) 301 } 302 } 303 304 sub.Unsubscribe() 305 select { 306 case v := <-nc: 307 t.Fatal("received value after unsubscribe:", v) 308 case err := <-sub.Err(): 309 if err != nil { 310 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 311 } 312 case <-time.After(1 * time.Second): 313 t.Fatalf("subscription not closed within 1s after unsubscribe") 314 } 315 } 316 317 // In this test, the connection drops while Subscribe is waiting for a response. 318 func TestClientSubscribeClose(t *testing.T) { 319 server := newTestServer() 320 service := ¬ificationTestService{ 321 gotHangSubscriptionReq: make(chan struct{}), 322 unblockHangSubscription: make(chan struct{}), 323 } 324 if err := server.RegisterName("nftest2", service); err != nil { 325 t.Fatal(err) 326 } 327 328 defer server.Stop() 329 client := DialInProc(server) 330 defer client.Close() 331 332 var ( 333 nc = make(chan int) 334 errc = make(chan error, 1) 335 sub *ClientSubscription 336 err error 337 ) 338 go func() { 339 sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999) 340 errc <- err 341 }() 342 343 <-service.gotHangSubscriptionReq 344 client.Close() 345 service.unblockHangSubscription <- struct{}{} 346 347 select { 348 case err := <-errc: 349 if err == nil { 350 t.Errorf("Subscribe returned nil error after Close") 351 } 352 if sub != nil { 353 t.Error("Subscribe returned non-nil subscription after Close") 354 } 355 case <-time.After(1 * time.Second): 356 t.Fatalf("Subscribe did not return within 1s after Close") 357 } 358 } 359 360 // This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the 361 // client hangs during shutdown when Unsubscribe races with Client.Close. 362 func TestClientCloseUnsubscribeRace(t *testing.T) { 363 server := newTestServer() 364 defer server.Stop() 365 366 for i := 0; i < 20; i++ { 367 client := DialInProc(server) 368 nc := make(chan int) 369 sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1) 370 if err != nil { 371 t.Fatal(err) 372 } 373 go client.Close() 374 go sub.Unsubscribe() 375 select { 376 case <-sub.Err(): 377 case <-time.After(5 * time.Second): 378 t.Fatal("subscription not closed within timeout") 379 } 380 } 381 } 382 383 // unsubscribeRecorder collects the subscription IDs of *_unsubscribe calls. 384 type unsubscribeRecorder struct { 385 ServerCodec 386 unsubscribes map[string]bool 387 } 388 389 func (r *unsubscribeRecorder) readBatch() ([]*jsonrpcMessage, bool, error) { 390 if r.unsubscribes == nil { 391 r.unsubscribes = make(map[string]bool) 392 } 393 394 msgs, batch, err := r.ServerCodec.readBatch() 395 for _, msg := range msgs { 396 if msg.isUnsubscribe() { 397 var params []string 398 if err := json.Unmarshal(msg.Params, ¶ms); err != nil { 399 panic("unsubscribe decode error: " + err.Error()) 400 } 401 r.unsubscribes[params[0]] = true 402 } 403 } 404 return msgs, batch, err 405 } 406 407 // This checks that Client calls the _unsubscribe method on the server when Unsubscribe is 408 // called on a subscription. 409 func TestClientSubscriptionUnsubscribeServer(t *testing.T) { 410 t.Parallel() 411 412 // Create the server. 413 srv := NewServer(0, 0) 414 srv.RegisterName("nftest", new(notificationTestService)) 415 p1, p2 := net.Pipe() 416 recorder := &unsubscribeRecorder{ServerCodec: NewCodec(p1)} 417 go srv.ServeCodec(recorder, OptionMethodInvocation|OptionSubscriptions) 418 defer srv.Stop() 419 420 // Create the client on the other end of the pipe. 421 client, _ := newClient(context.Background(), func(context.Context) (ServerCodec, error) { 422 return NewCodec(p2), nil 423 }) 424 defer client.Close() 425 426 // Create the subscription. 427 ch := make(chan int) 428 sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 1, 1) 429 if err != nil { 430 t.Fatal(err) 431 } 432 433 // Unsubscribe and check that unsubscribe was called. 434 sub.Unsubscribe() 435 if !recorder.unsubscribes[sub.subid] { 436 t.Fatal("client did not call unsubscribe method") 437 } 438 if _, open := <-sub.Err(); open { 439 t.Fatal("subscription error channel not closed after unsubscribe") 440 } 441 } 442 443 // This checks that the subscribed channel can be closed after Unsubscribe. 444 // It is the reproducer for https://github.com/ethereum/go-ethereum/issues/22322 445 func TestClientSubscriptionChannelClose(t *testing.T) { 446 t.Parallel() 447 448 var ( 449 srv = NewServer(0, 0) 450 httpsrv = httptest.NewServer(srv.WebsocketHandler(nil)) 451 wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") 452 ) 453 defer srv.Stop() 454 defer httpsrv.Close() 455 456 srv.RegisterName("nftest", new(notificationTestService)) 457 client, _ := Dial(wsURL) 458 459 for i := 0; i < 100; i++ { 460 ch := make(chan int, 100) 461 sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", maxClientSubscriptionBuffer-1, 1) 462 if err != nil { 463 t.Fatal(err) 464 } 465 sub.Unsubscribe() 466 close(ch) 467 } 468 } 469 470 // This test checks that Client doesn't lock up when a single subscriber 471 // doesn't read subscription events. 472 func TestClientNotificationStorm(t *testing.T) { 473 server := newTestServer() 474 defer server.Stop() 475 476 doTest := func(count int, wantError bool) { 477 client := DialInProc(server) 478 defer client.Close() 479 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 480 defer cancel() 481 482 // Subscribe on the server. It will start sending many notifications 483 // very quickly. 484 nc := make(chan int) 485 sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0) 486 if err != nil { 487 t.Fatal("can't subscribe:", err) 488 } 489 defer sub.Unsubscribe() 490 491 // Process each notification, try to run a call in between each of them. 492 for i := 0; i < count; i++ { 493 select { 494 case val := <-nc: 495 if val != i { 496 t.Fatalf("(%d/%d) unexpected value %d", i, count, val) 497 } 498 case err := <-sub.Err(): 499 if wantError && err != ErrSubscriptionQueueOverflow { 500 t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow) 501 } else if !wantError { 502 t.Fatalf("(%d/%d) got unexpected error %q", i, count, err) 503 } 504 return 505 } 506 var r int 507 err := client.CallContext(ctx, &r, "nftest_echo", i) 508 if err != nil { 509 if !wantError { 510 t.Fatalf("(%d/%d) call error: %v", i, count, err) 511 } 512 return 513 } 514 } 515 if wantError { 516 t.Fatalf("didn't get expected error") 517 } 518 } 519 520 doTest(8000, false) 521 doTest(24000, true) 522 } 523 524 func TestClientSetHeader(t *testing.T) { 525 var gotHeader bool 526 srv := newTestServer() 527 httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 528 if r.Header.Get("test") == "ok" { 529 gotHeader = true 530 } 531 srv.ServeHTTP(w, r) 532 })) 533 defer httpsrv.Close() 534 defer srv.Stop() 535 536 client, err := Dial(httpsrv.URL) 537 if err != nil { 538 t.Fatal(err) 539 } 540 defer client.Close() 541 542 client.SetHeader("test", "ok") 543 if _, err := client.SupportedModules(); err != nil { 544 t.Fatal(err) 545 } 546 if !gotHeader { 547 t.Fatal("client did not set custom header") 548 } 549 550 // Check that Content-Type can be replaced. 551 client.SetHeader("content-type", "application/x-garbage") 552 _, err = client.SupportedModules() 553 if err == nil { 554 t.Fatal("no error for invalid content-type header") 555 } else if !strings.Contains(err.Error(), "Unsupported Media Type") { 556 t.Fatalf("error is not related to content-type: %q", err) 557 } 558 } 559 560 func TestClientHTTP(t *testing.T) { 561 server := newTestServer() 562 defer server.Stop() 563 564 client, hs := httpTestClient(server, "http", nil) 565 defer hs.Close() 566 defer client.Close() 567 568 // Launch concurrent requests. 569 var ( 570 results = make([]echoResult, 100) 571 errc = make(chan error, len(results)) 572 wantResult = echoResult{"a", 1, new(echoArgs)} 573 ) 574 defer client.Close() 575 for i := range results { 576 i := i 577 go func() { 578 errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args) 579 }() 580 } 581 582 // Wait for all of them to complete. 583 timeout := time.NewTimer(5 * time.Second) 584 defer timeout.Stop() 585 for i := range results { 586 select { 587 case err := <-errc: 588 if err != nil { 589 t.Fatal(err) 590 } 591 case <-timeout.C: 592 t.Fatalf("timeout (got %d/%d) results)", i+1, len(results)) 593 } 594 } 595 596 // Check results. 597 for i := range results { 598 if !reflect.DeepEqual(results[i], wantResult) { 599 t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult) 600 } 601 } 602 } 603 604 func TestClientReconnect(t *testing.T) { 605 startServer := func(addr string) (*Server, net.Listener) { 606 srv := newTestServer() 607 l, err := net.Listen("tcp", addr) 608 if err != nil { 609 t.Fatal("can't listen:", err) 610 } 611 go http.Serve(l, srv.WebsocketHandler([]string{"*"})) 612 return srv, l 613 } 614 615 ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) 616 defer cancel() 617 618 // Start a server and corresponding client. 619 s1, l1 := startServer("127.0.0.1:0") 620 client, err := DialContext(ctx, "ws://"+l1.Addr().String()) 621 defer client.Close() 622 if err != nil { 623 t.Fatal("can't dial", err) 624 } 625 626 // Perform a call. This should work because the server is up. 627 var resp echoResult 628 if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil { 629 t.Fatal(err) 630 } 631 632 // Shut down the server and allow for some cool down time so we can listen on the same 633 // address again. 634 l1.Close() 635 s1.Stop() 636 time.Sleep(2 * time.Second) 637 638 // Try calling again. It shouldn't work. 639 if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil { 640 t.Error("successful call while the server is down") 641 t.Logf("resp: %#v", resp) 642 } 643 644 // Start it up again and call again. The connection should be reestablished. 645 // We spawn multiple calls here to check whether this hangs somehow. 646 s2, l2 := startServer(l1.Addr().String()) 647 defer l2.Close() 648 defer s2.Stop() 649 650 start := make(chan struct{}) 651 errors := make(chan error, 20) 652 for i := 0; i < cap(errors); i++ { 653 go func() { 654 <-start 655 var resp echoResult 656 errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil) 657 }() 658 } 659 close(start) 660 errcount := 0 661 for i := 0; i < cap(errors); i++ { 662 if err = <-errors; err != nil { 663 errcount++ 664 } 665 } 666 t.Logf("%d errors, last error: %v", errcount, err) 667 if errcount > 1 { 668 t.Errorf("expected one error after disconnect, got %d", errcount) 669 } 670 } 671 672 func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { 673 // Create the HTTP server. 674 var hs *httptest.Server 675 switch transport { 676 case "ws": 677 hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"})) 678 case "http": 679 hs = httptest.NewUnstartedServer(srv) 680 default: 681 panic("unknown HTTP transport: " + transport) 682 } 683 // Wrap the listener if required. 684 if fl != nil { 685 fl.Listener = hs.Listener 686 hs.Listener = fl 687 } 688 // Connect the client. 689 hs.Start() 690 client, err := Dial(transport + "://" + hs.Listener.Addr().String()) 691 if err != nil { 692 panic(err) 693 } 694 return client, hs 695 } 696 697 func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { 698 // Listen on a random endpoint. 699 endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63()) 700 if runtime.GOOS == "windows" { 701 endpoint = `\\.\pipe\` + endpoint 702 } else { 703 endpoint = os.TempDir() + "/" + endpoint 704 } 705 l, err := ipcListen(endpoint) 706 if err != nil { 707 panic(err) 708 } 709 // Connect the listener to the server. 710 if fl != nil { 711 fl.Listener = l 712 l = fl 713 } 714 go srv.ServeListener(l) 715 // Connect the client. 716 client, err := Dial(endpoint) 717 if err != nil { 718 panic(err) 719 } 720 return client, l 721 } 722 723 // flakeyListener kills accepted connections after a random timeout. 724 type flakeyListener struct { 725 net.Listener 726 maxKillTimeout time.Duration 727 maxAcceptDelay time.Duration 728 } 729 730 func (l *flakeyListener) Accept() (net.Conn, error) { 731 delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay))) 732 time.Sleep(delay) 733 734 c, err := l.Listener.Accept() 735 if err == nil { 736 timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout))) 737 time.AfterFunc(timeout, func() { 738 log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout)) 739 c.Close() 740 }) 741 } 742 return c, err 743 }