github.com/noirx94/tendermintmp@v0.0.1/rpc/client/http/http.go (about) 1 package http 2 3 import ( 4 "context" 5 "errors" 6 "net/http" 7 "strings" 8 "time" 9 10 "github.com/tendermint/tendermint/libs/bytes" 11 tmjson "github.com/tendermint/tendermint/libs/json" 12 "github.com/tendermint/tendermint/libs/log" 13 tmpubsub "github.com/tendermint/tendermint/libs/pubsub" 14 "github.com/tendermint/tendermint/libs/service" 15 tmsync "github.com/tendermint/tendermint/libs/sync" 16 rpcclient "github.com/tendermint/tendermint/rpc/client" 17 ctypes "github.com/tendermint/tendermint/rpc/core/types" 18 jsonrpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client" 19 "github.com/tendermint/tendermint/types" 20 ) 21 22 /* 23 HTTP is a Client implementation that communicates with a Tendermint node over 24 JSON RPC and WebSockets. 25 26 This is the main implementation you probably want to use in production code. 27 There are other implementations when calling the Tendermint node in-process 28 (Local), or when you want to mock out the server for test code (mock). 29 30 You can subscribe for any event published by Tendermint using Subscribe method. 31 Note delivery is best-effort. If you don't read events fast enough or network is 32 slow, Tendermint might cancel the subscription. The client will attempt to 33 resubscribe (you don't need to do anything). It will keep trying every second 34 indefinitely until successful. 35 36 Request batching is available for JSON RPC requests over HTTP, which conforms to 37 the JSON RPC specification (https://www.jsonrpc.org/specification#batch). See 38 the example for more details. 39 40 Example: 41 42 c, err := New("http://192.168.1.10:26657", "/websocket") 43 if err != nil { 44 // handle error 45 } 46 47 // call Start/Stop if you're subscribing to events 48 err = c.Start() 49 if err != nil { 50 // handle error 51 } 52 defer c.Stop() 53 54 res, err := c.Status() 55 if err != nil { 56 // handle error 57 } 58 59 // handle result 60 */ 61 type HTTP struct { 62 remote string 63 rpc *jsonrpcclient.Client 64 65 *baseRPCClient 66 *WSEvents 67 } 68 69 // BatchHTTP provides the same interface as `HTTP`, but allows for batching of 70 // requests (as per https://www.jsonrpc.org/specification#batch). Do not 71 // instantiate directly - rather use the HTTP.NewBatch() method to create an 72 // instance of this struct. 73 // 74 // Batching of HTTP requests is thread-safe in the sense that multiple 75 // goroutines can each create their own batches and send them using the same 76 // HTTP client. Multiple goroutines could also enqueue transactions in a single 77 // batch, but ordering of transactions in the batch cannot be guaranteed in such 78 // an example. 79 type BatchHTTP struct { 80 rpcBatch *jsonrpcclient.RequestBatch 81 *baseRPCClient 82 } 83 84 // rpcClient is an internal interface to which our RPC clients (batch and 85 // non-batch) must conform. Acts as an additional code-level sanity check to 86 // make sure the implementations stay coherent. 87 type rpcClient interface { 88 rpcclient.ABCIClient 89 rpcclient.HistoryClient 90 rpcclient.NetworkClient 91 rpcclient.SignClient 92 rpcclient.StatusClient 93 } 94 95 // baseRPCClient implements the basic RPC method logic without the actual 96 // underlying RPC call functionality, which is provided by `caller`. 97 type baseRPCClient struct { 98 caller jsonrpcclient.Caller 99 } 100 101 var _ rpcClient = (*HTTP)(nil) 102 var _ rpcClient = (*BatchHTTP)(nil) 103 var _ rpcClient = (*baseRPCClient)(nil) 104 105 //----------------------------------------------------------------------------- 106 // HTTP 107 108 // New takes a remote endpoint in the form <protocol>://<host>:<port> and 109 // the websocket path (which always seems to be "/websocket") 110 // An error is returned on invalid remote. The function panics when remote is nil. 111 func New(remote, wsEndpoint string) (*HTTP, error) { 112 httpClient, err := jsonrpcclient.DefaultHTTPClient(remote) 113 if err != nil { 114 return nil, err 115 } 116 return NewWithClient(remote, wsEndpoint, httpClient) 117 } 118 119 // Create timeout enabled http client 120 func NewWithTimeout(remote, wsEndpoint string, timeout uint) (*HTTP, error) { 121 httpClient, err := jsonrpcclient.DefaultHTTPClient(remote) 122 if err != nil { 123 return nil, err 124 } 125 httpClient.Timeout = time.Duration(timeout) * time.Second 126 return NewWithClient(remote, wsEndpoint, httpClient) 127 } 128 129 // NewWithClient allows for setting a custom http client (See New). 130 // An error is returned on invalid remote. The function panics when remote is nil. 131 func NewWithClient(remote, wsEndpoint string, client *http.Client) (*HTTP, error) { 132 if client == nil { 133 panic("nil http.Client provided") 134 } 135 136 rc, err := jsonrpcclient.NewWithHTTPClient(remote, client) 137 if err != nil { 138 return nil, err 139 } 140 141 wsEvents, err := newWSEvents(remote, wsEndpoint) 142 if err != nil { 143 return nil, err 144 } 145 146 httpClient := &HTTP{ 147 rpc: rc, 148 remote: remote, 149 baseRPCClient: &baseRPCClient{caller: rc}, 150 WSEvents: wsEvents, 151 } 152 153 return httpClient, nil 154 } 155 156 var _ rpcclient.Client = (*HTTP)(nil) 157 158 // SetLogger sets a logger. 159 func (c *HTTP) SetLogger(l log.Logger) { 160 c.WSEvents.SetLogger(l) 161 } 162 163 // Remote returns the remote network address in a string form. 164 func (c *HTTP) Remote() string { 165 return c.remote 166 } 167 168 // NewBatch creates a new batch client for this HTTP client. 169 func (c *HTTP) NewBatch() *BatchHTTP { 170 rpcBatch := c.rpc.NewRequestBatch() 171 return &BatchHTTP{ 172 rpcBatch: rpcBatch, 173 baseRPCClient: &baseRPCClient{ 174 caller: rpcBatch, 175 }, 176 } 177 } 178 179 //----------------------------------------------------------------------------- 180 // BatchHTTP 181 182 // Send is a convenience function for an HTTP batch that will trigger the 183 // compilation of the batched requests and send them off using the client as a 184 // single request. On success, this returns a list of the deserialized results 185 // from each request in the sent batch. 186 func (b *BatchHTTP) Send(ctx context.Context) ([]interface{}, error) { 187 return b.rpcBatch.Send(ctx) 188 } 189 190 // Clear will empty out this batch of requests and return the number of requests 191 // that were cleared out. 192 func (b *BatchHTTP) Clear() int { 193 return b.rpcBatch.Clear() 194 } 195 196 // Count returns the number of enqueued requests waiting to be sent. 197 func (b *BatchHTTP) Count() int { 198 return b.rpcBatch.Count() 199 } 200 201 //----------------------------------------------------------------------------- 202 // baseRPCClient 203 204 func (c *baseRPCClient) Status(ctx context.Context) (*ctypes.ResultStatus, error) { 205 result := new(ctypes.ResultStatus) 206 _, err := c.caller.Call(ctx, "status", map[string]interface{}{}, result) 207 if err != nil { 208 return nil, err 209 } 210 211 return result, nil 212 } 213 214 func (c *baseRPCClient) ABCIInfo(ctx context.Context) (*ctypes.ResultABCIInfo, error) { 215 result := new(ctypes.ResultABCIInfo) 216 _, err := c.caller.Call(ctx, "abci_info", map[string]interface{}{}, result) 217 if err != nil { 218 return nil, err 219 } 220 221 return result, nil 222 } 223 224 func (c *baseRPCClient) ABCIQuery( 225 ctx context.Context, 226 path string, 227 data bytes.HexBytes, 228 ) (*ctypes.ResultABCIQuery, error) { 229 return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) 230 } 231 232 func (c *baseRPCClient) ABCIQueryWithOptions( 233 ctx context.Context, 234 path string, 235 data bytes.HexBytes, 236 opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { 237 result := new(ctypes.ResultABCIQuery) 238 _, err := c.caller.Call(ctx, "abci_query", 239 map[string]interface{}{"path": path, "data": data, "height": opts.Height, "prove": opts.Prove}, 240 result) 241 if err != nil { 242 return nil, err 243 } 244 245 return result, nil 246 } 247 248 func (c *baseRPCClient) BroadcastTxCommit( 249 ctx context.Context, 250 tx types.Tx, 251 ) (*ctypes.ResultBroadcastTxCommit, error) { 252 result := new(ctypes.ResultBroadcastTxCommit) 253 _, err := c.caller.Call(ctx, "broadcast_tx_commit", map[string]interface{}{"tx": tx}, result) 254 if err != nil { 255 return nil, err 256 } 257 return result, nil 258 } 259 260 func (c *baseRPCClient) BroadcastTxAsync( 261 ctx context.Context, 262 tx types.Tx, 263 ) (*ctypes.ResultBroadcastTx, error) { 264 return c.broadcastTX(ctx, "broadcast_tx_async", tx) 265 } 266 267 func (c *baseRPCClient) BroadcastTxSync( 268 ctx context.Context, 269 tx types.Tx, 270 ) (*ctypes.ResultBroadcastTx, error) { 271 return c.broadcastTX(ctx, "broadcast_tx_sync", tx) 272 } 273 274 func (c *baseRPCClient) broadcastTX( 275 ctx context.Context, 276 route string, 277 tx types.Tx, 278 ) (*ctypes.ResultBroadcastTx, error) { 279 result := new(ctypes.ResultBroadcastTx) 280 _, err := c.caller.Call(ctx, route, map[string]interface{}{"tx": tx}, result) 281 if err != nil { 282 return nil, err 283 } 284 return result, nil 285 } 286 287 func (c *baseRPCClient) UnconfirmedTxs( 288 ctx context.Context, 289 limit *int, 290 ) (*ctypes.ResultUnconfirmedTxs, error) { 291 result := new(ctypes.ResultUnconfirmedTxs) 292 params := make(map[string]interface{}) 293 if limit != nil { 294 params["limit"] = limit 295 } 296 _, err := c.caller.Call(ctx, "unconfirmed_txs", params, result) 297 if err != nil { 298 return nil, err 299 } 300 return result, nil 301 } 302 303 func (c *baseRPCClient) NumUnconfirmedTxs(ctx context.Context) (*ctypes.ResultUnconfirmedTxs, error) { 304 result := new(ctypes.ResultUnconfirmedTxs) 305 _, err := c.caller.Call(ctx, "num_unconfirmed_txs", map[string]interface{}{}, result) 306 if err != nil { 307 return nil, err 308 } 309 return result, nil 310 } 311 312 func (c *baseRPCClient) CheckTx(ctx context.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) { 313 result := new(ctypes.ResultCheckTx) 314 _, err := c.caller.Call(ctx, "check_tx", map[string]interface{}{"tx": tx}, result) 315 if err != nil { 316 return nil, err 317 } 318 return result, nil 319 } 320 321 func (c *baseRPCClient) NetInfo(ctx context.Context) (*ctypes.ResultNetInfo, error) { 322 result := new(ctypes.ResultNetInfo) 323 _, err := c.caller.Call(ctx, "net_info", map[string]interface{}{}, result) 324 if err != nil { 325 return nil, err 326 } 327 return result, nil 328 } 329 330 func (c *baseRPCClient) DumpConsensusState(ctx context.Context) (*ctypes.ResultDumpConsensusState, error) { 331 result := new(ctypes.ResultDumpConsensusState) 332 _, err := c.caller.Call(ctx, "dump_consensus_state", map[string]interface{}{}, result) 333 if err != nil { 334 return nil, err 335 } 336 return result, nil 337 } 338 339 func (c *baseRPCClient) ConsensusState(ctx context.Context) (*ctypes.ResultConsensusState, error) { 340 result := new(ctypes.ResultConsensusState) 341 _, err := c.caller.Call(ctx, "consensus_state", map[string]interface{}{}, result) 342 if err != nil { 343 return nil, err 344 } 345 return result, nil 346 } 347 348 func (c *baseRPCClient) ConsensusParams( 349 ctx context.Context, 350 height *int64, 351 ) (*ctypes.ResultConsensusParams, error) { 352 result := new(ctypes.ResultConsensusParams) 353 params := make(map[string]interface{}) 354 if height != nil { 355 params["height"] = height 356 } 357 _, err := c.caller.Call(ctx, "consensus_params", params, result) 358 if err != nil { 359 return nil, err 360 } 361 return result, nil 362 } 363 364 func (c *baseRPCClient) Health(ctx context.Context) (*ctypes.ResultHealth, error) { 365 result := new(ctypes.ResultHealth) 366 _, err := c.caller.Call(ctx, "health", map[string]interface{}{}, result) 367 if err != nil { 368 return nil, err 369 } 370 return result, nil 371 } 372 373 func (c *baseRPCClient) BlockchainInfo( 374 ctx context.Context, 375 minHeight, 376 maxHeight int64, 377 ) (*ctypes.ResultBlockchainInfo, error) { 378 result := new(ctypes.ResultBlockchainInfo) 379 _, err := c.caller.Call(ctx, "blockchain", 380 map[string]interface{}{"minHeight": minHeight, "maxHeight": maxHeight}, 381 result) 382 if err != nil { 383 return nil, err 384 } 385 return result, nil 386 } 387 388 func (c *baseRPCClient) Genesis(ctx context.Context) (*ctypes.ResultGenesis, error) { 389 result := new(ctypes.ResultGenesis) 390 _, err := c.caller.Call(ctx, "genesis", map[string]interface{}{}, result) 391 if err != nil { 392 return nil, err 393 } 394 return result, nil 395 } 396 397 func (c *baseRPCClient) GenesisChunked(ctx context.Context, id uint) (*ctypes.ResultGenesisChunk, error) { 398 result := new(ctypes.ResultGenesisChunk) 399 _, err := c.caller.Call(ctx, "genesis_chunked", map[string]interface{}{"chunk": id}, result) 400 if err != nil { 401 return nil, err 402 } 403 return result, nil 404 } 405 406 func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) { 407 result := new(ctypes.ResultBlock) 408 params := make(map[string]interface{}) 409 if height != nil { 410 params["height"] = height 411 } 412 _, err := c.caller.Call(ctx, "block", params, result) 413 if err != nil { 414 return nil, err 415 } 416 return result, nil 417 } 418 419 func (c *baseRPCClient) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) { 420 result := new(ctypes.ResultBlock) 421 params := map[string]interface{}{ 422 "hash": hash, 423 } 424 _, err := c.caller.Call(ctx, "block_by_hash", params, result) 425 if err != nil { 426 return nil, err 427 } 428 return result, nil 429 } 430 431 func (c *baseRPCClient) BlockResults( 432 ctx context.Context, 433 height *int64, 434 ) (*ctypes.ResultBlockResults, error) { 435 result := new(ctypes.ResultBlockResults) 436 params := make(map[string]interface{}) 437 if height != nil { 438 params["height"] = height 439 } 440 _, err := c.caller.Call(ctx, "block_results", params, result) 441 if err != nil { 442 return nil, err 443 } 444 return result, nil 445 } 446 447 func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) { 448 result := new(ctypes.ResultCommit) 449 params := make(map[string]interface{}) 450 if height != nil { 451 params["height"] = height 452 } 453 _, err := c.caller.Call(ctx, "commit", params, result) 454 if err != nil { 455 return nil, err 456 } 457 return result, nil 458 } 459 460 func (c *baseRPCClient) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) { 461 result := new(ctypes.ResultTx) 462 params := map[string]interface{}{ 463 "hash": hash, 464 "prove": prove, 465 } 466 _, err := c.caller.Call(ctx, "tx", params, result) 467 if err != nil { 468 return nil, err 469 } 470 return result, nil 471 } 472 473 func (c *baseRPCClient) TxSearch( 474 ctx context.Context, 475 query string, 476 prove bool, 477 page, 478 perPage *int, 479 orderBy string, 480 ) (*ctypes.ResultTxSearch, error) { 481 482 result := new(ctypes.ResultTxSearch) 483 params := map[string]interface{}{ 484 "query": query, 485 "prove": prove, 486 "order_by": orderBy, 487 } 488 489 if page != nil { 490 params["page"] = page 491 } 492 if perPage != nil { 493 params["per_page"] = perPage 494 } 495 496 _, err := c.caller.Call(ctx, "tx_search", params, result) 497 if err != nil { 498 return nil, err 499 } 500 501 return result, nil 502 } 503 504 func (c *baseRPCClient) BlockSearch( 505 ctx context.Context, 506 query string, 507 page, perPage *int, 508 orderBy string, 509 ) (*ctypes.ResultBlockSearch, error) { 510 511 result := new(ctypes.ResultBlockSearch) 512 params := map[string]interface{}{ 513 "query": query, 514 "order_by": orderBy, 515 } 516 517 if page != nil { 518 params["page"] = page 519 } 520 if perPage != nil { 521 params["per_page"] = perPage 522 } 523 524 _, err := c.caller.Call(ctx, "block_search", params, result) 525 if err != nil { 526 return nil, err 527 } 528 529 return result, nil 530 } 531 532 func (c *baseRPCClient) Validators( 533 ctx context.Context, 534 height *int64, 535 page, 536 perPage *int, 537 ) (*ctypes.ResultValidators, error) { 538 result := new(ctypes.ResultValidators) 539 params := make(map[string]interface{}) 540 if page != nil { 541 params["page"] = page 542 } 543 if perPage != nil { 544 params["per_page"] = perPage 545 } 546 if height != nil { 547 params["height"] = height 548 } 549 _, err := c.caller.Call(ctx, "validators", params, result) 550 if err != nil { 551 return nil, err 552 } 553 return result, nil 554 } 555 556 func (c *baseRPCClient) BroadcastEvidence( 557 ctx context.Context, 558 ev types.Evidence, 559 ) (*ctypes.ResultBroadcastEvidence, error) { 560 result := new(ctypes.ResultBroadcastEvidence) 561 _, err := c.caller.Call(ctx, "broadcast_evidence", map[string]interface{}{"evidence": ev}, result) 562 if err != nil { 563 return nil, err 564 } 565 return result, nil 566 } 567 568 //----------------------------------------------------------------------------- 569 // WSEvents 570 571 var errNotRunning = errors.New("client is not running. Use .Start() method to start") 572 573 // WSEvents is a wrapper around WSClient, which implements EventsClient. 574 type WSEvents struct { 575 service.BaseService 576 remote string 577 endpoint string 578 ws *jsonrpcclient.WSClient 579 580 mtx tmsync.RWMutex 581 subscriptions map[string]chan ctypes.ResultEvent // query -> chan 582 } 583 584 func newWSEvents(remote, endpoint string) (*WSEvents, error) { 585 w := &WSEvents{ 586 endpoint: endpoint, 587 remote: remote, 588 subscriptions: make(map[string]chan ctypes.ResultEvent), 589 } 590 w.BaseService = *service.NewBaseService(nil, "WSEvents", w) 591 592 var err error 593 w.ws, err = jsonrpcclient.NewWS(w.remote, w.endpoint, jsonrpcclient.OnReconnect(func() { 594 // resubscribe immediately 595 w.redoSubscriptionsAfter(0 * time.Second) 596 })) 597 if err != nil { 598 return nil, err 599 } 600 w.ws.SetLogger(w.Logger) 601 602 return w, nil 603 } 604 605 // OnStart implements service.Service by starting WSClient and event loop. 606 func (w *WSEvents) OnStart() error { 607 if err := w.ws.Start(); err != nil { 608 return err 609 } 610 611 go w.eventListener() 612 613 return nil 614 } 615 616 // OnStop implements service.Service by stopping WSClient. 617 func (w *WSEvents) OnStop() { 618 if err := w.ws.Stop(); err != nil { 619 w.Logger.Error("Can't stop ws client", "err", err) 620 } 621 } 622 623 // Subscribe implements EventsClient by using WSClient to subscribe given 624 // subscriber to query. By default, returns a channel with cap=1. Error is 625 // returned if it fails to subscribe. 626 // 627 // Channel is never closed to prevent clients from seeing an erroneous event. 628 // 629 // It returns an error if WSEvents is not running. 630 func (w *WSEvents) Subscribe(ctx context.Context, subscriber, query string, 631 outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { 632 633 if !w.IsRunning() { 634 return nil, errNotRunning 635 } 636 637 if err := w.ws.Subscribe(ctx, query); err != nil { 638 return nil, err 639 } 640 641 outCap := 1 642 if len(outCapacity) > 0 { 643 outCap = outCapacity[0] 644 } 645 646 outc := make(chan ctypes.ResultEvent, outCap) 647 w.mtx.Lock() 648 // subscriber param is ignored because Tendermint will override it with 649 // remote IP anyway. 650 w.subscriptions[query] = outc 651 w.mtx.Unlock() 652 653 return outc, nil 654 } 655 656 // Unsubscribe implements EventsClient by using WSClient to unsubscribe given 657 // subscriber from query. 658 // 659 // It returns an error if WSEvents is not running. 660 func (w *WSEvents) Unsubscribe(ctx context.Context, subscriber, query string) error { 661 if !w.IsRunning() { 662 return errNotRunning 663 } 664 665 if err := w.ws.Unsubscribe(ctx, query); err != nil { 666 return err 667 } 668 669 w.mtx.Lock() 670 _, ok := w.subscriptions[query] 671 if ok { 672 delete(w.subscriptions, query) 673 } 674 w.mtx.Unlock() 675 676 return nil 677 } 678 679 // UnsubscribeAll implements EventsClient by using WSClient to unsubscribe 680 // given subscriber from all the queries. 681 // 682 // It returns an error if WSEvents is not running. 683 func (w *WSEvents) UnsubscribeAll(ctx context.Context, subscriber string) error { 684 if !w.IsRunning() { 685 return errNotRunning 686 } 687 688 if err := w.ws.UnsubscribeAll(ctx); err != nil { 689 return err 690 } 691 692 w.mtx.Lock() 693 w.subscriptions = make(map[string]chan ctypes.ResultEvent) 694 w.mtx.Unlock() 695 696 return nil 697 } 698 699 // After being reconnected, it is necessary to redo subscription to server 700 // otherwise no data will be automatically received. 701 func (w *WSEvents) redoSubscriptionsAfter(d time.Duration) { 702 time.Sleep(d) 703 704 w.mtx.RLock() 705 defer w.mtx.RUnlock() 706 for q := range w.subscriptions { 707 err := w.ws.Subscribe(context.Background(), q) 708 if err != nil { 709 w.Logger.Error("Failed to resubscribe", "err", err) 710 } 711 } 712 } 713 714 func isErrAlreadySubscribed(err error) bool { 715 return strings.Contains(err.Error(), tmpubsub.ErrAlreadySubscribed.Error()) 716 } 717 718 func (w *WSEvents) eventListener() { 719 for { 720 select { 721 case resp, ok := <-w.ws.ResponsesCh: 722 if !ok { 723 return 724 } 725 726 if resp.Error != nil { 727 w.Logger.Error("WS error", "err", resp.Error.Error()) 728 // Error can be ErrAlreadySubscribed or max client (subscriptions per 729 // client) reached or Tendermint exited. 730 // We can ignore ErrAlreadySubscribed, but need to retry in other 731 // cases. 732 if !isErrAlreadySubscribed(resp.Error) { 733 // Resubscribe after 1 second to give Tendermint time to restart (if 734 // crashed). 735 w.redoSubscriptionsAfter(1 * time.Second) 736 } 737 continue 738 } 739 740 result := new(ctypes.ResultEvent) 741 err := tmjson.Unmarshal(resp.Result, result) 742 if err != nil { 743 w.Logger.Error("failed to unmarshal response", "err", err) 744 continue 745 } 746 747 w.mtx.RLock() 748 if out, ok := w.subscriptions[result.Query]; ok { 749 if cap(out) == 0 { 750 out <- *result 751 } else { 752 select { 753 case out <- *result: 754 default: 755 w.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query) 756 } 757 } 758 } 759 w.mtx.RUnlock() 760 case <-w.Quit(): 761 return 762 } 763 } 764 }