[
  {
    "path": ".github/workflows/go.yml",
    "content": "name: Go\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v2\n      with:\n        go-version: ^1.13\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v2\n\n    - name: Get dependencies\n      run: |\n        go get -v -t -d ./...\n\n    - name: Build\n      run: go build -v ./...\n\n    - name: Test\n      run: go test -v ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "# \nvendor/\n\n# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736\n.glide/\n.idea\n\ncover.html\n\n.history"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\n\ngo:\n  - 1.17.x\n\nenv:\n  - GO111MODULE=on\n\nbefore_script:\n  - rm -f go.sum\n  - go get -tags \"quic kcp\" -v github.com/smallnest/rpcx@HEAD\n  - go get github.com/mattn/goveralls\n\nscript:\n  - go test -v ./...\n  - goveralls -service=travis-ci\n\nnotifications:\n  email:\n    recipients: smallnest@gmail.com\n    on_success: change\n    on_failure: always\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# [rpcx](http://rpcx.io)\n\n## 1.9.0\n- unregister all services on close automatically\n- add PostHTTPRequestPlugin \n- support io_uring\n- add CacheDiscovery\n- add Oneshot method for XClient\n- support RDMA\n\n\n## 1.8.0\n- supports distributed rate limiter based on go-redis/redis-rate\n- move zookeeper plugin to https://github.com/smallnest/rpcx-zookeepr\n- move consul plugin to https://github.com/smallnest/rpcx-consul\n- move redis plugin to https://github.com/smallnest/rpcx-redis\n- move influxd/opentelemetry plugin to https://github.com/smallnest/rpcx-plugins\n- you can write customized error, for example `{\"code\": 500, err: \"internal error\"}`\n- server support the work pool by `WithPool`\n- support to write services like `go std http router` style without reflect\n- simplify async write for service\n- improve performance\n\n\n## 1.7.0\n- move etcd support to github.com/rpcxio/rpcx-etcd\n- Broken API: NewXXXDiscovery returns error instead of panic\n- support AdvertiseAddr in FileTransfer\n- support Auth for OneClientPool\n- support Auth for XClientPool\n- Broken API: add meta parameter for SendFile/DownloadFile \n- support streaming between server side and client side\n- support DNS as service discovery\n- support rpcx flow tracing\n- support websocket as the transport like tcp,kcp and quic\n- add CMuxPlugin to allow developing customzied services by using the same single port\n- re-tag rpcx to make sure the version is less than 2 (for go module)\n- support visit grpc services by rpcx clients: https://github.com/rpcxio/rpcxplus/tree/master/grpcx\n- support configing grpc servicves in rpcx server side\n- improve rpcx performance\n- add Inform method in XClient\n- add memory connection for unit tests\n- supports opentelemetry\n\n## 1.6.0 \n\n- support reflection\n- add kubernetes config example\n- improve nacos support\n- improve message.Encode performance\n- re-register services in etcd v3\n- avoid duplicated client creation\n- add SelectNodePlugin that can interrupt the Select method\n- support TcpCopy by TeePlugin\n- support reuseport for http invoke\n- return reply even in case of server errors\n- Change two methods' name of client plugin!\n- Broken API: add error parameter in `PreWriteResponse`(#486)\n- Broken API: change ReadTimeout/WriteTimeout to IdleTimeout\n- Support passing Deadline of client contexts to server side\n- remove InprocessClient plugin\n- use heartbeat/tcp_keepalive to avoid client hanging\n\n\n## 1.5.0 \n\n- support jsonrpc 2.0\n- support CORS for jsonrpc 2.0\n- support opentracing and opencensus\n- upload/download files by streaming\n- add Pool for XClient and OneClient\n- remove rudp support\n- add ConnCreated plugin. Yu can use it to set KCP UDPSession\n- update client plugins. All plugin returns error instead of bool\n- support ETCD 3.0 API\n- support redis as registry\n- support redis DB selection\n- fix RegisterFunction issues\n- add Filter for clients\n- remove most of build tags such as etcd, zookeeper,consul,reuseport\n- add Nacos as registry http://nacos.io\n- support blacklist and whitlist\n\n## 1.4.0\n\n- Support utp and rudp\n- Add OneClient to support invoke multile servicesby one client\n- Finish compress feature\n- Add more plugins for monitoring connection\n- Support dynamic port allocation\n- Use go module to manage dependencies\n- Support shutdown graceful\n- Add [rpcx-java](https://github.com/smallnest/rpcx-java) to support develop raw java services and clients\n- Support thrift codec \n- Setup rpcx offcial site: http://rpcx.io\n- Add Chinese document: http://cn.doc.rpcx.io or https://smallnest.gitbooks.io/go-rpc-programming-guide\n\n## 1.3.1\n\n- Add http gateway: https://github.com/rpcxio/rpcx-gateway\n- Add direct http invoke\n- Add bidirectional communication \n- Add xgen tool to generate codes for services automatically\n\n\nfix bugs.\n\n## 1.3.0\n\n- Rewrite rpcx. It implements its protocol and won't implemented based on wrapper of go standard rpc lib\n- Add go tags for pluggable plugins\n- Add English document: https://github.com/smallnest/rpcx-programming\n- Add rpcx 3.0 examples: https://github.com/rpcxio/rpcx-examples\n\nrpcx 3.0 is not compatible with rpcx 2.0 and below"
  },
  {
    "path": "LICENSE",
    "content": "Copyright  2017  Chao yuepan\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License."
  },
  {
    "path": "Makefile",
    "content": "WORKDIR=`pwd`\n\ndefault: build\n\nvet:\n\tgo vet ./...\n\ntools:\n\tgo get github.com/golangci/golangci-lint/cmd/golangci-lint\n\tgo get github.com/golang/lint/golint\n\tgo get github.com/axw/gocov/gocov\n\tgo get github.com/matm/gocov-html\n\ngolangci-lint:\n\tgolangci-lint run -D errcheck --build-tags 'quic kcp'\n\nlint:\n\tgolint ./...\n\ndoc:\n\tgodoc -http=:6060\n\ndeps:\n\tgo list -f '{{ join .Deps  \"\\n\"}}' ./... |grep \"/\" | grep -v \"github.com/smallnest/rpcx\"| grep \"\\.\" | sort |uniq\n\nfmt:\n\tgo fmt ./...\n\nbuild:\n\tgo build ./...\n\nbuild-all:\n\tgo build -tags \"kcp quic\" ./...\n\ntest:\n\tgo test -race -tags \"kcp quic\" ./...\n\ncover:\n\tgocov test -tags \"kcp quic\" ./... | gocov-html > cover.html\n\topen cover.html\n\ncheck-libs:\n\tGIT_TERMINAL_PROMPT=1 GO111MODULE=on go list -m -u all | column -t\n\nupdate-libs:\n\tGIT_TERMINAL_PROMPT=1 GO111MODULE=on go get -u -v ./...\n\nmod-tidy:\n\tGIT_TERMINAL_PROMPT=1 GO111MODULE=on go mod tidy\n"
  },
  {
    "path": "README.md",
    "content": "- **stable branch**: v1.7.x\n- **development branch**: master\n\n<a href=\"https://rpcx.io/\"><img height=\"160\" src=\"http://rpcx.io/logos/rpcx-logo-text.png\"></a>\n\nOfficial site: [http://rpcx.io](http://rpcx.io/)\n\n[![License](https://img.shields.io/:license-apache%202-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/smallnest/rpcx?status.png)](http://godoc.org/github.com/smallnest/rpcx)  [![github actions](https://github.com/smallnest/rpcx/actions)](https://github.com/smallnest/rpcx/actions/workflows/go.yml/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/smallnest/rpcx)](https://goreportcard.com/report/github.com/smallnest/rpcx) [![coveralls](https://coveralls.io/repos/smallnest/rpcx/badge.svg?branch=master&service=github)](https://coveralls.io/github/smallnest/rpcx?branch=master) [![QQ3群](https://img.shields.io/:QQ3群-953962236-blue.svg)](_documents/rpcx_dev_qq3.jpg) \n\n**Notice: etcd**\n\nsince rpcx 1.7.6, some plugins have been moved to the independent project:\n\n- `etcd` plugin has been moved to [rpcx-etcd](https://github.com/rpcxio/rpcx-etcd)\n- `zookeeper` plugin has been moved to [rpcx-zookeeper](https://github.com/rpcxio/rpcx-zookeeper)\n- `consul` plugin has been moved to [rpcx-consul](https://github.com/rpcxio/rpcx-consul)\n- `redis` plugin has been moved to [rpcx-redis](https://github.com/rpcxio/rpcx-redis)\n- `influxdb` plugin has been moved to [rpcx-plugins](https://github.com/rpcxio/rpcx-plugins)\n- `opentelemetry` plugin has been moved to [rpcx-plugins](https://github.com/rpcxio/rpcx-plugins)\n\n## Announce\n\nA tcpdump-like tool added: [rpcxdump](https://github.com/smallnest/rpcxdump)。 You can use it to debug communications between rpcx services and clients.\n\n![](https://github.com/smallnest/rpcxdump/blob/master/snapshoot.png)\n\n\n## Cross-Languages\nyou can use other programming languages besides Go to access rpcx services.\n\n- **rpcx-gateway**: You can write clients in any programming languages to call rpcx services via [rpcx-gateway](https://github.com/rpcxio/rpcx-gateway)\n- **http invoke**: you can use the same http requests to access rpcx gateway\n- **Java Services/Clients**: You can use [rpcx-java](https://github.com/smallnest/rpcx-java) to implement/access rpcx services via raw protocol.\n- **rust rpcx**: You can write rpcx services in rust by [rpcx-rs](https://github.com/smallnest/rpcx-rs)\n\n> If you can write Go methods, you can also write rpc services. It is so easy to write rpc applications with rpcx.\n\n## Installation\n\ninstall the basic features:\n\n`go get -v github.com/smallnest/rpcx/...`\n\n\nIf you want to use `quic`、`kcp` registry, use those tags to `go get` 、 `go build` or `go run`. For example, if you want to use all features, you can:\n\n```sh\ngo get -v -tags \"quic kcp\" github.com/smallnest/rpcx/...\n```\n\n**_tags_**:\n- **quic**: support quic transport\n- **kcp**: support kcp transport\n\n## Which companies are using rpcx?\n\n<p float=\"left\">\n  <img style=\"padding-bottom: 20px;\" src=\"https://user-images.githubusercontent.com/865763/102993220-b967f000-4557-11eb-9747-703a6cbb9fb1.png\" width=\"200\" />\n  <img style=\"padding-bottom: 20px;\" src=\"https://user-images.githubusercontent.com/865763/113414067-a8e4d280-93ee-11eb-9f42-1373d7e766c1.png\" width=\"200\" />\n  <img style=\"padding-bottom: 20px;\" src=\"https://user-images.githubusercontent.com/865763/102993433-267b8580-4558-11eb-9e45-4e1a86d61688.png\" width=\"200\" /> \n  <img style=\"padding-bottom: 20px;\" src=\"https://user-images.githubusercontent.com/865763/102993530-4743db00-4558-11eb-9f76-1ee69e992b82.png\" width=\"200\" />\n  <img style=\"padding-bottom: 20px;\" src=\"https://user-images.githubusercontent.com/865763/102993612-722e2f00-4558-11eb-849a-3264c430aef9.png\" width=\"200\" />\n  <img style=\"padding-bottom: 20px;\" src=\"https://user-images.githubusercontent.com/865763/102993785-c20cf600-4558-11eb-82b9-27b801aca4ff.png\" width=\"200\" />\n</p>\n\n## Features\nrpcx is a RPC framework like [Alibaba Dubbo](http://dubbo.io/) and [Weibo Motan](https://github.com/weibocom/motan).\n\n**rpcx** is created for targets:\n1. **Simple**: easy to learn, easy to develop, easy to integrate and easy to deploy\n2. **Performance**: high performance (>= grpc-go)\n3. **Cross-platform**: support _raw slice of bytes_, _JSON_, _Protobuf_ and _MessagePack_. Theoretically it can be used with java, php, python, c/c++, node.js, c# and other platforms\n4. **Service discovery and service governance**: support zookeeper, etcd and consul.\n\n\nIt contains below features\n- Support raw Go functions. There's no need to define proto files.\n- Pluggable. Features can be extended such as service discovery, tracing.\n- Support TCP, HTTP, [QUIC](https://en.wikipedia.org/wiki/QUIC) and [KCP](https://github.com/skywind3000/kcp)\n- Support multiple codecs such as JSON, Protobuf, [MessagePack](https://msgpack.org/index.html) and raw bytes.\n- Service discovery. Support peer2peer, configured peers, [zookeeper](https://zookeeper.apache.org), [etcd](https://github.com/coreos/etcd), [consul](https://www.consul.io) and [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS).\n- Fault tolerance：Failover, Failfast, Failtry.\n- Load banlancing：support Random, RoundRobin, Consistent hashing, Weighted, network quality and Geography.\n- Support Compression.\n- Support passing metadata.\n- Support Authorization.\n- Support heartbeat and one-way request.\n- Other features: metrics, log, timeout, alias, circuit breaker.\n- Support bidirectional communication.\n- Support access via HTTP so you can write clients in any programming languages.\n- Support API gateway.\n- Support backup request, forking and broadcast.\n\n\nrpcx uses a binary protocol and platform-independent, which means you can develop services in other languages such as Java, python, nodejs, and you can use other prorgramming languages to invoke services developed in Go.\n\nThere is a UI manager: [rpcx-ui](https://github.com/smallnest/rpcx-ui).\n\n## Performance\n\nTest results show rpcx has better performance than other rpc framework except standard rpc lib.\n\n\nThe benchmark code is at [rpcx-benchmark](https://github.com/rpcx-ecosystem/rpcx-benchmark).\n\n**Listen to others, but test by yourself**.\n\n**_Test Environment_**\n\n- **CPU**: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz, 32 cores\n- **Memory**: 32G\n- **Go**: 1.9.0\n- **OS**: CentOS 7 / 3.10.0-229.el7.x86_64\n\n**_Use_**\n- protobuf\n- the client and the server on the same server\n- 581 bytes payload\n- 500/2000/5000 concurrent clients\n- mock processing time: 0ms, 10ms and 30ms\n\n**_Test Result_**\n\n### mock 0ms process time\n\n<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-throughput.png\"></td><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-latency.png\"></td><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-p99.png\"></td></tr></table>\n\n\n### mock 10ms process time\n\n<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-throughput.png\"></td><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-latency.png\"></td><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-p99.png\"></td></tr></table>\n\n\n### mock 30ms process time\n\n<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-throughput.png\"></td><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-latency.png\"></td><td width=\"30%\"><img src=\"http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-p99.png\"></td></tr></table>\n\n\n## Examples\n\nYou can find all examples at [rpcxio/rpcx-examples](https://github.com/rpcxio/rpcx-examples).\n\nThe below is a simple example.\n\n\n**Server**\n\n```go\n    // define example.Arith\n    ……\n\n    s := server.NewServer()\n\ts.RegisterName(\"Arith\", new(example.Arith), \"\")\n\ts.Serve(\"tcp\", addr)\n\n```\n\n\n**Client**\n\n```go\n    // prepare requests\n    ……\n\n    d, err := client.NewPeer2PeerDiscovery(\"tcp@\"+addr, \"\")\n\txclient := client.NewXClient(\"Arith\", client.Failtry, client.RandomSelect, d, client.DefaultOption)\n\tdefer xclient.Close()\n\terr = xclient.Call(context.Background(), \"Mul\", args, reply, nil)\n```\n\n## Contributors\n\n<a href=\"https://github.com/smallnest/rpcx/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=smallnest/rpcx\" />\n</a>\n\n\n## Contribute\n\nsee [contributors](https://github.com/smallnest/rpcx/graphs/contributors).\n\nWelcome to contribute:\n- submit issues or requirements\n- send PRs\n- write projects to use rpcx\n- write tutorials or articles to introduce rpcx\n\n## License\n\nApache License, Version 2.0 \n"
  },
  {
    "path": "_testutils/GoUnusedProtection__.go",
    "content": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testutils\r\n\r\nvar GoUnusedProtection__ int\r\n"
  },
  {
    "path": "_testutils/arith_service.go",
    "content": "// Code generated by protoc-gen-gogo.\n// source: arith_service.proto\n// DO NOT EDIT!\n\n/*\n\tPackage client is a generated protocol buffer package.\n\n\tIt is generated from these files:\n\t\tarith_service.proto\n\n\tIt has these top-level messages:\n\t\tProtoArgs\n\t\tProtoReply\n*/\npackage testutils\n\nimport proto \"github.com/gogo/protobuf/proto\"\nimport fmt \"fmt\"\nimport math \"math\"\n\nimport io \"io\"\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package\n\ntype ProtoArgs struct {\n\tA int32 `protobuf:\"varint,1,opt,name=A,proto3\" json:\"A,omitempty\"`\n\tB int32 `protobuf:\"varint,2,opt,name=B,proto3\" json:\"B,omitempty\"`\n}\n\nfunc (m *ProtoArgs) Reset()                    { *m = ProtoArgs{} }\nfunc (m *ProtoArgs) String() string            { return proto.CompactTextString(m) }\nfunc (*ProtoArgs) ProtoMessage()               {}\nfunc (*ProtoArgs) Descriptor() ([]byte, []int) { return fileDescriptorArithService, []int{0} }\n\nfunc (m *ProtoArgs) GetA() int32 {\n\tif m != nil {\n\t\treturn m.A\n\t}\n\treturn 0\n}\n\nfunc (m *ProtoArgs) GetB() int32 {\n\tif m != nil {\n\t\treturn m.B\n\t}\n\treturn 0\n}\n\ntype ProtoReply struct {\n\tC int32 `protobuf:\"varint,1,opt,name=C,proto3\" json:\"C,omitempty\"`\n}\n\nfunc (m *ProtoReply) Reset()                    { *m = ProtoReply{} }\nfunc (m *ProtoReply) String() string            { return proto.CompactTextString(m) }\nfunc (*ProtoReply) ProtoMessage()               {}\nfunc (*ProtoReply) Descriptor() ([]byte, []int) { return fileDescriptorArithService, []int{1} }\n\nfunc (m *ProtoReply) GetC() int32 {\n\tif m != nil {\n\t\treturn m.C\n\t}\n\treturn 0\n}\n\nfunc init() {\n\tproto.RegisterType((*ProtoArgs)(nil), \"client.ProtoArgs\")\n\tproto.RegisterType((*ProtoReply)(nil), \"client.ProtoReply\")\n}\nfunc (m *ProtoArgs) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalTo(dAtA)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *ProtoArgs) MarshalTo(dAtA []byte) (int, error) {\n\tvar i int\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.A != 0 {\n\t\tdAtA[i] = 0x8\n\t\ti++\n\t\ti = encodeVarintArithService(dAtA, i, uint64(m.A))\n\t}\n\tif m.B != 0 {\n\t\tdAtA[i] = 0x10\n\t\ti++\n\t\ti = encodeVarintArithService(dAtA, i, uint64(m.B))\n\t}\n\treturn i, nil\n}\n\nfunc (m *ProtoReply) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalTo(dAtA)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *ProtoReply) MarshalTo(dAtA []byte) (int, error) {\n\tvar i int\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.C != 0 {\n\t\tdAtA[i] = 0x8\n\t\ti++\n\t\ti = encodeVarintArithService(dAtA, i, uint64(m.C))\n\t}\n\treturn i, nil\n}\n\nfunc encodeFixed64ArithService(dAtA []byte, offset int, v uint64) int {\n\tdAtA[offset] = uint8(v)\n\tdAtA[offset+1] = uint8(v >> 8)\n\tdAtA[offset+2] = uint8(v >> 16)\n\tdAtA[offset+3] = uint8(v >> 24)\n\tdAtA[offset+4] = uint8(v >> 32)\n\tdAtA[offset+5] = uint8(v >> 40)\n\tdAtA[offset+6] = uint8(v >> 48)\n\tdAtA[offset+7] = uint8(v >> 56)\n\treturn offset + 8\n}\nfunc encodeFixed32ArithService(dAtA []byte, offset int, v uint32) int {\n\tdAtA[offset] = uint8(v)\n\tdAtA[offset+1] = uint8(v >> 8)\n\tdAtA[offset+2] = uint8(v >> 16)\n\tdAtA[offset+3] = uint8(v >> 24)\n\treturn offset + 4\n}\nfunc encodeVarintArithService(dAtA []byte, offset int, v uint64) int {\n\tfor v >= 1<<7 {\n\t\tdAtA[offset] = uint8(v&0x7f | 0x80)\n\t\tv >>= 7\n\t\toffset++\n\t}\n\tdAtA[offset] = uint8(v)\n\treturn offset + 1\n}\nfunc (m *ProtoArgs) Size() (n int) {\n\tvar l int\n\t_ = l\n\tif m.A != 0 {\n\t\tn += 1 + sovArithService(uint64(m.A))\n\t}\n\tif m.B != 0 {\n\t\tn += 1 + sovArithService(uint64(m.B))\n\t}\n\treturn n\n}\n\nfunc (m *ProtoReply) Size() (n int) {\n\tvar l int\n\t_ = l\n\tif m.C != 0 {\n\t\tn += 1 + sovArithService(uint64(m.C))\n\t}\n\treturn n\n}\n\nfunc sovArithService(x uint64) (n int) {\n\tfor {\n\t\tn++\n\t\tx >>= 7\n\t\tif x == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn n\n}\nfunc sozArithService(x uint64) (n int) {\n\treturn sovArithService(uint64((x << 1) ^ uint64((int64(x) >> 63))))\n}\nfunc (m *ProtoArgs) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowArithService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: ProtoArgs: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: ProtoArgs: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field A\", wireType)\n\t\t\t}\n\t\t\tm.A = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowArithService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.A |= (int32(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field B\", wireType)\n\t\t\t}\n\t\t\tm.B = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowArithService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.B |= (int32(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipArithService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif skippy < 0 {\n\t\t\t\treturn ErrInvalidLengthArithService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *ProtoReply) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowArithService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: ProtoReply: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: ProtoReply: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field C\", wireType)\n\t\t\t}\n\t\t\tm.C = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowArithService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.C |= (int32(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipArithService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif skippy < 0 {\n\t\t\t\treturn ErrInvalidLengthArithService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc skipArithService(dAtA []byte) (n int, err error) {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn 0, ErrIntOverflowArithService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twireType := int(wire & 0x7)\n\t\tswitch wireType {\n\t\tcase 0:\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowArithService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tiNdEx++\n\t\t\t\tif dAtA[iNdEx-1] < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn iNdEx, nil\n\t\tcase 1:\n\t\t\tiNdEx += 8\n\t\t\treturn iNdEx, nil\n\t\tcase 2:\n\t\t\tvar length int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowArithService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tlength |= (int(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tiNdEx += length\n\t\t\tif length < 0 {\n\t\t\t\treturn 0, ErrInvalidLengthArithService\n\t\t\t}\n\t\t\treturn iNdEx, nil\n\t\tcase 3:\n\t\t\tfor {\n\t\t\t\tvar innerWire uint64\n\t\t\t\tvar start int = iNdEx\n\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\treturn 0, ErrIntOverflowArithService\n\t\t\t\t\t}\n\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\tiNdEx++\n\t\t\t\t\tinnerWire |= (uint64(b) & 0x7F) << shift\n\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tinnerWireType := int(innerWire & 0x7)\n\t\t\t\tif innerWireType == 4 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tnext, err := skipArithService(dAtA[start:])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\tiNdEx = start + next\n\t\t\t}\n\t\t\treturn iNdEx, nil\n\t\tcase 4:\n\t\t\treturn iNdEx, nil\n\t\tcase 5:\n\t\t\tiNdEx += 4\n\t\t\treturn iNdEx, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"proto: illegal wireType %d\", wireType)\n\t\t}\n\t}\n\tpanic(\"unreachable\")\n}\n\nvar (\n\tErrInvalidLengthArithService = fmt.Errorf(\"proto: negative length found during unmarshaling\")\n\tErrIntOverflowArithService   = fmt.Errorf(\"proto: integer overflow\")\n)\n\nfunc init() { proto.RegisterFile(\"arith_service.proto\", fileDescriptorArithService) }\n\nvar fileDescriptorArithService = []byte{\n\t// 126 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x2c, 0xca, 0x2c,\n\t0xc9, 0x88, 0x2f, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,\n\t0x62, 0x4b, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0x51, 0x52, 0xe7, 0xe2, 0x0c, 0x00, 0x09, 0x38, 0x16,\n\t0xa5, 0x17, 0x0b, 0xf1, 0x70, 0x31, 0x3a, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x06, 0x31, 0x3a,\n\t0x82, 0x78, 0x4e, 0x12, 0x4c, 0x10, 0x9e, 0x93, 0x92, 0x14, 0x17, 0x17, 0x58, 0x61, 0x50, 0x6a,\n\t0x41, 0x4e, 0x25, 0x48, 0xce, 0x19, 0xa6, 0xd2, 0xd9, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b,\n\t0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0x6c, 0x8b,\n\t0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xfa, 0x7e, 0x25, 0x8d, 0x7c, 0x00, 0x00, 0x00,\n}\n"
  },
  {
    "path": "_testutils/arith_service.proto",
    "content": "// protoc --gogofaster_out=. arith_service.proto\n// mv arith_service.pb.go arith_service_test.go \nsyntax = \"proto3\";\n\npackage client;\n\nmessage ProtoArgs { \n    int32 A = 1;\n    int32 B = 2;\n}\n\nmessage ProtoReply { \n    int32 C = 1;\n}\n"
  },
  {
    "path": "_testutils/thrift_arith_service-consts.go",
    "content": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testutils\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"fmt\"\r\n\t\"github.com/apache/thrift/lib/go/thrift\"\r\n\t\"time\"\r\n)\r\n\r\n// (needed to ensure safety because of naive import list construction.)\r\nvar _ = thrift.ZERO\r\nvar _ = fmt.Printf\r\nvar _ = context.Background\r\nvar _ = time.Now\r\nvar _ = bytes.Equal\r\n\r\nfunc init() {\r\n}\r\n"
  },
  {
    "path": "_testutils/thrift_arith_service.go",
    "content": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testutils\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"fmt\"\r\n\t\"github.com/apache/thrift/lib/go/thrift\"\r\n\t\"time\"\r\n)\r\n\r\n// (needed to ensure safety because of naive import list construction.)\r\nvar _ = thrift.ZERO\r\nvar _ = fmt.Printf\r\nvar _ = context.Background\r\nvar _ = time.Now\r\nvar _ = bytes.Equal\r\n\r\n// Attributes:\r\n//  - A\r\n//  - B\r\ntype ThriftArgs_ struct {\r\n\tA int32 `thrift:\"a,1\" db:\"a\" json:\"a\"`\r\n\tB int32 `thrift:\"b,2\" db:\"b\" json:\"b\"`\r\n}\r\n\r\nfunc NewThriftArgs_() *ThriftArgs_ {\r\n\treturn &ThriftArgs_{}\r\n}\r\n\r\nfunc (p *ThriftArgs_) GetA() int32 {\r\n\treturn p.A\r\n}\r\n\r\nfunc (p *ThriftArgs_) GetB() int32 {\r\n\treturn p.B\r\n}\r\nfunc (p *ThriftArgs_) Read(ctx context.Context, iprot thrift.TProtocol) error {\r\n\tif _, err := iprot.ReadStructBegin(ctx); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T read error: \", p), err)\r\n\t}\r\n\r\n\tfor {\r\n\t\t_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx)\r\n\t\tif err != nil {\r\n\t\t\treturn thrift.PrependError(fmt.Sprintf(\"%T field %d read error: \", p, fieldId), err)\r\n\t\t}\r\n\t\tif fieldTypeId == thrift.STOP {\r\n\t\t\tbreak\r\n\t\t}\r\n\t\tswitch fieldId {\r\n\t\tcase 1:\r\n\t\t\tif fieldTypeId == thrift.I32 {\r\n\t\t\t\tif err := p.ReadField1(ctx, iprot); err != nil {\r\n\t\t\t\t\treturn err\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n\t\t\t\t\treturn err\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\tcase 2:\r\n\t\t\tif fieldTypeId == thrift.I32 {\r\n\t\t\t\tif err := p.ReadField2(ctx, iprot); err != nil {\r\n\t\t\t\t\treturn err\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n\t\t\t\t\treturn err\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\tdefault:\r\n\t\t\tif err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t}\r\n\t\tif err := iprot.ReadFieldEnd(ctx); err != nil {\r\n\t\t\treturn err\r\n\t\t}\r\n\t}\r\n\tif err := iprot.ReadStructEnd(ctx); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T read struct end error: \", p), err)\r\n\t}\r\n\treturn nil\r\n}\r\n\r\nfunc (p *ThriftArgs_) ReadField1(ctx context.Context, iprot thrift.TProtocol) error {\r\n\tif v, err := iprot.ReadI32(ctx); err != nil {\r\n\t\treturn thrift.PrependError(\"error reading field 1: \", err)\r\n\t} else {\r\n\t\tp.A = v\r\n\t}\r\n\treturn nil\r\n}\r\n\r\nfunc (p *ThriftArgs_) ReadField2(ctx context.Context, iprot thrift.TProtocol) error {\r\n\tif v, err := iprot.ReadI32(ctx); err != nil {\r\n\t\treturn thrift.PrependError(\"error reading field 2: \", err)\r\n\t} else {\r\n\t\tp.B = v\r\n\t}\r\n\treturn nil\r\n}\r\n\r\nfunc (p *ThriftArgs_) Write(ctx context.Context, oprot thrift.TProtocol) error {\r\n\tif err := oprot.WriteStructBegin(ctx, \"ThriftArgs\"); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T write struct begin error: \", p), err)\r\n\t}\r\n\tif p != nil {\r\n\t\tif err := p.writeField1(ctx, oprot); err != nil {\r\n\t\t\treturn err\r\n\t\t}\r\n\t\tif err := p.writeField2(ctx, oprot); err != nil {\r\n\t\t\treturn err\r\n\t\t}\r\n\t}\r\n\tif err := oprot.WriteFieldStop(ctx); err != nil {\r\n\t\treturn thrift.PrependError(\"write field stop error: \", err)\r\n\t}\r\n\tif err := oprot.WriteStructEnd(ctx); err != nil {\r\n\t\treturn thrift.PrependError(\"write struct stop error: \", err)\r\n\t}\r\n\treturn nil\r\n}\r\n\r\nfunc (p *ThriftArgs_) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {\r\n\tif err := oprot.WriteFieldBegin(ctx, \"a\", thrift.I32, 1); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T write field begin error 1:a: \", p), err)\r\n\t}\r\n\tif err := oprot.WriteI32(ctx, int32(p.A)); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T.a (1) field write error: \", p), err)\r\n\t}\r\n\tif err := oprot.WriteFieldEnd(ctx); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T write field end error 1:a: \", p), err)\r\n\t}\r\n\treturn err\r\n}\r\n\r\nfunc (p *ThriftArgs_) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) {\r\n\tif err := oprot.WriteFieldBegin(ctx, \"b\", thrift.I32, 2); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T write field begin error 2:b: \", p), err)\r\n\t}\r\n\tif err := oprot.WriteI32(ctx, int32(p.B)); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T.b (2) field write error: \", p), err)\r\n\t}\r\n\tif err := oprot.WriteFieldEnd(ctx); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T write field end error 2:b: \", p), err)\r\n\t}\r\n\treturn err\r\n}\r\n\r\nfunc (p *ThriftArgs_) Equals(other *ThriftArgs_) bool {\r\n\tif p == other {\r\n\t\treturn true\r\n\t} else if p == nil || other == nil {\r\n\t\treturn false\r\n\t}\r\n\tif p.A != other.A {\r\n\t\treturn false\r\n\t}\r\n\tif p.B != other.B {\r\n\t\treturn false\r\n\t}\r\n\treturn true\r\n}\r\n\r\nfunc (p *ThriftArgs_) String() string {\r\n\tif p == nil {\r\n\t\treturn \"<nil>\"\r\n\t}\r\n\treturn fmt.Sprintf(\"ThriftArgs_(%+v)\", *p)\r\n}\r\n\r\n// Attributes:\r\n//  - C\r\ntype ThriftReply struct {\r\n\tC int32 `thrift:\"c,1\" db:\"c\" json:\"c\"`\r\n}\r\n\r\nfunc NewThriftReply() *ThriftReply {\r\n\treturn &ThriftReply{}\r\n}\r\n\r\nfunc (p *ThriftReply) GetC() int32 {\r\n\treturn p.C\r\n}\r\nfunc (p *ThriftReply) Read(ctx context.Context, iprot thrift.TProtocol) error {\r\n\tif _, err := iprot.ReadStructBegin(ctx); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T read error: \", p), err)\r\n\t}\r\n\r\n\tfor {\r\n\t\t_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx)\r\n\t\tif err != nil {\r\n\t\t\treturn thrift.PrependError(fmt.Sprintf(\"%T field %d read error: \", p, fieldId), err)\r\n\t\t}\r\n\t\tif fieldTypeId == thrift.STOP {\r\n\t\t\tbreak\r\n\t\t}\r\n\t\tswitch fieldId {\r\n\t\tcase 1:\r\n\t\t\tif fieldTypeId == thrift.I32 {\r\n\t\t\t\tif err := p.ReadField1(ctx, iprot); err != nil {\r\n\t\t\t\t\treturn err\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n\t\t\t\t\treturn err\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\tdefault:\r\n\t\t\tif err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t}\r\n\t\tif err := iprot.ReadFieldEnd(ctx); err != nil {\r\n\t\t\treturn err\r\n\t\t}\r\n\t}\r\n\tif err := iprot.ReadStructEnd(ctx); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T read struct end error: \", p), err)\r\n\t}\r\n\treturn nil\r\n}\r\n\r\nfunc (p *ThriftReply) ReadField1(ctx context.Context, iprot thrift.TProtocol) error {\r\n\tif v, err := iprot.ReadI32(ctx); err != nil {\r\n\t\treturn thrift.PrependError(\"error reading field 1: \", err)\r\n\t} else {\r\n\t\tp.C = v\r\n\t}\r\n\treturn nil\r\n}\r\n\r\nfunc (p *ThriftReply) Write(ctx context.Context, oprot thrift.TProtocol) error {\r\n\tif err := oprot.WriteStructBegin(ctx, \"ThriftReply\"); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T write struct begin error: \", p), err)\r\n\t}\r\n\tif p != nil {\r\n\t\tif err := p.writeField1(ctx, oprot); err != nil {\r\n\t\t\treturn err\r\n\t\t}\r\n\t}\r\n\tif err := oprot.WriteFieldStop(ctx); err != nil {\r\n\t\treturn thrift.PrependError(\"write field stop error: \", err)\r\n\t}\r\n\tif err := oprot.WriteStructEnd(ctx); err != nil {\r\n\t\treturn thrift.PrependError(\"write struct stop error: \", err)\r\n\t}\r\n\treturn nil\r\n}\r\n\r\nfunc (p *ThriftReply) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {\r\n\tif err := oprot.WriteFieldBegin(ctx, \"c\", thrift.I32, 1); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T write field begin error 1:c: \", p), err)\r\n\t}\r\n\tif err := oprot.WriteI32(ctx, int32(p.C)); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T.c (1) field write error: \", p), err)\r\n\t}\r\n\tif err := oprot.WriteFieldEnd(ctx); err != nil {\r\n\t\treturn thrift.PrependError(fmt.Sprintf(\"%T write field end error 1:c: \", p), err)\r\n\t}\r\n\treturn err\r\n}\r\n\r\nfunc (p *ThriftReply) Equals(other *ThriftReply) bool {\r\n\tif p == other {\r\n\t\treturn true\r\n\t} else if p == nil || other == nil {\r\n\t\treturn false\r\n\t}\r\n\tif p.C != other.C {\r\n\t\treturn false\r\n\t}\r\n\treturn true\r\n}\r\n\r\nfunc (p *ThriftReply) String() string {\r\n\tif p == nil {\r\n\t\treturn \"<nil>\"\r\n\t}\r\n\treturn fmt.Sprintf(\"ThriftReply(%+v)\", *p)\r\n}\r\n"
  },
  {
    "path": "_testutils/thrift_arith_service.thrift",
    "content": "struct ThriftArgs\n{\n    1: i32 a,\n    2: i32 b,\n}\n\n\nstruct ThriftReply\n{\n    1: i32 c,\n}"
  },
  {
    "path": "client/cache_client_builder.go",
    "content": "package client\r\n\r\nimport \"sync\"\r\n\r\n// CacheClientBuilder defines builder interface to generate RPCCient.\r\ntype CacheClientBuilder interface {\r\n\tSetCachedClient(client RPCClient, k, servicePath, serviceMethod string)\r\n\tFindCachedClient(k, servicePath, serviceMethod string) RPCClient\r\n\tDeleteCachedClient(client RPCClient, k, servicePath, serviceMethod string)\r\n\tGenerateClient(k, servicePath, serviceMethod string) (client RPCClient, err error)\r\n}\r\n\r\n// RegisterCacheClientBuilder(network string, builder CacheClientBuilder)\r\n\r\nvar cacheClientBuildersMutex sync.RWMutex\r\nvar cacheClientBuilders = make(map[string]CacheClientBuilder)\r\n\r\nfunc RegisterCacheClientBuilder(network string, builder CacheClientBuilder) {\r\n\tcacheClientBuildersMutex.Lock()\r\n\tdefer cacheClientBuildersMutex.Unlock()\r\n\r\n\tcacheClientBuilders[network] = builder\r\n}\r\n\r\nfunc getCacheClientBuilder(network string) (CacheClientBuilder, bool) {\r\n\tcacheClientBuildersMutex.RLock()\r\n\tdefer cacheClientBuildersMutex.RUnlock()\r\n\r\n\tbuilder, ok := cacheClientBuilders[network]\r\n\treturn builder, ok\r\n}\r\n"
  },
  {
    "path": "client/circuit_breaker.go",
    "content": "package client\n\nimport (\n\t\"errors\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nvar (\n\tErrBreakerOpen    = errors.New(\"breaker open\")\n\tErrBreakerTimeout = errors.New(\"breaker time out\")\n)\n\n// ConsecCircuitBreaker is window sliding CircuitBreaker with failure threshold.\ntype ConsecCircuitBreaker struct {\n\tlastFailureTime int64\n\n\tfailures         uint64\n\tfailureThreshold uint64\n\twindow           time.Duration\n}\n\n// NewConsecCircuitBreaker returns a new ConsecCircuitBreaker.\nfunc NewConsecCircuitBreaker(failureThreshold uint64, window time.Duration) *ConsecCircuitBreaker {\n\treturn &ConsecCircuitBreaker{\n\t\tfailureThreshold: failureThreshold,\n\t\twindow:           window,\n\t}\n}\n\n// Call Circuit function\nfunc (cb *ConsecCircuitBreaker) Call(fn func() error, d time.Duration) error {\n\tvar err error\n\n\tif !cb.ready() {\n\t\treturn ErrBreakerOpen\n\t}\n\n\tif d == 0 {\n\t\terr = fn()\n\t} else {\n\t\tc := make(chan error, 1)\n\t\tgo func() {\n\t\t\tc <- fn()\n\t\t\tclose(c)\n\t\t}()\n\n\t\tt := time.NewTimer(d)\n\t\tselect {\n\t\tcase e := <-c:\n\t\t\terr = e\n\t\tcase <-t.C:\n\t\t\terr = ErrBreakerTimeout\n\t\t}\n\t\tt.Stop()\n\t}\n\n\tif err == nil {\n\t\tcb.success()\n\t} else {\n\t\tcb.fail()\n\t}\n\n\treturn err\n}\n\nfunc (cb *ConsecCircuitBreaker) ready() bool {\n\tlastFailureTime := time.Unix(0, atomic.LoadInt64(&cb.lastFailureTime))\n\tif time.Since(lastFailureTime) > cb.window {\n\t\tcb.reset()\n\t\treturn true\n\t}\n\n\tfailures := atomic.LoadUint64(&cb.failures)\n\treturn failures < cb.failureThreshold\n}\n\nfunc (cb *ConsecCircuitBreaker) success() {\n\tcb.reset()\n}\n\nfunc (cb *ConsecCircuitBreaker) fail() {\n\tatomic.AddUint64(&cb.failures, 1)\n\tatomic.StoreInt64(&cb.lastFailureTime, time.Now().UnixNano())\n}\n\nfunc (cb *ConsecCircuitBreaker) Success() {\n\tcb.success()\n}\n\nfunc (cb *ConsecCircuitBreaker) Fail() {\n\tcb.fail()\n}\n\nfunc (cb *ConsecCircuitBreaker) Ready() bool {\n\treturn cb.ready()\n}\n\nfunc (cb *ConsecCircuitBreaker) reset() {\n\tatomic.StoreUint64(&cb.failures, 0)\n\tatomic.StoreInt64(&cb.lastFailureTime, time.Now().UnixNano())\n}\n"
  },
  {
    "path": "client/circuit_breaker_test.go",
    "content": "package client\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestConsecCircuitBreaker(t *testing.T) {\n\tcount := -1\n\tfn := func() error {\n\t\tcount++\n\t\tif count >= 5 && count < 10 {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn errors.New(\"test error\")\n\t}\n\n\tcb := NewConsecCircuitBreaker(5, 100*time.Millisecond)\n\n\tfor i := 0; i < 25; i++ {\n\t\terr := cb.Call(fn, 200*time.Millisecond)\n\t\tswitch {\n\t\tcase i < 5:\n\t\t\tif err.Error() != \"test error\" {\n\t\t\t\tt.Fatalf(\"expect %v, got %v\", \"test error\", err)\n\t\t\t}\n\t\tcase i >= 5 && i < 10:\n\t\t\tif err != ErrBreakerOpen {\n\t\t\t\tt.Fatalf(\"expect %v, got %v\", ErrBreakerOpen, err)\n\t\t\t}\n\t\tcase i >= 10 && i < 15:\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"expect success, got %v\", err)\n\t\t\t}\n\t\tcase i >= 15 && i < 20:\n\t\t\tif err.Error() != \"test error\" {\n\t\t\t\tt.Fatalf(\"expect %v, got %v\", \"test error\", err)\n\t\t\t}\n\t\tcase i >= 20 && i < 25:\n\t\t\tif err != ErrBreakerOpen {\n\t\t\t\tt.Fatalf(\"expect %v, got %v\", ErrBreakerOpen, err)\n\t\t\t}\n\t\t}\n\n\t\tif i == 9 { // expired\n\t\t\ttime.Sleep(150 * time.Millisecond)\n\t\t}\n\n\t}\n\n}\n\nfunc TestCircuitBreakerRace(t *testing.T) {\n\tcb := NewConsecCircuitBreaker(2, 50*time.Millisecond)\n\troutines := 100\n\tloop := 100000\n\n\tfn := func() error {\n\t\tif rand.Intn(2) == 1 {\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.New(\"test error\")\n\t}\n\n\tfor r := 0; r < routines; r++ {\n\t\tgo func() {\n\t\t\tfor i := 0; i < loop; i++ {\n\t\t\t\tcb.Call(fn, 100*time.Millisecond)\n\t\t\t}\n\t\t}()\n\t}\n}\n"
  },
  {
    "path": "client/client.go",
    "content": "package client\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tcircuit \"github.com/rubyist/circuitbreaker\"\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n)\n\nconst (\n\tXVersion           = \"X-RPCX-Version\"\n\tXMessageType       = \"X-RPCX-MesssageType\"\n\tXHeartbeat         = \"X-RPCX-Heartbeat\"\n\tXOneway            = \"X-RPCX-Oneway\"\n\tXMessageStatusType = \"X-RPCX-MessageStatusType\"\n\tXSerializeType     = \"X-RPCX-SerializeType\"\n\tXMessageID         = \"X-RPCX-MessageID\"\n\tXServicePath       = \"X-RPCX-ServicePath\"\n\tXServiceMethod     = \"X-RPCX-ServiceMethod\"\n\tXMeta              = \"X-RPCX-Meta\"\n\tXErrorMessage      = \"X-RPCX-ErrorMessage\"\n)\n\n// ServiceError is an error from server.\ntype ServiceError interface {\n\tError() string\n\tIsServiceError() bool\n}\n\n// NewServiceError creates a ServiceError with the error message.\nfunc NewServiceError(s string) ServiceError {\n\treturn strErr(s)\n}\n\n// ClientErrorFunc is a function to create a customized error.\nvar ClientErrorFunc func(res *protocol.Message, e string) ServiceError\n\ntype strErr string\n\nfunc (s strErr) Error() string {\n\treturn string(s)\n}\n\nfunc (s strErr) IsServiceError() bool {\n\treturn true\n}\n\n// DefaultOption is a common option configuration for client.\nvar DefaultOption = Option{\n\tRetries:             3,\n\tRPCPath:             share.DefaultRPCPath,\n\tConnectTimeout:      time.Second,\n\tSerializeType:       protocol.MsgPack,\n\tCompressType:        protocol.None,\n\tBackupLatency:       10 * time.Millisecond,\n\tMaxWaitForHeartbeat: 30 * time.Second,\n\tTCPKeepAlivePeriod:  time.Minute,\n\tBidirectionalBlock:  false,\n\tTimeToDisallow:      time.Minute,\n}\n\n// Breaker is a CircuitBreaker interface.\ntype Breaker interface {\n\tCall(func() error, time.Duration) error\n\tFail()\n\tSuccess()\n\tReady() bool\n}\n\n// CircuitBreaker is a default circuit breaker (RateBreaker(0.95, 100)).\nvar CircuitBreaker Breaker = circuit.NewRateBreaker(0.95, 100)\n\n// ErrShutdown connection is closed.\nvar (\n\tErrShutdown         = errors.New(\"connection is shut down\")\n\tErrUnsupportedCodec = errors.New(\"unsupported codec\")\n)\n\nconst (\n\t// ReaderBuffsize is used for bufio reader.\n\tReaderBuffsize = 16 * 1024\n\t// WriterBuffsize is used for bufio writer.\n\tWriterBuffsize = 16 * 1024\n)\n\ntype seqKey struct{}\n\n// RPCClient is interface that defines one client to call one server.\ntype RPCClient interface {\n\tConnect(network, address string) error\n\tGo(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call\n\tCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}) error\n\tSendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error)\n\tClose() error\n\tRemoteAddr() string\n\n\tRegisterServerMessageChan(ch chan<- *protocol.Message)\n\tUnregisterServerMessageChan()\n\n\tIsClosing() bool\n\tIsShutdown() bool\n\n\tGetConn() net.Conn\n}\n\n// Client represents a RPC client.\ntype Client struct {\n\toption Option\n\n\tConn net.Conn\n\tr    *bufio.Reader\n\t// w    *bufio.Writer\n\n\tmutex        sync.Mutex // protects following\n\tseq          uint64\n\tpending      map[uint64]*Call\n\tclosing      bool // user has called Close\n\tshutdown     bool // server has told us to stop\n\tpluginClosed bool // the plugin has been called\n\n\tPlugins PluginContainer\n\n\tServerMessageChan chan<- *protocol.Message\n}\n\n// NewClient returns a new Client with the option.\nfunc NewClient(option Option) *Client {\n\treturn &Client{\n\t\toption: option,\n\t}\n}\n\n// RemoteAddr returns the remote address.\nfunc (client *Client) RemoteAddr() string {\n\treturn client.Conn.RemoteAddr().String()\n}\n\n// GetConn returns the underlying conn.\nfunc (client *Client) GetConn() net.Conn {\n\treturn client.Conn\n}\n\n// Option contains all options for creating clients.\ntype Option struct {\n\t// Group is used to select the services in the same group. Services set group info in their meta.\n\t// If it is empty, clients will ignore group.\n\tGroup string\n\n\t// Retries retries to send\n\tRetries int\n\t// RetryInterval is the interval between retries\n\tRetryInterval time.Duration\n\t// Time to disallow the bad server not to be selected\n\tTimeToDisallow time.Duration\n\n\t// TLSConfig for tcp and quic\n\tTLSConfig *tls.Config\n\t// kcp.BlockCrypt\n\tBlock interface{}\n\t// RPCPath for http connection\n\tRPCPath string\n\t// ConnectTimeout sets timeout for dialing\n\tConnectTimeout time.Duration\n\t// IdleTimeout sets max idle time for underlying net.Conns\n\tIdleTimeout time.Duration\n\n\t// BackupLatency is used for Failbackup mode. rpcx will sends another request if the first response doesn't return in BackupLatency time.\n\tBackupLatency time.Duration\n\n\t// Breaker is used to config CircuitBreaker\n\tGenBreaker func() Breaker\n\n\tSerializeType protocol.SerializeType\n\tCompressType  protocol.CompressType\n\n\t// send heartbeat message to service and check responses\n\tHeartbeat bool\n\t// interval for heartbeat\n\tHeartbeatInterval   time.Duration\n\tMaxWaitForHeartbeat time.Duration\n\n\t// TCPKeepAlive, if it is zero we don't set keepalive\n\tTCPKeepAlivePeriod time.Duration\n\t// bidirectional mode, if true serverMessageChan will block to wait message for consume. default false.\n\tBidirectionalBlock bool\n\n\t// alaways use the selected server until it is bad\n\tSticky bool\n\n\t// not call server message handler\n\tNilCallServerMessageHandler func(msg *protocol.Message)\n}\n\n// Call represents an active RPC.\ntype Call struct {\n\tServicePath   string            // The name of the service and method to call.\n\tServiceMethod string            // The name of the service and method to call.\n\tMetadata      map[string]string // metadata\n\tResMetadata   map[string]string\n\tArgs          interface{} // The argument to the function (*struct).\n\tReply         interface{} // The reply from the function (*struct).\n\tError         error       // After completion, the error status.\n\tDone          chan *Call  // Strobes when call is complete.\n\tRaw           bool        // raw message or not\n}\n\nfunc (call *Call) done() {\n\tif call.Done == nil { // Oneshot\n\t\treturn\n\t}\n\n\tselect {\n\tcase call.Done <- call:\n\t\t// ok\n\tdefault:\n\t\tlog.Debug(\"rpc: discarding Call reply due to insufficient Done chan capacity\")\n\n\t}\n}\n\n// RegisterServerMessageChan registers the channel that receives server requests.\nfunc (client *Client) RegisterServerMessageChan(ch chan<- *protocol.Message) {\n\tclient.ServerMessageChan = ch\n}\n\n// UnregisterServerMessageChan removes ServerMessageChan.\nfunc (client *Client) UnregisterServerMessageChan() {\n\tclient.ServerMessageChan = nil\n}\n\n// IsClosing client is closing or not.\nfunc (client *Client) IsClosing() bool {\n\tclient.mutex.Lock()\n\tdefer client.mutex.Unlock()\n\treturn client.closing\n}\n\n// IsShutdown client is shutdown or not.\nfunc (client *Client) IsShutdown() bool {\n\tclient.mutex.Lock()\n\tdefer client.mutex.Unlock()\n\treturn client.shutdown\n}\n\n// Go invokes the function asynchronously. It returns the Call structure representing\n// the invocation. The done channel will signal when the call is complete by returning\n// the same Call object. If done is nil, Go will allocate a new channel.\n// If non-nil, done must be buffered or Go will deliberately crash.\nfunc (client *Client) Go(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {\n\tcall := new(Call)\n\tcall.ServicePath = servicePath\n\tcall.ServiceMethod = serviceMethod\n\tmeta := ctx.Value(share.ReqMetaDataKey)\n\tif meta != nil { // copy meta in context to meta in requests\n\t\tcall.Metadata = meta.(map[string]string)\n\t}\n\n\tif !share.IsShareContext(ctx) {\n\t\tctx = share.NewContext(ctx)\n\t}\n\n\tcall.Args = args\n\tcall.Reply = reply\n\n\t// // we allow done is nil\n\t// if done == nil {\n\t// \tdone = make(chan *Call, 10) // buffered.\n\t// } else {\n\t// \t// If caller passes done != nil, it must arrange that\n\t// \t// done has enough buffer for the number of simultaneous\n\t// \t// RPCs that will be using that channel. If the channel\n\t// \t// is totally unbuffered, it's best not to run at all.\n\t// \tif cap(done) == 0 {\n\t// \t\tlog.Panic(\"rpc: done channel is unbuffered\")\n\t// \t}\n\t// }\n\n\tcall.Done = done\n\n\tif share.Trace {\n\t\tlog.Debugf(\"client.Go send request for %s.%s, args: %+v in case of client call\", servicePath, serviceMethod, args)\n\t}\n\n\tgo client.send(ctx, call)\n\n\treturn call\n}\n\n// Call invokes the named function, waits for it to complete, and returns its error status.\nfunc (client *Client) Call(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}) error {\n\treturn client.call(ctx, servicePath, serviceMethod, args, reply)\n}\n\nfunc (client *Client) call(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}) error {\n\tseq := new(uint64)\n\n\tif sharedCtx, ok := ctx.(*share.Context); ok {\n\t\tsharedCtx.SetValue(seqKey{}, seq)\n\t} else {\n\t\tctx = context.WithValue(ctx, seqKey{}, seq)\n\t}\n\n\tif share.Trace {\n\t\tlog.Debugf(\"client.call for %s.%s, args: %+v in case of client call\", servicePath, serviceMethod, args)\n\t\tdefer func() {\n\t\t\tlog.Debugf(\"client.call done for %s.%s, args: %+v in case of client call\", servicePath, serviceMethod, args)\n\t\t}()\n\t}\n\n\tDone := client.Go(ctx, servicePath, serviceMethod, args, reply, make(chan *Call, 10)).Done\n\n\tvar err error\n\tselect {\n\tcase <-ctx.Done(): // cancel by context\n\t\tclient.mutex.Lock()\n\t\tcall := client.pending[*seq]\n\t\tdelete(client.pending, *seq)\n\t\tclient.mutex.Unlock()\n\t\tif call != nil {\n\t\t\tcall.Error = ctx.Err()\n\t\t\tcall.done()\n\t\t}\n\n\t\treturn ctx.Err()\n\tcase call := <-Done:\n\t\terr = call.Error\n\t\tmeta := ctx.Value(share.ResMetaDataKey)\n\t\tif meta != nil && len(call.ResMetadata) > 0 {\n\t\t\tresMeta := meta.(map[string]string)\n\t\t\tlocker, ok := ctx.Value(share.ContextTagsLock).(*sync.Mutex)\n\t\t\tif ok {\n\n\t\t\t\tlocker.Lock()\n\t\t\t\tfor k, v := range call.ResMetadata {\n\t\t\t\t\tresMeta[k] = v\n\t\t\t\t}\n\t\t\t\tresMeta[share.ServerAddress] = client.Conn.RemoteAddr().String()\n\t\t\t\tlocker.Unlock()\n\n\t\t\t} else {\n\t\t\t\tfor k, v := range call.ResMetadata {\n\t\t\t\t\tresMeta[k] = v\n\t\t\t\t}\n\t\t\t\tresMeta[share.ServerAddress] = client.Conn.RemoteAddr().String()\n\t\t\t}\n\t\t}\n\t}\n\n\treturn err\n}\n\n// SendRaw sends raw messages. You don't care args and replies.\nfunc (client *Client) SendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error) {\n\tif sharedCtx, ok := ctx.(*share.Context); ok {\n\t\tsharedCtx.SetValue(seqKey{}, r.Seq())\n\t} else {\n\t\tctx = context.WithValue(ctx, seqKey{}, r.Seq())\n\t}\n\n\tcall := new(Call)\n\tcall.Raw = true\n\tcall.ServicePath = r.ServicePath\n\tcall.ServiceMethod = r.ServiceMethod\n\tmeta := ctx.Value(share.ReqMetaDataKey)\n\n\trmeta := make(map[string]string)\n\n\t// copy meta to rmeta\n\tif meta != nil {\n\t\tfor k, v := range meta.(map[string]string) {\n\t\t\trmeta[k] = v\n\t\t}\n\t}\n\t// copy r.Metadata to rmeta\n\tif r.Metadata != nil {\n\t\tfor k, v := range r.Metadata {\n\t\t\trmeta[k] = v\n\t\t}\n\t}\n\n\tif meta != nil { // copy meta in context to meta in requests\n\t\tcall.Metadata = rmeta\n\t}\n\tr.Metadata = rmeta\n\n\tif !share.IsShareContext(ctx) {\n\t\tctx = share.NewContext(ctx)\n\t}\n\n\tdone := make(chan *Call, 10)\n\tcall.Done = done\n\n\tseq := r.Seq()\n\tclient.mutex.Lock()\n\tif client.pending == nil {\n\t\tclient.pending = make(map[uint64]*Call)\n\t}\n\tclient.pending[seq] = call\n\tclient.mutex.Unlock()\n\n\tdata := r.EncodeSlicePointer()\n\t_, err := client.Conn.Write(*data)\n\tprotocol.PutData(data)\n\n\tif err != nil {\n\t\tclient.mutex.Lock()\n\t\tcall = client.pending[seq]\n\t\tdelete(client.pending, seq)\n\t\tclient.mutex.Unlock()\n\t\tif call != nil {\n\t\t\tcall.Error = err\n\t\t\tcall.done()\n\t\t}\n\t\treturn nil, nil, err\n\t}\n\tif r.IsOneway() {\n\t\tclient.mutex.Lock()\n\t\tcall = client.pending[seq]\n\t\tdelete(client.pending, seq)\n\t\tclient.mutex.Unlock()\n\t\tif call != nil {\n\t\t\tcall.done()\n\t\t}\n\t\treturn nil, nil, nil\n\t}\n\n\tvar m map[string]string\n\tvar payload []byte\n\n\tselect {\n\tcase <-ctx.Done(): // cancel by context\n\t\tclient.mutex.Lock()\n\t\tcall := client.pending[seq]\n\t\tdelete(client.pending, seq)\n\t\tclient.mutex.Unlock()\n\t\tif call != nil {\n\t\t\tcall.Error = ctx.Err()\n\t\t\tcall.done()\n\t\t}\n\n\t\treturn nil, nil, ctx.Err()\n\tcase call := <-done:\n\t\terr = call.Error\n\t\tm = call.ResMetadata\n\t\tif call.Reply != nil {\n\t\t\tpayload = call.Reply.([]byte)\n\t\t}\n\t}\n\n\treturn m, payload, err\n}\n\nfunc convertRes2Raw(res *protocol.Message) (map[string]string, []byte, error) {\n\tm := make(map[string]string)\n\tm[XVersion] = strconv.Itoa(int(res.Version()))\n\tif res.IsHeartbeat() {\n\t\tm[XHeartbeat] = \"true\"\n\t}\n\tif res.IsOneway() {\n\t\tm[XOneway] = \"true\"\n\t}\n\tif res.MessageStatusType() == protocol.Error {\n\t\tm[XMessageStatusType] = \"Error\"\n\t} else {\n\t\tm[XMessageStatusType] = \"Normal\"\n\t}\n\n\t// if res.CompressType() == protocol.Gzip {\n\t// \tm[\"Content-Encoding\"] = \"gzip\"\n\t// }\n\n\tm[XMeta] = urlencode(res.Metadata)\n\tm[XSerializeType] = strconv.Itoa(int(res.SerializeType()))\n\tm[XMessageID] = strconv.FormatUint(res.Seq(), 10)\n\tm[XServicePath] = res.ServicePath\n\tm[XServiceMethod] = res.ServiceMethod\n\n\treturn m, res.Payload, nil\n}\n\nfunc urlencode(data map[string]string) string {\n\tif len(data) == 0 {\n\t\treturn \"\"\n\t}\n\tvar buf strings.Builder\n\tfirst := true\n\tfor k, v := range data {\n\t\tif !first {\n\t\t\tbuf.WriteByte('&')\n\t\t}\n\t\tbuf.WriteString(url.QueryEscape(k))\n\t\tbuf.WriteByte('=')\n\t\tbuf.WriteString(url.QueryEscape(v))\n\t\tfirst = false\n\t}\n\treturn buf.String()\n}\n\nfunc (client *Client) send(ctx context.Context, call *Call) {\n\t// Register this call.\n\tclient.mutex.Lock()\n\tif client.shutdown || client.closing {\n\t\tcall.Error = ErrShutdown\n\t\tclient.mutex.Unlock()\n\t\tcall.done()\n\t\treturn\n\t}\n\n\tisHeartbeat := call.ServicePath == \"\" && call.ServiceMethod == \"\"\n\tserializeType := client.option.SerializeType\n\tif isHeartbeat {\n\t\tserializeType = protocol.MsgPack\n\t}\n\tcodec := share.Codecs[serializeType]\n\tif codec == nil {\n\t\tcall.Error = ErrUnsupportedCodec\n\t\tclient.mutex.Unlock()\n\t\tcall.done()\n\t\treturn\n\t}\n\n\tif client.pending == nil {\n\t\tclient.pending = make(map[uint64]*Call)\n\t}\n\n\tseq := client.seq\n\tclient.seq++\n\tclient.pending[seq] = call\n\tclient.mutex.Unlock()\n\n\tif cseq, ok := ctx.Value(seqKey{}).(*uint64); ok {\n\t\t*cseq = seq\n\t}\n\n\t// req := protocol.NewMessage()\n\treq := protocol.NewMessage()\n\treq.SetMessageType(protocol.Request)\n\treq.SetSeq(seq)\n\tif call.Reply == nil {\n\t\treq.SetOneway(true)\n\t}\n\n\t// heartbeat, and use default SerializeType (msgpack)\n\tif isHeartbeat {\n\t\treq.SetHeartbeat(true)\n\t\treq.SetSerializeType(protocol.MsgPack)\n\t} else {\n\t\treq.SetSerializeType(client.option.SerializeType)\n\t}\n\n\tif call.Metadata != nil {\n\t\treq.Metadata = call.Metadata\n\t}\n\n\treq.ServicePath = call.ServicePath\n\treq.ServiceMethod = call.ServiceMethod\n\n\tdata, err := codec.Encode(call.Args)\n\tif err != nil {\n\t\tclient.mutex.Lock()\n\t\tdelete(client.pending, seq)\n\t\tclient.mutex.Unlock()\n\t\tcall.Error = err\n\t\tcall.done()\n\t\treturn\n\t}\n\tif len(data) > 1024 && client.option.CompressType != protocol.None {\n\t\treq.SetCompressType(client.option.CompressType)\n\t}\n\n\treq.Payload = data\n\n\tif client.Plugins != nil {\n\t\t_ = client.Plugins.DoClientBeforeEncode(req)\n\t}\n\n\tif share.Trace {\n\t\tlog.Debugf(\"client.send for %s.%s, args: %+v in case of client call\", call.ServicePath, call.ServiceMethod, call.Args)\n\t}\n\tallData := req.EncodeSlicePointer()\n\t_, err = client.Conn.Write(*allData)\n\tprotocol.PutData(allData)\n\tif share.Trace {\n\t\tlog.Debugf(\"client.sent for %s.%s, args: %+v in case of client call\", call.ServicePath, call.ServiceMethod, call.Args)\n\t}\n\n\tif err != nil {\n\t\tif e, ok := err.(*net.OpError); ok {\n\t\t\tif e.Err != nil {\n\t\t\t\terr = fmt.Errorf(\"net.OpError: %s\", e.Err.Error())\n\t\t\t} else {\n\t\t\t\terr = errors.New(\"net.OpError\")\n\t\t\t}\n\n\t\t}\n\t\tclient.mutex.Lock()\n\t\tcall = client.pending[seq]\n\t\tdelete(client.pending, seq)\n\t\tclient.mutex.Unlock()\n\t\tif call != nil {\n\t\t\tcall.Error = err\n\t\t\tcall.done()\n\t\t}\n\n\t\treturn\n\t}\n\n\tisOneway := req.IsOneway()\n\n\tif isOneway {\n\t\tclient.mutex.Lock()\n\t\tcall = client.pending[seq]\n\t\tdelete(client.pending, seq)\n\t\tclient.mutex.Unlock()\n\t\tif call != nil {\n\t\t\tcall.done()\n\t\t}\n\t}\n\n\tif client.option.IdleTimeout != 0 {\n\t\t_ = client.Conn.SetDeadline(time.Now().Add(client.option.IdleTimeout))\n\t}\n}\n\nfunc (client *Client) input() {\n\tvar err error\n\n\tfor err == nil {\n\t\tres := protocol.NewMessage()\n\t\tif client.option.IdleTimeout != 0 {\n\t\t\t_ = client.Conn.SetDeadline(time.Now().Add(client.option.IdleTimeout))\n\t\t}\n\n\t\terr = res.Decode(client.r)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif client.Plugins != nil {\n\t\t\t_ = client.Plugins.DoClientAfterDecode(res)\n\t\t}\n\n\t\tseq := res.Seq()\n\t\tvar call *Call\n\t\tisServerMessage := (res.MessageType() == protocol.Request && !res.IsHeartbeat() && res.IsOneway())\n\t\tif !isServerMessage {\n\t\t\tclient.mutex.Lock()\n\t\t\tcall = client.pending[seq]\n\t\t\tdelete(client.pending, seq)\n\t\t\tclient.mutex.Unlock()\n\t\t}\n\n\t\tif share.Trace {\n\t\t\tlog.Debugf(\"client.input received %v\", res)\n\t\t}\n\n\t\tswitch {\n\t\tcase call == nil:\n\t\t\tif isServerMessage {\n\t\t\t\tif client.ServerMessageChan != nil {\n\t\t\t\t\tclient.handleServerRequest(res)\n\t\t\t\t} else if client.option.NilCallServerMessageHandler != nil {\n\t\t\t\t\tclient.option.NilCallServerMessageHandler(res)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase res.MessageStatusType() == protocol.Error:\n\t\t\t// We've got an error response. Give this to the request\n\t\t\tif len(res.Metadata) > 0 {\n\t\t\t\tcall.ResMetadata = res.Metadata\n\n\t\t\t\t// convert server error to a customized error, which implements ServerError interface\n\t\t\t\tif ClientErrorFunc != nil {\n\t\t\t\t\tcall.Error = ClientErrorFunc(res, res.Metadata[protocol.ServiceError])\n\t\t\t\t} else {\n\t\t\t\t\tcall.Error = strErr(res.Metadata[protocol.ServiceError])\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif call.Raw {\n\t\t\t\tcall.Metadata, call.Reply, _ = convertRes2Raw(res)\n\t\t\t\tcall.Metadata[XErrorMessage] = call.Error.Error()\n\t\t\t\tcall.ResMetadata = res.Metadata\n\t\t\t} else if len(res.Payload) > 0 {\n\t\t\t\tdata := res.Payload\n\t\t\t\tcodec := share.Codecs[res.SerializeType()]\n\t\t\t\tif codec != nil {\n\t\t\t\t\t_ = codec.Decode(data, call.Reply)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcall.done()\n\t\tdefault:\n\t\t\tif call.Raw {\n\t\t\t\tcall.Metadata, call.Reply, _ = convertRes2Raw(res)\n\t\t\t\tcall.ResMetadata = res.Metadata\n\t\t\t} else {\n\t\t\t\tdata := res.Payload\n\t\t\t\tif len(data) > 0 {\n\t\t\t\t\tcodec := share.Codecs[res.SerializeType()]\n\t\t\t\t\tif codec == nil {\n\t\t\t\t\t\tcall.Error = strErr(ErrUnsupportedCodec.Error())\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = codec.Decode(data, call.Reply)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tcall.Error = strErr(err.Error())\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(res.Metadata) > 0 {\n\t\t\t\t\tcall.ResMetadata = res.Metadata\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tcall.done()\n\t\t}\n\t}\n\t// Terminate pending calls.\n\n\tif client.ServerMessageChan != nil {\n\t\treq := protocol.NewMessage()\n\t\treq.SetMessageType(protocol.Request)\n\t\treq.SetMessageStatusType(protocol.Error)\n\t\tif req.Metadata == nil {\n\t\t\treq.Metadata = make(map[string]string)\n\t\t\tif err != nil {\n\t\t\t\treq.Metadata[protocol.ServiceError] = err.Error()\n\t\t\t}\n\t\t}\n\t\treq.Metadata[\"server\"] = client.Conn.RemoteAddr().String()\n\t\tclient.handleServerRequest(req)\n\t}\n\n\tclient.mutex.Lock()\n\tif !client.pluginClosed {\n\t\tif client.Plugins != nil {\n\t\t\tclient.Plugins.DoClientConnectionClose(client.Conn)\n\t\t}\n\t\tclient.pluginClosed = true\n\t}\n\tclient.Conn.Close()\n\tclient.shutdown = true\n\tclosing := client.closing\n\tif e, ok := err.(*net.OpError); ok {\n\t\tif e.Addr != nil || e.Err != nil {\n\t\t\terr = fmt.Errorf(\"net.OpError: %s\", e.Err.Error())\n\t\t} else {\n\t\t\terr = errors.New(\"net.OpError\")\n\t\t}\n\n\t}\n\tif err == io.EOF {\n\t\tif closing {\n\t\t\terr = ErrShutdown\n\t\t} else {\n\t\t\terr = io.ErrUnexpectedEOF\n\t\t}\n\t}\n\tfor _, call := range client.pending {\n\t\tcall.Error = err\n\t\tcall.done()\n\t}\n\n\tclient.mutex.Unlock()\n\n\tif err != nil && !closing {\n\t\tlog.Errorf(\"rpcx: client protocol error: %v\", err)\n\t}\n}\n\nfunc (client *Client) handleServerRequest(msg *protocol.Message) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Errorf(\"ServerMessageChan may be closed so client remove it. Please add it again if you want to handle server requests. error is %v\", r)\n\t\t\tclient.ServerMessageChan = nil\n\t\t}\n\t}()\n\n\tserverMessageChan := client.ServerMessageChan\n\tif serverMessageChan != nil {\n\t\tif client.option.BidirectionalBlock {\n\t\t\tserverMessageChan <- msg\n\t\t} else {\n\t\t\tselect {\n\t\t\tcase serverMessageChan <- msg:\n\t\t\tdefault:\n\t\t\t\tlog.Warnf(\"ServerMessageChan may be full so the server request %d has been dropped\", msg.Seq())\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (client *Client) heartbeat() {\n\tt := time.NewTicker(client.option.HeartbeatInterval)\n\n\tif client.option.MaxWaitForHeartbeat == 0 {\n\t\tclient.option.MaxWaitForHeartbeat = 30 * time.Second\n\t}\n\n\tfor range t.C {\n\t\tif client.IsShutdown() || client.IsClosing() {\n\t\t\tt.Stop()\n\t\t\treturn\n\t\t}\n\n\t\trequest := time.Now().UnixNano()\n\t\treply := int64(0)\n\t\tctx, cancel := context.WithTimeout(context.Background(), client.option.MaxWaitForHeartbeat)\n\t\terr := client.Call(ctx, \"\", \"\", &request, &reply)\n\t\tabnormal := false\n\t\tif ctx.Err() != nil {\n\t\t\tlog.Warnf(\"failed to heartbeat to %s, context err: %v\", client.Conn.RemoteAddr().String(), ctx.Err())\n\t\t\tabnormal = true\n\t\t}\n\t\tcancel()\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"failed to heartbeat to %s: %v\", client.Conn.RemoteAddr().String(), err)\n\t\t\tabnormal = true\n\t\t}\n\n\t\tif reply != request {\n\t\t\tlog.Warnf(\"reply %d in heartbeat to %s is different from request %d\", reply, client.Conn.RemoteAddr().String(), request)\n\t\t}\n\n\t\tif abnormal {\n\t\t\tclient.Close()\n\t\t}\n\t}\n}\n\n// Close calls the underlying connection's Close method. If the connection is already\n// shutting down, ErrShutdown is returned.\nfunc (client *Client) Close() error {\n\tclient.mutex.Lock()\n\n\tfor seq, call := range client.pending {\n\t\tdelete(client.pending, seq)\n\t\tif call != nil {\n\t\t\tcall.Error = ErrShutdown\n\t\t\tcall.done()\n\t\t}\n\t}\n\n\tvar err error\n\tif !client.pluginClosed {\n\t\tif client.Plugins != nil {\n\t\t\tclient.Plugins.DoClientConnectionClose(client.Conn)\n\t\t}\n\n\t\tclient.pluginClosed = true\n\t\terr = client.Conn.Close()\n\t}\n\n\tif client.closing || client.shutdown {\n\t\tclient.mutex.Unlock()\n\t\treturn ErrShutdown\n\t}\n\n\tclient.closing = true\n\tclient.mutex.Unlock()\n\treturn err\n}\n"
  },
  {
    "path": "client/client_test.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\ttestutils \"github.com/smallnest/rpcx/_testutils\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/server\"\n)\n\ntype Args struct {\n\tA int\n\tB int\n}\n\ntype Reply struct {\n\tC int\n}\n\ntype Arith int\n\nfunc (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) error {\n\treply.C = args.A * args.B\n\treturn nil\n}\n\ntype PBArith int\n\nfunc (t *PBArith) Mul(ctx context.Context, args *testutils.ProtoArgs, reply *testutils.ProtoReply) error {\n\treply.C = args.A * args.B\n\treturn nil\n}\n\nfunc (t *Arith) ThriftMul(ctx context.Context, args *testutils.ThriftArgs_, reply *testutils.ThriftReply) error {\n\treply.C = args.A * args.B\n\treturn nil\n}\n\ntype Bidirectional struct {\n\t*server.Server\n}\n\nfunc (t *Bidirectional) Mul(ctx context.Context, args *Args, reply *Reply) error {\n\tconn := ctx.Value(server.RemoteConnContextKey).(net.Conn)\n\treply.C = args.A * args.B\n\tt.SendMessage(conn, \"test_service_path\", \"test_service_method\", nil, []byte(\"abcde\"))\n\treturn nil\n}\n\nfunc TestClient_IT(t *testing.T) {\n\ts := server.NewServer()\n\t_ = s.RegisterName(\"Arith\", new(Arith), \"\")\n\t_ = s.RegisterName(\"PBArith\", new(PBArith), \"\")\n\tgo func() {\n\t\t_ = s.Serve(\"tcp\", \"127.0.0.1:0\")\n\t}()\n\tdefer s.Close()\n\ttime.Sleep(500 * time.Millisecond)\n\n\taddr := s.Address().String()\n\n\tclient := &Client{\n\t\toption: DefaultOption,\n\t}\n\n\terr := client.Connect(\"tcp\", addr)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to connect: %v\", err)\n\t}\n\tdefer client.Close()\n\n\targs := &Args{\n\t\tA: 10,\n\t\tB: 20,\n\t}\n\n\treply := &Reply{}\n\terr = client.Call(context.Background(), \"Arith\", \"Mul\", args, reply)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to call: %v\", err)\n\t}\n\n\tif reply.C != 200 {\n\t\tt.Fatalf(\"expect 200 but got %d\", reply.C)\n\t}\n\n\terr = client.Call(context.Background(), \"Arith\", \"Add\", args, reply)\n\tif err == nil {\n\t\tt.Fatal(\"expect an error but got nil\")\n\t}\n\n\tclient.option.SerializeType = protocol.MsgPack\n\treply = &Reply{}\n\terr = client.Call(context.Background(), \"Arith\", \"Mul\", args, reply)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to call: %v\", err)\n\t}\n\n\tif reply.C != 200 {\n\t\tt.Fatalf(\"expect 200 but got %d\", reply.C)\n\t}\n\n\tclient.option.SerializeType = protocol.ProtoBuffer\n\n\tpbArgs := &testutils.ProtoArgs{\n\t\tA: 10,\n\t\tB: 20,\n\t}\n\tpbReply := &testutils.ProtoReply{}\n\terr = client.Call(context.Background(), \"PBArith\", \"Mul\", pbArgs, pbReply)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to call: %v\", err)\n\t}\n\n\tif pbReply.C != 200 {\n\t\tt.Fatalf(\"expect 200 but got %d\", pbReply.C)\n\t}\n}\n\nfunc TestClient_IT_Concurrency(t *testing.T) {\n\ts := server.NewServer()\n\t_ = s.RegisterName(\"PBArith\", new(PBArith), \"\")\n\tgo func() {\n\t\t_ = s.Serve(\"tcp\", \"127.0.0.1:0\")\n\t}()\n\tdefer s.Close()\n\ttime.Sleep(500 * time.Millisecond)\n\n\taddr := s.Address().String()\n\n\tclient := &Client{\n\t\toption: DefaultOption,\n\t}\n\n\terr := client.Connect(\"tcp\", addr)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to connect: %v\", err)\n\t}\n\tdefer client.Close()\n\n\tvar wg sync.WaitGroup\n\twg.Add(100)\n\tfor i := 0; i < 100; i++ {\n\t\ti := i\n\t\tgo testSendRaw(t, client, uint64(i), rand.Int31(), rand.Int31(), &wg)\n\t}\n\twg.Wait()\n\n}\n\nfunc testSendRaw(t *testing.T, client *Client, seq uint64, x, y int32, wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\trpcxReq := protocol.NewMessage()\n\trpcxReq.SetMessageType(protocol.Request)\n\trpcxReq.SetSeq(seq)\n\trpcxReq.ServicePath = \"PBArith\"\n\trpcxReq.ServiceMethod = \"Mul\"\n\trpcxReq.SetSerializeType(protocol.ProtoBuffer)\n\trpcxReq.SetOneway(false)\n\n\tpbArgs := &testutils.ProtoArgs{\n\t\tA: x,\n\t\tB: y,\n\t}\n\tdata, _ := pbArgs.Marshal()\n\trpcxReq.Payload = data\n\t_, reply, err := client.SendRaw(context.Background(), rpcxReq)\n\tif err != nil {\n\t\tt.Errorf(\"failed to call SendRaw: %v\", err)\n\t\treturn\n\t}\n\n\tpbReply := &testutils.ProtoReply{}\n\terr = pbReply.Unmarshal(reply)\n\tif err != nil {\n\t\tt.Errorf(\"failed to unmarshal reply: %v\", err)\n\t\treturn\n\t}\n\n\tif pbReply.C != x*y {\n\t\tt.Errorf(\"expect %d but got %d\", x*y, pbReply.C)\n\t\treturn\n\t}\n}\n\nfunc TestClient_Res_Reset(t *testing.T) {\n\tvar res = protocol.NewMessage()\n\tres.Payload = []byte{1, 2, 3, 4, 5, 6, 7, 8}\n\tdata := res.Payload\n\tres.Reset()\n\n\tif len(data) == 0 {\n\t\tt.Fatalf(\"data has been set to empty after response has been reset: %v\", data)\n\t}\n}\n\nfunc TestClient_Bidirectional(t *testing.T) {\n\ts := server.NewServer()\n\t_ = s.RegisterName(\"Bidirectional\", &Bidirectional{Server: s}, \"\")\n\tgo func() {\n\t\t_ = s.Serve(\"tcp\", \"127.0.0.1:0\")\n\t}()\n\tdefer s.Close()\n\ttime.Sleep(500 * time.Millisecond)\n\n\taddr := s.Address().String()\n\n\topt := DefaultOption\n\n\tvar receive string\n\n\topt.NilCallServerMessageHandler = func(msg *protocol.Message) {\n\t\tfmt.Printf(\"receive msg from server: %s\\n\", msg.Payload)\n\t\treceive = string(msg.Payload)\n\t}\n\tclient := &Client{\n\t\toption: opt,\n\t}\n\n\terr := client.Connect(\"tcp\", addr)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to connect: %v\", err)\n\t}\n\tdefer client.Close()\n\n\targs := &Args{\n\t\tA: 10,\n\t\tB: 20,\n\t}\n\treply := &Reply{}\n\terr = client.Call(context.Background(), \"Bidirectional\", \"Mul\", args, reply)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to call: %v\", err)\n\t}\n\tif receive != \"abcde\" {\n\t\tt.Fatalf(\"expect abcde but got %s\", receive)\n\t}\n\tif reply.C != 200 {\n\t\tt.Fatalf(\"expect 200 but got %d\", reply.C)\n\t}\n\n}\n"
  },
  {
    "path": "client/connection.go",
    "content": "package client\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/share\"\n\t\"golang.org/x/net/websocket\"\n)\n\ntype ConnFactoryFn func(c *Client, network, address string) (net.Conn, error)\n\nvar ConnFactories = make(map[string]ConnFactoryFn)\n\nfunc init() {\n\tConnFactories[\"http\"] = newDirectHTTPConn\n\tConnFactories[\"kcp\"] = newDirectKCPConn\n\tConnFactories[\"quic\"] = newDirectQuicConn\n\tConnFactories[\"unix\"] = newDirectConn\n\tConnFactories[\"memu\"] = newMemuConn\n\tConnFactories[\"iouring\"] = newIOUringConn\n}\n\n// Connect connects the server via specified network.\nfunc (client *Client) Connect(network, address string) error {\n\tvar conn net.Conn\n\tvar err error\n\n\tswitch network {\n\tcase \"http\":\n\t\tconn, err = newDirectHTTPConn(client, network, address)\n\tcase \"ws\", \"wss\":\n\t\tconn, err = newDirectWSConn(client, network, address)\n\tdefault:\n\t\tfn := ConnFactories[network]\n\t\tif fn != nil {\n\t\t\tconn, err = fn(client, network, address)\n\t\t} else {\n\t\t\tconn, err = newDirectConn(client, network, address)\n\t\t}\n\t}\n\n\tif err == nil && conn != nil {\n\t\tif tc, ok := conn.(*net.TCPConn); ok && client.option.TCPKeepAlivePeriod > 0 {\n\t\t\t_ = tc.SetKeepAlive(true)\n\t\t\t_ = tc.SetKeepAlivePeriod(client.option.TCPKeepAlivePeriod)\n\t\t}\n\n\t\tif client.option.IdleTimeout != 0 {\n\t\t\t_ = conn.SetDeadline(time.Now().Add(client.option.IdleTimeout))\n\t\t}\n\n\t\tif client.Plugins != nil {\n\t\t\tconn, err = client.Plugins.DoConnCreated(conn)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tclient.Conn = conn\n\t\tclient.r = bufio.NewReaderSize(conn, ReaderBuffsize)\n\t\t// c.w = bufio.NewWriterSize(conn, WriterBuffsize)\n\n\t\t// start reading and writing since connected\n\t\tgo client.input()\n\n\t\tif client.option.Heartbeat && client.option.HeartbeatInterval > 0 {\n\t\t\tgo client.heartbeat()\n\t\t}\n\n\t}\n\n\tif err != nil && client.Plugins != nil {\n\t\tclient.Plugins.DoConnCreateFailed(network, address)\n\t}\n\n\treturn err\n}\n\nfunc newDirectConn(c *Client, network, address string) (net.Conn, error) {\n\tvar conn net.Conn\n\tvar tlsConn *tls.Conn\n\tvar err error\n\n\tif c == nil {\n\t\terr = fmt.Errorf(\"nil client\")\n\t\treturn nil, err\n\t}\n\n\tif c.option.TLSConfig != nil {\n\t\tdialer := &net.Dialer{\n\t\t\tTimeout: c.option.ConnectTimeout,\n\t\t}\n\t\ttlsConn, err = tls.DialWithDialer(dialer, network, address, c.option.TLSConfig)\n\t\t// or conn:= tls.Client(netConn, &config)\n\t\tconn = net.Conn(tlsConn)\n\t} else {\n\t\tconn, err = net.DialTimeout(network, address, c.option.ConnectTimeout)\n\t}\n\n\tif err != nil {\n\t\tlog.Warnf(\"failed to dial server: %v\", err)\n\t\treturn nil, err\n\t}\n\n\treturn conn, nil\n}\n\nvar connected = \"200 Connected to rpcx\"\n\nfunc newDirectHTTPConn(c *Client, network, address string) (net.Conn, error) {\n\tif c == nil {\n\t\treturn nil, errors.New(\"empty client\")\n\t}\n\tpath := c.option.RPCPath\n\tif path == \"\" {\n\t\tpath = share.DefaultRPCPath\n\t}\n\n\tvar conn net.Conn\n\tvar tlsConn *tls.Conn\n\tvar err error\n\n\tif c.option.TLSConfig != nil {\n\t\tdialer := &net.Dialer{\n\t\t\tTimeout: c.option.ConnectTimeout,\n\t\t}\n\t\ttlsConn, err = tls.DialWithDialer(dialer, \"tcp\", address, c.option.TLSConfig)\n\t\t// or conn:= tls.Client(netConn, &config)\n\n\t\tconn = net.Conn(tlsConn)\n\t} else {\n\t\tconn, err = net.DialTimeout(\"tcp\", address, c.option.ConnectTimeout)\n\t}\n\tif err != nil {\n\t\tlog.Errorf(\"failed to dial server: %v\", err)\n\t\treturn nil, err\n\t}\n\n\t_, err = io.WriteString(conn, \"CONNECT \"+path+\" HTTP/1.0\\n\\n\")\n\tif err != nil {\n\t\t// Dial() success but Write() failed here, close the successfully\n\t\t// created conn before return.\n\t\tconn.Close()\n\n\t\tlog.Errorf(\"failed to make CONNECT: %v\", err)\n\t\treturn nil, err\n\t}\n\n\t// Require successful HTTP response\n\t// before switching to RPC protocol.\n\tresp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: \"CONNECT\"})\n\tif err == nil && resp.Status == connected {\n\t\treturn conn, nil\n\t}\n\tif err == nil {\n\t\tlog.Errorf(\"unexpected HTTP response: %v\", err)\n\t\terr = errors.New(\"unexpected HTTP response: \" + resp.Status)\n\t}\n\tconn.Close()\n\treturn nil, &net.OpError{\n\t\tOp:   \"dial-http\",\n\t\tNet:  network + \" \" + address,\n\t\tAddr: nil,\n\t\tErr:  err,\n\t}\n}\n\nfunc newDirectWSConn(c *Client, network, address string) (net.Conn, error) {\n\tif c == nil {\n\t\treturn nil, errors.New(\"empty client\")\n\t}\n\tpath := c.option.RPCPath\n\tif path == \"\" {\n\t\tpath = share.DefaultRPCPath\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\n\t// url := \"ws://localhost:12345/ws\"\n\n\tvar url, origin string\n\tif network == \"ws\" {\n\t\turl = fmt.Sprintf(\"ws://%s%s\", address, path)\n\t\torigin = fmt.Sprintf(\"http://%s\", address)\n\t} else {\n\t\turl = fmt.Sprintf(\"wss://%s%s\", address, path)\n\t\torigin = fmt.Sprintf(\"https://%s\", address)\n\t}\n\n\tif c.option.TLSConfig != nil {\n\t\tconfig, erri := websocket.NewConfig(url, origin)\n\t\tif erri != nil {\n\t\t\treturn nil, erri\n\t\t}\n\t\tconfig.TlsConfig = c.option.TLSConfig\n\t\tconn, err = websocket.DialConfig(config)\n\t} else {\n\t\tconn, err = websocket.Dial(url, \"\", origin)\n\t}\n\n\treturn conn, err\n}\n"
  },
  {
    "path": "client/connection_iouring.go",
    "content": "package client\n\nimport (\n\t\"net\"\n)\n\n// experimental\nfunc newIOUringConn(c *Client, network, address string) (net.Conn, error) {\n\treturn newDirectConn(c, \"tcp\", address)\n}\n"
  },
  {
    "path": "client/connection_iouring_test.go",
    "content": "//go:build linux\n// +build linux\n\npackage client\n\n// func TestXClient_IOUring(t *testing.T) {\n// \ts := server.NewServer()\n// \ts.RegisterName(\"Arith\", new(Arith), \"\")\n// \tgo s.Serve(\"iouring\", \"127.0.0.1:8972\")\n// \tdefer s.Close()\n// \ttime.Sleep(500 * time.Millisecond)\n\n// \taddr := s.Address().String()\n\n// \td, err := NewPeer2PeerDiscovery(\"iouring@\"+addr, \"desc=a test service\")\n// \tif err != nil {\n// \t\tt.Fatalf(\"failed to NewPeer2PeerDiscovery: %v\", err)\n// \t}\n\n// \txclient := NewXClient(\"Arith\", Failtry, RandomSelect, d, DefaultOption)\n\n// \tdefer xclient.Close()\n\n// \targs := &Args{\n// \t\tA: 10,\n// \t\tB: 20,\n// \t}\n\n// \treply := &Reply{}\n// \terr = xclient.Call(context.Background(), \"Mul\", args, reply)\n// \tif err != nil {\n// \t\tt.Fatalf(\"failed to call: %v\", err)\n// \t}\n\n// \tif reply.C != 200 {\n// \t\tt.Fatalf(\"expect 200 but got %d\", reply.C)\n// \t}\n// }\n"
  },
  {
    "path": "client/connection_kcp.go",
    "content": "// +build kcp\n\npackage client\n\nimport (\n\t\"net\"\n\n\tkcp \"github.com/xtaci/kcp-go\"\n)\n\nfunc newDirectKCPConn(c *Client, network, address string) (net.Conn, error) {\n\treturn kcp.DialWithOptions(address, c.option.Block.(kcp.BlockCrypt), 10, 3)\n}\n"
  },
  {
    "path": "client/connection_memu.go",
    "content": "package client\n\nimport (\n\t\"net\"\n\n\t\"github.com/akutz/memconn\"\n)\n\nfunc newMemuConn(c *Client, network, address string) (net.Conn, error) {\n\treturn memconn.Dial(network, address)\n}\n"
  },
  {
    "path": "client/connection_nonkcp.go",
    "content": "// +build !kcp\n\npackage client\n\nimport (\n\t\"errors\"\n\t\"net\"\n)\n\nfunc newDirectKCPConn(c *Client, network, address string) (net.Conn, error) {\n\treturn nil, errors.New(\"kcp unsupported\")\n}\n"
  },
  {
    "path": "client/connection_nonquic.go",
    "content": "//go:build !quic\n// +build !quic\n\npackage client\n\nimport (\n\t\"errors\"\n\t\"net\"\n)\n\nfunc newDirectQuicConn(c *Client, network, address string) (net.Conn, error) {\n\treturn nil, errors.New(\"quic unsupported\")\n}\n"
  },
  {
    "path": "client/connection_quic.go",
    "content": "//go:build quic\n// +build quic\n\npackage client\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/smallnest/quick\"\n)\n\nfunc newDirectQuicConn(c *Client, network, address string) (net.Conn, error) {\n\ttlsConf := c.option.TLSConfig\n\tif tlsConf == nil {\n\t\ttlsConf = &tls.Config{InsecureSkipVerify: true}\n\t}\n\n\tif len(tlsConf.NextProtos) == 0 {\n\t\ttlsConf.NextProtos = []string{\"rpcx\"}\n\t}\n\n\tquicConfig := &quic.Config{}\n\n\treturn quick.Dial(address, tlsConf, quicConfig)\n}\n"
  },
  {
    "path": "client/connection_rdma.go",
    "content": "//go:build rdma\n// +build rdma\n\npackage client\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\t\"github.com/smallnest/rsocket\"\n)\n\nfunc init() {\n\tConnFactories[\"rdma\"] = newRDMAConn\n}\n\nfunc newRDMAConn(c *Client, network, address string) (net.Conn, error) {\n\tif network != \"rdma\" {\n\t\treturn nil, errors.New(\"network is not rdma\")\n\t}\n\n\treturn rsocket.DialTCP(address)\n}\n"
  },
  {
    "path": "client/discovery.go",
    "content": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n)\n\n// ServiceDiscoveryFilter can be used to filter services with customized logics.\n// Servers can register its services but clients can use the customized filter to select some services.\n// It returns true if ServiceDiscovery wants to use this service, otherwise it returns false.\ntype ServiceDiscoveryFilter func(kvp *KVPair) bool\n\n// ServiceDiscovery defines ServiceDiscovery of zookeeper, etcd and consul\ntype ServiceDiscovery interface {\n\tGetServices() []*KVPair       // return all services in the registry\n\tWatchService() chan []*KVPair // watch the change of services, it's a golang channel\n\tRemoveWatcher(ch chan []*KVPair)\n\tClone(servicePath string) (ServiceDiscovery, error)\n\tSetFilter(ServiceDiscoveryFilter) // set customized filter to filter services\n\tClose()\n}\n\ntype cachedServiceDiscovery struct {\n\tthreshold  int\n\tcachedFile string\n\tcached     []*KVPair\n\n\tchansLock sync.RWMutex\n\tchans     map[chan []*KVPair]chan []*KVPair\n\n\tServiceDiscovery\n}\n\n// CacheDiscovery caches the services in a file, it will return the cached services if the number of services is greater than threshold.\n// It is very useful when the register center is lost.\nfunc CacheDiscovery(threshold int, cachedFile string, discovery ServiceDiscovery) ServiceDiscovery {\n\tif cachedFile == \"\" {\n\t\tcachedFile = \".cache/discovery.json\"\n\t}\n\n\tcachedFileDir := filepath.Dir(cachedFile)\n\n\tif _, err := os.Stat(cachedFileDir); os.IsNotExist(err) {\n\t\t// 目录不存在，创建目录\n\t\tif err := os.MkdirAll(cachedFileDir, os.ModePerm); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\treturn &cachedServiceDiscovery{\n\t\tthreshold:        threshold,\n\t\tcachedFile:       cachedFile,\n\t\tServiceDiscovery: discovery,\n\t\tchans:            make(map[chan []*KVPair]chan []*KVPair),\n\t}\n}\n\nfunc (cd *cachedServiceDiscovery) GetServices() []*KVPair {\n\tkvPairs := cd.ServiceDiscovery.GetServices()\n\n\tn := len(kvPairs)\n\tif n > cd.threshold {\n\t\tif n > len(cd.cached) { // strictly we should compare the content of the cached file, but only compare the length for performance\n\t\t\tcd.cached = kvPairs\n\t\t\tcd.storeCached(kvPairs)\n\t\t}\n\n\t\treturn kvPairs\n\t}\n\n\tif len(cd.cached) == 0 {\n\t\tcd.loadCached()\n\t}\n\n\treturn cd.cached\n}\n\nfunc (cd *cachedServiceDiscovery) WatchService() chan []*KVPair {\n\tch := cd.ServiceDiscovery.WatchService()\n\n\tcachedCh := make(chan []*KVPair, 10)\n\tcd.chansLock.Lock()\n\tcd.chans[cachedCh] = ch\n\tcd.chansLock.Unlock()\n\n\tgo func() {\n\t\tdefer recover()\n\n\t\tfor {\n\t\t\tkvPairs, ok := <-ch\n\t\t\tif !ok {\n\t\t\t\tclose(cachedCh)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tn := len(kvPairs)\n\t\t\tif n > len(cd.cached) {\n\t\t\t\tcd.cached = kvPairs\n\t\t\t\tcd.storeCached(kvPairs)\n\t\t\t}\n\n\t\t\tcachedCh <- kvPairs\n\t\t}\n\t}()\n\n\treturn cachedCh\n}\n\nfunc (cd *cachedServiceDiscovery) RemoveWatcher(ch chan []*KVPair) {\n\tcd.chansLock.Lock()\n\torigin := cd.chans[ch]\n\tdelete(cd.chans, ch)\n\tcd.chansLock.Unlock()\n\n\tif origin != nil {\n\t\tcd.ServiceDiscovery.RemoveWatcher(origin)\n\t}\n\n}\n\nfunc (cd *cachedServiceDiscovery) storeCached(kvPairs []*KVPair) {\n\tdata, _ := json.Marshal(kvPairs)\n\tos.WriteFile(cd.cachedFile, data, 0644)\n}\n\nfunc (cd *cachedServiceDiscovery) loadCached() (kvPairs []*KVPair) {\n\tdata, err := os.ReadFile(cd.cachedFile)\n\tif err != nil || len(data) == 0 {\n\t\treturn\n\t}\n\n\tjson.Unmarshal(data, &kvPairs)\n\n\treturn kvPairs\n}\n"
  },
  {
    "path": "client/dns_discovery.go",
    "content": "package client\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/smallnest/rpcx/log\"\n)\n\n// DNSDiscovery is based on DNS a record.\n// You must set port and network info when you create the DNSDiscovery.\ntype DNSDiscovery struct {\n\tdomain  string\n\tnetwork string\n\tport    int\n\td       time.Duration\n\n\tpairsMu sync.RWMutex\n\tpairs   []*KVPair\n\tchans   []chan []*KVPair\n\n\tmu sync.Mutex\n\n\tfilter ServiceDiscoveryFilter\n\n\tstopCh chan struct{}\n}\n\n// NewDNSDiscovery returns a new DNSDiscovery.\nfunc NewDNSDiscovery(domain string, network string, port int, d time.Duration) (*DNSDiscovery, error) {\n\tdiscovery := &DNSDiscovery{domain: domain, network: network, port: port, d: d}\n\tdiscovery.lookup()\n\tgo discovery.watch()\n\treturn discovery, nil\n}\n\n// Clone clones this ServiceDiscovery with new servicePath.\nfunc (d *DNSDiscovery) Clone(servicePath string) (ServiceDiscovery, error) {\n\treturn NewDNSDiscovery(d.domain, d.network, d.port, d.d)\n}\n\n// SetFilter sets the filer.\nfunc (d *DNSDiscovery) SetFilter(filter ServiceDiscoveryFilter) {\n\td.filter = filter\n}\n\n// GetServices returns the static server\nfunc (d *DNSDiscovery) GetServices() []*KVPair {\n\td.pairsMu.RLock()\n\tdefer d.pairsMu.RUnlock()\n\treturn d.pairs\n}\n\n// WatchService returns a nil chan.\nfunc (d *DNSDiscovery) WatchService() chan []*KVPair {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tch := make(chan []*KVPair, 10)\n\td.chans = append(d.chans, ch)\n\treturn ch\n}\n\nfunc (d *DNSDiscovery) RemoveWatcher(ch chan []*KVPair) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tvar chans []chan []*KVPair\n\tfor _, c := range d.chans {\n\t\tif c == ch {\n\t\t\tcontinue\n\t\t}\n\n\t\tchans = append(chans, c)\n\t}\n\n\td.chans = chans\n}\n\nfunc (d *DNSDiscovery) lookup() {\n\tvar pairs []*KVPair // latest servers\n\n\tips, err := net.LookupIP(d.domain)\n\tif err != nil {\n\t\tlog.Errorf(\"failed to lookup %s: %v\", d.domain, err)\n\t\treturn\n\t}\n\n\tfor _, ip := range ips {\n\t\tpair := &KVPair{Key: fmt.Sprintf(\"%s@%s:%d\", d.network, ip.String(), d.port)}\n\t\tif d.filter != nil && !d.filter(pair) {\n\t\t\tcontinue\n\t\t}\n\t\tpairs = append(pairs, pair)\n\t}\n\n\tif len(pairs) > 0 {\n\t\tsort.Slice(pairs, func(i, j int) bool {\n\t\t\treturn pairs[i].Key < pairs[j].Key\n\t\t})\n\t}\n\n\td.pairsMu.Lock()\n\td.pairs = pairs\n\td.pairsMu.Unlock()\n\n\td.mu.Lock()\n\tfor _, ch := range d.chans {\n\t\tch := ch\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\trecover()\n\t\t\t}()\n\t\t\tselect {\n\t\t\tcase ch <- pairs:\n\t\t\tcase <-time.After(time.Minute):\n\t\t\t\tlog.Warn(\"chan is full and new change has been dropped\")\n\t\t\t}\n\t\t}()\n\t}\n\td.mu.Unlock()\n}\n\nfunc (d *DNSDiscovery) watch() {\n\ttick := time.NewTicker(d.d)\n\tdefer tick.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-d.stopCh:\n\t\t\treturn\n\t\tcase <-tick.C:\n\t\t\td.lookup()\n\t\t}\n\t}\n}\n\nfunc (d *DNSDiscovery) Close() {\n\tclose(d.stopCh)\n}\n"
  },
  {
    "path": "client/failmode_enumer.go",
    "content": "// Code generated by \"enumer -type=FailMode\"; DO NOT EDIT.\n\npackage client\n\nimport (\n\t\"fmt\"\n)\n\nconst _FailModeName = \"FailoverFailfastFailtryFailbackup\"\n\nvar _FailModeIndex = [...]uint8{0, 8, 16, 23, 33}\n\nfunc (i FailMode) String() string {\n\tif i < 0 || i >= FailMode(len(_FailModeIndex)-1) {\n\t\treturn fmt.Sprintf(\"FailMode(%d)\", i)\n\t}\n\treturn _FailModeName[_FailModeIndex[i]:_FailModeIndex[i+1]]\n}\n\nvar _FailModeValues = []FailMode{0, 1, 2, 3}\n\nvar _FailModeNameToValueMap = map[string]FailMode{\n\t_FailModeName[0:8]:   0,\n\t_FailModeName[8:16]:  1,\n\t_FailModeName[16:23]: 2,\n\t_FailModeName[23:33]: 3,\n}\n\n// FailModeString retrieves an enum value from the enum constants string name.\n// Throws an error if the param is not part of the enum.\nfunc FailModeString(s string) (FailMode, error) {\n\tif val, ok := _FailModeNameToValueMap[s]; ok {\n\t\treturn val, nil\n\t}\n\treturn 0, fmt.Errorf(\"%s does not belong to FailMode values\", s)\n}\n\n// FailModeValues returns all values of the enum\nfunc FailModeValues() []FailMode {\n\treturn _FailModeValues\n}\n\n// IsAFailMode returns \"true\" if the value is listed in the enum definition. \"false\" otherwise\nfunc (i FailMode) IsAFailMode() bool {\n\tfor _, v := range _FailModeValues {\n\t\tif i == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "client/geo_utils.go",
    "content": "package client\n\nimport (\n\t\"math\"\n)\n\n// https://gist.github.com/cdipaolo/d3f8db3848278b49db68\nfunc getDistanceFrom(lat1, lon1, lat2, lon2 float64) float64 {\n\tvar la1, lo1, la2, lo2, r float64\n\tla1 = lat1 * math.Pi / 180\n\tlo1 = lon1 * math.Pi / 180\n\tla2 = lat2 * math.Pi / 180\n\tlo2 = lon2 * math.Pi / 180\n\n\tr = 6378100 // Earth radius in METERS\n\n\t// calculate\n\th := hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1)\n\n\treturn 2 * r * math.Asin(math.Sqrt(h))\n}\n\nfunc hsin(theta float64) float64 {\n\treturn math.Pow(math.Sin(theta/2), 2)\n}\n"
  },
  {
    "path": "client/hash_utils.go",
    "content": "package client\n\nimport (\n\t\"fmt\"\n\t\"hash/fnv\"\n)\n\n// Hash consistently chooses a hash bucket number in the range [0, numBuckets) for the given key. numBuckets must be >= 1.\nfunc Hash(key uint64, buckets int32) int32 {\n\tif buckets <= 0 {\n\t\tbuckets = 1\n\t}\n\n\tvar b, j int64\n\n\tfor j < int64(buckets) {\n\t\tb = j\n\t\tkey = key*2862933555777941757 + 1\n\t\tj = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))\n\t}\n\n\treturn int32(b)\n}\n\n// HashString get a hash value of a string\nfunc HashString(s string) uint64 {\n\th := fnv.New64a()\n\th.Write([]byte(s))\n\treturn h.Sum64()\n}\n\n// HashServiceAndArgs define a hash function\ntype HashServiceAndArgs func(len int, options ...interface{}) int\n\n// ConsistentFunction define a hash function\n// Return service address, like \"tcp@127.0.0.1:8970\"\ntype ConsistentAddrStrFunction func(options ...interface{}) string\n\nfunc genKey(options ...interface{}) uint64 {\n\tkeyString := \"\"\n\tfor _, opt := range options {\n\t\tkeyString = keyString + \"/\" + toString(opt)\n\t}\n\n\treturn HashString(keyString)\n}\n\n// JumpConsistentHash selects a server by serviceMethod and args\nfunc JumpConsistentHash(len int, options ...interface{}) int {\n\treturn int(Hash(genKey(options...), int32(len)))\n}\n\nfunc toString(obj interface{}) string {\n\treturn fmt.Sprintf(\"%v\", obj)\n}\n"
  },
  {
    "path": "client/mdns_discovery.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/url\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/grandcat/zeroconf\"\n\t\"github.com/smallnest/rpcx/log\"\n)\n\ntype serviceMeta struct {\n\tService        string `json:\"service,omitempty\"`\n\tMeta           string `json:\"meta,omitempty\"`\n\tServiceAddress string `json:\"service_address,omitempty\"`\n}\n\n// MDNSDiscovery is a mdns service discovery.\n// It always returns the registered servers in mdns.\ntype MDNSDiscovery struct {\n\tTimeout       time.Duration\n\tWatchInterval time.Duration\n\tdomain        string\n\tservice       string\n\tpairsMu       sync.RWMutex\n\tpairs         []*KVPair\n\tchans         []chan []*KVPair\n\n\tmu sync.Mutex\n\n\tfilter ServiceDiscoveryFilter\n\n\tstopCh chan struct{}\n}\n\n// NewMDNSDiscovery returns a new MDNSDiscovery.\n// If domain is empty, use \"local.\" in default.\nfunc NewMDNSDiscovery(service string, timeout time.Duration, watchInterval time.Duration, domain string) (*MDNSDiscovery, error) {\n\tif domain == \"\" {\n\t\tdomain = \"local.\"\n\t}\n\td := &MDNSDiscovery{service: service, Timeout: timeout, WatchInterval: watchInterval, domain: domain}\n\td.stopCh = make(chan struct{})\n\n\tvar err error\n\td.pairsMu.Lock()\n\td.pairs, err = d.browse()\n\td.pairsMu.Unlock()\n\tif err != nil {\n\t\tlog.Warnf(\"failed to browse services: %v\", err)\n\t}\n\tgo d.watch()\n\treturn d, nil\n}\n\n// Clone clones this ServiceDiscovery with new servicePath.\nfunc (d *MDNSDiscovery) Clone(servicePath string) (ServiceDiscovery, error) {\n\treturn NewMDNSDiscovery(servicePath, d.Timeout, d.WatchInterval, d.domain)\n}\n\n// SetFilter sets the filer.\nfunc (d *MDNSDiscovery) SetFilter(filter ServiceDiscoveryFilter) {\n\td.filter = filter\n}\n\n// GetServices returns the servers\nfunc (d *MDNSDiscovery) GetServices() []*KVPair {\n\td.pairsMu.RLock()\n\tdefer d.pairsMu.RUnlock()\n\n\treturn d.pairs\n}\n\n// WatchService returns a nil chan.\nfunc (d *MDNSDiscovery) WatchService() chan []*KVPair {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tch := make(chan []*KVPair, 10)\n\td.chans = append(d.chans, ch)\n\treturn ch\n}\n\nfunc (d *MDNSDiscovery) RemoveWatcher(ch chan []*KVPair) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tvar chans []chan []*KVPair\n\tfor _, c := range d.chans {\n\t\tif c == ch {\n\t\t\tcontinue\n\t\t}\n\n\t\tchans = append(chans, c)\n\t}\n\n\td.chans = chans\n}\n\nfunc (d *MDNSDiscovery) watch() {\n\tt := time.NewTicker(d.WatchInterval)\n\n\tfor {\n\t\tselect {\n\t\tcase <-d.stopCh:\n\t\t\tt.Stop()\n\t\t\tlog.Info(\"discovery has been closed\")\n\t\t\treturn\n\t\tcase <-t.C:\n\t\t\tpairs, err := d.browse()\n\t\t\tif err == nil {\n\t\t\t\td.pairsMu.Lock()\n\t\t\t\td.pairs = pairs\n\t\t\t\td.pairsMu.Unlock()\n\n\t\t\t\td.mu.Lock()\n\t\t\t\tfor _, ch := range d.chans {\n\t\t\t\t\tch := ch\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tdefer func() {\n\t\t\t\t\t\t\trecover()\n\t\t\t\t\t\t}()\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase ch <- pairs:\n\t\t\t\t\t\tcase <-time.After(time.Minute):\n\t\t\t\t\t\t\tlog.Warn(\"chan is full and new change has ben dropped\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t\td.mu.Unlock()\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (d *MDNSDiscovery) browse() ([]*KVPair, error) {\n\tresolver, err := zeroconf.NewResolver(nil)\n\tif err != nil {\n\t\tlog.Warnf(\"Failed to initialize resolver: %v\", err)\n\t\treturn nil, err\n\t}\n\tentries := make(chan *zeroconf.ServiceEntry)\n\n\tvar totalServices []*KVPair\n\tvar services []*serviceMeta\n\n\tdone := make(chan struct{})\n\tgo func(results <-chan *zeroconf.ServiceEntry) {\n\t\tfor entry := range entries {\n\t\t\ts, _ := url.QueryUnescape(entry.Text[0])\n\t\t\terr := json.Unmarshal([]byte(s), &services)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warnf(\"Failed to browse: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, sm := range services {\n\n\t\t\t\tpair := &KVPair{\n\t\t\t\t\tKey:   sm.ServiceAddress,\n\t\t\t\t\tValue: sm.Meta,\n\t\t\t\t}\n\t\t\t\tif d.filter != nil && !d.filter(pair) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttotalServices = append(totalServices, pair)\n\t\t\t}\n\t\t}\n\n\t\tclose(done)\n\t}(entries)\n\n\tctx, cancel := context.WithTimeout(context.Background(), d.Timeout)\n\tdefer cancel()\n\terr = resolver.Browse(ctx, \"_rpcxservices\", d.domain, entries)\n\tif err != nil {\n\t\tlog.Warnf(\"Failed to browse: %v\", err)\n\t}\n\n\t<-done\n\treturn totalServices, nil\n}\n\nfunc (d *MDNSDiscovery) Close() {\n\tclose(d.stopCh)\n}\n"
  },
  {
    "path": "client/mode.go",
    "content": "package client\n\n// FailMode decides how clients action when clients fail to invoke services\ntype FailMode int\n\nconst (\n\t// Failover selects another server automaticaly\n\tFailover FailMode = iota\n\t// Failfast returns error immediately\n\tFailfast\n\t// Failtry use current client again\n\tFailtry\n\t// Failbackup select another server if the first server doesn't respond in specified time and use the fast response.\n\tFailbackup\n)\n\n// SelectMode defines the algorithm of selecting a services from candidates.\ntype SelectMode int\n\nconst (\n\t// RandomSelect is selecting randomly\n\tRandomSelect SelectMode = iota\n\t// RoundRobin is selecting by round robin\n\tRoundRobin\n\t// WeightedRoundRobin is selecting by weighted round robin\n\tWeightedRoundRobin\n\t// WeightedICMP is selecting by weighted Ping time\n\tWeightedICMP\n\t// ConsistentHash is selecting by hashing\n\tConsistentHash\n\t// Closest is selecting the closest server\n\tClosest\n\n\t// SelectByUser is selecting by implementation of users\n\tSelectByUser = 1000\n)\n"
  },
  {
    "path": "client/multiple_servers_discovery.go",
    "content": "package client\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/smallnest/rpcx/log\"\n)\n\n// MultipleServersDiscovery is a multiple servers service discovery.\n// It always returns the current servers and users can change servers dynamically.\ntype MultipleServersDiscovery struct {\n\tpairsMu sync.RWMutex\n\tpairs   []*KVPair\n\tchans   []chan []*KVPair\n\n\tmu sync.Mutex\n}\n\n// NewMultipleServersDiscovery returns a new MultipleServersDiscovery.\nfunc NewMultipleServersDiscovery(pairs []*KVPair) (*MultipleServersDiscovery, error) {\n\treturn &MultipleServersDiscovery{\n\t\tpairs: pairs,\n\t}, nil\n}\n\n// Clone clones this ServiceDiscovery with new servicePath.\nfunc (d *MultipleServersDiscovery) Clone(servicePath string) (ServiceDiscovery, error) {\n\treturn d, nil\n}\n\n// SetFilter sets the filer.\nfunc (d *MultipleServersDiscovery) SetFilter(filter ServiceDiscoveryFilter) {\n}\n\n// GetServices returns the configured server\nfunc (d *MultipleServersDiscovery) GetServices() []*KVPair {\n\td.pairsMu.RLock()\n\tdefer d.pairsMu.RUnlock()\n\n\treturn d.pairs\n}\n\n// WatchService returns a nil chan.\nfunc (d *MultipleServersDiscovery) WatchService() chan []*KVPair {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tch := make(chan []*KVPair, 10)\n\td.chans = append(d.chans, ch)\n\treturn ch\n}\n\nfunc (d *MultipleServersDiscovery) RemoveWatcher(ch chan []*KVPair) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tvar chans []chan []*KVPair\n\tfor _, c := range d.chans {\n\t\tif c == ch {\n\t\t\tcontinue\n\t\t}\n\n\t\tchans = append(chans, c)\n\t}\n\n\td.chans = chans\n}\n\n// Update is used to update servers at runtime.\nfunc (d *MultipleServersDiscovery) Update(pairs []*KVPair) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tfor _, ch := range d.chans {\n\t\tch := ch\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\trecover()\n\t\t\t}()\n\t\t\tselect {\n\t\t\tcase ch <- pairs:\n\t\t\tcase <-time.After(time.Minute):\n\t\t\t\tlog.Warn(\"chan is full and new change has been dropped\")\n\t\t\t}\n\t\t}()\n\t}\n\n\td.pairsMu.Lock()\n\td.pairs = pairs\n\td.pairsMu.Unlock()\n}\n\nfunc (d *MultipleServersDiscovery) Close() {\n}\n"
  },
  {
    "path": "client/oneclient.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/smallnest/rpcx/share\"\n\n\tmultierror \"github.com/hashicorp/go-multierror\"\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\n// OneClient wraps servicesPath and XClients.\n// Users can use a shared oneclient to access multiple services.\ntype OneClient struct {\n\txclients map[string]XClient\n\tmu       sync.RWMutex\n\n\tfailMode   FailMode\n\tselectMode SelectMode\n\tdiscovery  ServiceDiscovery\n\toption     Option\n\n\tselectors map[string]Selector\n\tPlugins   PluginContainer\n\tlatitude  float64\n\tlongitude float64\n\tauth      string\n\n\tserverMessageChan chan<- *protocol.Message\n}\n\n// NewOneClient creates a OneClient that supports service discovery and service governance.\nfunc NewOneClient(failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) *OneClient {\n\treturn &OneClient{\n\t\tfailMode:   failMode,\n\t\tselectMode: selectMode,\n\t\tdiscovery:  discovery,\n\t\toption:     option,\n\t\txclients:   make(map[string]XClient),\n\t\tselectors:  make(map[string]Selector),\n\t}\n}\n\n// NewBidirectionalOneClient creates a new xclient that can receive notifications from servers.\nfunc NewBidirectionalOneClient(failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) *OneClient {\n\treturn &OneClient{\n\t\tfailMode:          failMode,\n\t\tselectMode:        selectMode,\n\t\tdiscovery:         discovery,\n\t\toption:            option,\n\t\txclients:          make(map[string]XClient),\n\t\tselectors:         make(map[string]Selector),\n\t\tserverMessageChan: serverMessageChan,\n\t}\n}\n\n// SetSelector sets customized selector by users.\nfunc (c *OneClient) SetSelector(servicePath string, s Selector) {\n\tc.mu.Lock()\n\tc.selectors[servicePath] = s\n\tif xclient, ok := c.xclients[servicePath]; ok {\n\t\txclient.SetSelector(s)\n\t}\n\tc.mu.Unlock()\n}\n\n// SetPlugins sets client's plugins.\nfunc (c *OneClient) SetPlugins(plugins PluginContainer) {\n\tc.Plugins = plugins\n\tc.mu.RLock()\n\tfor _, v := range c.xclients {\n\t\tv.SetPlugins(plugins)\n\t}\n\tc.mu.RUnlock()\n}\n\nfunc (c *OneClient) GetPlugins() PluginContainer {\n\treturn c.Plugins\n}\n\n// ConfigGeoSelector sets location of client's latitude and longitude,\n// and use newGeoSelector.\nfunc (c *OneClient) ConfigGeoSelector(latitude, longitude float64) {\n\tc.selectMode = Closest\n\tc.latitude = latitude\n\tc.longitude = longitude\n\n\tc.mu.RLock()\n\tfor _, v := range c.xclients {\n\t\tv.ConfigGeoSelector(latitude, longitude)\n\t}\n\tc.mu.RUnlock()\n}\n\n// Auth sets s token for Authentication.\nfunc (c *OneClient) Auth(auth string) {\n\tc.auth = auth\n\tc.mu.RLock()\n\tfor _, v := range c.xclients {\n\t\tv.Auth(auth)\n\t}\n\tc.mu.RUnlock()\n}\n\n// Go invokes the function asynchronously. It returns the Call structure representing the invocation. The done channel will signal when the call is complete by returning the same Call object. If done is nil, Go will allocate a new channel. If non-nil, done must be buffered or Go will deliberately crash.\n// It does not use FailMode.\nfunc (c *OneClient) Go(ctx context.Context, servicePath string, serviceMethod string, args interface{}, reply interface{}, done chan *Call) (*Call, error) {\n\tc.mu.RLock()\n\txclient := c.xclients[servicePath]\n\tc.mu.RUnlock()\n\n\tif xclient == nil {\n\t\tvar err error\n\t\tc.mu.Lock()\n\t\txclient = c.xclients[servicePath]\n\t\tif xclient == nil {\n\t\t\txclient, err = c.newXClient(servicePath)\n\t\t\tc.xclients[servicePath] = xclient\n\t\t}\n\t\tc.mu.Unlock()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn xclient.Go(ctx, serviceMethod, args, reply, done)\n}\n\nfunc (c *OneClient) newXClient(servicePath string) (xclient XClient, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tif e, ok := r.(error); ok {\n\t\t\t\terr = e\n\t\t\t} else {\n\t\t\t\terr = fmt.Errorf(\"%v\", r)\n\t\t\t}\n\t\t}\n\t}()\n\n\td, err := c.discovery.Clone(servicePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif c.serverMessageChan == nil {\n\t\txclient = NewXClient(servicePath, c.failMode, c.selectMode, d, c.option)\n\t} else {\n\t\txclient = NewBidirectionalXClient(servicePath, c.failMode, c.selectMode, d, c.option, c.serverMessageChan)\n\t}\n\n\tif c.Plugins != nil {\n\t\txclient.SetPlugins(c.Plugins)\n\t}\n\n\tif s, ok := c.selectors[servicePath]; ok {\n\t\txclient.SetSelector(s)\n\t}\n\n\tif c.selectMode == Closest {\n\t\txclient.ConfigGeoSelector(c.latitude, c.longitude)\n\t}\n\n\tif c.auth != \"\" {\n\t\txclient.Auth(c.auth)\n\t}\n\n\treturn xclient, err\n}\n\n// Call invokes the named function, waits for it to complete, and returns its error status.\n// It handles errors base on FailMode.\nfunc (c *OneClient) Call(ctx context.Context, servicePath string, serviceMethod string, args interface{}, reply interface{}) error {\n\tc.mu.RLock()\n\txclient := c.xclients[servicePath]\n\tc.mu.RUnlock()\n\n\tif xclient == nil {\n\t\tvar err error\n\t\tc.mu.Lock()\n\t\txclient = c.xclients[servicePath]\n\t\tif xclient == nil {\n\t\t\txclient, err = c.newXClient(servicePath)\n\t\t\tc.xclients[servicePath] = xclient\n\t\t}\n\t\tc.mu.Unlock()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn xclient.Call(ctx, serviceMethod, args, reply)\n}\n\nfunc (c *OneClient) SendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error) {\n\tservicePath := r.ServicePath\n\n\tc.mu.RLock()\n\txclient := c.xclients[servicePath]\n\tc.mu.RUnlock()\n\n\tif xclient == nil {\n\t\tvar err error\n\t\tc.mu.Lock()\n\t\txclient = c.xclients[servicePath]\n\t\tif xclient == nil {\n\t\t\txclient, err = c.newXClient(servicePath)\n\t\t\tc.xclients[servicePath] = xclient\n\t\t}\n\t\tc.mu.Unlock()\n\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\treturn xclient.SendRaw(ctx, r)\n}\n\n// Broadcast sends requests to all servers and Success only when all servers return OK.\n// FailMode and SelectMode are meanless for this method.\n// Please set timeout to avoid hanging.\nfunc (c *OneClient) Broadcast(ctx context.Context, servicePath string, serviceMethod string, args interface{}, reply interface{}) error {\n\tc.mu.RLock()\n\txclient := c.xclients[servicePath]\n\tc.mu.RUnlock()\n\n\tif xclient == nil {\n\t\tvar err error\n\t\tc.mu.Lock()\n\t\txclient = c.xclients[servicePath]\n\t\tif xclient == nil {\n\t\t\txclient, err = c.newXClient(servicePath)\n\t\t\tc.xclients[servicePath] = xclient\n\t\t}\n\t\tc.mu.Unlock()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn xclient.Broadcast(ctx, serviceMethod, args, reply)\n}\n\n// Fork sends requests to all servers and Success once one server returns OK.\n// FailMode and SelectMode are meanless for this method.\nfunc (c *OneClient) Fork(ctx context.Context, servicePath string, serviceMethod string, args interface{}, reply interface{}) error {\n\tc.mu.RLock()\n\txclient := c.xclients[servicePath]\n\tc.mu.RUnlock()\n\n\tif xclient == nil {\n\t\tvar err error\n\t\tc.mu.Lock()\n\t\txclient = c.xclients[servicePath]\n\t\tif xclient == nil {\n\t\t\txclient, err = c.newXClient(servicePath)\n\t\t\tc.xclients[servicePath] = xclient\n\t\t}\n\t\tc.mu.Unlock()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn xclient.Fork(ctx, serviceMethod, args, reply)\n}\n\nfunc (c *OneClient) SendFile(ctx context.Context, fileName string, rateInBytesPerSecond int64, meta map[string]string) error {\n\tc.mu.RLock()\n\txclient := c.xclients[share.SendFileServiceName]\n\tc.mu.RUnlock()\n\n\tif xclient == nil {\n\t\tvar err error\n\t\tc.mu.Lock()\n\t\txclient = c.xclients[share.SendFileServiceName]\n\t\tif xclient == nil {\n\t\t\txclient, err = c.newXClient(share.SendFileServiceName)\n\t\t\tc.xclients[share.SendFileServiceName] = xclient\n\t\t}\n\t\tc.mu.Unlock()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn xclient.SendFile(ctx, fileName, rateInBytesPerSecond, meta)\n}\n\nfunc (c *OneClient) DownloadFile(ctx context.Context, requestFileName string, saveTo io.Writer, meta map[string]string) error {\n\tc.mu.RLock()\n\txclient := c.xclients[share.SendFileServiceName]\n\tc.mu.RUnlock()\n\n\tif xclient == nil {\n\t\tvar err error\n\t\tc.mu.Lock()\n\t\txclient = c.xclients[share.SendFileServiceName]\n\t\tif xclient == nil {\n\t\t\txclient, err = c.newXClient(share.SendFileServiceName)\n\t\t\tc.xclients[share.SendFileServiceName] = xclient\n\t\t}\n\t\tc.mu.Unlock()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn xclient.DownloadFile(ctx, requestFileName, saveTo, meta)\n}\n\nfunc (c *OneClient) Stream(ctx context.Context, meta map[string]string) (net.Conn, error) {\n\tc.mu.RLock()\n\txclient := c.xclients[share.StreamServiceName]\n\tc.mu.RUnlock()\n\n\tif xclient == nil {\n\t\tvar err error\n\t\tc.mu.Lock()\n\t\txclient = c.xclients[share.StreamServiceName]\n\t\tif xclient == nil {\n\t\t\txclient, err = c.newXClient(share.StreamServiceName)\n\t\t\tc.xclients[share.StreamServiceName] = xclient\n\t\t}\n\t\tc.mu.Unlock()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn xclient.Stream(ctx, meta)\n}\n\n// Close closes all xclients and its underlying connections to services.\nfunc (c *OneClient) Close() error {\n\tvar result error\n\n\tc.mu.RLock()\n\tfor _, v := range c.xclients {\n\t\terr := v.Close()\n\t\tif err != nil {\n\t\t\tresult = multierror.Append(result, err)\n\t\t}\n\t}\n\tc.mu.RUnlock()\n\n\treturn result\n}\n"
  },
  {
    "path": "client/oneclient_pool.go",
    "content": "package client\n\nimport (\n\t\"sync/atomic\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\n// OneClientPool is a oneclient pool with fixed size.\n// It uses roundrobin algorithm to call its xclients.\n// All oneclients share the same configurations such as ServiceDiscovery and serverMessageChan.\ntype OneClientPool struct {\n\tcount      uint64\n\tindex      uint64\n\toneclients []*OneClient\n\tauth       string\n\tPlugins    PluginContainer\n\n\tfailMode          FailMode\n\tselectMode        SelectMode\n\tdiscovery         ServiceDiscovery\n\toption            Option\n\tserverMessageChan chan<- *protocol.Message\n}\n\n// NewOneClientPool creates a fixed size OneClient pool.\nfunc NewOneClientPool(count int, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) *OneClientPool {\n\tpool := &OneClientPool{\n\t\tcount:      uint64(count),\n\t\toneclients: make([]*OneClient, count),\n\t\tfailMode:   failMode,\n\t\tselectMode: selectMode,\n\t\tdiscovery:  discovery,\n\t\toption:     option,\n\t}\n\n\tfor i := 0; i < count; i++ {\n\t\toneclient := NewOneClient(failMode, selectMode, discovery, option)\n\t\tpool.oneclients[i] = oneclient\n\t}\n\treturn pool\n}\n\n// NewBidirectionalOneClientPool creates a BidirectionalOneClient pool with fixed size.\nfunc NewBidirectionalOneClientPool(count int, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) *OneClientPool {\n\tpool := &OneClientPool{\n\t\tcount:             uint64(count),\n\t\toneclients:        make([]*OneClient, count),\n\t\tfailMode:          failMode,\n\t\tselectMode:        selectMode,\n\t\tdiscovery:         discovery,\n\t\toption:            option,\n\t\tserverMessageChan: serverMessageChan,\n\t}\n\n\tfor i := 0; i < count; i++ {\n\t\toneclient := NewBidirectionalOneClient(failMode, selectMode, discovery, option, serverMessageChan)\n\t\tpool.oneclients[i] = oneclient\n\t}\n\treturn pool\n}\n\n// Auth sets s token for Authentication.\nfunc (p *OneClientPool) Auth(auth string) {\n\tp.auth = auth\n\n\tfor _, v := range p.oneclients {\n\t\tv.Auth(auth)\n\t}\n}\n\n// SetPlugins sets client's plugins.\nfunc (p *OneClientPool) SetPlugins(plugins PluginContainer) {\n\tp.Plugins = plugins\n\n\tfor _, v := range p.oneclients {\n\t\tv.SetPlugins(plugins)\n\t}\n}\n\n// GetPlugins returns client's plugins.\nfunc (p *OneClientPool) GetPlugins() PluginContainer {\n\treturn p.Plugins\n}\n\n// Get returns a OneClient.\n// It does not remove this OneClient from its cache so you don't need to put it back.\n// Don't close this OneClient because maybe other goroutines are using this OneClient.\nfunc (p *OneClientPool) Get() *OneClient {\n\ti := atomic.AddUint64(&p.index, 1)\n\tpicked := int(i % p.count)\n\treturn p.oneclients[picked]\n}\n\n// Close this pool.\n// Please make sure it won't be used any more.\nfunc (p *OneClientPool) Close() {\n\tfor _, c := range p.oneclients {\n\t\tc.Close()\n\t}\n\tp.oneclients = nil\n}\n"
  },
  {
    "path": "client/oneclient_pool_test.go",
    "content": "package client\n\nimport (\n\t\"testing\"\n)\n\nfunc TestOneClientPool_SetPlugins(t *testing.T) {\n\t// Create a simple discovery\n\tpairs := []*KVPair{\n\t\t{Key: \"tcp@127.0.0.1:8972\", Value: \"\"},\n\t}\n\tdiscovery, err := NewMultipleServersDiscovery(pairs)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create discovery: %v\", err)\n\t}\n\tdefer discovery.Close()\n\n\t// Create a pool\n\tpool := NewOneClientPool(3, Failtry, RandomSelect, discovery, DefaultOption)\n\tdefer pool.Close()\n\n\t// Create plugins\n\tplugins := NewPluginContainer()\n\ttp := &testPlugin{name: \"test-plugin\"}\n\tplugins.Add(tp)\n\n\t// Test SetPlugins\n\tpool.SetPlugins(plugins)\n\n\t// Verify plugins are set on pool\n\tif pool.GetPlugins() == nil {\n\t\tt.Error(\"plugins should not be nil after SetPlugins\")\n\t}\n\tif pool.GetPlugins() != plugins {\n\t\tt.Error(\"pool plugins should be the same as the set plugins\")\n\t}\n\n\t// Verify plugins are set on all oneclients\n\tfor i := 0; i < 3; i++ {\n\t\toneclient := pool.Get()\n\t\tif oneclient.GetPlugins() == nil {\n\t\t\tt.Errorf(\"oneclient %d plugins should not be nil\", i)\n\t\t}\n\t\tif oneclient.GetPlugins() != plugins {\n\t\t\tt.Errorf(\"oneclient %d plugins should be the same as the set plugins\", i)\n\t\t}\n\t}\n}\n\nfunc TestOneClientPool_GetPlugins(t *testing.T) {\n\t// Create a simple discovery\n\tpairs := []*KVPair{\n\t\t{Key: \"tcp@127.0.0.1:8972\", Value: \"\"},\n\t}\n\tdiscovery, err := NewMultipleServersDiscovery(pairs)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create discovery: %v\", err)\n\t}\n\tdefer discovery.Close()\n\n\t// Create a pool\n\tpool := NewOneClientPool(2, Failtry, RandomSelect, discovery, DefaultOption)\n\tdefer pool.Close()\n\n\t// Initially, plugins should be nil\n\tif pool.GetPlugins() != nil {\n\t\tt.Error(\"plugins should be nil initially\")\n\t}\n\n\t// Create and set plugins\n\tplugins := NewPluginContainer()\n\ttp := &testPlugin{name: \"test-plugin\"}\n\tplugins.Add(tp)\n\tpool.SetPlugins(plugins)\n\n\t// Verify GetPlugins returns the correct plugins\n\tretrievedPlugins := pool.GetPlugins()\n\tif retrievedPlugins == nil {\n\t\tt.Error(\"plugins should not be nil after SetPlugins\")\n\t}\n\tif retrievedPlugins != plugins {\n\t\tt.Error(\"GetPlugins should return the same plugins as SetPlugins\")\n\t}\n\n\t// Verify plugins contain the test plugin\n\tallPlugins := retrievedPlugins.All()\n\tif len(allPlugins) != 1 {\n\t\tt.Errorf(\"expected 1 plugin, got %d\", len(allPlugins))\n\t}\n\tif p, ok := allPlugins[0].(*testPlugin); !ok || p.name != \"test-plugin\" {\n\t\tt.Error(\"plugin should be the test plugin\")\n\t}\n}\n\nfunc TestOneClientPool_SetPlugins_Concurrent(t *testing.T) {\n\t// Create a simple discovery\n\tpairs := []*KVPair{\n\t\t{Key: \"tcp@127.0.0.1:8972\", Value: \"\"},\n\t}\n\tdiscovery, err := NewMultipleServersDiscovery(pairs)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create discovery: %v\", err)\n\t}\n\tdefer discovery.Close()\n\n\t// Create a pool\n\tpool := NewOneClientPool(5, Failtry, RandomSelect, discovery, DefaultOption)\n\tdefer pool.Close()\n\n\t// Test concurrent SetPlugins calls\n\tdone := make(chan bool, 10)\n\tfor i := 0; i < 10; i++ {\n\t\tgo func(id int) {\n\t\t\tplugins := NewPluginContainer()\n\t\t\ttp := &testPlugin{name: \"test-plugin\"}\n\t\t\tplugins.Add(tp)\n\t\t\tpool.SetPlugins(plugins)\n\t\t\tdone <- true\n\t\t}(i)\n\t}\n\n\t// Wait for all goroutines to complete\n\tfor i := 0; i < 10; i++ {\n\t\t<-done\n\t}\n\n\t// Verify plugins are set\n\tif pool.GetPlugins() == nil {\n\t\tt.Error(\"plugins should not be nil after concurrent SetPlugins\")\n\t}\n}\n\n"
  },
  {
    "path": "client/peer2peer_discovery.go",
    "content": "package client\n\n// Peer2PeerDiscovery is a peer-to-peer service discovery.\n// It always returns the static server.\ntype Peer2PeerDiscovery struct {\n\tserver   string\n\tmetadata string\n}\n\n// NewPeer2PeerDiscovery returns a new Peer2PeerDiscovery.\nfunc NewPeer2PeerDiscovery(server, metadata string) (*Peer2PeerDiscovery, error) {\n\treturn &Peer2PeerDiscovery{server: server, metadata: metadata}, nil\n}\n\n// Clone clones this ServiceDiscovery with new servicePath.\nfunc (d *Peer2PeerDiscovery) Clone(servicePath string) (ServiceDiscovery, error) {\n\treturn d, nil\n}\n\n// SetFilter sets the filer.\nfunc (d *Peer2PeerDiscovery) SetFilter(filter ServiceDiscoveryFilter) {\n\n}\n\n// GetServices returns the static server\nfunc (d *Peer2PeerDiscovery) GetServices() []*KVPair {\n\treturn []*KVPair{&KVPair{Key: d.server, Value: d.metadata}}\n}\n\n// WatchService returns a nil chan.\nfunc (d *Peer2PeerDiscovery) WatchService() chan []*KVPair {\n\treturn nil\n}\n\nfunc (d *Peer2PeerDiscovery) RemoveWatcher(ch chan []*KVPair) {}\n\nfunc (d *Peer2PeerDiscovery) Close() {\n\n}\n"
  },
  {
    "path": "client/ping_utils.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\tping \"github.com/go-ping/ping\"\n)\n\n// weightedICMPSelector selects servers with ping result.\ntype weightedICMPSelector struct {\n\tservers []*Weighted\n\twrs     *weightedRoundRobinSelector\n}\n\nfunc newWeightedICMPSelector(servers map[string]string) Selector {\n\tss := createICMPWeighted(servers)\n\twicmps := weightedICMPSelector{\n\t\tservers: ss,\n\t\twrs:     &weightedRoundRobinSelector{servers: ss},\n\t}\n\twicmps.wrs.servers = ss\n\twicmps.wrs.buildRing()\n\treturn &wicmps\n}\n\nfunc (s *weightedICMPSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {\n\treturn s.wrs.Select(ctx, servicePath, serviceMethod, args)\n}\n\nfunc (s *weightedICMPSelector) UpdateServer(servers map[string]string) {\n\tss := createICMPWeighted(servers)\n\ts.wrs.servers = ss\n\ts.servers = ss\n\ts.wrs.buildRing()\n}\n\nfunc createICMPWeighted(servers map[string]string) []*Weighted {\n\tvar ss = make([]*Weighted, 0, len(servers))\n\tfor k := range servers {\n\t\tw := &Weighted{Server: k, Weight: 1, EffectiveWeight: 1}\n\t\tserver := strings.Split(k, \"@\")\n\t\thost, _, _ := net.SplitHostPort(server[1])\n\t\trtt, _ := Ping(host)\n\t\trtt = CalculateWeight(rtt)\n\t\tw.Weight = rtt\n\t\tw.EffectiveWeight = rtt\n\t\tss = append(ss, w)\n\t}\n\treturn ss\n}\n\n// Ping gets network traffic by ICMP\nfunc Ping(host string) (rtt int, err error) {\n\trtt = 1000 // default and timeout is 1000 ms\n\n\tpinger, err := ping.NewPinger(host)\n\tif err != nil {\n\t\treturn rtt, err\n\t}\n\tpinger.Count = 3\n\tpinger.Timeout = 3 * time.Second\n\terr = pinger.Run()\n\tif err != nil {\n\t\treturn rtt, err\n\t}\n\tstats := pinger.Statistics()\n\t// ping failed\n\tif len(stats.Rtts) == 0 {\n\t\treturn rtt, err\n\t}\n\trtt = int(stats.AvgRtt) / 1e6\n\n\treturn rtt, err\n}\n\n// CalculateWeight converts the rtt to weighted by:\n//  1. weight=191 if t <= 10\n//  2. weight=201 -t if 10 < t <=200\n//  3. weight=1 if 200 < t < 1000\n//  4. weight = 0 if t >= 1000\n//\n// It means servers that ping time t < 10 will be preferred\n// and servers won't be selected if t > 1000.\n// It is hard coded based on Ops experience.\nfunc CalculateWeight(rtt int) int {\n\tswitch {\n\tcase rtt >= 0 && rtt <= 10:\n\t\treturn 191\n\tcase rtt > 10 && rtt <= 200:\n\t\treturn 201 - rtt\n\tcase rtt > 100 && rtt < 1000:\n\t\treturn 1\n\tdefault:\n\t\treturn 0\n\t}\n}\n"
  },
  {
    "path": "client/plugin.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\n// pluginContainer implements PluginContainer interface.\ntype pluginContainer struct {\n\tplugins []Plugin\n}\n\nfunc NewPluginContainer() PluginContainer {\n\treturn &pluginContainer{}\n}\n\n// Plugin is the client plugin interface.\ntype Plugin interface{}\n\n// Add adds a plugin.\nfunc (p *pluginContainer) Add(plugin Plugin) {\n\tp.plugins = append(p.plugins, plugin)\n}\n\n// Remove removes a plugin by its name.\nfunc (p *pluginContainer) Remove(plugin Plugin) {\n\tif p.plugins == nil {\n\t\treturn\n\t}\n\n\tvar plugins []Plugin\n\tfor _, pp := range p.plugins {\n\t\tif pp != plugin {\n\t\t\tplugins = append(plugins, pp)\n\t\t}\n\t}\n\n\tp.plugins = plugins\n}\n\n// All returns all plugins\nfunc (p *pluginContainer) All() []Plugin {\n\treturn p.plugins\n}\n\n// DoPreCall executes before call\nfunc (p *pluginContainer) DoPreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PreCallPlugin); ok {\n\t\t\terr := plugin.PreCall(ctx, servicePath, serviceMethod, args)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoPostCall executes after call\nfunc (p *pluginContainer) DoPostCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, err error) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PostCallPlugin); ok {\n\t\t\terr = plugin.PostCall(ctx, servicePath, serviceMethod, args, reply, err)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// DoConnCreated is called in case of client connection created.\nfunc (p *pluginContainer) DoConnCreated(conn net.Conn) (net.Conn, error) {\n\tvar err error\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(ConnCreatedPlugin); ok {\n\t\t\tconn, err = plugin.ConnCreated(conn)\n\t\t\tif err != nil {\n\t\t\t\treturn conn, err\n\t\t\t}\n\t\t}\n\t}\n\treturn conn, nil\n}\n\n// DoConnCreateFailed is called in case of client connection create failed.\nfunc (p *pluginContainer) DoConnCreateFailed(network, address string) {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(ConnCreateFailedPlugin); ok {\n\t\t\tplugin.ConnCreateFailed(network, address)\n\t\t}\n\t}\n}\n\n// DoClientConnected is called in case of connected.\nfunc (p *pluginContainer) DoClientConnected(conn net.Conn) (net.Conn, error) {\n\tvar err error\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(ClientConnectedPlugin); ok {\n\t\t\tconn, err = plugin.ClientConnected(conn)\n\t\t\tif err != nil {\n\t\t\t\treturn conn, err\n\t\t\t}\n\t\t}\n\t}\n\treturn conn, nil\n}\n\n// DoClientConnectionClose is called in case of connection close.\nfunc (p *pluginContainer) DoClientConnectionClose(conn net.Conn) error {\n\tvar err error\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(ClientConnectionClosePlugin); ok {\n\t\t\terr = plugin.ClientConnectionClose(conn)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n\n// DoClientBeforeEncode is called when requests are encoded and sent.\nfunc (p *pluginContainer) DoClientBeforeEncode(req *protocol.Message) error {\n\tvar err error\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(ClientBeforeEncodePlugin); ok {\n\t\t\terr = plugin.ClientBeforeEncode(req)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// DoClientAfterDecode is called when requests are decoded and received.\nfunc (p *pluginContainer) DoClientAfterDecode(req *protocol.Message) error {\n\tvar err error\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(ClientAfterDecodePlugin); ok {\n\t\t\terr = plugin.ClientAfterDecode(req)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// DoWrapSelect is called when select a node.\nfunc (p *pluginContainer) DoWrapSelect(fn SelectFunc) SelectFunc {\n\trt := fn\n\tfor i := range p.plugins {\n\t\tif pn, ok := p.plugins[i].(SelectNodePlugin); ok {\n\t\t\trt = pn.WrapSelect(rt)\n\t\t}\n\t}\n\n\treturn rt\n}\n\ntype (\n\t// PreCallPlugin is invoked before the client calls a server.\n\tPreCallPlugin interface {\n\t\tPreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error\n\t}\n\n\t// PostCallPlugin is invoked after the client calls a server.\n\tPostCallPlugin interface {\n\t\tPostCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, err error) error\n\t}\n\n\t// ConnCreatedPlugin is invoked when the client connection has created.\n\tConnCreatedPlugin interface {\n\t\tConnCreated(net.Conn) (net.Conn, error)\n\t}\n\n\tConnCreateFailedPlugin interface {\n\t\tConnCreateFailed(network, address string)\n\t}\n\n\t// ClientConnectedPlugin is invoked when the client has connected the server.\n\tClientConnectedPlugin interface {\n\t\tClientConnected(net.Conn) (net.Conn, error)\n\t}\n\n\t// ClientConnectionClosePlugin is invoked when the connection is closing.\n\tClientConnectionClosePlugin interface {\n\t\tClientConnectionClose(net.Conn) error\n\t}\n\n\t// ClientBeforeEncodePlugin is invoked when the message is encoded and sent.\n\tClientBeforeEncodePlugin interface {\n\t\tClientBeforeEncode(*protocol.Message) error\n\t}\n\n\t// ClientAfterDecodePlugin is invoked when the message is decoded.\n\tClientAfterDecodePlugin interface {\n\t\tClientAfterDecode(*protocol.Message) error\n\t}\n\n\t// SelectNodePlugin can interrupt selecting of xclient and add customized logics such as skipping some nodes.\n\tSelectNodePlugin interface {\n\t\tWrapSelect(SelectFunc) SelectFunc\n\t}\n\n\t// PluginContainer represents a plugin container that defines all methods to manage plugins.\n\t// And it also defines all extension points.\n\tPluginContainer interface {\n\t\tAdd(plugin Plugin)\n\t\tRemove(plugin Plugin)\n\t\tAll() []Plugin\n\n\t\tDoConnCreated(net.Conn) (net.Conn, error)\n\t\tDoConnCreateFailed(network, address string)\n\t\tDoClientConnected(net.Conn) (net.Conn, error)\n\t\tDoClientConnectionClose(net.Conn) error\n\n\t\tDoPreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error\n\t\tDoPostCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, err error) error\n\n\t\tDoClientBeforeEncode(*protocol.Message) error\n\t\tDoClientAfterDecode(*protocol.Message) error\n\n\t\tDoWrapSelect(SelectFunc) SelectFunc\n\t}\n)\n"
  },
  {
    "path": "client/selectmode_enumer.go",
    "content": "// Code generated by \"enumer -type=SelectMode\"; DO NOT EDIT.\n\npackage client\n\nimport (\n\t\"fmt\"\n)\n\nconst _SelectModeName = \"RandomSelectRoundRobinWeightedRoundRobinWeightedICMPConsistentHashClosest\"\n\nvar _SelectModeIndex = [...]uint8{0, 12, 22, 40, 52, 66, 73}\n\nfunc (i SelectMode) String() string {\n\tif i < 0 || i >= SelectMode(len(_SelectModeIndex)-1) {\n\t\treturn fmt.Sprintf(\"SelectMode(%d)\", i)\n\t}\n\treturn _SelectModeName[_SelectModeIndex[i]:_SelectModeIndex[i+1]]\n}\n\nvar _SelectModeValues = []SelectMode{0, 1, 2, 3, 4, 5}\n\nvar _SelectModeNameToValueMap = map[string]SelectMode{\n\t_SelectModeName[0:12]:  0,\n\t_SelectModeName[12:22]: 1,\n\t_SelectModeName[22:40]: 2,\n\t_SelectModeName[40:52]: 3,\n\t_SelectModeName[52:66]: 4,\n\t_SelectModeName[66:73]: 5,\n}\n\n// SelectModeString retrieves an enum value from the enum constants string name.\n// Throws an error if the param is not part of the enum.\nfunc SelectModeString(s string) (SelectMode, error) {\n\tif val, ok := _SelectModeNameToValueMap[s]; ok {\n\t\treturn val, nil\n\t}\n\treturn 0, fmt.Errorf(\"%s does not belong to SelectMode values\", s)\n}\n\n// SelectModeValues returns all values of the enum\nfunc SelectModeValues() []SelectMode {\n\treturn _SelectModeValues\n}\n\n// IsASelectMode returns \"true\" if the value is listed in the enum definition. \"false\" otherwise\nfunc (i SelectMode) IsASelectMode() bool {\n\tfor _, v := range _SelectModeValues {\n\t\tif i == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "client/selector.go",
    "content": "package client\n\nimport (\n\t\"container/ring\"\n\t\"context\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net/url\"\n\t\"sort\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/edwingeng/doublejump\"\n\n\t\"github.com/valyala/fastrand\"\n)\n\ntype SelectFunc func(ctx context.Context, servicePath, serviceMethod string, args interface{}) string\n\n// Selector defines selector that selects one service from candidates.\ntype Selector interface {\n\tSelect(ctx context.Context, servicePath, serviceMethod string, args interface{}) string // SelectFunc\n\tUpdateServer(servers map[string]string)\n}\n\nfunc newSelector(selectMode SelectMode, servers map[string]string) Selector {\n\tswitch selectMode {\n\tcase RandomSelect:\n\t\treturn newRandomSelector(servers)\n\tcase RoundRobin:\n\t\treturn newRoundRobinSelector(servers)\n\tcase WeightedRoundRobin:\n\t\treturn newWeightedRoundRobinSelector(servers)\n\tcase WeightedICMP:\n\t\treturn newWeightedICMPSelector(servers)\n\tcase ConsistentHash:\n\t\treturn newConsistentHashSelector(servers)\n\tcase SelectByUser:\n\t\treturn nil\n\tdefault:\n\t\treturn newRandomSelector(servers)\n\t}\n}\n\n// randomSelector selects randomly.\ntype randomSelector struct {\n\tservers []string\n}\n\nfunc newRandomSelector(servers map[string]string) Selector {\n\tss := make([]string, 0, len(servers))\n\tfor k := range servers {\n\t\tss = append(ss, k)\n\t}\n\n\treturn &randomSelector{servers: ss}\n}\n\nfunc (s *randomSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {\n\tss := s.servers\n\tif len(ss) == 0 {\n\t\treturn \"\"\n\t}\n\ti := fastrand.Uint32n(uint32(len(ss)))\n\treturn ss[i]\n}\n\nfunc (s *randomSelector) UpdateServer(servers map[string]string) {\n\tss := make([]string, 0, len(servers))\n\tfor k := range servers {\n\t\tss = append(ss, k)\n\t}\n\n\ts.servers = ss\n}\n\n// roundRobinSelector selects servers with roundrobin.\ntype roundRobinSelector struct {\n\tservers []string\n\ti       int\n}\n\nfunc newRoundRobinSelector(servers map[string]string) Selector {\n\tss := make([]string, 0, len(servers))\n\tfor k := range servers {\n\t\tss = append(ss, k)\n\t}\n\n\treturn &roundRobinSelector{servers: ss}\n}\n\nfunc (s *roundRobinSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {\n\tss := s.servers\n\tif len(ss) == 0 {\n\t\treturn \"\"\n\t}\n\ti := s.i\n\ti = i % len(ss)\n\ts.i = i + 1\n\n\treturn ss[i]\n}\n\nfunc (s *roundRobinSelector) UpdateServer(servers map[string]string) {\n\tss := make([]string, 0, len(servers))\n\tfor k := range servers {\n\t\tss = append(ss, k)\n\t}\n\n\ts.servers = ss\n}\n\n// weightedRoundRobinSelector selects servers with weighted.\ntype weightedRoundRobinSelector struct {\n\tservers     []*Weighted\n\ttotalWeight int\n\trr          *ring.Ring\n}\n\nfunc newWeightedRoundRobinSelector(servers map[string]string) Selector {\n\tss := createWeighted(servers)\n\ts := &weightedRoundRobinSelector{servers: ss}\n\ts.buildRing()\n\treturn s\n}\n\nfunc (s *weightedRoundRobinSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {\n\tss := s.servers\n\tif len(ss) == 0 {\n\t\treturn \"\"\n\t}\n\tval := s.rr.Value\n\ts.rr = s.rr.Next()\n\treturn val.(*Weighted).Server\n\n}\n\nfunc (s *weightedRoundRobinSelector) UpdateServer(servers map[string]string) {\n\tnewServer := newWeightedRoundRobinSelector(servers).(*weightedRoundRobinSelector)\n\t*s = *newServer\n}\n\nfunc (s *weightedRoundRobinSelector) buildRing() {\n\ts.totalWeight = 0\n\tfor _, w := range s.servers {\n\t\ts.totalWeight += w.Weight\n\t}\n\ts.rr = ring.New(s.totalWeight)\n\tfor i := 0; i < s.totalWeight; i++ {\n\t\tn := s.next()\n\t\ts.rr.Value = n\n\t\ts.rr = s.rr.Next()\n\t}\n}\nfunc (s *weightedRoundRobinSelector) next() *Weighted {\n\tif len(s.servers) == 0 {\n\t\treturn nil\n\t}\n\tn := len(s.servers)\n\tif n == 0 {\n\t\treturn nil\n\t}\n\tif n == 1 {\n\t\treturn s.servers[0]\n\t}\n\tflag := 0\n\tm := 0\n\tfor i := 0; i < n; i++ {\n\t\ts.servers[i].CurrentWeight += s.servers[i].Weight\n\t\tif s.servers[i].CurrentWeight > m {\n\t\t\tm = s.servers[i].CurrentWeight\n\t\t\tflag = i\n\t\t}\n\t}\n\ts.servers[flag].CurrentWeight -= s.totalWeight\n\treturn s.servers[flag]\n}\nfunc createWeighted(servers map[string]string) []*Weighted {\n\tss := make([]*Weighted, 0, len(servers))\n\tfor k, metadata := range servers {\n\t\tw := &Weighted{Server: k, Weight: 1}\n\n\t\tif v, err := url.ParseQuery(metadata); err == nil {\n\t\t\tww := v.Get(\"weight\")\n\t\t\tif ww != \"\" {\n\t\t\t\tif weight, err := strconv.Atoi(ww); err == nil {\n\t\t\t\t\tw.Weight = weight\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tss = append(ss, w)\n\t}\n\n\treturn ss\n}\n\ntype geoServer struct {\n\tServer    string\n\tLatitude  float64\n\tLongitude float64\n}\n\n// geoSelector selects servers based on location.\ntype geoSelector struct {\n\tservers   []*geoServer\n\tLatitude  float64\n\tLongitude float64\n\tr         *rand.Rand\n}\n\nfunc newGeoSelector(servers map[string]string, latitude, longitude float64) Selector {\n\tss := createGeoServer(servers)\n\tr := rand.New(rand.NewSource(time.Now().UnixNano()))\n\treturn &geoSelector{servers: ss, Latitude: latitude, Longitude: longitude, r: r}\n}\n\nfunc (s *geoSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {\n\tif len(s.servers) == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar server []string\n\tminNum := math.MaxFloat64\n\tfor _, gs := range s.servers {\n\t\td := getDistanceFrom(s.Latitude, s.Longitude, gs.Latitude, gs.Longitude)\n\t\tif d < minNum {\n\t\t\tserver = []string{gs.Server}\n\t\t\tminNum = d\n\t\t} else if d == minNum {\n\t\t\tserver = append(server, gs.Server)\n\t\t}\n\t}\n\n\tif len(server) == 1 {\n\t\treturn server[0]\n\t}\n\n\treturn server[s.r.Intn(len(server))]\n}\n\nfunc (s *geoSelector) UpdateServer(servers map[string]string) {\n\tss := createGeoServer(servers)\n\ts.servers = ss\n}\n\nfunc createGeoServer(servers map[string]string) []*geoServer {\n\tgeoServers := make([]*geoServer, 0, len(servers))\n\n\tfor s, metadata := range servers {\n\t\tif v, err := url.ParseQuery(metadata); err == nil {\n\t\t\tlatStr := v.Get(\"latitude\")\n\t\t\tlonStr := v.Get(\"longitude\")\n\n\t\t\tif latStr == \"\" || lonStr == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlat, err := strconv.ParseFloat(latStr, 64)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlon, err := strconv.ParseFloat(lonStr, 64)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tgeoServers = append(geoServers, &geoServer{Server: s, Latitude: lat, Longitude: lon})\n\n\t\t}\n\t}\n\n\treturn geoServers\n}\n\n// consistentHashSelector selects based on JumpConsistentHash.\ntype consistentHashSelector struct {\n\th       *doublejump.Hash\n\tservers []string\n}\n\nfunc newConsistentHashSelector(servers map[string]string) Selector {\n\th := doublejump.NewHash()\n\tss := make([]string, 0, len(servers))\n\tfor k := range servers {\n\t\tss = append(ss, k)\n\t\th.Add(k)\n\t}\n\n\tsort.Slice(ss, func(i, j int) bool { return ss[i] < ss[j] })\n\treturn &consistentHashSelector{servers: ss, h: h}\n}\n\nfunc (s *consistentHashSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {\n\tss := s.servers\n\tif len(ss) == 0 {\n\t\treturn \"\"\n\t}\n\n\tkey := genKey(servicePath, serviceMethod, args)\n\tselected, _ := s.h.Get(key).(string)\n\treturn selected\n}\n\nfunc (s *consistentHashSelector) UpdateServer(servers map[string]string) {\n\tss := make([]string, 0, len(servers))\n\tfor k := range servers {\n\t\ts.h.Add(k)\n\t\tss = append(ss, k)\n\t}\n\n\tsort.Slice(ss, func(i, j int) bool { return ss[i] < ss[j] })\n\n\tfor _, k := range s.servers {\n\t\tif _, exist := servers[k]; !exist { // remove\n\t\t\ts.h.Remove(k)\n\t\t}\n\t}\n\ts.servers = ss\n}\n"
  },
  {
    "path": "client/selector_test.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc Test_consistentHashSelector_Select(t *testing.T) {\n\tservers := map[string]string{\n\t\t\"tcp@192.168.1.16:9392\": \"\",\n\t\t\"tcp@192.168.1.16:9393\": \"\",\n\t}\n\ts := newConsistentHashSelector(servers).(*consistentHashSelector)\n\n\tkey := uint64(9280147620691907957)\n\tselected, _ := s.h.Get(key).(string)\n\n\tfor i := 0; i < 10000; i++ {\n\t\tselected2, _ := s.h.Get(key).(string)\n\t\tif selected != selected2 {\n\t\t\tt.Errorf(\"expected %s but got %s\", selected, selected2)\n\t\t}\n\t}\n}\n\nfunc Test_consistentHashSelector_UpdateServer(t *testing.T) {\n\tservers := map[string]string{\n\t\t\"tcp@192.168.1.16:9392\": \"\",\n\t\t\"tcp@192.168.1.16:9393\": \"\",\n\t}\n\ts := newConsistentHashSelector(servers).(*consistentHashSelector)\n\tif len(s.h.All()) != len(servers) {\n\t\tt.Errorf(\"NewSelector: expected %d server but got %d\", len(servers), len(s.h.All()))\n\t}\n\ts.UpdateServer(servers)\n\tif len(s.h.All()) != len(servers) {\n\t\tt.Errorf(\"UpdateServer: expected %d server but got %d\", len(servers), len(s.h.All()))\n\t}\n}\n\nfunc TestWeightedRoundRobinSelector_Select(t *testing.T) {\n\tcalc := make(map[string]int)\n\tservers := make(map[string]string)\n\tservers[\"ServerA\"] = \"weight=4\"\n\tservers[\"ServerB\"] = \"weight=2\"\n\tservers[\"ServerC\"] = \"weight=1\"\n\tweightSelector := newWeightedRoundRobinSelector(servers).(*weightedRoundRobinSelector)\n\tctx := context.Background()\n\tfor i := 0; i < 7; i++ {\n\t\ts := weightSelector.Select(ctx, \"\", \"\", nil)\n\t\tif _, ok := calc[s]; ok {\n\t\t\tcalc[s]++\n\t\t} else {\n\t\t\tcalc[s] = 1\n\t\t}\n\t}\n\tif calc[\"ServerA\"] != 4 {\n\t\tt.Errorf(\"expected %d but got %d\", 4, calc[\"ServerA\"])\n\t}\n\tif calc[\"ServerB\"] != 2 {\n\t\tt.Errorf(\"expected %d but got %d\", 2, calc[\"ServerB\"])\n\t}\n\tif calc[\"ServerC\"] != 1 {\n\t\tt.Errorf(\"expected %d but got %d\", 1, calc[\"ServerC\"])\n\t}\n}\nfunc TestWeightedRoundRobinSelector_UpdateServer(t *testing.T) {\n\tcalc := make(map[string]int)\n\tservers := make(map[string]string)\n\tservers[\"ServerA\"] = \"weight=4\"\n\tservers[\"ServerB\"] = \"weight=2\"\n\tservers[\"ServerC\"] = \"weight=1\"\n\tweightSelector := newWeightedRoundRobinSelector(servers).(*weightedRoundRobinSelector)\n\tctx := context.Background()\n\tservers[\"ServerA\"] = \"weight=5\"\n\tweightSelector.UpdateServer(servers)\n\tfor i := 0; i < 8; i++ {\n\t\ts := weightSelector.Select(ctx, \"\", \"\", nil)\n\t\tif _, ok := calc[s]; ok {\n\t\t\tcalc[s]++\n\t\t} else {\n\t\t\tcalc[s] = 1\n\t\t}\n\t}\n\tif calc[\"ServerA\"] != 5 {\n\t\tt.Errorf(\"expected %d but got %d\", 4, calc[\"ServerA\"])\n\t}\n\tif calc[\"ServerB\"] != 2 {\n\t\tt.Errorf(\"expected %d but got %d\", 2, calc[\"ServerB\"])\n\t}\n\tif calc[\"ServerC\"] != 1 {\n\t\tt.Errorf(\"expected %d but got %d\", 1, calc[\"ServerC\"])\n\t}\n}\n\nfunc BenchmarkWeightedRoundRobinSelector_Select(b *testing.B) {\n\tservers := make(map[string]string)\n\tservers[\"ServerA\"] = \"weight=4\"\n\tservers[\"ServerB\"] = \"weight=2\"\n\tservers[\"ServerC\"] = \"weight=1\"\n\tctx := context.Background()\n\tweightSelector := newWeightedRoundRobinSelector(servers).(*weightedRoundRobinSelector)\n\n\tfor i := 0; i < b.N; i++ {\n\t\tweightSelector.Select(ctx, \"\", \"\", nil)\n\t}\n}\n\n//\n//func TestWeightedICMPSelector(t *testing.T) {\n//\tcalc := make(map[string]int)\n//\tservers := make(map[string]string)\n//\tservers[\"@localhost:3333\"] = \"\"\n//\tservers[\"@www.baidu.com:3334\"] = \"\"\n//\tservers[\"@xxxx.xxxx:333\"] = \"\"\n//\ts := newWeightedICMPSelector(servers)\n//\tctx := context.Background()\n//\tfor i := 0; i < 10; i++ {\n//\t\thost := s.Select(ctx, \"\", \"\", nil)\n//\t\tif _, ok := calc[host]; ok {\n//\t\t\tcalc[host]++\n//\t\t} else {\n//\t\t\tcalc[host] = 0\n//\t\t}\n//\t}\n//\tif len(calc) != 2 {\n//\t\tt.Errorf(\"expected %d but got %d\", 2, len(servers))\n//\t}\n//}\n//func TestWeightedICMPSelector_UpdateServer(t *testing.T) {\n//\tcalc := make(map[string]int)\n//\tservers := make(map[string]string)\n//\tservers[\"@localhost:3333\"] = \"\"\n//\tservers[\"@www.baidu.com:3334\"] = \"\"\n//\tservers[\"@xxxx.xxxx:333\"] = \"\"\n//\ts := newWeightedICMPSelector(servers)\n//\tctx := context.Background()\n//\tservers[\"@www.sina.com:3333\"] = \"\"\n//\ts.UpdateServer(servers)\n//\tfor i := 0; i < 10; i++ {\n//\t\thost := s.Select(ctx, \"\", \"\", nil)\n//\t\tif _, ok := calc[host]; ok {\n//\t\t\tcalc[host]++\n//\t\t} else {\n//\t\t\tcalc[host] = 0\n//\t\t}\n//\t}\n//\tif len(calc) != 3 {\n//\t\tt.Errorf(\"expected %d but got %d\", 3, len(servers))\n//\t}\n//}\n"
  },
  {
    "path": "client/smooth_weighted_round_robin.go",
    "content": "package client\n\n// Weighted is a wrapped server with  weight\ntype Weighted struct {\n\tServer          string\n\tWeight          int\n\tCurrentWeight   int\n\tEffectiveWeight int\n}\n"
  },
  {
    "path": "client/xclient.go",
    "content": "package client\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/juju/ratelimit\"\n\t\"golang.org/x/sync/singleflight\"\n\n\tex \"github.com/smallnest/rpcx/errors\"\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n)\n\nconst (\n\tFileTransferBufferSize = 1024\n)\n\nvar (\n\t// ErrXClientShutdown xclient is shutdown.\n\tErrXClientShutdown = errors.New(\"xClient is shut down\")\n\t// ErrXClientNoServer selector can't found one server.\n\tErrXClientNoServer = errors.New(\"can not found any server\")\n\t// ErrServerUnavailable selected server is unavailable.\n\tErrServerUnavailable = errors.New(\"selected server is unavailable\")\n)\n\n// Receipt represents the result of the service returned.\ntype Receipt struct {\n\tAddress string\n\tReply   interface{}\n\tError   error\n}\n\n// XClient is an interface that used by client with service discovery and service governance.\n// One XClient is used only for one service. You should create multiple XClient for multiple services.\ntype XClient interface {\n\tSetPlugins(plugins PluginContainer)\n\tGetPlugins() PluginContainer\n\tSetSelector(s Selector)\n\tConfigGeoSelector(latitude, longitude float64)\n\tAuth(auth string)\n\n\tGo(ctx context.Context, serviceMethod string, args interface{}, reply interface{}, done chan *Call) (*Call, error)\n\tCall(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error\n\tOneshot(ctx context.Context, serviceMethod string, args interface{}) error\n\tBroadcast(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error\n\tFork(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error\n\tInform(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) ([]Receipt, error)\n\tSendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error)\n\tSendFile(ctx context.Context, fileName string, rateInBytesPerSecond int64, meta map[string]string) error\n\tDownloadFile(ctx context.Context, requestFileName string, saveTo io.Writer, meta map[string]string) error\n\tStream(ctx context.Context, meta map[string]string) (net.Conn, error)\n\tClose() error\n}\n\n// SetSelector sets customized selector by users.\nfunc (c *xClient) SetSelector(s Selector) {\n\tc.mu.RLock()\n\ts.UpdateServer(c.servers)\n\tc.mu.RUnlock()\n\n\tc.selector = s\n}\n\n// KVPair contains a key and a string.\ntype KVPair struct {\n\tKey   string\n\tValue string\n}\n\ntype xClient struct {\n\tfailMode     FailMode\n\tselectMode   SelectMode\n\tcachedClient map[string]RPCClient\n\tbreakers     sync.Map\n\tservicePath  string\n\toption       Option\n\n\tmu              sync.RWMutex\n\tservers         map[string]string\n\tunstableServers map[string]time.Time // 一些服务器重启，如果和它们建立链接，可能会耗费非常长的时间，这里记录袭来需要临时屏蔽\n\tdiscovery       ServiceDiscovery\n\tselector        Selector\n\tstickyRPCClient RPCClient\n\tstickyK         string\n\n\tslGroup singleflight.Group\n\n\tisShutdown bool\n\n\t// auth is a string for Authentication, for example, \"Bearer mF_9.B5f-4.1JqM\"\n\tauth string\n\n\tPlugins PluginContainer\n\n\tch chan []*KVPair\n\n\tserverMessageChan chan<- *protocol.Message\n}\n\n// NewXClient creates a XClient that supports service discovery and service governance.\nfunc NewXClient(servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) XClient {\n\tclient := &xClient{\n\t\tfailMode:        failMode,\n\t\tselectMode:      selectMode,\n\t\tdiscovery:       discovery,\n\t\tservicePath:     servicePath,\n\t\tcachedClient:    make(map[string]RPCClient),\n\t\tunstableServers: make(map[string]time.Time),\n\t\toption:          option,\n\t}\n\n\tpairs := discovery.GetServices()\n\tsort.Slice(pairs, func(i, j int) bool {\n\t\treturn strings.Compare(pairs[i].Key, pairs[j].Key) <= 0\n\t})\n\tservers := make(map[string]string, len(pairs))\n\tfor _, p := range pairs {\n\t\tservers[p.Key] = p.Value\n\t}\n\tfilterByStateAndGroup(client.option.Group, servers)\n\n\tclient.servers = servers\n\tif selectMode != Closest && selectMode != SelectByUser {\n\t\tclient.selector = newSelector(selectMode, servers)\n\t}\n\n\tclient.Plugins = &pluginContainer{}\n\n\tch := client.discovery.WatchService()\n\tif ch != nil {\n\t\tclient.ch = ch\n\t\tgo client.watch(ch)\n\t}\n\n\treturn client\n}\n\n// NewBidirectionalXClient creates a new xclient that can receive notifications from servers.\nfunc NewBidirectionalXClient(servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) XClient {\n\tclient := &xClient{\n\t\tfailMode:          failMode,\n\t\tselectMode:        selectMode,\n\t\tdiscovery:         discovery,\n\t\tservicePath:       servicePath,\n\t\tcachedClient:      make(map[string]RPCClient),\n\t\toption:            option,\n\t\tunstableServers:   make(map[string]time.Time),\n\t\tserverMessageChan: serverMessageChan,\n\t}\n\n\tpairs := discovery.GetServices()\n\tsort.Slice(pairs, func(i, j int) bool {\n\t\treturn strings.Compare(pairs[i].Key, pairs[j].Key) <= 0\n\t})\n\tservers := make(map[string]string, len(pairs))\n\tfor _, p := range pairs {\n\t\tservers[p.Key] = p.Value\n\t}\n\tfilterByStateAndGroup(client.option.Group, servers)\n\tclient.servers = servers\n\tif selectMode != Closest && selectMode != SelectByUser {\n\t\tclient.selector = newSelector(selectMode, servers)\n\t}\n\n\tclient.Plugins = &pluginContainer{}\n\n\tch := client.discovery.WatchService()\n\tif ch != nil {\n\t\tclient.ch = ch\n\t\tgo client.watch(ch)\n\t}\n\n\treturn client\n}\n\n// SetPlugins sets client's plugins.\nfunc (c *xClient) SetPlugins(plugins PluginContainer) {\n\tc.Plugins = plugins\n}\n\nfunc (c *xClient) GetPlugins() PluginContainer {\n\treturn c.Plugins\n}\n\n// ConfigGeoSelector sets location of client's latitude and longitude,\n// and use newGeoSelector.\nfunc (c *xClient) ConfigGeoSelector(latitude, longitude float64) {\n\tc.selector = newGeoSelector(c.servers, latitude, longitude)\n\tc.selectMode = Closest\n}\n\n// Auth sets s token for Authentication.\nfunc (c *xClient) Auth(auth string) {\n\tc.auth = auth\n}\n\n// watch changes of service and update cached clients.\nfunc (c *xClient) watch(ch chan []*KVPair) {\n\tfor pairs := range ch {\n\t\tsort.Slice(pairs, func(i, j int) bool {\n\t\t\treturn strings.Compare(pairs[i].Key, pairs[j].Key) <= 0\n\t\t})\n\t\tservers := make(map[string]string, len(pairs))\n\t\tfor _, p := range pairs {\n\t\t\tservers[p.Key] = p.Value\n\t\t}\n\t\tc.mu.Lock()\n\t\tfilterByStateAndGroup(c.option.Group, servers)\n\t\tc.servers = servers\n\n\t\tif c.selector != nil {\n\t\t\tc.selector.UpdateServer(servers)\n\t\t}\n\n\t\tc.mu.Unlock()\n\t}\n}\n\nfunc filterByStateAndGroup(group string, servers map[string]string) {\n\tfor k, v := range servers {\n\t\tif values, err := url.ParseQuery(v); err == nil {\n\t\t\tif state := values.Get(\"state\"); state == \"inactive\" {\n\t\t\t\tdelete(servers, k)\n\t\t\t}\n\t\t\tgroups := values[\"group\"] // Directly access the map to get all values associated with \"group\" as a slice\n\t\t\tif group != \"\" {\n\t\t\t\tfound := false\n\t\t\t\tfor _, g := range groups {\n\t\t\t\t\tif group == g {\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tbreak // A matching group is found, stop the search\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !found {\n\t\t\t\t\tdelete(servers, k) // If no matching group is found, delete the corresponding server from the map\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// selects a client from candidates base on c.selectMode\nfunc (c *xClient) selectClient(ctx context.Context, servicePath, serviceMethod string, args interface{}) (string, RPCClient, error) {\n\tc.mu.Lock()\n\n\tif c.option.Sticky && c.stickyRPCClient != nil {\n\t\tif c.stickyRPCClient.IsClosing() || c.stickyRPCClient.IsShutdown() {\n\t\t\tc.stickyRPCClient = nil\n\t\t}\n\t}\n\n\tif c.option.Sticky && c.stickyRPCClient != nil {\n\t\tc.mu.Unlock()\n\t\treturn c.stickyK, c.stickyRPCClient, nil\n\t}\n\n\tfn := c.selector.Select\n\tif c.Plugins != nil {\n\t\tfn = c.Plugins.DoWrapSelect(fn)\n\t}\n\tk := fn(ctx, servicePath, serviceMethod, args)\n\tc.mu.Unlock()\n\n\tif k == \"\" {\n\t\treturn \"\", nil, ErrXClientNoServer\n\t}\n\n\tclient, err := c.getCachedClient(k, servicePath, serviceMethod, args)\n\n\tif c.option.Sticky && client != nil {\n\t\tc.mu.Lock()\n\t\tsafeCloseClient(c.stickyRPCClient)\n\n\t\tc.stickyK = k\n\t\tc.stickyRPCClient = client\n\t\tc.mu.Unlock()\n\t}\n\n\treturn k, client, err\n}\n\nfunc safeCloseClient(client RPCClient) {\n\tif client == nil {\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\t_ = recover()\n\t}()\n\n\tclient.Close()\n}\n\nfunc (c *xClient) getCachedClient(k string, servicePath, serviceMethod string, _ interface{}) (client RPCClient, err error) {\n\tvar needCallPlugin bool\n\tdefer func() {\n\t\tif needCallPlugin {\n\t\t\t_, err = c.Plugins.DoClientConnected(client.GetConn())\n\t\t}\n\t}()\n\n\tif c.isShutdown {\n\t\treturn nil, errors.New(\"this xclient is closed\")\n\t}\n\n\t// if this client is broken\n\tbreaker, ok := c.breakers.Load(k)\n\tif ok && !breaker.(Breaker).Ready() {\n\t\treturn nil, ErrBreakerOpen\n\t}\n\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\n\tclient = c.findCachedClient(k, servicePath, serviceMethod)\n\tif client != nil {\n\t\tif !client.IsClosing() && !client.IsShutdown() {\n\t\t\treturn client, nil\n\t\t}\n\t\tc.deleteCachedClient(client, k, servicePath, serviceMethod)\n\t}\n\n\tclient = c.findCachedClient(k, servicePath, serviceMethod)\n\n\tif client == nil || client.IsShutdown() {\n\t\tgeneratedClient, err, _ := c.slGroup.Do(k, func() (interface{}, error) {\n\t\t\treturn c.generateClient(k, servicePath, serviceMethod)\n\t\t})\n\n\t\tif err != nil {\n\t\t\tc.slGroup.Forget(k)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tclient = generatedClient.(RPCClient)\n\t\tif c.Plugins != nil {\n\t\t\tneedCallPlugin = true\n\t\t}\n\n\t\tclient.RegisterServerMessageChan(c.serverMessageChan)\n\n\t\tc.setCachedClient(client, k, servicePath, serviceMethod)\n\n\t\t// forget k only when client is cached\n\t\tc.slGroup.Forget(k)\n\t}\n\n\treturn client, nil\n}\n\nfunc (c *xClient) setCachedClient(client RPCClient, k, servicePath, serviceMethod string) {\n\tnetwork, _ := splitNetworkAndAddress(k)\n\tif builder, ok := getCacheClientBuilder(network); ok {\n\t\tbuilder.SetCachedClient(client, k, servicePath, serviceMethod)\n\t\treturn\n\t}\n\n\tc.cachedClient[k] = client\n}\n\nfunc (c *xClient) findCachedClient(k, servicePath, serviceMethod string) RPCClient {\n\tnetwork, _ := splitNetworkAndAddress(k)\n\tif builder, ok := getCacheClientBuilder(network); ok {\n\t\treturn builder.FindCachedClient(k, servicePath, serviceMethod)\n\t}\n\n\treturn c.cachedClient[k]\n}\n\nfunc (c *xClient) deleteCachedClient(client RPCClient, k, servicePath, serviceMethod string) {\n\tnetwork, _ := splitNetworkAndAddress(k)\n\tif builder, ok := getCacheClientBuilder(network); ok && client != nil {\n\t\tbuilder.DeleteCachedClient(client, k, servicePath, serviceMethod)\n\t\tclient.Close()\n\t\treturn\n\t}\n\n\tdelete(c.cachedClient, k)\n\tif client != nil {\n\t\tclient.Close()\n\t}\n}\n\nfunc (c *xClient) removeClient(k, servicePath, serviceMethod string, client RPCClient) {\n\tc.mu.Lock()\n\tif c.option.Sticky {\n\t\tc.stickyK = \"\"\n\t\tc.stickyRPCClient = nil\n\t}\n\n\tcl := c.findCachedClient(k, servicePath, serviceMethod)\n\tif cl == client {\n\t\tc.deleteCachedClient(client, k, servicePath, serviceMethod)\n\t}\n\tc.mu.Unlock()\n\n\tif client != nil {\n\t\tclient.UnregisterServerMessageChan()\n\t\tclient.Close()\n\t}\n}\n\nfunc (c *xClient) generateClient(k, servicePath, serviceMethod string) (client RPCClient, err error) {\n\tnetwork, addr := splitNetworkAndAddress(k)\n\tif builder, ok := getCacheClientBuilder(network); ok && builder != nil {\n\t\treturn builder.GenerateClient(k, servicePath, serviceMethod)\n\t}\n\n\tclient = &Client{\n\t\toption:  c.option,\n\t\tPlugins: c.Plugins,\n\t}\n\n\tvar breaker interface{}\n\tif c.option.GenBreaker != nil {\n\t\tbreaker, _ = c.breakers.LoadOrStore(k, c.option.GenBreaker())\n\t}\n\n\terr = client.Connect(network, addr)\n\tif err != nil {\n\t\tif breaker != nil {\n\t\t\tbreaker.(Breaker).Fail()\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn client, err\n}\n\nfunc (c *xClient) getCachedClientWithoutLock(k, servicePath, serviceMethod string) (RPCClient, bool, error) {\n\tvar needCallPlugin bool\n\tclient := c.findCachedClient(k, servicePath, serviceMethod)\n\tif client != nil {\n\t\tif !client.IsClosing() && !client.IsShutdown() {\n\t\t\treturn client, needCallPlugin, nil\n\t\t}\n\t\tc.deleteCachedClient(client, k, servicePath, serviceMethod)\n\n\t\t// double check\n\t\tclient = c.findCachedClient(k, servicePath, serviceMethod)\n\t}\n\n\tif client == nil || client.IsShutdown() {\n\t\tgeneratedClient, err, _ := c.slGroup.Do(k, func() (interface{}, error) {\n\t\t\treturn c.generateClient(k, servicePath, serviceMethod)\n\t\t})\n\n\t\tif err != nil {\n\t\t\tc.slGroup.Forget(k)\n\t\t\treturn nil, needCallPlugin, err\n\t\t}\n\n\t\tclient = generatedClient.(RPCClient)\n\t\tif c.Plugins != nil {\n\t\t\tneedCallPlugin = true\n\t\t}\n\n\t\tclient.RegisterServerMessageChan(c.serverMessageChan)\n\n\t\tc.setCachedClient(client, k, servicePath, serviceMethod)\n\t\tc.slGroup.Forget(k)\n\t}\n\n\treturn client, needCallPlugin, nil\n}\n\nfunc splitNetworkAndAddress(server string) (string, string) {\n\tss := strings.SplitN(server, \"@\", 2)\n\tif len(ss) == 1 {\n\t\treturn \"tcp\", server\n\t}\n\n\treturn ss[0], ss[1]\n}\n\nfunc setServerTimeout(ctx context.Context) context.Context {\n\tif deadline, ok := ctx.Deadline(); ok {\n\t\tmetadata := ctx.Value(share.ReqMetaDataKey)\n\t\tif metadata == nil {\n\t\t\tmetadata = map[string]string{}\n\t\t\tctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)\n\t\t}\n\t\tm := metadata.(map[string]string)\n\t\tm[share.ServerTimeout] = fmt.Sprintf(\"%d\", time.Until(deadline).Milliseconds())\n\t}\n\n\treturn ctx\n}\n\n// Go invokes the function asynchronously. It returns the Call structure representing the invocation. The done channel will signal when the call is complete by returning the same Call object. If done is nil, Go will allocate a new channel. If non-nil, done must be buffered or Go will deliberately crash.\n// It does not use FailMode.\nfunc (c *xClient) Go(ctx context.Context, serviceMethod string, args interface{}, reply interface{}, done chan *Call) (*Call, error) {\n\tif c.isShutdown {\n\t\treturn nil, ErrXClientShutdown\n\t}\n\n\tif c.auth != \"\" {\n\t\tmetadata := ctx.Value(share.ReqMetaDataKey)\n\t\tif metadata == nil {\n\t\t\tmetadata = map[string]string{}\n\t\t\tctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)\n\t\t}\n\t\tm := metadata.(map[string]string)\n\t\tm[share.AuthKey] = c.auth\n\t}\n\n\tctx = setServerTimeout(ctx)\n\n\tif share.Trace {\n\t\tlog.Debugf(\"select a client for %s.%s, args: %+v in case of xclient Go\", c.servicePath, serviceMethod, args)\n\t}\n\t_, client, err := c.selectClient(ctx, c.servicePath, serviceMethod, args)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif share.Trace {\n\t\tlog.Debugf(\"selected a client %s for %s.%s, args: %+v in case of xclient Go\", client.RemoteAddr(), c.servicePath, serviceMethod, args)\n\t}\n\n\tif done == nil {\n\t\tdone = make(chan *Call, 10)\n\t}\n\n\treturn client.Go(ctx, c.servicePath, serviceMethod, args, reply, done), nil\n}\n\n// Call invokes the named function, waits for it to complete, and returns its error status.\n// It handles errors base on FailMode.\nfunc (c *xClient) Call(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {\n\tif c.isShutdown {\n\t\treturn ErrXClientShutdown\n\t}\n\n\tif c.auth != \"\" {\n\t\tmetadata := ctx.Value(share.ReqMetaDataKey)\n\t\tif metadata == nil {\n\t\t\tmetadata = map[string]string{}\n\t\t\tctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)\n\t\t}\n\t\tm := metadata.(map[string]string)\n\t\tm[share.AuthKey] = c.auth\n\t}\n\tctx = setServerTimeout(ctx)\n\n\tif share.Trace {\n\t\tlog.Debugf(\"select a client for %s.%s, failMode: %v, args: %+v in case of xclient Call\", c.servicePath, serviceMethod, c.failMode, args)\n\t}\n\n\tvar err error\n\tk, client, err := c.selectClient(ctx, c.servicePath, serviceMethod, args)\n\tif err != nil {\n\t\tif c.failMode == Failfast || contextCanceled(err) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif share.Trace {\n\t\tif client != nil {\n\t\t\tlog.Debugf(\"selected a client %s for %s.%s, failMode: %v, args: %+v in case of xclient Call\", client.RemoteAddr(), c.servicePath, serviceMethod, c.failMode, args)\n\t\t} else {\n\t\t\tlog.Debugf(\"selected a client %s for %s.%s, failMode: %v, args: %+v in case of xclient Call\", \"nil\", c.servicePath, serviceMethod, c.failMode, args)\n\t\t}\n\t}\n\n\tvar e error\n\tswitch c.failMode {\n\tcase Failtry:\n\t\tretries := c.option.Retries\n\t\tretryInterval := c.option.RetryInterval\n\t\tfor retries >= 0 {\n\t\t\tretries--\n\n\t\t\tif client != nil {\n\t\t\t\terr = c.wrapCall(ctx, client, serviceMethod, args, reply)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif contextCanceled(err) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif e, ok := err.(ServiceError); ok && e.IsServiceError() {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif uncoverError(err) {\n\t\t\t\tc.removeClient(k, c.servicePath, serviceMethod, client)\n\t\t\t}\n\t\t\tclient, e = c.getCachedClient(k, c.servicePath, serviceMethod, args)\n\t\t\ttime.Sleep(retryInterval)\n\t\t}\n\t\tif err == nil {\n\t\t\terr = e\n\t\t}\n\t\treturn err\n\tcase Failover:\n\t\tretries := c.option.Retries\n\t\tretryInterval := c.option.RetryInterval\n\t\tfor retries >= 0 {\n\t\t\tretries--\n\n\t\t\tif client != nil {\n\t\t\t\terr = c.wrapCall(ctx, client, serviceMethod, args, reply)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif contextCanceled(err) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif e, ok := err.(ServiceError); ok && e.IsServiceError() {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif uncoverError(err) {\n\t\t\t\tc.removeClient(k, c.servicePath, serviceMethod, client)\n\t\t\t}\n\t\t\ttime.Sleep(retryInterval)\n\t\t\t// select another server\n\t\t\tk, client, e = c.selectClient(ctx, c.servicePath, serviceMethod, args)\n\t\t}\n\n\t\tif err == nil {\n\t\t\terr = e\n\t\t}\n\t\treturn err\n\tcase Failbackup:\n\t\tctx, cancelFn := context.WithCancel(ctx)\n\t\tdefer cancelFn()\n\t\tcall1 := make(chan *Call, 10)\n\t\tcall2 := make(chan *Call, 10)\n\n\t\tvar reply1, reply2 interface{}\n\n\t\tif reply != nil {\n\t\t\treply1 = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()\n\t\t\treply2 = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()\n\t\t}\n\n\t\t_, err1 := c.Go(ctx, serviceMethod, args, reply1, call1)\n\n\t\tt := time.NewTimer(c.option.BackupLatency)\n\t\tselect {\n\t\tcase <-ctx.Done(): // cancel by context\n\t\t\terr = ctx.Err()\n\t\t\treturn err\n\t\tcase call := <-call1:\n\t\t\terr = call.Error\n\t\t\tif err == nil && reply != nil {\n\t\t\t\treflect.ValueOf(reply).Elem().Set(reflect.ValueOf(reply1).Elem())\n\t\t\t}\n\t\t\treturn err\n\t\tcase <-t.C:\n\n\t\t}\n\t\t_, err2 := c.Go(ctx, serviceMethod, args, reply2, call2)\n\t\tif err2 != nil {\n\t\t\tif uncoverError(err2) {\n\t\t\t\tc.removeClient(k, c.servicePath, serviceMethod, client)\n\t\t\t}\n\t\t\terr = err1\n\t\t\treturn err\n\t\t}\n\n\t\tselect {\n\t\tcase <-ctx.Done(): // cancel by context\n\t\t\terr = ctx.Err()\n\t\tcase call := <-call1:\n\t\t\terr = call.Error\n\t\t\tif err == nil && reply != nil && reply1 != nil {\n\t\t\t\treflect.ValueOf(reply).Elem().Set(reflect.ValueOf(reply1).Elem())\n\t\t\t}\n\t\tcase call := <-call2:\n\t\t\terr = call.Error\n\t\t\tif err == nil && reply != nil && reply2 != nil {\n\t\t\t\treflect.ValueOf(reply).Elem().Set(reflect.ValueOf(reply2).Elem())\n\t\t\t}\n\t\t}\n\n\t\treturn err\n\tdefault: // Failfast\n\t\terr = c.wrapCall(ctx, client, serviceMethod, args, reply)\n\t\tif err != nil {\n\t\t\tif uncoverError(err) {\n\t\t\t\tc.removeClient(k, c.servicePath, serviceMethod, client)\n\t\t\t}\n\t\t}\n\n\t\treturn err\n\t}\n}\n\n// Oneshot invokes the named function, ** DOEST NOT ** wait for it to complete, and returns immediately.\nfunc (c *xClient) Oneshot(ctx context.Context, serviceMethod string, args interface{}) error {\n\tif c.isShutdown {\n\t\treturn ErrXClientShutdown\n\t}\n\n\tif c.auth != \"\" {\n\t\tmetadata := ctx.Value(share.ReqMetaDataKey)\n\t\tif metadata == nil {\n\t\t\tmetadata = map[string]string{}\n\t\t\tctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)\n\t\t}\n\t\tm := metadata.(map[string]string)\n\t\tm[share.AuthKey] = c.auth\n\t}\n\n\tctx = setServerTimeout(ctx)\n\n\tif share.Trace {\n\t\tlog.Debugf(\"select a client for %s.%s, args: %+v in case of xclient Go\", c.servicePath, serviceMethod, args)\n\t}\n\t_, client, err := c.selectClient(ctx, c.servicePath, serviceMethod, args)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif share.Trace {\n\t\tlog.Debugf(\"selected a client %s for %s.%s, args: %+v in case of xclient Go\", client.RemoteAddr(), c.servicePath, serviceMethod, args)\n\t}\n\n\tclient.Go(ctx, c.servicePath, serviceMethod, args, nil, nil)\n\n\treturn nil\n}\n\nfunc uncoverError(err error) bool {\n\tif e, ok := err.(ServiceError); ok && e.IsServiceError() {\n\t\treturn false\n\t}\n\n\tif err == context.DeadlineExceeded {\n\t\treturn false\n\t}\n\n\tif err == context.Canceled {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc contextCanceled(err error) bool {\n\tif err == context.DeadlineExceeded {\n\t\treturn true\n\t}\n\n\tif err == context.Canceled {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (c *xClient) SendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error) {\n\tif c.isShutdown {\n\t\treturn nil, nil, ErrXClientShutdown\n\t}\n\n\tif c.auth != \"\" {\n\t\tmetadata := ctx.Value(share.ReqMetaDataKey)\n\t\tif metadata == nil {\n\t\t\tmetadata = map[string]string{}\n\t\t\tctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)\n\t\t}\n\t\tm := metadata.(map[string]string)\n\t\tm[share.AuthKey] = c.auth\n\t}\n\n\tctx = setServerTimeout(ctx)\n\n\tif share.Trace {\n\t\tlog.Debugf(\"select a client for %s.%s, failMode: %v, args: %+v in case of xclient SendRaw\", r.ServicePath, r.ServiceMethod, c.failMode, r.Payload)\n\t}\n\n\tvar err error\n\tk, client, err := c.selectClient(ctx, r.ServicePath, r.ServiceMethod, r.Payload)\n\tif err != nil {\n\t\tif c.failMode == Failfast {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tif contextCanceled(err) {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tif e, ok := err.(ServiceError); ok && e.IsServiceError() {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\tif share.Trace {\n\t\tlog.Debugf(\"selected a client %s for %s.%s, failMode: %v, args: %+v in case of xclient Call\", client.RemoteAddr(), r.ServicePath, r.ServiceMethod, c.failMode, r.Payload)\n\t}\n\n\tvar e error\n\tswitch c.failMode {\n\tcase Failtry:\n\t\tretries := c.option.Retries\n\t\tfor retries >= 0 {\n\t\t\tretries--\n\t\t\tif client != nil {\n\t\t\t\tm, payload, err := c.wrapSendRaw(ctx, client, r)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn m, payload, nil\n\t\t\t\t}\n\t\t\t\tif contextCanceled(err) {\n\t\t\t\t\treturn nil, nil, err\n\t\t\t\t}\n\t\t\t\tif _, ok := err.(ServiceError); ok {\n\t\t\t\t\treturn nil, nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif uncoverError(err) {\n\t\t\t\tc.removeClient(k, r.ServicePath, r.ServiceMethod, client)\n\t\t\t}\n\t\t\tclient, e = c.getCachedClient(k, r.ServicePath, r.ServiceMethod, r.Payload)\n\t\t}\n\n\t\tif err == nil {\n\t\t\terr = e\n\t\t}\n\t\treturn nil, nil, err\n\tcase Failover:\n\t\tretries := c.option.Retries\n\t\tfor retries >= 0 {\n\t\t\tretries--\n\t\t\tif client != nil {\n\t\t\t\tm, payload, err := c.wrapSendRaw(ctx, client, r)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn m, payload, nil\n\t\t\t\t}\n\t\t\t\tif contextCanceled(err) {\n\t\t\t\t\treturn nil, nil, err\n\t\t\t\t}\n\t\t\t\tif e, ok := err.(ServiceError); ok && e.IsServiceError() {\n\t\t\t\t\treturn nil, nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif uncoverError(err) {\n\t\t\t\tc.removeClient(k, r.ServicePath, r.ServiceMethod, client)\n\t\t\t}\n\t\t\t// select another server\n\t\t\tk, client, e = c.selectClient(ctx, r.ServicePath, r.ServiceMethod, r.Payload)\n\t\t}\n\n\t\tif err == nil {\n\t\t\terr = e\n\t\t}\n\t\treturn nil, nil, err\n\n\tdefault: // Failfast\n\t\tm, payload, err := c.wrapSendRaw(ctx, client, r)\n\t\tif err != nil {\n\t\t\tif uncoverError(err) {\n\t\t\t\tc.removeClient(k, r.ServicePath, r.ServiceMethod, client)\n\t\t\t}\n\t\t}\n\n\t\treturn m, payload, nil\n\t}\n}\n\nfunc (c *xClient) wrapCall(ctx context.Context, client RPCClient, serviceMethod string, args interface{}, reply interface{}) error {\n\tif client == nil {\n\t\treturn ErrServerUnavailable\n\t}\n\n\tif share.Trace {\n\t\tlog.Debugf(\"call a client for %s.%s, args: %+v in case of xclient wrapCall\", c.servicePath, serviceMethod, args)\n\t}\n\n\tif _, ok := ctx.(*share.Context); !ok {\n\t\tctx = share.NewContext(ctx)\n\t}\n\n\terr := c.Plugins.DoPreCall(ctx, c.servicePath, serviceMethod, args)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = client.Call(ctx, c.servicePath, serviceMethod, args, reply)\n\tc.Plugins.DoPostCall(ctx, c.servicePath, serviceMethod, args, reply, err)\n\n\tif share.Trace {\n\t\tlog.Debugf(\"called a client for %s.%s, args: %+v, err: %v in case of xclient wrapCall\", c.servicePath, serviceMethod, args, err)\n\t}\n\n\treturn err\n}\n\n// wrapSendRaw wrap SendRaw to support client plugins\nfunc (c *xClient) wrapSendRaw(ctx context.Context, client RPCClient, r *protocol.Message) (map[string]string, []byte, error) {\n\tif client == nil {\n\t\treturn nil, nil, ErrServerUnavailable\n\t}\n\n\tif share.Trace {\n\t\tlog.Debugf(\"call a client for %s.%s, args: %+v in case of xclient wrapSendRaw\", c.servicePath, r.ServiceMethod, r.Payload)\n\t}\n\n\tctx = share.NewContext(ctx)\n\terr := c.Plugins.DoPreCall(ctx, c.servicePath, r.ServiceMethod, r.Payload)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tm, payload, err := client.SendRaw(ctx, r)\n\tc.Plugins.DoPostCall(ctx, c.servicePath, r.ServiceMethod, r.Payload, nil, err)\n\n\tif share.Trace {\n\t\tlog.Debugf(\"called a client for %s.%s, args: %+v, err: %v in case of xclient wrapSendRaw\", c.servicePath, r.ServiceMethod, r.Payload, err)\n\t}\n\n\treturn m, payload, err\n}\n\n// Broadcast sends requests to all servers and Success only when all servers return OK.\n// FailMode and SelectMode are meanless for this method.\n// Please set timeout to avoid hanging.\nfunc (c *xClient) Broadcast(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {\n\tif c.isShutdown {\n\t\treturn ErrXClientShutdown\n\t}\n\n\tif c.auth != \"\" {\n\t\tmetadata := ctx.Value(share.ReqMetaDataKey)\n\t\tif metadata == nil {\n\t\t\tmetadata = map[string]string{}\n\t\t\tctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)\n\t\t}\n\t\tm := metadata.(map[string]string)\n\t\tm[share.AuthKey] = c.auth\n\t}\n\n\tvar replyOnce sync.Once\n\n\tctx = setServerTimeout(ctx)\n\t// add timeout after set server timeout, only prevent client hanging\n\tctx, cancel := context.WithTimeout(ctx, time.Minute)\n\tdefer cancel()\n\tcallPlugins := make([]RPCClient, 0, len(c.servers))\n\tclients := make(map[string]RPCClient)\n\tc.mu.Lock()\n\tfor k := range c.servers {\n\t\tclient, needCallPlugin, err := c.getCachedClientWithoutLock(k, c.servicePath, serviceMethod)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tclients[k] = client\n\t\tif needCallPlugin {\n\t\t\tcallPlugins = append(callPlugins, client)\n\t\t}\n\t}\n\tc.mu.Unlock()\n\n\tfor i := range callPlugins {\n\t\tif c.Plugins != nil {\n\t\t\tc.Plugins.DoClientConnected(callPlugins[i].GetConn())\n\t\t}\n\t}\n\n\tif len(clients) == 0 {\n\t\treturn ErrXClientNoServer\n\t}\n\n\terr := &ex.MultiError{}\n\tl := len(clients)\n\tdone := make(chan bool, l)\n\tfor k, client := range clients {\n\t\tk := k\n\t\tclient := client\n\t\tgo func() {\n\t\t\tvar clonedReply interface{}\n\t\t\tif reply != nil {\n\t\t\t\tclonedReply = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()\n\t\t\t}\n\n\t\t\te := c.wrapCall(ctx, client, serviceMethod, args, clonedReply)\n\t\t\tdefer func() {\n\t\t\t\tdone <- (e == nil)\n\t\t\t}()\n\t\t\tif e != nil {\n\t\t\t\tif uncoverError(e) {\n\t\t\t\t\tc.removeClient(k, c.servicePath, serviceMethod, client)\n\t\t\t\t}\n\t\t\t\terr.Append(e)\n\t\t\t}\n\n\t\t\tif e == nil && reply != nil && clonedReply != nil {\n\t\t\t\treplyOnce.Do(func() {\n\t\t\t\t\treflect.ValueOf(reply).Elem().Set(reflect.ValueOf(clonedReply).Elem())\n\t\t\t\t})\n\t\t\t}\n\t\t}()\n\t}\n\ncheck:\n\tfor {\n\t\tselect {\n\t\tcase result := <-done:\n\t\t\tl--\n\t\t\tif l == 0 || !result { // all returns or some one returns an error\n\t\t\t\tbreak check\n\t\t\t}\n\t\t}\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\t\terr.Append(errors.New((\"timeout\")))\n\tdefault:\n\t}\n\n\treturn err.ErrorOrNil()\n}\n\n// Fork sends requests to all servers and Success once one server returns OK.\n// FailMode and SelectMode are meanless for this method.\nfunc (c *xClient) Fork(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {\n\tif c.isShutdown {\n\t\treturn ErrXClientShutdown\n\t}\n\n\tif c.auth != \"\" {\n\t\tmetadata := ctx.Value(share.ReqMetaDataKey)\n\t\tif metadata == nil {\n\t\t\tmetadata = map[string]string{}\n\t\t\tctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)\n\t\t}\n\t\tm := metadata.(map[string]string)\n\t\tm[share.AuthKey] = c.auth\n\t}\n\n\tctx = setServerTimeout(ctx)\n\n\t// add timeout after set server timeout, only prevent client hanging\n\tctx, cancel := context.WithTimeout(ctx, time.Minute)\n\tdefer cancel()\n\tcallPlugins := make([]RPCClient, 0, len(c.servers))\n\tclients := make(map[string]RPCClient)\n\tc.mu.Lock()\n\tfor k := range c.servers {\n\t\tclient, needCallPlugin, err := c.getCachedClientWithoutLock(k, c.servicePath, serviceMethod)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tclients[k] = client\n\t\tif needCallPlugin {\n\t\t\tcallPlugins = append(callPlugins, client)\n\t\t}\n\t}\n\tc.mu.Unlock()\n\n\tfor i := range callPlugins {\n\t\tif c.Plugins != nil {\n\t\t\tc.Plugins.DoClientConnected(callPlugins[i].GetConn())\n\t\t}\n\t}\n\n\tif len(clients) == 0 {\n\t\treturn ErrXClientNoServer\n\t}\n\n\tvar replyOnce sync.Once\n\n\terr := &ex.MultiError{}\n\tl := len(clients)\n\tdone := make(chan bool, l)\n\tfor k, client := range clients {\n\t\tk := k\n\t\tclient := client\n\t\tgo func() {\n\t\t\tvar clonedReply interface{}\n\t\t\tif reply != nil {\n\t\t\t\tclonedReply = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()\n\t\t\t}\n\n\t\t\te := c.wrapCall(ctx, client, serviceMethod, args, clonedReply)\n\t\t\tif e == nil && reply != nil && clonedReply != nil {\n\t\t\t\treplyOnce.Do(func() {\n\t\t\t\t\treflect.ValueOf(reply).Elem().Set(reflect.ValueOf(clonedReply).Elem())\n\t\t\t\t})\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\tdone <- (e == nil)\n\t\t\t}()\n\t\t\tif e != nil {\n\t\t\t\tif uncoverError(e) {\n\t\t\t\t\tc.removeClient(k, c.servicePath, serviceMethod, client)\n\t\t\t\t}\n\t\t\t\terr.Append(e)\n\t\t\t}\n\t\t}()\n\t}\n\ncheck:\n\tfor {\n\t\tselect {\n\t\tcase result := <-done:\n\t\t\tl--\n\t\t\tif result {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif l == 0 { // all returns or some one returns an error\n\t\t\t\tbreak check\n\t\t\t}\n\t\t}\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\t\terr.Append(errors.New((\"timeout\")))\n\tdefault:\n\t}\n\n\treturn err.ErrorOrNil()\n}\n\n// Inform sends requests to all servers and returns all results from services.\n// FailMode and SelectMode are meanless for this method.\n// Please set timeout to avoid hanging.\nfunc (c *xClient) Inform(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) ([]Receipt, error) {\n\tif c.isShutdown {\n\t\treturn nil, ErrXClientShutdown\n\t}\n\n\tif c.auth != \"\" {\n\t\tmetadata := ctx.Value(share.ReqMetaDataKey)\n\t\tif metadata == nil {\n\t\t\tmetadata = map[string]string{}\n\t\t\tctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)\n\t\t}\n\t\tm := metadata.(map[string]string)\n\t\tm[share.AuthKey] = c.auth\n\t}\n\n\tctx = setServerTimeout(ctx)\n\n\t// add timeout after set server timeout, only prevent client hanging\n\tctx, cancel := context.WithTimeout(ctx, time.Minute)\n\tdefer cancel()\n\tcallPlugins := make([]RPCClient, 0, len(c.servers))\n\tclients := make(map[string]RPCClient)\n\tc.mu.Lock()\n\tfor k := range c.servers {\n\t\tclient, needCallPlugin, err := c.getCachedClientWithoutLock(k, c.servicePath, serviceMethod)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tclients[k] = client\n\t\tif needCallPlugin {\n\t\t\tcallPlugins = append(callPlugins, client)\n\t\t}\n\t}\n\tc.mu.Unlock()\n\n\tfor i := range callPlugins {\n\t\tif c.Plugins != nil {\n\t\t\tc.Plugins.DoClientConnected(callPlugins[i].GetConn())\n\t\t}\n\t}\n\n\tif len(clients) == 0 {\n\t\treturn nil, ErrXClientNoServer\n\t}\n\n\tvar receiptsLock sync.Mutex\n\tvar receipts []Receipt\n\n\tvar replyOnce sync.Once\n\n\terr := &ex.MultiError{}\n\tl := len(clients)\n\tdone := make(chan bool, l)\n\tfor k, client := range clients {\n\t\tk := k\n\t\tclient := client\n\t\tgo func() {\n\t\t\tvar clonedReply interface{}\n\t\t\tif reply != nil {\n\t\t\t\tclonedReply = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()\n\t\t\t}\n\n\t\t\te := c.wrapCall(ctx, client, serviceMethod, args, clonedReply)\n\t\t\tdefer func() {\n\t\t\t\tdone <- (e == nil)\n\t\t\t}()\n\t\t\tif e != nil {\n\t\t\t\tif uncoverError(e) {\n\t\t\t\t\tc.removeClient(k, c.servicePath, serviceMethod, client)\n\t\t\t\t}\n\t\t\t\terr.Append(e)\n\t\t\t}\n\t\t\tif e == nil && reply != nil && clonedReply != nil {\n\t\t\t\treplyOnce.Do(func() {\n\t\t\t\t\treflect.ValueOf(reply).Elem().Set(reflect.ValueOf(clonedReply).Elem())\n\t\t\t\t})\n\t\t\t}\n\n\t\t\taddr := k\n\t\t\tss := strings.SplitN(k, \"@\", 2)\n\t\t\tif len(ss) == 2 {\n\t\t\t\taddr = ss[1]\n\t\t\t}\n\t\t\treceiptsLock.Lock()\n\n\t\t\treceipts = append(receipts, Receipt{\n\t\t\t\tAddress: addr,\n\t\t\t\tReply:   clonedReply,\n\t\t\t\tError:   err,\n\t\t\t})\n\t\t\treceiptsLock.Unlock()\n\t\t}()\n\t}\n\ncheck:\n\tfor {\n\t\tselect {\n\t\tcase <-done:\n\t\t\tl--\n\t\t\tif l == 0 { // all returns or some one returns an error\n\t\t\t\tbreak check\n\t\t\t}\n\t\t}\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\t\terr.Append(errors.New((\"timeout\")))\n\tdefault:\n\t}\n\n\treturn receipts, err.ErrorOrNil()\n}\n\n// SendFile sends a local file to the server.\n// fileName is the path of local file.\n// rateInBytesPerSecond can limit bandwidth of sending,  0 means does not limit the bandwidth, unit is bytes / second.\nfunc (c *xClient) SendFile(ctx context.Context, fileName string, rateInBytesPerSecond int64, meta map[string]string) error {\n\tfile, err := os.Open(fileName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer file.Close()\n\n\tfi, err := os.Stat(fileName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\targs := share.FileTransferArgs{\n\t\tFileName: fi.Name(),\n\t\tFileSize: fi.Size(),\n\t\tMeta:     meta,\n\t}\n\n\tctx = setServerTimeout(ctx)\n\n\treply := &share.FileTransferReply{}\n\terr = c.Call(ctx, \"TransferFile\", args, reply)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconn, err := net.DialTimeout(\"tcp\", reply.Addr, c.option.ConnectTimeout)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer conn.Close()\n\n\t_, err = conn.Write(reply.Token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar tb *ratelimit.Bucket\n\n\tif rateInBytesPerSecond > 0 {\n\t\ttb = ratelimit.NewBucketWithRate(float64(rateInBytesPerSecond), rateInBytesPerSecond)\n\t}\n\n\tsendBuffer := make([]byte, FileTransferBufferSize)\nloop:\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = ctx.Err()\n\t\t\tbreak loop\n\t\tdefault:\n\t\t\tif tb != nil {\n\t\t\t\ttb.Wait(FileTransferBufferSize)\n\t\t\t}\n\t\t\tn, err := file.Read(sendBuffer)\n\t\t\tif err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\treturn nil\n\t\t\t\t} else {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif n == 0 {\n\t\t\t\tbreak loop\n\t\t\t}\n\t\t\t_, err = conn.Write(sendBuffer[:n])\n\t\t\tif err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\treturn nil\n\t\t\t\t} else {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *xClient) DownloadFile(ctx context.Context, requestFileName string, saveTo io.Writer, meta map[string]string) error {\n\tctx = setServerTimeout(ctx)\n\n\targs := share.DownloadFileArgs{\n\t\tFileName: requestFileName,\n\t\tMeta:     meta,\n\t}\n\n\treply := &share.FileTransferReply{}\n\terr := c.Call(ctx, \"DownloadFile\", args, reply)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconn, err := net.DialTimeout(\"tcp\", reply.Addr, c.option.ConnectTimeout)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer conn.Close()\n\n\t_, err = conn.Write(reply.Token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbuf := make([]byte, FileTransferBufferSize)\n\tr := bufio.NewReader(conn)\nloop:\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = ctx.Err()\n\t\t\tbreak loop\n\t\tdefault:\n\t\t\tn, er := r.Read(buf)\n\t\t\tif n > 0 {\n\t\t\t\t_, ew := saveTo.Write(buf[0:n])\n\t\t\t\tif ew != nil {\n\t\t\t\t\terr = ew\n\t\t\t\t\tbreak loop\n\t\t\t\t}\n\t\t\t}\n\t\t\tif er != nil {\n\t\t\t\tif er != io.EOF {\n\t\t\t\t\terr = er\n\t\t\t\t}\n\t\t\t\tbreak loop\n\t\t\t}\n\t\t}\n\t}\n\n\treturn err\n}\n\n// Close closes this client and its underlying connections to services.\nfunc (c *xClient) Close() error {\n\tvar errs []error\n\tc.mu.Lock()\n\tc.isShutdown = true\n\tfor k, v := range c.cachedClient {\n\t\te := v.Close()\n\t\tif e != nil {\n\t\t\terrs = append(errs, e)\n\t\t}\n\n\t\tdelete(c.cachedClient, k)\n\n\t}\n\tc.mu.Unlock()\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\trecover()\n\t\t}()\n\n\t\tc.discovery.RemoveWatcher(c.ch)\n\t\tclose(c.ch)\n\t}()\n\n\tif len(errs) > 0 {\n\t\treturn ex.NewMultiError(errs)\n\t}\n\treturn nil\n}\n\nfunc (c *xClient) Stream(ctx context.Context, meta map[string]string) (net.Conn, error) {\n\targs := share.StreamServiceArgs{\n\t\tMeta: meta,\n\t}\n\n\tctx = setServerTimeout(ctx)\n\n\treply := &share.StreamServiceReply{}\n\terr := c.Call(ctx, \"Stream\", args, reply)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconn, err := net.DialTimeout(\"tcp\", reply.Addr, c.option.ConnectTimeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = conn.Write(reply.Token)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, err\n\t}\n\n\treturn conn, nil\n}\n"
  },
  {
    "path": "client/xclient_pool.go",
    "content": "package client\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\n// XClientPool is a xclient pool with fixed size.\n// It uses roundrobin algorithm to call its xclients.\n// All xclients share the same configurations such as ServiceDiscovery and serverMessageChan.\ntype XClientPool struct {\n\tcount    uint64\n\tindex    uint64\n\txclients []XClient\n\tmu       sync.RWMutex\n\n\tservicePath string\n\tfailMode    FailMode\n\tselectMode  SelectMode\n\tdiscovery   ServiceDiscovery\n\toption      Option\n\tauth        string\n\tPlugins     PluginContainer\n\n\tserverMessageChan chan<- *protocol.Message\n}\n\n// NewXClientPool creates a fixed size XClient pool.\nfunc NewXClientPool(count int, servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) *XClientPool {\n\tpool := &XClientPool{\n\t\tcount:       uint64(count),\n\t\txclients:    make([]XClient, count),\n\t\tservicePath: servicePath,\n\t\tfailMode:    failMode,\n\t\tselectMode:  selectMode,\n\t\tdiscovery:   discovery,\n\t\toption:      option,\n\t}\n\n\tfor i := 0; i < count; i++ {\n\t\txclient := NewXClient(servicePath, failMode, selectMode, discovery, option)\n\t\tpool.xclients[i] = xclient\n\t}\n\treturn pool\n}\n\n// NewBidirectionalXClientPool creates a BidirectionalXClient pool with fixed size.\nfunc NewBidirectionalXClientPool(count int, servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) *XClientPool {\n\tpool := &XClientPool{\n\t\tcount:             uint64(count),\n\t\txclients:          make([]XClient, count),\n\t\tservicePath:       servicePath,\n\t\tfailMode:          failMode,\n\t\tselectMode:        selectMode,\n\t\tdiscovery:         discovery,\n\t\toption:            option,\n\t\tserverMessageChan: serverMessageChan,\n\t}\n\n\tfor i := 0; i < count; i++ {\n\t\txclient := NewBidirectionalXClient(servicePath, failMode, selectMode, discovery, option, serverMessageChan)\n\t\tpool.xclients[i] = xclient\n\t}\n\treturn pool\n}\n\n// Auth sets s token for Authentication.\nfunc (c *XClientPool) Auth(auth string) {\n\tc.auth = auth\n\tc.mu.RLock()\n\tfor _, v := range c.xclients {\n\t\tv.Auth(auth)\n\t}\n\tc.mu.RUnlock()\n}\n\n// SetPlugins sets client's plugins.\nfunc (p *XClientPool) SetPlugins(plugins PluginContainer) {\n\tp.Plugins = plugins\n\tp.mu.RLock()\n\tfor _, v := range p.xclients {\n\t\tv.SetPlugins(plugins)\n\t}\n\tp.mu.RUnlock()\n}\n\n// GetPlugins returns client's plugins.\nfunc (p *XClientPool) GetPlugins() PluginContainer {\n\treturn p.Plugins\n}\n\n// Get returns a xclient.\n// It does not remove this xclient from its cache so you don't need to put it back.\n// Don't close this xclient because maybe other goroutines are using this xclient.\nfunc (p *XClientPool) Get() XClient {\n\ti := atomic.AddUint64(&p.index, 1)\n\tpicked := int(i % p.count)\n\treturn p.xclients[picked]\n}\n\n// Close this pool.\n// Please make sure it won't be used any more.\nfunc (p *XClientPool) Close() {\n\tfor _, c := range p.xclients {\n\t\tc.Close()\n\t}\n\tp.xclients = nil\n}\n"
  },
  {
    "path": "client/xclient_pool_test.go",
    "content": "package client\n\nimport (\n\t\"testing\"\n)\n\n// testPlugin is a simple test plugin implementation\ntype testPlugin struct {\n\tname string\n}\n\nfunc TestXClientPool_SetPlugins(t *testing.T) {\n\t// Create a simple discovery\n\tpairs := []*KVPair{\n\t\t{Key: \"tcp@127.0.0.1:8972\", Value: \"\"},\n\t}\n\tdiscovery, err := NewMultipleServersDiscovery(pairs)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create discovery: %v\", err)\n\t}\n\tdefer discovery.Close()\n\n\t// Create a pool\n\tpool := NewXClientPool(3, \"Arith\", Failtry, RandomSelect, discovery, DefaultOption)\n\tdefer pool.Close()\n\n\t// Create plugins\n\tplugins := NewPluginContainer()\n\ttp := &testPlugin{name: \"test-plugin\"}\n\tplugins.Add(tp)\n\n\t// Test SetPlugins\n\tpool.SetPlugins(plugins)\n\n\t// Verify plugins are set on pool\n\tif pool.GetPlugins() == nil {\n\t\tt.Error(\"plugins should not be nil after SetPlugins\")\n\t}\n\tif pool.GetPlugins() != plugins {\n\t\tt.Error(\"pool plugins should be the same as the set plugins\")\n\t}\n\n\t// Verify plugins are set on all xclients\n\tfor i := 0; i < 3; i++ {\n\t\txclient := pool.Get()\n\t\tif xclient.GetPlugins() == nil {\n\t\t\tt.Errorf(\"xclient %d plugins should not be nil\", i)\n\t\t}\n\t\tif xclient.GetPlugins() != plugins {\n\t\t\tt.Errorf(\"xclient %d plugins should be the same as the set plugins\", i)\n\t\t}\n\t}\n}\n\nfunc TestXClientPool_GetPlugins(t *testing.T) {\n\t// Create a simple discovery\n\tpairs := []*KVPair{\n\t\t{Key: \"tcp@127.0.0.1:8972\", Value: \"\"},\n\t}\n\tdiscovery, err := NewMultipleServersDiscovery(pairs)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create discovery: %v\", err)\n\t}\n\tdefer discovery.Close()\n\n\t// Create a pool\n\tpool := NewXClientPool(2, \"Arith\", Failtry, RandomSelect, discovery, DefaultOption)\n\tdefer pool.Close()\n\n\t// Initially, plugins should be nil\n\tif pool.GetPlugins() != nil {\n\t\tt.Error(\"plugins should be nil initially\")\n\t}\n\n\t// Create and set plugins\n\tplugins := NewPluginContainer()\n\ttp := &testPlugin{name: \"test-plugin\"}\n\tplugins.Add(tp)\n\tpool.SetPlugins(plugins)\n\n\t// Verify GetPlugins returns the correct plugins\n\tretrievedPlugins := pool.GetPlugins()\n\tif retrievedPlugins == nil {\n\t\tt.Error(\"plugins should not be nil after SetPlugins\")\n\t}\n\tif retrievedPlugins != plugins {\n\t\tt.Error(\"GetPlugins should return the same plugins as SetPlugins\")\n\t}\n\n\t// Verify plugins contain the test plugin\n\tallPlugins := retrievedPlugins.All()\n\tif len(allPlugins) != 1 {\n\t\tt.Errorf(\"expected 1 plugin, got %d\", len(allPlugins))\n\t}\n\tif p, ok := allPlugins[0].(*testPlugin); !ok || p.name != \"test-plugin\" {\n\t\tt.Error(\"plugin should be the test plugin\")\n\t}\n}\n\nfunc TestXClientPool_SetPlugins_Concurrent(t *testing.T) {\n\t// Create a simple discovery\n\tpairs := []*KVPair{\n\t\t{Key: \"tcp@127.0.0.1:8972\", Value: \"\"},\n\t}\n\tdiscovery, err := NewMultipleServersDiscovery(pairs)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create discovery: %v\", err)\n\t}\n\tdefer discovery.Close()\n\n\t// Create a pool\n\tpool := NewXClientPool(5, \"Arith\", Failtry, RandomSelect, discovery, DefaultOption)\n\tdefer pool.Close()\n\n\t// Test concurrent SetPlugins calls\n\tdone := make(chan bool, 10)\n\tfor i := 0; i < 10; i++ {\n\t\tgo func(id int) {\n\t\t\tplugins := NewPluginContainer()\n\t\t\ttp := &testPlugin{name: \"test-plugin\"}\n\t\t\tplugins.Add(tp)\n\t\t\tpool.SetPlugins(plugins)\n\t\t\tdone <- true\n\t\t}(i)\n\t}\n\n\t// Wait for all goroutines to complete\n\tfor i := 0; i < 10; i++ {\n\t\t<-done\n\t}\n\n\t// Verify plugins are set\n\tif pool.GetPlugins() == nil {\n\t\tt.Error(\"plugins should not be nil after concurrent SetPlugins\")\n\t}\n}\n\n"
  },
  {
    "path": "client/xclient_test.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"fmt\"\n\n\ttestutils \"github.com/smallnest/rpcx/_testutils\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/server\"\n\t\"github.com/smallnest/rpcx/share\"\n)\n\nfunc TestXClient_Thrift(t *testing.T) {\n\ts := server.NewServer()\n\ts.RegisterName(\"Arith\", new(Arith), \"\")\n\tgo s.Serve(\"tcp\", \"127.0.0.1:0\")\n\tdefer s.Close()\n\ttime.Sleep(500 * time.Millisecond)\n\n\taddr := s.Address().String()\n\n\topt := Option{\n\t\tRetries:        1,\n\t\tRPCPath:        share.DefaultRPCPath,\n\t\tConnectTimeout: 10 * time.Second,\n\t\tSerializeType:  protocol.Thrift,\n\t\tCompressType:   protocol.None,\n\t\tBackupLatency:  10 * time.Millisecond,\n\t}\n\n\td, err := NewPeer2PeerDiscovery(\"tcp@\"+addr, \"desc=a test service\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to NewPeer2PeerDiscovery: %v\", err)\n\t}\n\n\txclient := NewXClient(\"Arith\", Failtry, RandomSelect, d, opt)\n\n\tdefer xclient.Close()\n\n\targs := testutils.ThriftArgs_{}\n\targs.A = 200\n\targs.B = 100\n\n\treply := testutils.ThriftReply{}\n\n\terr = xclient.Call(context.Background(), \"ThriftMul\", &args, &reply)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to call: %v\", err)\n\t}\n\n\tfmt.Println(reply.C)\n\tif reply.C != 20000 {\n\t\tt.Fatalf(\"expect 20000 but got %d\", reply.C)\n\t}\n}\n\nfunc TestXClient_IT(t *testing.T) {\n\ts := server.NewServer()\n\ts.RegisterName(\"Arith\", new(Arith), \"\")\n\tgo s.Serve(\"tcp\", \"127.0.0.1:0\")\n\tdefer s.Close()\n\ttime.Sleep(500 * time.Millisecond)\n\n\taddr := s.Address().String()\n\n\td, err := NewPeer2PeerDiscovery(\"tcp@\"+addr, \"desc=a test service\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to NewPeer2PeerDiscovery: %v\", err)\n\t}\n\n\txclient := NewXClient(\"Arith\", Failtry, RandomSelect, d, DefaultOption)\n\n\tdefer xclient.Close()\n\n\targs := &Args{\n\t\tA: 10,\n\t\tB: 20,\n\t}\n\n\treply := &Reply{}\n\terr = xclient.Call(context.Background(), \"Mul\", args, reply)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to call: %v\", err)\n\t}\n\n\tif reply.C != 200 {\n\t\tt.Fatalf(\"expect 200 but got %d\", reply.C)\n\t}\n}\n\nfunc TestXClient_filterByStateAndGroup(t *testing.T) {\n\tservers := map[string]string{\"a\": \"\", \"b\": \"state=inactive&ops=10\", \"c\": \"ops=20\", \"d\": \"group=test1&group=test&ops=20\"}\n\tfilterByStateAndGroup(\"test\", servers)\n\tif _, ok := servers[\"b\"]; ok {\n\t\tt.Error(\"has not remove inactive node\")\n\t}\n\tif _, ok := servers[\"a\"]; ok {\n\t\tt.Error(\"has not remove inactive node\")\n\t}\n\tif _, ok := servers[\"c\"]; ok {\n\t\tt.Error(\"has not remove inactive node\")\n\t}\n\tif _, ok := servers[\"d\"]; !ok {\n\t\tt.Error(\"node must be removed\")\n\t}\n\n\tfilterByStateAndGroup(\"test1\", servers)\n\n\tif _, ok := servers[\"d\"]; !ok {\n\t\tt.Error(\"node must be removed\")\n\t}\n}\n\nfunc TestUncoverError(t *testing.T) {\n\tvar e error = strErr(\"error\")\n\tif uncoverError(e) {\n\t\tt.Fatalf(\"expect false but get true\")\n\t}\n\n\tif uncoverError(context.DeadlineExceeded) {\n\t\tt.Fatalf(\"expect false but get true\")\n\t}\n\n\tif uncoverError(context.Canceled) {\n\t\tt.Fatalf(\"expect false but get true\")\n\t}\n\n\te = errors.New(\"error\")\n\tif !uncoverError(e) {\n\t\tt.Fatalf(\"expect true but get false\")\n\t}\n}\n"
  },
  {
    "path": "clientplugin/req_rate_limiting_redis.go",
    "content": "package clientplugin\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/go-redis/redis_rate/v10\"\n\t\"github.com/redis/go-redis/v9\"\n\t\"github.com/smallnest/rpcx/client\"\n\t\"github.com/smallnest/rpcx/server\"\n)\n\nvar _ client.PreCallPlugin = (*RedisRateLimitingPlugin)(nil)\n\n// RedisRateLimitingPlugin can limit requests per unit time\ntype RedisRateLimitingPlugin struct {\n\taddrs   []string\n\tlimiter redis_rate.Limiter\n\tlimit   redis_rate.Limit\n}\n\n// NewRedisRateLimitingPlugin creates a new RateLimitingPlugin\nfunc NewRedisRateLimitingPlugin(addrs []string, rate int, burst int, period time.Duration) *RedisRateLimitingPlugin {\n\tlimit := redis_rate.Limit{\n\t\tRate:   rate,\n\t\tBurst:  burst,\n\t\tPeriod: period,\n\t}\n\trdb := redis.NewClusterClient(&redis.ClusterOptions{\n\t\tAddrs: addrs,\n\t})\n\n\tlimiter := redis_rate.NewLimiter(rdb)\n\n\treturn &RedisRateLimitingPlugin{\n\t\taddrs:   addrs,\n\t\tlimiter: *limiter,\n\t\tlimit:   limit,\n\t}\n}\n\n// PreCall can limit request processing.\nfunc (plugin *RedisRateLimitingPlugin) PreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error {\n\tres, err := plugin.limiter.Allow(ctx, servicePath+\"/\"+serviceMethod, plugin.limit)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif res.Allowed > 0 {\n\t\treturn nil\n\t}\n\treturn server.ErrReqReachLimit\n}\n"
  },
  {
    "path": "codec/codec.go",
    "content": "package codec\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\n\tpb \"google.golang.org/protobuf/proto\"\n\n\t\"github.com/apache/thrift/lib/go/thrift\"\n\t\"github.com/gogo/protobuf/proto\"\n\t\"github.com/tinylib/msgp/msgp\"\n\t\"github.com/vmihailenco/msgpack/v5\"\n)\n\n// Codec defines the interface that decode/encode payload.\ntype Codec interface {\n\tEncode(i interface{}) ([]byte, error)\n\tDecode(data []byte, i interface{}) error\n}\n\n// ByteCodec uses raw slice pf bytes and don't encode/decode.\ntype ByteCodec struct{}\n\n// Encode returns raw slice of bytes.\nfunc (c ByteCodec) Encode(i interface{}) ([]byte, error) {\n\tif data, ok := i.([]byte); ok {\n\t\treturn data, nil\n\t}\n\tif data, ok := i.(*[]byte); ok {\n\t\treturn *data, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"%T is not a []byte\", i)\n}\n\n// Decode returns raw slice of bytes.\nfunc (c ByteCodec) Decode(data []byte, i interface{}) error {\n\treflect.Indirect(reflect.ValueOf(i)).SetBytes(data)\n\treturn nil\n}\n\n// JSONCodec uses json marshaler and unmarshaler.\ntype JSONCodec struct{}\n\n// Encode encodes an object into slice of bytes.\nfunc (c JSONCodec) Encode(i interface{}) ([]byte, error) {\n\treturn json.Marshal(i)\n}\n\n// Decode decodes an object from slice of bytes.\nfunc (c JSONCodec) Decode(data []byte, i interface{}) error {\n\td := json.NewDecoder(bytes.NewBuffer(data))\n\td.UseNumber()\n\treturn d.Decode(i)\n}\n\n// PBCodec uses protobuf marshaler and unmarshaler.\ntype PBCodec struct{}\n\n// Encode encodes an object into slice of bytes.\nfunc (c PBCodec) Encode(i interface{}) ([]byte, error) {\n\tif m, ok := i.(proto.Marshaler); ok {\n\t\treturn m.Marshal()\n\t}\n\n\tif m, ok := i.(pb.Message); ok {\n\t\treturn pb.Marshal(m)\n\t}\n\n\treturn nil, fmt.Errorf(\"%T is not a proto.Marshaler or pb.Message\", i)\n}\n\n// Decode decodes an object from slice of bytes.\nfunc (c PBCodec) Decode(data []byte, i interface{}) error {\n\tif m, ok := i.(proto.Unmarshaler); ok {\n\t\treturn m.Unmarshal(data)\n\t}\n\n\tif m, ok := i.(pb.Message); ok {\n\t\treturn pb.Unmarshal(data, m)\n\t}\n\n\treturn fmt.Errorf(\"%T is not a proto.Unmarshaler  or pb.Message\", i)\n}\n\n// MsgpackCodec uses messagepack marshaler and unmarshaler.\ntype MsgpackCodec struct{}\n\n// Encode encodes an object into slice of bytes.\nfunc (c MsgpackCodec) Encode(i interface{}) ([]byte, error) {\n\tif m, ok := i.(msgp.Marshaler); ok {\n\t\treturn m.MarshalMsg(nil)\n\t}\n\tvar buf bytes.Buffer\n\tenc := msgpack.NewEncoder(&buf)\n\t// enc.UseJSONTag(true)\n\terr := enc.Encode(i)\n\treturn buf.Bytes(), err\n}\n\n// Decode decodes an object from slice of bytes.\nfunc (c MsgpackCodec) Decode(data []byte, i interface{}) error {\n\tif m, ok := i.(msgp.Unmarshaler); ok {\n\t\t_, err := m.UnmarshalMsg(data)\n\t\treturn err\n\t}\n\tdec := msgpack.NewDecoder(bytes.NewReader(data))\n\t// dec.UseJSONTag(true)\n\terr := dec.Decode(i)\n\treturn err\n}\n\ntype ThriftCodec struct{}\n\nfunc (c ThriftCodec) Encode(i interface{}) ([]byte, error) {\n\tb := thrift.NewTMemoryBufferLen(1024)\n\tp := thrift.NewTBinaryProtocolFactoryConf(&thrift.TConfiguration{}).\n\t\tGetProtocol(b)\n\tt := &thrift.TSerializer{\n\t\tTransport: b,\n\t\tProtocol:  p,\n\t}\n\tt.Transport.Close()\n\tif msg, ok := i.(thrift.TStruct); ok {\n\t\treturn t.Write(context.Background(), msg)\n\t}\n\treturn nil, errors.New(\"type assertion failed\")\n}\n\nfunc (c ThriftCodec) Decode(data []byte, i interface{}) error {\n\tt := thrift.NewTMemoryBufferLen(1024)\n\tp := thrift.NewTBinaryProtocolFactoryConf(&thrift.TConfiguration{}).\n\t\tGetProtocol(t)\n\td := &thrift.TDeserializer{\n\t\tTransport: t,\n\t\tProtocol:  p,\n\t}\n\td.Transport.Close()\n\treturn d.Read(context.Background(), i.(thrift.TStruct), data)\n}\n"
  },
  {
    "path": "codec/codec_test.go",
    "content": "package codec\n\nimport (\n\t\"testing\"\n\n\t\"github.com/smallnest/rpcx/codec/testdata\"\n)\n\ntype ColorGroup struct {\n\tId     int      `json:\"id\" xml:\"id,attr\" msg:\"id\"`\n\tName   string   `json:\"name\" xml:\"name\" msg:\"name\"`\n\tColors []string `json:\"colors\" xml:\"colors\" msg:\"colors\"`\n}\n\nvar group = ColorGroup{\n\tId:     1,\n\tName:   \"Reds\",\n\tColors: []string{\"Crimson\", \"Red\", \"Ruby\", \"Maroon\"},\n}\n\nfunc BenchmarkJSONCodec_Encode(b *testing.B) {\n\tvar raw = make([]byte, 0, 1024)\n\tserializer := JSONCodec{}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\traw, _ = serializer.Encode(group)\n\t}\n\tb.ReportMetric(float64(len(raw)), \"bytes\")\n}\n\nfunc BenchmarkPBCodec_Encode(b *testing.B) {\n\tvar raw = make([]byte, 0, 1024)\n\tserializer := PBCodec{}\n\tgroup := testdata.ProtoColorGroup{\n\t\tId:     1,\n\t\tName:   \"Reds\",\n\t\tColors: []string{\"Crimson\", \"Red\", \"Ruby\", \"Maroon\"},\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\traw, _ = serializer.Encode(&group)\n\t}\n\tb.ReportMetric(float64(len(raw)), \"bytes\")\n}\n\nfunc BenchmarkMsgpackCodec_Encode(b *testing.B) {\n\tvar raw = make([]byte, 0, 1024)\n\tserializer := MsgpackCodec{}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\traw, _ = serializer.Encode(group)\n\t}\n\tb.ReportMetric(float64(len(raw)), \"bytes\")\n}\n\nfunc BenchmarkThriftCodec_Encode(b *testing.B) {\n\tvar bb = make([]byte, 0, 1024)\n\tserializer := ThriftCodec{}\n\tthriftColorGroup := testdata.ThriftColorGroup{\n\t\tID:     1,\n\t\tName:   \"Reds\",\n\t\tColors: []string{\"Crimson\", \"Red\", \"Ruby\", \"Maroon\"},\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tbb, _ = serializer.Encode(&thriftColorGroup)\n\t}\n\n\tb.ReportMetric(float64(len(bb)), \"bytes\")\n}\n\nfunc BenchmarkJSONCodec_Decode(b *testing.B) {\n\tserializer := JSONCodec{}\n\tbytes, _ := serializer.Encode(group)\n\tresult := ColorGroup{}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = serializer.Decode(bytes, &result)\n\t}\n}\n\nfunc BenchmarkPBCodec_Decode(b *testing.B) {\n\tserializer := PBCodec{}\n\tbytes, _ := serializer.Encode(group)\n\tresult := ColorGroup{}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = serializer.Decode(bytes, &result)\n\t}\n}\n\nfunc BenchmarkMsgpackCodec_Decode(b *testing.B) {\n\tserializer := MsgpackCodec{}\n\tbytes, _ := serializer.Encode(group)\n\tresult := ColorGroup{}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = serializer.Decode(bytes, &result)\n\t}\n}\n\nfunc BenchmarkThriftCodec_Decode(b *testing.B) {\n\tserializer := ThriftCodec{}\n\tthriftColorGroup := testdata.ThriftColorGroup{\n\t\tID:     1,\n\t\tName:   \"Reds\",\n\t\tColors: []string{\"Crimson\", \"Red\", \"Ruby\", \"Maroon\"},\n\t}\n\tbytes, _ := serializer.Encode(&thriftColorGroup)\n\tresult := testdata.ThriftColorGroup{}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = serializer.Decode(bytes, &result)\n\t}\n}\n"
  },
  {
    "path": "codec/testdata/GoUnusedProtection__.go",
    "content": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testdata\r\n\r\nvar GoUnusedProtection__ int;\r\n\r\n"
  },
  {
    "path": "codec/testdata/gen.sh",
    "content": "# generate .go files from IDL\nprotoc -I.  --go_out=. --go_opt=module=\"testdata\" ./protobuf.proto\n\nthrift -r -out ../ --gen go ./thrift_colorgroup.thrift\n\n# # run benchmarks\n# go test -bench=. -run=none\n\n# # clean files\n# rm -rf ./testdata/*.go"
  },
  {
    "path": "codec/testdata/protobuf.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc        v5.26.0\n// source: protobuf.proto\n\npackage testdata\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype ProtoColorGroup struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId     int32    `protobuf:\"varint,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tName   string   `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tColors []string `protobuf:\"bytes,3,rep,name=colors,proto3\" json:\"colors,omitempty\"`\n}\n\nfunc (x *ProtoColorGroup) Reset() {\n\t*x = ProtoColorGroup{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_protobuf_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ProtoColorGroup) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ProtoColorGroup) ProtoMessage() {}\n\nfunc (x *ProtoColorGroup) ProtoReflect() protoreflect.Message {\n\tmi := &file_protobuf_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ProtoColorGroup.ProtoReflect.Descriptor instead.\nfunc (*ProtoColorGroup) Descriptor() ([]byte, []int) {\n\treturn file_protobuf_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ProtoColorGroup) GetId() int32 {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn 0\n}\n\nfunc (x *ProtoColorGroup) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *ProtoColorGroup) GetColors() []string {\n\tif x != nil {\n\t\treturn x.Colors\n\t}\n\treturn nil\n}\n\nvar File_protobuf_proto protoreflect.FileDescriptor\n\nvar file_protobuf_proto_rawDesc = []byte{\n\t0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x12, 0x08, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4d, 0x0a, 0x0f, 0x50, 0x72,\n\t0x6f, 0x74, 0x6f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x0e, 0x0a,\n\t0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a,\n\t0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x74,\n\t0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_protobuf_proto_rawDescOnce sync.Once\n\tfile_protobuf_proto_rawDescData = file_protobuf_proto_rawDesc\n)\n\nfunc file_protobuf_proto_rawDescGZIP() []byte {\n\tfile_protobuf_proto_rawDescOnce.Do(func() {\n\t\tfile_protobuf_proto_rawDescData = protoimpl.X.CompressGZIP(file_protobuf_proto_rawDescData)\n\t})\n\treturn file_protobuf_proto_rawDescData\n}\n\nvar file_protobuf_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_protobuf_proto_goTypes = []interface{}{\n\t(*ProtoColorGroup)(nil), // 0: testdata.ProtoColorGroup\n}\nvar file_protobuf_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_protobuf_proto_init() }\nfunc file_protobuf_proto_init() {\n\tif File_protobuf_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_protobuf_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ProtoColorGroup); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_protobuf_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_protobuf_proto_goTypes,\n\t\tDependencyIndexes: file_protobuf_proto_depIdxs,\n\t\tMessageInfos:      file_protobuf_proto_msgTypes,\n\t}.Build()\n\tFile_protobuf_proto = out.File\n\tfile_protobuf_proto_rawDesc = nil\n\tfile_protobuf_proto_goTypes = nil\n\tfile_protobuf_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "codec/testdata/protobuf.proto",
    "content": "syntax = \"proto3\";\n\npackage testdata;\n\noption go_package = \"./testdata\";\n\nmessage ProtoColorGroup {\n  int32 id = 1;\n  string name = 2;\n  repeated string colors = 3;\n}"
  },
  {
    "path": "codec/testdata/thrift_colorgroup-consts.go",
    "content": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testdata\r\n\r\nimport(\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"fmt\"\r\n\t\"time\"\r\n\t\"github.com/apache/thrift/lib/go/thrift\"\r\n)\r\n\r\n// (needed to ensure safety because of naive import list construction.)\r\nvar _ = thrift.ZERO\r\nvar _ = fmt.Printf\r\nvar _ = context.Background\r\nvar _ = time.Now\r\nvar _ = bytes.Equal\r\n\r\n\r\nfunc init() {\r\n}\r\n\r\n"
  },
  {
    "path": "codec/testdata/thrift_colorgroup.go",
    "content": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testdata\r\n\r\nimport(\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"fmt\"\r\n\t\"time\"\r\n\t\"github.com/apache/thrift/lib/go/thrift\"\r\n)\r\n\r\n// (needed to ensure safety because of naive import list construction.)\r\nvar _ = thrift.ZERO\r\nvar _ = fmt.Printf\r\nvar _ = context.Background\r\nvar _ = time.Now\r\nvar _ = bytes.Equal\r\n\r\n// Attributes:\r\n//  - ID\r\n//  - Name\r\n//  - Colors\r\ntype ThriftColorGroup struct {\r\n  ID int32 `thrift:\"id,1\" db:\"id\" json:\"id\"`\r\n  Name string `thrift:\"name,2\" db:\"name\" json:\"name\"`\r\n  Colors []string `thrift:\"colors,3\" db:\"colors\" json:\"colors\"`\r\n}\r\n\r\nfunc NewThriftColorGroup() *ThriftColorGroup {\r\n  return &ThriftColorGroup{}\r\n}\r\n\r\n\r\nfunc (p *ThriftColorGroup) GetID() int32 {\r\n  return p.ID\r\n}\r\n\r\nfunc (p *ThriftColorGroup) GetName() string {\r\n  return p.Name\r\n}\r\n\r\nfunc (p *ThriftColorGroup) GetColors() []string {\r\n  return p.Colors\r\n}\r\nfunc (p *ThriftColorGroup) Read(ctx context.Context, iprot thrift.TProtocol) error {\r\n  if _, err := iprot.ReadStructBegin(ctx); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T read error: \", p), err)\r\n  }\r\n\r\n\r\n  for {\r\n    _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx)\r\n    if err != nil {\r\n      return thrift.PrependError(fmt.Sprintf(\"%T field %d read error: \", p, fieldId), err)\r\n    }\r\n    if fieldTypeId == thrift.STOP { break; }\r\n    switch fieldId {\r\n    case 1:\r\n      if fieldTypeId == thrift.I32 {\r\n        if err := p.ReadField1(ctx, iprot); err != nil {\r\n          return err\r\n        }\r\n      } else {\r\n        if err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n          return err\r\n        }\r\n      }\r\n    case 2:\r\n      if fieldTypeId == thrift.STRING {\r\n        if err := p.ReadField2(ctx, iprot); err != nil {\r\n          return err\r\n        }\r\n      } else {\r\n        if err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n          return err\r\n        }\r\n      }\r\n    case 3:\r\n      if fieldTypeId == thrift.LIST {\r\n        if err := p.ReadField3(ctx, iprot); err != nil {\r\n          return err\r\n        }\r\n      } else {\r\n        if err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n          return err\r\n        }\r\n      }\r\n    default:\r\n      if err := iprot.Skip(ctx, fieldTypeId); err != nil {\r\n        return err\r\n      }\r\n    }\r\n    if err := iprot.ReadFieldEnd(ctx); err != nil {\r\n      return err\r\n    }\r\n  }\r\n  if err := iprot.ReadStructEnd(ctx); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T read struct end error: \", p), err)\r\n  }\r\n  return nil\r\n}\r\n\r\nfunc (p *ThriftColorGroup)  ReadField1(ctx context.Context, iprot thrift.TProtocol) error {\r\n  if v, err := iprot.ReadI32(ctx); err != nil {\r\n  return thrift.PrependError(\"error reading field 1: \", err)\r\n} else {\r\n  p.ID = v\r\n}\r\n  return nil\r\n}\r\n\r\nfunc (p *ThriftColorGroup)  ReadField2(ctx context.Context, iprot thrift.TProtocol) error {\r\n  if v, err := iprot.ReadString(ctx); err != nil {\r\n  return thrift.PrependError(\"error reading field 2: \", err)\r\n} else {\r\n  p.Name = v\r\n}\r\n  return nil\r\n}\r\n\r\nfunc (p *ThriftColorGroup)  ReadField3(ctx context.Context, iprot thrift.TProtocol) error {\r\n  _, size, err := iprot.ReadListBegin(ctx)\r\n  if err != nil {\r\n    return thrift.PrependError(\"error reading list begin: \", err)\r\n  }\r\n  tSlice := make([]string, 0, size)\r\n  p.Colors =  tSlice\r\n  for i := 0; i < size; i ++ {\r\nvar _elem0 string\r\n    if v, err := iprot.ReadString(ctx); err != nil {\r\n    return thrift.PrependError(\"error reading field 0: \", err)\r\n} else {\r\n    _elem0 = v\r\n}\r\n    p.Colors = append(p.Colors, _elem0)\r\n  }\r\n  if err := iprot.ReadListEnd(ctx); err != nil {\r\n    return thrift.PrependError(\"error reading list end: \", err)\r\n  }\r\n  return nil\r\n}\r\n\r\nfunc (p *ThriftColorGroup) Write(ctx context.Context, oprot thrift.TProtocol) error {\r\n  if err := oprot.WriteStructBegin(ctx, \"ThriftColorGroup\"); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T write struct begin error: \", p), err) }\r\n  if p != nil {\r\n    if err := p.writeField1(ctx, oprot); err != nil { return err }\r\n    if err := p.writeField2(ctx, oprot); err != nil { return err }\r\n    if err := p.writeField3(ctx, oprot); err != nil { return err }\r\n  }\r\n  if err := oprot.WriteFieldStop(ctx); err != nil {\r\n    return thrift.PrependError(\"write field stop error: \", err) }\r\n  if err := oprot.WriteStructEnd(ctx); err != nil {\r\n    return thrift.PrependError(\"write struct stop error: \", err) }\r\n  return nil\r\n}\r\n\r\nfunc (p *ThriftColorGroup) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {\r\n  if err := oprot.WriteFieldBegin(ctx, \"id\", thrift.I32, 1); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T write field begin error 1:id: \", p), err) }\r\n  if err := oprot.WriteI32(ctx, int32(p.ID)); err != nil {\r\n  return thrift.PrependError(fmt.Sprintf(\"%T.id (1) field write error: \", p), err) }\r\n  if err := oprot.WriteFieldEnd(ctx); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T write field end error 1:id: \", p), err) }\r\n  return err\r\n}\r\n\r\nfunc (p *ThriftColorGroup) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) {\r\n  if err := oprot.WriteFieldBegin(ctx, \"name\", thrift.STRING, 2); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T write field begin error 2:name: \", p), err) }\r\n  if err := oprot.WriteString(ctx, string(p.Name)); err != nil {\r\n  return thrift.PrependError(fmt.Sprintf(\"%T.name (2) field write error: \", p), err) }\r\n  if err := oprot.WriteFieldEnd(ctx); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T write field end error 2:name: \", p), err) }\r\n  return err\r\n}\r\n\r\nfunc (p *ThriftColorGroup) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) {\r\n  if err := oprot.WriteFieldBegin(ctx, \"colors\", thrift.LIST, 3); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T write field begin error 3:colors: \", p), err) }\r\n  if err := oprot.WriteListBegin(ctx, thrift.STRING, len(p.Colors)); err != nil {\r\n    return thrift.PrependError(\"error writing list begin: \", err)\r\n  }\r\n  for _, v := range p.Colors {\r\n    if err := oprot.WriteString(ctx, string(v)); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T. (0) field write error: \", p), err) }\r\n  }\r\n  if err := oprot.WriteListEnd(ctx); err != nil {\r\n    return thrift.PrependError(\"error writing list end: \", err)\r\n  }\r\n  if err := oprot.WriteFieldEnd(ctx); err != nil {\r\n    return thrift.PrependError(fmt.Sprintf(\"%T write field end error 3:colors: \", p), err) }\r\n  return err\r\n}\r\n\r\nfunc (p *ThriftColorGroup) Equals(other *ThriftColorGroup) bool {\r\n  if p == other {\r\n    return true\r\n  } else if p == nil || other == nil {\r\n    return false\r\n  }\r\n  if p.ID != other.ID { return false }\r\n  if p.Name != other.Name { return false }\r\n  if len(p.Colors) != len(other.Colors) { return false }\r\n  for i, _tgt := range p.Colors {\r\n    _src1 := other.Colors[i]\r\n    if _tgt != _src1 { return false }\r\n  }\r\n  return true\r\n}\r\n\r\nfunc (p *ThriftColorGroup) String() string {\r\n  if p == nil {\r\n    return \"<nil>\"\r\n  }\r\n  return fmt.Sprintf(\"ThriftColorGroup(%+v)\", *p)\r\n}\r\n\r\n"
  },
  {
    "path": "codec/testdata/thrift_colorgroup.thrift",
    "content": "namespace go testdata\n\nstruct ThriftColorGroup {\n  1: i32 id = 0,\n  2: string name,\n  3: list<string> colors,\n}\n"
  },
  {
    "path": "errors/error.go",
    "content": "package errors\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\n// MultiError holds multiple errors\ntype MultiError struct {\n\tErrors []error\n\tmu     sync.Mutex\n}\n\n// Error returns the message of the actual error\nfunc (e *MultiError) Error() string {\n\treturn fmt.Sprintf(\"%v\", e.Errors)\n}\n\nfunc (e *MultiError) Append(err error) {\n\te.mu.Lock()\n\tdefer e.mu.Unlock()\n\te.Errors = append(e.Errors, err)\n}\n\nfunc (e *MultiError) ErrorOrNil() error {\n\tif e == nil || len(e.Errors) == 0 {\n\t\treturn nil\n\t}\n\treturn e\n}\n\n// NewMultiError creates and returns an Error with error splice\nfunc NewMultiError(errors []error) *MultiError {\n\treturn &MultiError{Errors: errors}\n}\n"
  },
  {
    "path": "errors/error_test.go",
    "content": "package errors\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewMultiError(t *testing.T) {\n\tvar errorSet []error\n\terrorSet = append(errorSet, errors.New(\"invalid\"))\n\terrorSet = append(errorSet, errors.New(\"fatal\"))\n\n\tmultiError := NewMultiError(errorSet)\n\tassert.Equal(t, fmt.Sprintf(\"%v\", errorSet), multiError.Error(), \"Test NewMultiError()\")\n}\n\nfunc TestMultiError_Append(t *testing.T) {\n\tmultiErrors := MultiError{}\n\tmultiErrors.Errors = append(multiErrors.Errors, errors.New(\"invalid\"))\n\tmultiErrors.Errors = append(multiErrors.Errors, errors.New(\"fatal\"))\n\n\tassert.Equal(t, 2, len(multiErrors.Errors), \"Test Append()\")\n}\n\nfunc TestMultiError_Error(t *testing.T) {\n\tmultiErrors := MultiError{}\n\tmultiErrors.Errors = append(multiErrors.Errors, errors.New(\"invalid\"))\n\tmultiErrors.Errors = append(multiErrors.Errors, errors.New(\"fatal\"))\n\n\tassert.Equal(t, \"[invalid fatal]\", multiErrors.Error(), \"Test Error()\")\n}\n\nfunc TestMultiError_ErrorOrNil(t *testing.T) {\n\tmultiErrors := MultiError{}\n\tassert.Equal(t, nil, multiErrors.ErrorOrNil(), \"Test ErrorOrNil() nil\")\n\n\tmultiErrors.Errors = append(multiErrors.Errors, errors.New(\"invalid\"))\n\tmultiErrors.Errors = append(multiErrors.Errors, errors.New(\"fatal\"))\n\tassert.Equal(t, &multiErrors, multiErrors.ErrorOrNil(), \"Test ErrorOrNil() error\")\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/smallnest/rpcx\n\ngo 1.24\n\nrequire (\n\tgithub.com/akutz/memconn v0.1.0\n\tgithub.com/alitto/pond v1.9.2\n\tgithub.com/apache/thrift v0.21.0\n\tgithub.com/edwingeng/doublejump v1.0.1\n\tgithub.com/fatih/color v1.18.0\n\tgithub.com/go-ping/ping v1.2.0\n\tgithub.com/go-redis/redis_rate/v10 v10.0.1\n\tgithub.com/godzie44/go-uring v0.0.0-20220926161041-69611e8b13d5\n\tgithub.com/gogo/protobuf v1.3.2\n\tgithub.com/golang/snappy v0.0.4\n\tgithub.com/grandcat/zeroconf v1.0.0\n\tgithub.com/hashicorp/go-multierror v1.1.1\n\tgithub.com/hashicorp/golang-lru v1.0.2\n\tgithub.com/juju/ratelimit v1.0.2\n\tgithub.com/julienschmidt/httprouter v1.3.0\n\tgithub.com/kavu/go_reuseport v1.5.0\n\tgithub.com/kr/pretty v0.3.1\n\tgithub.com/quic-go/quic-go v0.57.0\n\tgithub.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475\n\tgithub.com/redis/go-redis/v9 v9.7.3\n\tgithub.com/rpcxio/libkv v0.5.1\n\tgithub.com/rs/cors v1.11.1\n\tgithub.com/rubyist/circuitbreaker v2.2.1+incompatible\n\tgithub.com/smallnest/quick v0.2.0\n\tgithub.com/smallnest/rsocket v0.0.0-20241130031020-4a72eb6ff62a\n\tgithub.com/soheilhy/cmux v0.1.5\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/tinylib/msgp v1.2.5\n\tgithub.com/twpayne/go-jsonstruct/v3 v3.1.0\n\tgithub.com/valyala/fastrand v1.1.0\n\tgithub.com/vmihailenco/msgpack/v5 v5.4.1\n\tgithub.com/xtaci/kcp-go v5.4.20+incompatible\n\tgolang.org/x/net v0.43.0\n\tgolang.org/x/sync v0.16.0\n\tgoogle.golang.org/protobuf v1.36.4\n)\n\nrequire (\n\tgithub.com/cenk/backoff v2.2.1+incompatible // indirect\n\tgithub.com/cenkalti/backoff v2.2.1+incompatible // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 // indirect\n\tgithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect\n\tgithub.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect\n\tgithub.com/fatih/camelcase v1.0.0 // indirect\n\tgithub.com/fatih/structtag v1.2.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.6.0 // indirect\n\tgithub.com/go-redis/redis/v8 v8.11.5 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.9 // indirect\n\tgithub.com/klauspost/reedsolomon v1.12.4 // indirect\n\tgithub.com/kr/text v0.2.0 // indirect\n\tgithub.com/libp2p/go-sockaddr v0.2.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/miekg/dns v1.1.63 // indirect\n\tgithub.com/onsi/gomega v1.36.2 // indirect\n\tgithub.com/peterbourgon/g2s v0.0.0-20140925154142-ec76db4c1ac1 // indirect\n\tgithub.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/rogpeppe/go-internal v1.10.0 // indirect\n\tgithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect\n\tgithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect\n\tgithub.com/tjfoc/gmsm v1.4.1 // indirect\n\tgithub.com/vmihailenco/tagparser/v2 v2.0.0 // indirect\n\tgithub.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect\n\tgolang.org/x/crypto v0.41.0 // indirect\n\tgolang.org/x/mod v0.27.0 // indirect\n\tgolang.org/x/sys v0.35.0 // indirect\n\tgolang.org/x/text v0.28.0 // indirect\n\tgolang.org/x/tools v0.36.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=\ngithub.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=\ngithub.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=\ngithub.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=\ngithub.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=\ngithub.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=\ngithub.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=\ngithub.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE=\ngithub.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw=\ngithub.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-metrics v0.3.6/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=\ngithub.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=\ngithub.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=\ngithub.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=\ngithub.com/cenk/backoff v2.2.1+incompatible h1:djdFT7f4gF2ttuzRKPbMOWgZajgesItGLwG5FTQKmmE=\ngithub.com/cenk/backoff v2.2.1+incompatible/go.mod h1:7FtoeaSnHoZnmZzz47cM35Y9nSW7tNyaidugnHTaFDE=\ngithub.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=\ngithub.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=\ngithub.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgryski/go-jump v0.0.0-20170409065014-e1f439676b57/go.mod h1:4hKCXuwrJoYvHZxJ86+bRVTOMyJ0Ej+RqfSm8mHi6KA=\ngithub.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 h1:0wH6nO9QEa02Qx8sIQGw6ieKdz+BXjpccSOo9vXNl4U=\ngithub.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0/go.mod h1:4hKCXuwrJoYvHZxJ86+bRVTOMyJ0Ej+RqfSm8mHi6KA=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/edwingeng/doublejump v1.0.1 h1:wJ6QgNyyF23Of9vw+ThbwJ/obe9KdxaWEg/Brpv5S1o=\ngithub.com/edwingeng/doublejump v1.0.1/go.mod h1:ykMWX8JWePtMtk2OGjNE9kwtgpI+SF2FNIyXV4gS36k=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=\ngithub.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=\ngithub.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=\ngithub.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=\ngithub.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=\ngithub.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=\ngithub.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ=\ngithub.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=\ngithub.com/go-redis/redis/v8 v8.8.2/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y=\ngithub.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=\ngithub.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=\ngithub.com/go-redis/redis_rate/v10 v10.0.1 h1:calPxi7tVlxojKunJwQ72kwfozdy25RjA0bCj1h0MUo=\ngithub.com/go-redis/redis_rate/v10 v10.0.1/go.mod h1:EMiuO9+cjRkR7UvdvwMO7vbgqJkltQHtwbdIQvaBKIU=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/godzie44/go-uring v0.0.0-20220926161041-69611e8b13d5 h1:5zELAgnSz0gqmr4Q5DWCoOzNHoeBAxVUXB7LS1eG+sw=\ngithub.com/godzie44/go-uring v0.0.0-20220926161041-69611e8b13d5/go.mod h1:ermjEDUoT/fS+3Ona5Vd6t6mZkw1eHp99ILO5jGRBkM=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=\ngithub.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=\ngithub.com/hashicorp/consul/api v1.8.1/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk=\ngithub.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-hclog v0.16.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=\ngithub.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=\ngithub.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=\ngithub.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=\ngithub.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=\ngithub.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=\ngithub.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kavu/go_reuseport v1.5.0 h1:UNuiY2OblcqAtVDE8Gsg1kZz8zbBWg907sP1ceBV+bk=\ngithub.com/kavu/go_reuseport v1.5.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWoUx+JZ5/CU=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=\ngithub.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=\ngithub.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=\ngithub.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/libp2p/go-sockaddr v0.2.0 h1:Alhhj6lGxVAon9O32tOO89T601EugSx6YiGjy5BVjWk=\ngithub.com/libp2p/go-sockaddr v0.2.0/go.mod h1:5NxulaB17yJ07IpzRIleys4un0PJ7WLWgMDLBBWrGw8=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=\ngithub.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=\ngithub.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=\ngithub.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=\ngithub.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=\ngithub.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=\ngithub.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=\ngithub.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=\ngithub.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=\ngithub.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=\ngithub.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/peterbourgon/g2s v0.0.0-20140925154142-ec76db4c1ac1 h1:5Dl+ADmsGerAqHwWzyLqkNaUBQ+48DQwfDCaW1gHAQM=\ngithub.com/peterbourgon/g2s v0.0.0-20140925154142-ec76db4c1ac1/go.mod h1:1VcHEd3ro4QMoHfiNl/j7Jkln9+KQuorp0PItHMJYNg=\ngithub.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=\ngithub.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=\ngithub.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=\ngithub.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=\ngithub.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=\ngithub.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=\ngithub.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/rpcxio/libkv v0.5.1 h1:M0/QqwTcdXz7us0NB+2i8Kq5+wikTm7zZ4Hyb/jNgME=\ngithub.com/rpcxio/libkv v0.5.1/go.mod h1:zHGgtLr3cFhGtbalum0BrMPOjhFZFJXCKiws/25ewls=\ngithub.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=\ngithub.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=\ngithub.com/rubyist/circuitbreaker v2.2.1+incompatible h1:KUKd/pV8Geg77+8LNDwdow6rVCAYOp8+kHUyFvL6Mhk=\ngithub.com/rubyist/circuitbreaker v2.2.1+incompatible/go.mod h1:Ycs3JgJADPuzJDwffe12k6BZT8hxVi6lFK+gWYJLN4A=\ngithub.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=\ngithub.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/smallnest/quick v0.2.0 h1:AEvm7ZovZ6Utv+asFDBh866G4ufMNhRNMKbZHVMFYPE=\ngithub.com/smallnest/quick v0.2.0/go.mod h1:ODNivpfZTaMgYrNb/fhDtqoEe2TTPxSRo8JaIT/QThI=\ngithub.com/smallnest/rsocket v0.0.0-20241130031020-4a72eb6ff62a h1:GI6kCNC5AVFbKA6ZKbVd4r+fk+Z7XZCRQm9LURZY4t4=\ngithub.com/smallnest/rsocket v0.0.0-20241130031020-4a72eb6ff62a/go.mod h1:VJeIKKrDEzT4ZNVe87JN9uRLw1XLp/ZnnE9PfsyJ1jY=\ngithub.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=\ngithub.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=\ngithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=\ngithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=\ngithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=\ngithub.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=\ngithub.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=\ngithub.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=\ngithub.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=\ngithub.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=\ngithub.com/twpayne/go-jsonstruct/v3 v3.1.0 h1:NDK73LAXyMKW/FlkxopMKjOcIGw5HgV0ayUWS1xll68=\ngithub.com/twpayne/go-jsonstruct/v3 v3.1.0/go.mod h1:2pXzrqn1yuRpon60R7JTPQTxYBiWLMSsrh/CDmk1i0Q=\ngithub.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=\ngithub.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=\ngithub.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=\ngithub.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=\ngithub.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg=\ngithub.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=\ngithub.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=\ngithub.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=\ngo.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc=\ngo.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA=\ngo.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=\ngo.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=\ngo.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=\ngolang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=\ngolang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=\ngolang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=\ngolang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=\ngolang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=\ngolang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=\ngolang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=\ngolang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=\ngolang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=\ngoogle.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "log/default_logger.go",
    "content": "package log\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/fatih/color\"\n)\n\n// The default logger output at level error, to turn everything on like this:\n// \texport RPCX_LOG_LEVEL=99\n\n// type of log level\ntype Level int\n\n// log level constant\nconst (\n\tLvPanic Level = iota\n\tLvFatal\n\tLvError\n\tLvWarn\n\tLvInfo\n\tLvDebug\n\n\tLvMax\n)\n\ntype outputFn func(calldepth int, s string) error\n\nfunc dropOutput(calldepth int, s string) error {\n\treturn nil\n}\n\ntype defaultLogger struct {\n\t*log.Logger\n\tout [LvMax]outputFn\n}\n\nfunc NewDefaultLogger(out io.Writer, prefix string, flag int, lv Level) *defaultLogger {\n\t// set default level to error\n\tlevel := lv\n\n\t// read level from system enviroment to override\n\tlevelStr := os.Getenv(\"RPCX_LOG_LEVEL\")\n\tif len(levelStr) != 0 {\n\t\tif vl, err := strconv.Atoi(levelStr); err == nil {\n\t\t\tlevel = Level(vl)\n\t\t}\n\t}\n\n\tl := &defaultLogger{}\n\tl.Logger = log.New(out, prefix, flag)\n\n\tfor i := int(LvFatal); i < int(LvMax); i++ {\n\t\t// always enable fatal and panic\n\n\t\t// enable only lv <= RPCX_LOG_LEVEL\n\t\tif i <= int(level) {\n\t\t\tl.out[i] = l.Output\n\t\t} else {\n\t\t\tl.out[i] = dropOutput\n\t\t}\n\t}\n\treturn l\n}\n\nfunc (l *defaultLogger) Debug(v ...interface{}) {\n\t_ = l.out[int(LvDebug)](calldepth, header(\"DEBUG\", fmt.Sprint(v...)))\n}\n\nfunc (l *defaultLogger) Debugf(format string, v ...interface{}) {\n\t_ = l.out[int(LvDebug)](calldepth, header(\"DEBUG\", fmt.Sprintf(format, v...)))\n}\n\nfunc (l *defaultLogger) Info(v ...interface{}) {\n\t_ = l.out[int(LvInfo)](calldepth, header(color.GreenString(\"INFO \"), fmt.Sprint(v...)))\n}\n\nfunc (l *defaultLogger) Infof(format string, v ...interface{}) {\n\t_ = l.out[int(LvInfo)](calldepth, header(color.GreenString(\"INFO \"), fmt.Sprintf(format, v...)))\n}\n\nfunc (l *defaultLogger) Warn(v ...interface{}) {\n\t_ = l.out[int(LvWarn)](calldepth, header(color.YellowString(\"WARN \"), fmt.Sprint(v...)))\n}\n\nfunc (l *defaultLogger) Warnf(format string, v ...interface{}) {\n\t_ = l.out[int(LvWarn)](calldepth, header(color.YellowString(\"WARN \"), fmt.Sprintf(format, v...)))\n}\n\nfunc (l *defaultLogger) Error(v ...interface{}) {\n\t_ = l.out[int(LvError)](calldepth, header(color.RedString(\"ERROR\"), fmt.Sprint(v...)))\n}\n\nfunc (l *defaultLogger) Errorf(format string, v ...interface{}) {\n\t_ = l.out[int(LvError)](calldepth, header(color.RedString(\"ERROR\"), fmt.Sprintf(format, v...)))\n}\n\nfunc (l *defaultLogger) Fatal(v ...interface{}) {\n\t_ = l.Logger.Output(calldepth, header(color.MagentaString(\"FATAL\"), fmt.Sprint(v...)))\n\tos.Exit(1)\n}\n\nfunc (l *defaultLogger) Fatalf(format string, v ...interface{}) {\n\t_ = l.Logger.Output(calldepth, header(color.MagentaString(\"FATAL\"), fmt.Sprintf(format, v...)))\n\tos.Exit(1)\n}\n\nfunc (l *defaultLogger) Panic(v ...interface{}) {\n\tl.Logger.Panic(v...)\n}\n\nfunc (l *defaultLogger) Panicf(format string, v ...interface{}) {\n\tl.Logger.Panicf(format, v...)\n}\n\nfunc header(lvl, msg string) string {\n\treturn fmt.Sprintf(\"%s: %s\", lvl, msg)\n}\n"
  },
  {
    "path": "log/dummy_logger.go",
    "content": "package log\n\ntype dummyLogger struct{}\n\nfunc (l *dummyLogger) Debug(v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Debugf(format string, v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Info(v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Infof(format string, v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Warn(v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Warnf(format string, v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Error(v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Errorf(format string, v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Fatal(v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Fatalf(format string, v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Panic(v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Panicf(format string, v ...interface{}) {\n}\n"
  },
  {
    "path": "log/logger.go",
    "content": "package log\n\nimport (\n\t\"log\"\n\t\"os\"\n)\n\nconst (\n\tcalldepth = 3\n)\n\nvar l Logger = NewDefaultLogger(os.Stdout, \"\", log.LstdFlags|log.Lshortfile, LvError)\n\ntype Logger interface {\n\tDebug(v ...interface{})\n\tDebugf(format string, v ...interface{})\n\n\tInfo(v ...interface{})\n\tInfof(format string, v ...interface{})\n\n\tWarn(v ...interface{})\n\tWarnf(format string, v ...interface{})\n\n\tError(v ...interface{})\n\tErrorf(format string, v ...interface{})\n\n\tFatal(v ...interface{})\n\tFatalf(format string, v ...interface{})\n\n\tPanic(v ...interface{})\n\tPanicf(format string, v ...interface{})\n}\n\nfunc SetLogger(logger Logger) {\n\tl = logger\n}\n\nfunc GetLogger() Logger {\n\treturn l\n}\n\nfunc SetDummyLogger() {\n\tl = &dummyLogger{}\n}\n\nfunc Debug(v ...interface{}) {\n\tl.Debug(v...)\n}\nfunc Debugf(format string, v ...interface{}) {\n\tl.Debugf(format, v...)\n}\n\nfunc Info(v ...interface{}) {\n\tl.Info(v...)\n}\nfunc Infof(format string, v ...interface{}) {\n\tl.Infof(format, v...)\n}\n\nfunc Warn(v ...interface{}) {\n\tl.Warn(v...)\n}\nfunc Warnf(format string, v ...interface{}) {\n\tl.Warnf(format, v...)\n}\n\nfunc Error(v ...interface{}) {\n\tl.Error(v...)\n}\nfunc Errorf(format string, v ...interface{}) {\n\tl.Errorf(format, v...)\n}\n\nfunc Fatal(v ...interface{}) {\n\tl.Fatal(v...)\n}\nfunc Fatalf(format string, v ...interface{}) {\n\tl.Fatalf(format, v...)\n}\n\nfunc Panic(v ...interface{}) {\n\tl.Panic(v...)\n}\nfunc Panicf(format string, v ...interface{}) {\n\tl.Panicf(format, v...)\n}\n"
  },
  {
    "path": "protocol/compressor.go",
    "content": "package protocol\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\n\t\"github.com/golang/snappy\"\n\t\"github.com/smallnest/rpcx/util\"\n)\n\n// Compressor defines a common compression interface.\ntype Compressor interface {\n\tZip([]byte) ([]byte, error)\n\tUnzip([]byte) ([]byte, error)\n}\n\n// GzipCompressor implements gzip compressor.\ntype GzipCompressor struct {\n}\n\nfunc (c GzipCompressor) Zip(data []byte) ([]byte, error) {\n\treturn util.Zip(data)\n}\n\nfunc (c GzipCompressor) Unzip(data []byte) ([]byte, error) {\n\treturn util.Unzip(data)\n}\n\ntype RawDataCompressor struct {\n}\n\nfunc (c RawDataCompressor) Zip(data []byte) ([]byte, error) {\n\treturn data, nil\n}\n\nfunc (c RawDataCompressor) Unzip(data []byte) ([]byte, error) {\n\treturn data, nil\n}\n\n// SnappyCompressor implements snappy compressor\ntype SnappyCompressor struct {\n}\n\nfunc (c *SnappyCompressor) Zip(data []byte) ([]byte, error) {\n\tif len(data) == 0 {\n\t\treturn data, nil\n\t}\n\n\tvar buffer bytes.Buffer\n\twriter := snappy.NewBufferedWriter(&buffer)\n\t_, err := writer.Write(data)\n\tif err != nil {\n\t\twriter.Close()\n\t\treturn nil, err\n\t}\n\terr = writer.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buffer.Bytes(), nil\n}\n\nfunc (c *SnappyCompressor) Unzip(data []byte) ([]byte, error) {\n\tif len(data) == 0 {\n\t\treturn data, nil\n\t}\n\n\treader := snappy.NewReader(bytes.NewReader(data))\n\tout, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn out, err\n}\n"
  },
  {
    "path": "protocol/compressor_test.go",
    "content": "package protocol\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/smallnest/rpcx/codec\"\n\t\"github.com/smallnest/rpcx/protocol/testdata\"\n)\n\nfunc newBenchmarkMessage() *testdata.BenchmarkMessage {\n\tvar theAnswer = \"Answer to the Ultimate Question of Life, the Universe, and Everything\"\n\tvar args testdata.BenchmarkMessage\n\n\tv := reflect.ValueOf(&args).Elem()\n\n\tfor k := 0; k < v.NumField(); k++ {\n\t\tfield := v.Field(k)\n\t\tfieldName := v.Type().Field(k).Name\n\t\t// filter unexported fields\n\t\tif fieldName[0] >= 97 && fieldName[0] <= 122 {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch field.Kind() {\n\t\tcase reflect.Int, reflect.Int32, reflect.Int64:\n\t\t\tfield.SetInt(31415926)\n\t\tcase reflect.Bool:\n\t\t\tfield.SetBool(true)\n\t\tcase reflect.String:\n\t\t\tfield.SetString(theAnswer)\n\t\t}\n\t}\n\n\treturn &args\n}\n\nfunc BenchmarkGzipCompressor_Zip(b *testing.B) {\n\tcompressor := GzipCompressor{}\n\tserializer := codec.PBCodec{}\n\traw, _ := serializer.Encode(newBenchmarkMessage())\n\tzipped := make([]byte, 1024)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tzipped, _ = compressor.Zip(raw)\n\t}\n\tb.ReportMetric(float64(len(zipped)), \"bytes\")\n}\n\nfunc BenchmarkRawDataCompressor_Zip(b *testing.B) {\n\tcompressor := RawDataCompressor{}\n\tserializer := codec.PBCodec{}\n\traw, _ := serializer.Encode(newBenchmarkMessage())\n\tzipped := make([]byte, 1024)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tzipped, _ = compressor.Zip(raw)\n\t}\n\tb.ReportMetric(float64(len(zipped)), \"bytes\")\n}\n\nfunc BenchmarkSnappyCompressor_Zip(b *testing.B) {\n\tcompressor := SnappyCompressor{}\n\tserializer := codec.PBCodec{}\n\traw, _ := serializer.Encode(newBenchmarkMessage())\n\tzipped := make([]byte, 1024)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tzipped, _ = compressor.Zip(raw)\n\t}\n\tb.ReportMetric(float64(len(zipped)), \"bytes\")\n}\n\nfunc BenchmarkGzipCompressor_Unzip(b *testing.B) {\n\tcompressor := GzipCompressor{}\n\tserializer := codec.PBCodec{}\n\traw, _ := serializer.Encode(newBenchmarkMessage())\n\tzipped, _ := compressor.Zip(raw)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\traw, _ = compressor.Unzip(zipped)\n\t}\n\tb.ReportMetric(float64(len(raw)), \"bytes\")\n}\n\nfunc BenchmarkRawDataCompressor_Unzip(b *testing.B) {\n\tcompressor := RawDataCompressor{}\n\tserializer := codec.PBCodec{}\n\traw, _ := serializer.Encode(newBenchmarkMessage())\n\tzipped, _ := compressor.Zip(raw)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\traw, _ = compressor.Unzip(zipped)\n\t}\n\tb.ReportMetric(float64(len(raw)), \"bytes\")\n}\n\nfunc BenchmarkSnappyCompressor_Unzip(b *testing.B) {\n\tcompressor := SnappyCompressor{}\n\tserializer := codec.PBCodec{}\n\traw, _ := serializer.Encode(newBenchmarkMessage())\n\tzipped, _ := compressor.Zip(raw)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\traw, _ = compressor.Unzip(zipped)\n\t}\n\tb.ReportMetric(float64(len(raw)), \"bytes\")\n}\n"
  },
  {
    "path": "protocol/message.go",
    "content": "package protocol\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"runtime\"\n\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/util\"\n)\n\nvar bufferPool = util.NewLimitedPool(512, 4096)\n\n// Compressors are compressors supported by rpcx. You can add customized compressor in Compressors.\nvar Compressors = map[CompressType]Compressor{\n\tNone: &RawDataCompressor{},\n\tGzip: &GzipCompressor{},\n}\n\n// MaxMessageLength is the max length of a message.\n// Default is 0 that means does not limit length of messages.\n// It is used to validate when read messages from io.Reader.\nvar MaxMessageLength = 0\n\nconst (\n\tmagicNumber byte = 0x08\n)\n\nfunc MagicNumber() byte {\n\treturn magicNumber\n}\n\nvar (\n\t// ErrMetaKVMissing some keys or values are missing.\n\tErrMetaKVMissing = errors.New(\"wrong metadata lines. some keys or values are missing\")\n\t// ErrMessageTooLong message is too long\n\tErrMessageTooLong = errors.New(\"message is too long\")\n\n\tErrUnsupportedCompressor = errors.New(\"unsupported compressor\")\n)\n\nconst (\n\t// ServiceError contains error info of service invocation\n\tServiceError = \"__rpcx_error__\"\n)\n\n// MessageType is message type of requests and responses.\ntype MessageType byte\n\nconst (\n\t// Request is message type of request\n\tRequest MessageType = iota\n\t// Response is message type of response\n\tResponse\n)\n\n// MessageStatusType is status of messages.\ntype MessageStatusType byte\n\nconst (\n\t// Normal is normal requests and responses.\n\tNormal MessageStatusType = iota\n\t// Error indicates some errors occur.\n\tError\n)\n\n// CompressType defines decompression type.\ntype CompressType byte\n\nconst (\n\t// None does not compress.\n\tNone CompressType = iota\n\t// Gzip uses gzip compression.\n\tGzip\n)\n\n// SerializeType defines serialization type of payload.\ntype SerializeType byte\n\nconst (\n\t// SerializeNone uses raw []byte and don't serialize/deserialize\n\tSerializeNone SerializeType = iota\n\t// JSON for payload.\n\tJSON\n\t// ProtoBuffer for payload.\n\tProtoBuffer\n\t// MsgPack for payload\n\tMsgPack\n\t// Thrift\n\t// Thrift for payload\n\tThrift\n)\n\n// Message is the generic type of Request and Response.\ntype Message struct {\n\t*Header\n\tServicePath   string\n\tServiceMethod string\n\tMetadata      map[string]string\n\tPayload       []byte\n\tdata          []byte\n}\n\n// NewMessage creates an empty message.\nfunc NewMessage() *Message {\n\theader := Header([12]byte{})\n\theader[0] = magicNumber\n\n\treturn &Message{\n\t\tHeader: &header,\n\t}\n}\n\n// Header is the first part of Message and has fixed size.\n// Format:\ntype Header [12]byte\n\n// CheckMagicNumber checks whether header starts rpcx magic number.\nfunc (h Header) CheckMagicNumber() bool {\n\treturn h[0] == magicNumber\n}\n\n// Version returns version of rpcx protocol.\nfunc (h Header) Version() byte {\n\treturn h[1]\n}\n\n// SetVersion sets version for this header.\nfunc (h *Header) SetVersion(v byte) {\n\th[1] = v\n}\n\n// MessageType returns the message type.\nfunc (h Header) MessageType() MessageType {\n\treturn MessageType(h[2]&0x80) >> 7\n}\n\n// SetMessageType sets message type.\nfunc (h *Header) SetMessageType(mt MessageType) {\n\th[2] = h[2] | (byte(mt) << 7)\n}\n\n// IsHeartbeat returns whether the message is heartbeat message.\nfunc (h Header) IsHeartbeat() bool {\n\treturn h[2]&0x40 == 0x40\n}\n\n// SetHeartbeat sets the heartbeat flag.\nfunc (h *Header) SetHeartbeat(hb bool) {\n\tif hb {\n\t\th[2] = h[2] | 0x40\n\t} else {\n\t\th[2] = h[2] &^ 0x40\n\t}\n}\n\n// IsOneway returns whether the message is one-way message.\n// If true, server won't send responses.\nfunc (h Header) IsOneway() bool {\n\treturn h[2]&0x20 == 0x20\n}\n\n// SetOneway sets the oneway flag.\nfunc (h *Header) SetOneway(oneway bool) {\n\tif oneway {\n\t\th[2] = h[2] | 0x20\n\t} else {\n\t\th[2] = h[2] &^ 0x20\n\t}\n}\n\n// CompressType returns compression type of messages.\nfunc (h Header) CompressType() CompressType {\n\treturn CompressType((h[2] & 0x1C) >> 2)\n}\n\n// SetCompressType sets the compression type.\nfunc (h *Header) SetCompressType(ct CompressType) {\n\th[2] = (h[2] &^ 0x1C) | ((byte(ct) << 2) & 0x1C)\n}\n\n// MessageStatusType returns the message status type.\nfunc (h Header) MessageStatusType() MessageStatusType {\n\treturn MessageStatusType(h[2] & 0x03)\n}\n\n// SetMessageStatusType sets message status type.\nfunc (h *Header) SetMessageStatusType(mt MessageStatusType) {\n\th[2] = (h[2] &^ 0x03) | (byte(mt) & 0x03)\n}\n\n// SerializeType returns serialization type of payload.\nfunc (h Header) SerializeType() SerializeType {\n\treturn SerializeType((h[3] & 0xF0) >> 4)\n}\n\n// SetSerializeType sets the serialization type.\nfunc (h *Header) SetSerializeType(st SerializeType) {\n\th[3] = (h[3] &^ 0xF0) | (byte(st) << 4)\n}\n\n// Seq returns sequence number of messages.\nfunc (h Header) Seq() uint64 {\n\treturn binary.BigEndian.Uint64(h[4:])\n}\n\n// SetSeq sets  sequence number.\nfunc (h *Header) SetSeq(seq uint64) {\n\tbinary.BigEndian.PutUint64(h[4:], seq)\n}\n\n// Clone clones from an message.\nfunc (m Message) Clone() *Message {\n\theader := *m.Header\n\tc := NewMessage()\n\theader.SetCompressType(None)\n\tc.Header = &header\n\tc.ServicePath = m.ServicePath\n\tc.ServiceMethod = m.ServiceMethod\n\treturn c\n}\n\n// Encode encodes messages.\nfunc (m Message) Encode() []byte {\n\tdata := m.EncodeSlicePointer()\n\treturn *data\n}\n\n// EncodeSlicePointer encodes messages as a byte slice pointer we can use pool to improve.\nfunc (m Message) EncodeSlicePointer() *[]byte {\n\tvar bb = bytes.NewBuffer(make([]byte, 0, len(m.Metadata)*64))\n\tencodeMetadata(m.Metadata, bb)\n\tmeta := bb.Bytes()\n\n\tspL := len(m.ServicePath)\n\tsmL := len(m.ServiceMethod)\n\n\tvar err error\n\tpayload := m.Payload\n\tif m.CompressType() != None {\n\t\tcompressor := Compressors[m.CompressType()]\n\t\tif compressor == nil {\n\t\t\tm.SetCompressType(None)\n\t\t} else {\n\t\t\tpayload, err = compressor.Zip(m.Payload)\n\t\t\tif err != nil {\n\t\t\t\tm.SetCompressType(None)\n\t\t\t\tpayload = m.Payload\n\t\t\t}\n\t\t}\n\t}\n\n\ttotalL := (4 + spL) + (4 + smL) + (4 + len(meta)) + (4 + len(payload))\n\n\t// header + dataLen + spLen + sp + smLen + sm + metaL + meta + payloadLen + payload\n\tmetaStart := 12 + 4 + (4 + spL) + (4 + smL)\n\n\tpayLoadStart := metaStart + (4 + len(meta))\n\tl := 12 + 4 + totalL\n\n\tdata := bufferPool.Get(l)\n\tcopy(*data, m.Header[:])\n\n\t// totalLen\n\tbinary.BigEndian.PutUint32((*data)[12:16], uint32(totalL))\n\n\tbinary.BigEndian.PutUint32((*data)[16:20], uint32(spL))\n\tcopy((*data)[20:20+spL], util.StringToSliceByte(m.ServicePath))\n\n\tbinary.BigEndian.PutUint32((*data)[20+spL:24+spL], uint32(smL))\n\tcopy((*data)[24+spL:metaStart], util.StringToSliceByte(m.ServiceMethod))\n\n\tbinary.BigEndian.PutUint32((*data)[metaStart:metaStart+4], uint32(len(meta)))\n\tcopy((*data)[metaStart+4:], meta)\n\n\tbinary.BigEndian.PutUint32((*data)[payLoadStart:payLoadStart+4], uint32(len(payload)))\n\tcopy((*data)[payLoadStart+4:], payload)\n\n\treturn data\n}\n\n// PutData puts the byte slice into pool.\nfunc PutData(data *[]byte) {\n\tbufferPool.Put(data)\n}\n\n// WriteTo writes message to writers.\nfunc (m Message) WriteTo(w io.Writer) (int64, error) {\n\tnn, err := w.Write(m.Header[:])\n\tn := int64(nn)\n\tif err != nil {\n\t\treturn n, err\n\t}\n\n\tbb := bytes.NewBuffer(make([]byte, 0, len(m.Metadata)*64))\n\tencodeMetadata(m.Metadata, bb)\n\tmeta := bb.Bytes()\n\n\tspL := len(m.ServicePath)\n\tsmL := len(m.ServiceMethod)\n\n\tpayload := m.Payload\n\tif m.CompressType() != None {\n\t\tcompressor := Compressors[m.CompressType()]\n\t\tif compressor == nil {\n\t\t\treturn n, ErrUnsupportedCompressor\n\t\t}\n\t\tpayload, err = compressor.Zip(m.Payload)\n\t\tif err != nil {\n\t\t\treturn n, err\n\t\t}\n\t}\n\n\ttotalL := (4 + spL) + (4 + smL) + (4 + len(meta)) + (4 + len(payload))\n\terr = binary.Write(w, binary.BigEndian, uint32(totalL))\n\tif err != nil {\n\t\treturn n, err\n\t}\n\n\t// write servicePath and serviceMethod\n\terr = binary.Write(w, binary.BigEndian, uint32(len(m.ServicePath)))\n\tif err != nil {\n\t\treturn n, err\n\t}\n\t_, err = w.Write(util.StringToSliceByte(m.ServicePath))\n\tif err != nil {\n\t\treturn n, err\n\t}\n\terr = binary.Write(w, binary.BigEndian, uint32(len(m.ServiceMethod)))\n\tif err != nil {\n\t\treturn n, err\n\t}\n\t_, err = w.Write(util.StringToSliceByte(m.ServiceMethod))\n\tif err != nil {\n\t\treturn n, err\n\t}\n\n\t// write meta\n\terr = binary.Write(w, binary.BigEndian, uint32(len(meta)))\n\tif err != nil {\n\t\treturn n, err\n\t}\n\t_, err = w.Write(meta)\n\tif err != nil {\n\t\treturn n, err\n\t}\n\n\t// write payload\n\terr = binary.Write(w, binary.BigEndian, uint32(len(payload)))\n\tif err != nil {\n\t\treturn n, err\n\t}\n\n\tnn, err = w.Write(payload)\n\treturn int64(nn), err\n}\n\n// len,string,len,string,......\nfunc encodeMetadata(m map[string]string, bb *bytes.Buffer) {\n\tif len(m) == 0 {\n\t\treturn\n\t}\n\td := make([]byte, 4)\n\tfor k, v := range m {\n\t\tbinary.BigEndian.PutUint32(d, uint32(len(k)))\n\t\tbb.Write(d)\n\t\tbb.Write(util.StringToSliceByte(k))\n\t\tbinary.BigEndian.PutUint32(d, uint32(len(v)))\n\t\tbb.Write(d)\n\t\tbb.Write(util.StringToSliceByte(v))\n\t}\n}\n\nfunc decodeMetadata(l uint32, data []byte) (map[string]string, error) {\n\tm := make(map[string]string, 10)\n\tn := uint32(0)\n\tfor n < l {\n\t\t// parse one key and value\n\t\t// key\n\t\tsl := binary.BigEndian.Uint32(data[n : n+4])\n\t\tn = n + 4\n\t\tif n+sl > l-4 {\n\t\t\treturn m, ErrMetaKVMissing\n\t\t}\n\t\tk := string(data[n : n+sl])\n\t\tn = n + sl\n\n\t\t// value\n\t\tsl = binary.BigEndian.Uint32(data[n : n+4])\n\t\tn = n + 4\n\t\tif n+sl > l {\n\t\t\treturn m, ErrMetaKVMissing\n\t\t}\n\t\tv := string(data[n : n+sl])\n\t\tn = n + sl\n\t\tm[k] = v\n\t}\n\n\treturn m, nil\n}\n\n// Read reads a message from r.\nfunc Read(r io.Reader) (*Message, error) {\n\tmsg := NewMessage()\n\terr := msg.Decode(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn msg, nil\n}\n\n// Decode decodes a message from reader.\nfunc (m *Message) Decode(r io.Reader) error {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tvar errStack = make([]byte, 1024)\n\t\t\tn := runtime.Stack(errStack, true)\n\t\t\tlog.Errorf(\"panic in message decode: %v, stack: %s\", err, errStack[:n])\n\n\t\t}\n\t}()\n\n\t// parse header\n\t_, err := io.ReadFull(r, m.Header[:1])\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !m.Header.CheckMagicNumber() {\n\t\treturn fmt.Errorf(\"wrong magic number: %v\", m.Header[0])\n\t}\n\n\t_, err = io.ReadFull(r, m.Header[1:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// total\n\tlenData := make([]byte, 4)\n\t_, err = io.ReadFull(r, lenData)\n\tif err != nil {\n\t\treturn err\n\t}\n\tl := binary.BigEndian.Uint32(lenData)\n\n\tif MaxMessageLength > 0 && int(l) > MaxMessageLength {\n\t\treturn ErrMessageTooLong\n\t}\n\n\ttotalL := int(l)\n\tif cap(m.data) >= totalL { // reuse data\n\t\tm.data = m.data[:totalL]\n\t} else {\n\t\tm.data = make([]byte, totalL)\n\t}\n\tdata := m.data\n\t_, err = io.ReadFull(r, data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tn := 0\n\t// parse servicePath\n\tl = binary.BigEndian.Uint32(data[n:4])\n\tn = n + 4\n\tnEnd := n + int(l)\n\tm.ServicePath = util.SliceByteToString(data[n:nEnd])\n\tn = nEnd\n\n\t// parse serviceMethod\n\tl = binary.BigEndian.Uint32(data[n : n+4])\n\tn = n + 4\n\tnEnd = n + int(l)\n\tm.ServiceMethod = util.SliceByteToString(data[n:nEnd])\n\tn = nEnd\n\n\t// parse meta\n\tl = binary.BigEndian.Uint32(data[n : n+4])\n\tn = n + 4\n\tnEnd = n + int(l)\n\n\tif l > 0 {\n\t\tm.Metadata, err = decodeMetadata(l, data[n:nEnd])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tn = nEnd\n\n\t// parse payload\n\tl = binary.BigEndian.Uint32(data[n : n+4])\n\t_ = l\n\tn = n + 4\n\tm.Payload = data[n:]\n\n\tif m.CompressType() != None {\n\t\tcompressor := Compressors[m.CompressType()]\n\t\tif compressor == nil {\n\t\t\treturn ErrUnsupportedCompressor\n\t\t}\n\t\tm.Payload, err = compressor.Unzip(m.Payload)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn err\n}\n\n// Reset clean data of this message but keep allocated data\nfunc (m *Message) Reset() {\n\tresetHeader(m.Header)\n\tm.Metadata = nil\n\tm.Payload = []byte{}\n\tm.data = m.data[:0]\n\tm.ServicePath = \"\"\n\tm.ServiceMethod = \"\"\n}\n\nvar (\n\tzeroHeaderArray Header\n\tzeroHeader      = zeroHeaderArray[1:]\n)\n\nfunc resetHeader(h *Header) {\n\tcopy(h[1:], zeroHeader)\n}\n"
  },
  {
    "path": "protocol/message_chan_test.go",
    "content": "package protocol\n\nimport (\n    \"fmt\"\n    \"strings\"\n    \"sync/atomic\"\n    \"testing\"\n    \"time\"\n)\n\nfunc TestChanValue(t *testing.T) {\n    var ct uint64\n    ch := make(chan Message, 10000)\n    go func(ch <-chan Message) {\n        for range ch {\n            atomic.AddUint64(&ct, 1)\n        }\n    }(ch)\n\n    go func(ch chan Message) {\n        m := strings.Repeat(\"_\", 100)\n        p := strings.Repeat(\"_\", 100)\n        payload := make([]byte, 1024)\n        for {\n            ch <- Message{ServiceMethod: m, ServicePath: p, Payload: payload}\n        }\n    }(ch)\n    for i := 0; i < 5; i++ {\n        time.Sleep(time.Second)\n        fmt.Println(atomic.LoadUint64(&ct))\n        atomic.StoreUint64(&ct, 0)\n    }\n}\n\nfunc TestChanPtr(t *testing.T) {\n    var ct uint64\n    ch := make(chan *Message, 10000)\n    go func(ch <-chan *Message) {\n        for range ch {\n            atomic.AddUint64(&ct, 1)\n        }\n    }(ch)\n\n    go func(ch chan *Message) {\n        m := strings.Repeat(\"_\", 100)\n        p := strings.Repeat(\"_\", 100)\n        payload := make([]byte, 1024)\n        for {\n            ch <- &Message{ServiceMethod: m, ServicePath: p,Payload: payload}\n        }\n    }(ch)\n\n    for i := 0; i < 5; i++ {\n        time.Sleep(time.Second)\n        fmt.Println(atomic.LoadUint64(&ct))\n        atomic.StoreUint64(&ct, 0)\n    }\n}\n\nfunc BenchmarkChanValue(b *testing.B) {\n    ch := make(chan Message, 10000)\n    var ct uint64\n    go func(ch <-chan Message) {\n        for range ch {\n            atomic.AddUint64(&ct, 1)\n        }\n    }(ch)\n    b.ReportAllocs()\n    b.ResetTimer()\n    m := strings.Repeat(\"_\", 100)\n    p := strings.Repeat(\"_\", 100)\n    payload := make([]byte, 1024)\n    for i := 0; i < b.N; i++ {\n        ch <- Message{ServiceMethod: m, ServicePath: p,Payload: payload}\n    }\n}\n\nfunc BenchmarkChanPtr(b *testing.B) {\n    ch := make(chan *Message, 10000)\n    var ct uint64\n    go func(ch <-chan *Message) {\n        for range ch {\n            atomic.AddUint64(&ct, 1)\n        }\n    }(ch)\n    b.ReportAllocs()\n    b.ResetTimer()\n    m := strings.Repeat(\"_\", 100)\n    p := strings.Repeat(\"_\", 100)\n    payload := make([]byte, 1024)\n    for i := 0; i < b.N; i++ {\n        ch <- &Message{ServiceMethod: m, ServicePath: p,Payload: payload}\n    }\n}"
  },
  {
    "path": "protocol/message_test.go",
    "content": "package protocol\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestMessage(t *testing.T) {\n\treq := NewMessage()\n\treq.SetVersion(0)\n\treq.SetMessageType(Request)\n\treq.SetHeartbeat(false)\n\treq.SetOneway(false)\n\treq.SetCompressType(None)\n\treq.SetMessageStatusType(Normal)\n\treq.SetSerializeType(JSON)\n\n\treq.SetSeq(1234567890)\n\n\tm := make(map[string]string)\n\treq.ServicePath = \"Arith\"\n\treq.ServiceMethod = \"Add\"\n\tm[\"__ID\"] = \"6ba7b810-9dad-11d1-80b4-00c04fd430c9\"\n\treq.Metadata = m\n\n\tpayload := `{\n\t\t\"A\": 1,\n\t\t\"B\": 2,\n\t}\n\t`\n\treq.Payload = []byte(payload)\n\n\tvar buf bytes.Buffer\n\t_, err := req.WriteTo(&buf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tres, err := Read(&buf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tres.SetMessageType(Response)\n\n\tif res.Version() != 0 {\n\t\tt.Errorf(\"expect 0 but got %d\", res.Version())\n\t}\n\n\tif res.Seq() != 1234567890 {\n\t\tt.Errorf(\"expect 1234567890 but got %d\", res.Seq())\n\t}\n\n\tif res.ServicePath != \"Arith\" || res.ServiceMethod != \"Add\" || res.Metadata[\"__ID\"] != \"6ba7b810-9dad-11d1-80b4-00c04fd430c9\" {\n\t\tt.Errorf(\"got wrong metadata: %v\", res.Metadata)\n\t}\n\n\tif string(res.Payload) != payload {\n\t\tt.Errorf(\"got wrong payload: %v\", string(res.Payload))\n\t}\n}\n"
  },
  {
    "path": "protocol/testdata/benchmark.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc        v5.26.0\n// source: benchmark.proto\n\npackage testdata\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype BenchmarkMessage struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tField1   string   `protobuf:\"bytes,1,opt,name=field1,proto3\" json:\"field1,omitempty\"`\n\tField9   string   `protobuf:\"bytes,9,opt,name=field9,proto3\" json:\"field9,omitempty\"`\n\tField18  string   `protobuf:\"bytes,18,opt,name=field18,proto3\" json:\"field18,omitempty\"`\n\tField80  bool     `protobuf:\"varint,80,opt,name=field80,proto3\" json:\"field80,omitempty\"`\n\tField81  bool     `protobuf:\"varint,81,opt,name=field81,proto3\" json:\"field81,omitempty\"`\n\tField2   int32    `protobuf:\"varint,2,opt,name=field2,proto3\" json:\"field2,omitempty\"`\n\tField3   int32    `protobuf:\"varint,3,opt,name=field3,proto3\" json:\"field3,omitempty\"`\n\tField280 int32    `protobuf:\"varint,280,opt,name=field280,proto3\" json:\"field280,omitempty\"`\n\tField6   int32    `protobuf:\"varint,6,opt,name=field6,proto3\" json:\"field6,omitempty\"`\n\tField22  int64    `protobuf:\"varint,22,opt,name=field22,proto3\" json:\"field22,omitempty\"`\n\tField4   string   `protobuf:\"bytes,4,opt,name=field4,proto3\" json:\"field4,omitempty\"`\n\tField5   []uint64 `protobuf:\"fixed64,5,rep,packed,name=field5,proto3\" json:\"field5,omitempty\"`\n\tField59  bool     `protobuf:\"varint,59,opt,name=field59,proto3\" json:\"field59,omitempty\"`\n\tField7   string   `protobuf:\"bytes,7,opt,name=field7,proto3\" json:\"field7,omitempty\"`\n\tField16  int32    `protobuf:\"varint,16,opt,name=field16,proto3\" json:\"field16,omitempty\"`\n\tField130 int32    `protobuf:\"varint,130,opt,name=field130,proto3\" json:\"field130,omitempty\"`\n\tField12  bool     `protobuf:\"varint,12,opt,name=field12,proto3\" json:\"field12,omitempty\"`\n\tField17  bool     `protobuf:\"varint,17,opt,name=field17,proto3\" json:\"field17,omitempty\"`\n\tField13  bool     `protobuf:\"varint,13,opt,name=field13,proto3\" json:\"field13,omitempty\"`\n\tField14  bool     `protobuf:\"varint,14,opt,name=field14,proto3\" json:\"field14,omitempty\"`\n\tField104 int32    `protobuf:\"varint,104,opt,name=field104,proto3\" json:\"field104,omitempty\"`\n\tField100 int32    `protobuf:\"varint,100,opt,name=field100,proto3\" json:\"field100,omitempty\"`\n\tField101 int32    `protobuf:\"varint,101,opt,name=field101,proto3\" json:\"field101,omitempty\"`\n\tField102 string   `protobuf:\"bytes,102,opt,name=field102,proto3\" json:\"field102,omitempty\"`\n\tField103 string   `protobuf:\"bytes,103,opt,name=field103,proto3\" json:\"field103,omitempty\"`\n\tField29  int32    `protobuf:\"varint,29,opt,name=field29,proto3\" json:\"field29,omitempty\"`\n\tField30  bool     `protobuf:\"varint,30,opt,name=field30,proto3\" json:\"field30,omitempty\"`\n\tField60  int32    `protobuf:\"varint,60,opt,name=field60,proto3\" json:\"field60,omitempty\"`\n\tField271 int32    `protobuf:\"varint,271,opt,name=field271,proto3\" json:\"field271,omitempty\"`\n\tField272 int32    `protobuf:\"varint,272,opt,name=field272,proto3\" json:\"field272,omitempty\"`\n\tField150 int32    `protobuf:\"varint,150,opt,name=field150,proto3\" json:\"field150,omitempty\"`\n\tField23  int32    `protobuf:\"varint,23,opt,name=field23,proto3\" json:\"field23,omitempty\"`\n\tField24  bool     `protobuf:\"varint,24,opt,name=field24,proto3\" json:\"field24,omitempty\"`\n\tField25  int32    `protobuf:\"varint,25,opt,name=field25,proto3\" json:\"field25,omitempty\"`\n\tField78  bool     `protobuf:\"varint,78,opt,name=field78,proto3\" json:\"field78,omitempty\"`\n\tField67  int32    `protobuf:\"varint,67,opt,name=field67,proto3\" json:\"field67,omitempty\"`\n\tField68  int32    `protobuf:\"varint,68,opt,name=field68,proto3\" json:\"field68,omitempty\"`\n\tField128 int32    `protobuf:\"varint,128,opt,name=field128,proto3\" json:\"field128,omitempty\"`\n\tField129 string   `protobuf:\"bytes,129,opt,name=field129,proto3\" json:\"field129,omitempty\"`\n\tField131 int32    `protobuf:\"varint,131,opt,name=field131,proto3\" json:\"field131,omitempty\"`\n}\n\nfunc (x *BenchmarkMessage) Reset() {\n\t*x = BenchmarkMessage{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_benchmark_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *BenchmarkMessage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*BenchmarkMessage) ProtoMessage() {}\n\nfunc (x *BenchmarkMessage) ProtoReflect() protoreflect.Message {\n\tmi := &file_benchmark_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use BenchmarkMessage.ProtoReflect.Descriptor instead.\nfunc (*BenchmarkMessage) Descriptor() ([]byte, []int) {\n\treturn file_benchmark_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *BenchmarkMessage) GetField1() string {\n\tif x != nil {\n\t\treturn x.Field1\n\t}\n\treturn \"\"\n}\n\nfunc (x *BenchmarkMessage) GetField9() string {\n\tif x != nil {\n\t\treturn x.Field9\n\t}\n\treturn \"\"\n}\n\nfunc (x *BenchmarkMessage) GetField18() string {\n\tif x != nil {\n\t\treturn x.Field18\n\t}\n\treturn \"\"\n}\n\nfunc (x *BenchmarkMessage) GetField80() bool {\n\tif x != nil {\n\t\treturn x.Field80\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField81() bool {\n\tif x != nil {\n\t\treturn x.Field81\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField2() int32 {\n\tif x != nil {\n\t\treturn x.Field2\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField3() int32 {\n\tif x != nil {\n\t\treturn x.Field3\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField280() int32 {\n\tif x != nil {\n\t\treturn x.Field280\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField6() int32 {\n\tif x != nil {\n\t\treturn x.Field6\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField22() int64 {\n\tif x != nil {\n\t\treturn x.Field22\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField4() string {\n\tif x != nil {\n\t\treturn x.Field4\n\t}\n\treturn \"\"\n}\n\nfunc (x *BenchmarkMessage) GetField5() []uint64 {\n\tif x != nil {\n\t\treturn x.Field5\n\t}\n\treturn nil\n}\n\nfunc (x *BenchmarkMessage) GetField59() bool {\n\tif x != nil {\n\t\treturn x.Field59\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField7() string {\n\tif x != nil {\n\t\treturn x.Field7\n\t}\n\treturn \"\"\n}\n\nfunc (x *BenchmarkMessage) GetField16() int32 {\n\tif x != nil {\n\t\treturn x.Field16\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField130() int32 {\n\tif x != nil {\n\t\treturn x.Field130\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField12() bool {\n\tif x != nil {\n\t\treturn x.Field12\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField17() bool {\n\tif x != nil {\n\t\treturn x.Field17\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField13() bool {\n\tif x != nil {\n\t\treturn x.Field13\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField14() bool {\n\tif x != nil {\n\t\treturn x.Field14\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField104() int32 {\n\tif x != nil {\n\t\treturn x.Field104\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField100() int32 {\n\tif x != nil {\n\t\treturn x.Field100\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField101() int32 {\n\tif x != nil {\n\t\treturn x.Field101\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField102() string {\n\tif x != nil {\n\t\treturn x.Field102\n\t}\n\treturn \"\"\n}\n\nfunc (x *BenchmarkMessage) GetField103() string {\n\tif x != nil {\n\t\treturn x.Field103\n\t}\n\treturn \"\"\n}\n\nfunc (x *BenchmarkMessage) GetField29() int32 {\n\tif x != nil {\n\t\treturn x.Field29\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField30() bool {\n\tif x != nil {\n\t\treturn x.Field30\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField60() int32 {\n\tif x != nil {\n\t\treturn x.Field60\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField271() int32 {\n\tif x != nil {\n\t\treturn x.Field271\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField272() int32 {\n\tif x != nil {\n\t\treturn x.Field272\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField150() int32 {\n\tif x != nil {\n\t\treturn x.Field150\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField23() int32 {\n\tif x != nil {\n\t\treturn x.Field23\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField24() bool {\n\tif x != nil {\n\t\treturn x.Field24\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField25() int32 {\n\tif x != nil {\n\t\treturn x.Field25\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField78() bool {\n\tif x != nil {\n\t\treturn x.Field78\n\t}\n\treturn false\n}\n\nfunc (x *BenchmarkMessage) GetField67() int32 {\n\tif x != nil {\n\t\treturn x.Field67\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField68() int32 {\n\tif x != nil {\n\t\treturn x.Field68\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField128() int32 {\n\tif x != nil {\n\t\treturn x.Field128\n\t}\n\treturn 0\n}\n\nfunc (x *BenchmarkMessage) GetField129() string {\n\tif x != nil {\n\t\treturn x.Field129\n\t}\n\treturn \"\"\n}\n\nfunc (x *BenchmarkMessage) GetField131() int32 {\n\tif x != nil {\n\t\treturn x.Field131\n\t}\n\treturn 0\n}\n\nvar File_benchmark_proto protoreflect.FileDescriptor\n\nvar file_benchmark_proto_rawDesc = []byte{\n\t0x0a, 0x0f, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x12, 0x08, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x22, 0xb4, 0x08, 0x0a, 0x10,\n\t0x42, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72, 0x6b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,\n\t0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c,\n\t0x64, 0x39, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x39,\n\t0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x38, 0x18, 0x12, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x38, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69,\n\t0x65, 0x6c, 0x64, 0x38, 0x30, 0x18, 0x50, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x38, 0x30, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x38, 0x31, 0x18,\n\t0x51, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x38, 0x31, 0x12, 0x16,\n\t0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06,\n\t0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x33,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x33, 0x12, 0x1b,\n\t0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x38, 0x30, 0x18, 0x98, 0x02, 0x20, 0x01, 0x28,\n\t0x05, 0x52, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x38, 0x30, 0x12, 0x16, 0x0a, 0x06, 0x66,\n\t0x69, 0x65, 0x6c, 0x64, 0x36, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x36, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x32, 0x18, 0x16,\n\t0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x32, 0x12, 0x16, 0x0a,\n\t0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66,\n\t0x69, 0x65, 0x6c, 0x64, 0x34, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x35, 0x18,\n\t0x05, 0x20, 0x03, 0x28, 0x06, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x35, 0x12, 0x18, 0x0a,\n\t0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x35, 0x39, 0x18, 0x3b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,\n\t0x66, 0x69, 0x65, 0x6c, 0x64, 0x35, 0x39, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64,\n\t0x37, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x37, 0x12,\n\t0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x36, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05,\n\t0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x36, 0x12, 0x1b, 0x0a, 0x08, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x31, 0x33, 0x30, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x69,\n\t0x65, 0x6c, 0x64, 0x31, 0x33, 0x30, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31,\n\t0x32, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x32,\n\t0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x37, 0x18, 0x11, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x37, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69,\n\t0x65, 0x6c, 0x64, 0x31, 0x33, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x31, 0x33, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x34, 0x18,\n\t0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x34, 0x12, 0x1a,\n\t0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x30, 0x34, 0x18, 0x68, 0x20, 0x01, 0x28, 0x05,\n\t0x52, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x30, 0x34, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69,\n\t0x65, 0x6c, 0x64, 0x31, 0x30, 0x30, 0x18, 0x64, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x69,\n\t0x65, 0x6c, 0x64, 0x31, 0x30, 0x30, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31,\n\t0x30, 0x31, 0x18, 0x65, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31,\n\t0x30, 0x31, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x30, 0x32, 0x18, 0x66,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x30, 0x32, 0x12, 0x1a,\n\t0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x30, 0x33, 0x18, 0x67, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x30, 0x33, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69,\n\t0x65, 0x6c, 0x64, 0x32, 0x39, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x32, 0x39, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x33, 0x30, 0x18,\n\t0x1e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x33, 0x30, 0x12, 0x18,\n\t0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x36, 0x30, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x05, 0x52,\n\t0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x36, 0x30, 0x12, 0x1b, 0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c,\n\t0x64, 0x32, 0x37, 0x31, 0x18, 0x8f, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x32, 0x37, 0x31, 0x12, 0x1b, 0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x37,\n\t0x32, 0x18, 0x90, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32,\n\t0x37, 0x32, 0x12, 0x1b, 0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x35, 0x30, 0x18, 0x96,\n\t0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x35, 0x30, 0x12,\n\t0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x33, 0x18, 0x17, 0x20, 0x01, 0x28, 0x05,\n\t0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x33, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x32, 0x34, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c,\n\t0x64, 0x32, 0x34, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x35, 0x18, 0x19,\n\t0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x35, 0x12, 0x18, 0x0a,\n\t0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x37, 0x38, 0x18, 0x4e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,\n\t0x66, 0x69, 0x65, 0x6c, 0x64, 0x37, 0x38, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64,\n\t0x36, 0x37, 0x18, 0x43, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x36,\n\t0x37, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x36, 0x38, 0x18, 0x44, 0x20, 0x01,\n\t0x28, 0x05, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x36, 0x38, 0x12, 0x1b, 0x0a, 0x08, 0x66,\n\t0x69, 0x65, 0x6c, 0x64, 0x31, 0x32, 0x38, 0x18, 0x80, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08,\n\t0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x32, 0x38, 0x12, 0x1b, 0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c,\n\t0x64, 0x31, 0x32, 0x39, 0x18, 0x81, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x65,\n\t0x6c, 0x64, 0x31, 0x32, 0x39, 0x12, 0x1b, 0x0a, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x33,\n\t0x31, 0x18, 0x83, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31,\n\t0x33, 0x31, 0x42, 0x0e, 0x48, 0x01, 0x5a, 0x0a, 0x2e, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61,\n\t0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_benchmark_proto_rawDescOnce sync.Once\n\tfile_benchmark_proto_rawDescData = file_benchmark_proto_rawDesc\n)\n\nfunc file_benchmark_proto_rawDescGZIP() []byte {\n\tfile_benchmark_proto_rawDescOnce.Do(func() {\n\t\tfile_benchmark_proto_rawDescData = protoimpl.X.CompressGZIP(file_benchmark_proto_rawDescData)\n\t})\n\treturn file_benchmark_proto_rawDescData\n}\n\nvar file_benchmark_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_benchmark_proto_goTypes = []interface{}{\n\t(*BenchmarkMessage)(nil), // 0: testdata.BenchmarkMessage\n}\nvar file_benchmark_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_benchmark_proto_init() }\nfunc file_benchmark_proto_init() {\n\tif File_benchmark_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_benchmark_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*BenchmarkMessage); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_benchmark_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_benchmark_proto_goTypes,\n\t\tDependencyIndexes: file_benchmark_proto_depIdxs,\n\t\tMessageInfos:      file_benchmark_proto_msgTypes,\n\t}.Build()\n\tFile_benchmark_proto = out.File\n\tfile_benchmark_proto_rawDesc = nil\n\tfile_benchmark_proto_goTypes = nil\n\tfile_benchmark_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "protocol/testdata/benchmark.proto",
    "content": "syntax = \"proto3\";\n\npackage testdata;\n\noption optimize_for = SPEED;\noption go_package = \"./testdata\";\n\n\nmessage BenchmarkMessage {\n  string field1 = 1;\n  string field9 = 9;\n  string field18 = 18;\n  bool field80 = 80;\n  bool field81 = 81;\n  int32 field2 = 2;\n  int32 field3 = 3;\n  int32 field280 = 280;\n  int32 field6 = 6;\n  int64 field22 = 22;\n  string field4 = 4;\n  repeated fixed64 field5 = 5;\n  bool field59 = 59;\n  string field7 = 7;\n  int32 field16 = 16;\n  int32 field130 = 130;\n  bool field12 = 12;\n  bool field17 = 17;\n  bool field13 = 13;\n  bool field14 = 14;\n  int32 field104 = 104;\n  int32 field100 = 100;\n  int32 field101 = 101;\n  string field102 = 102;\n  string field103 = 103;\n  int32 field29 = 29;\n  bool field30 = 30;\n  int32 field60 = 60;\n  int32 field271 = 271;\n  int32 field272 = 272;\n  int32 field150 = 150;\n  int32 field23 = 23;\n  bool field24 = 24;\n  int32 field25 = 25;\n  bool field78 = 78;\n  int32 field67 = 67;\n  int32 field68 = 68;\n  int32 field128 = 128;\n  string field129 = 129;\n  int32 field131 = 131;\n}"
  },
  {
    "path": "protocol/testdata/gen.sh",
    "content": "# curl -O https://raw.githubusercontent.com/rpcxio/rpcx-benchmark/master/proto/benchmark.proto\n\n# generate .go files from IDL\n\nprotoc -I.  --go_out=. --go_opt=module=\"testdata\"   ./benchmark.proto"
  },
  {
    "path": "reflection/server_reflection.go",
    "content": "package reflection\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\t\"text/template\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/twpayne/go-jsonstruct/v3\"\n)\n\nvar (\n\ttypeOfError   = reflect.TypeFor[error]()\n\ttypeOfContext = reflect.TypeFor[context.Context]()\n)\n\n// ServiceInfo service info.\ntype ServiceInfo struct {\n\tName    string\n\tPkgPath string\n\tMethods []*MethodInfo\n}\n\n// MethodInfo method info\ntype MethodInfo struct {\n\tName      string\n\tReqName   string\n\tReq       string\n\tReplyName string\n\tReply     string\n}\n\nvar siTemplate = `package {{.PkgPath}}\n\ntype {{.Name}} struct{}\n{{$name := .Name}}\n{{range .Methods}}\n{{.Req}}\n{{.Reply}}\ntype (s *{{$name}}) {{.Name}}(ctx context.Context, arg *{{.ReqName}}, reply *{{.ReplyName}}) error {\n\treturn nil\n}\n{{end}}\n`\n\nfunc (si ServiceInfo) String() string {\n\ttpl := template.Must(template.New(\"service\").Parse(siTemplate))\n\tvar buf strings.Builder\n\t_ = tpl.Execute(&buf, si)\n\treturn buf.String()\n}\n\ntype Reflection struct {\n\tServices map[string]*ServiceInfo\n}\n\nfunc New() *Reflection {\n\treturn &Reflection{\n\t\tServices: make(map[string]*ServiceInfo),\n\t}\n}\n\nfunc (r *Reflection) Register(name string, rcvr interface{}, metadata string) error {\n\tsi := &ServiceInfo{}\n\n\tval := reflect.ValueOf(rcvr)\n\ttyp := reflect.TypeOf(rcvr)\n\tvTyp := reflect.Indirect(val).Type()\n\tsi.Name = vTyp.Name()\n\tpkg := vTyp.PkgPath()\n\tif strings.Index(pkg, \".\") > 0 {\n\t\tpkg = pkg[strings.LastIndex(pkg, \".\")+1:]\n\t}\n\tpkg = filepath.Base(pkg)\n\tpkg = strings.ReplaceAll(pkg, \"-\", \"_\")\n\tsi.PkgPath = pkg\n\n\tfor m := 0; m < val.NumMethod(); m++ {\n\t\tmethod := typ.Method(m)\n\t\tmtype := method.Type\n\n\t\tif method.PkgPath != \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif mtype.NumIn() != 4 {\n\t\t\tcontinue\n\t\t}\n\t\t// First arg must be context.Context\n\t\tctxType := mtype.In(1)\n\t\tif !ctxType.Implements(typeOfContext) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Second arg need not be a pointer.\n\t\targType := mtype.In(2)\n\t\tif !isExportedOrBuiltinType(argType) {\n\t\t\tcontinue\n\t\t}\n\t\t// Third arg must be a pointer.\n\t\treplyType := mtype.In(3)\n\t\tif replyType.Kind() != reflect.Ptr {\n\t\t\tcontinue\n\t\t}\n\t\t// Reply type must be exported.\n\t\tif !isExportedOrBuiltinType(replyType) {\n\t\t\tcontinue\n\t\t}\n\t\t// Method needs one out.\n\t\tif mtype.NumOut() != 1 {\n\t\t\tcontinue\n\t\t}\n\t\t// The return type of the method must be error.\n\t\tif returnType := mtype.Out(0); returnType != typeOfError {\n\t\t\tcontinue\n\t\t}\n\n\t\tmi := &MethodInfo{}\n\t\tmi.Name = method.Name\n\n\t\tif argType.Kind() == reflect.Ptr {\n\t\t\targType = argType.Elem()\n\t\t}\n\t\treplyType = replyType.Elem()\n\n\t\tmi.ReqName = argType.Name()\n\t\tmi.Req = generateTypeDefination(mi.ReqName, si.PkgPath, generateJSON(argType))\n\t\tmi.ReplyName = replyType.Name()\n\t\tmi.Reply = generateTypeDefination(mi.ReplyName, si.PkgPath, generateJSON(replyType))\n\n\t\tsi.Methods = append(si.Methods, mi)\n\t}\n\n\tif len(si.Methods) > 0 {\n\t\tr.Services[name] = si\n\t}\n\n\treturn nil\n}\n\nfunc (r *Reflection) Unregister(name string) error {\n\tdelete(r.Services, name)\n\treturn nil\n}\n\nfunc (r *Reflection) GetService(ctx context.Context, s string, reply *string) error {\n\tsi, ok := r.Services[s]\n\tif !ok {\n\t\treturn fmt.Errorf(\"not found service %s\", s)\n\t}\n\t*reply = si.String()\n\n\treturn nil\n}\n\nfunc (r *Reflection) GetServices(ctx context.Context, s string, reply *string) error {\n\tvar buf strings.Builder\n\n\tpkg := `package `\n\n\tfor _, si := range r.Services {\n\t\tif pkg == `package ` {\n\t\t\tpkg = pkg + si.PkgPath + \"\\n\\n\"\n\t\t}\n\t\tbuf.WriteString(strings.ReplaceAll(si.String(), pkg, \"\"))\n\t}\n\n\tif pkg != `package ` {\n\t\t*reply = pkg + buf.String()\n\t} else {\n\t\t*reply = buf.String()\n\t}\n\n\treturn nil\n}\n\nfunc generateTypeDefination(name, pkg string, jsonValue string) string {\n\tjsonValue = strings.TrimSpace(jsonValue)\n\tif jsonValue == \"\" || jsonValue == `\"\"` {\n\t\treturn \"\"\n\t}\n\tr := strings.NewReader(jsonValue)\n\tgenerator := jsonstruct.NewGenerator(\n\t\tjsonstruct.WithPackageName(pkg),\n\t\tjsonstruct.WithTypeName(name),\n\t\tjsonstruct.WithStructTagNames(nil),\n\t\tjsonstruct.WithIntType(\"int64\"),\n\t)\n\tif err := generator.ObserveJSONReader(r); err != nil {\n\t\tlog.Errorf(\"failed to observe json: %v\", err)\n\t\treturn \"\"\n\t}\n\toutput, err := generator.Generate()\n\tif err != nil {\n\t\tlog.Errorf(\"failed to generate json: %v\", err)\n\t\treturn \"\"\n\t}\n\trt := strings.ReplaceAll(string(output), \"``\", \"\")\n\treturn strings.ReplaceAll(rt, \"package \"+pkg+\"\\n\\n\", \"\")\n}\n\nfunc generateJSON(typ reflect.Type) string {\n\tv := reflect.New(typ).Interface()\n\n\tdata, _ := json.Marshal(v)\n\treturn string(data)\n}\n\nfunc isExported(name string) bool {\n\trune, _ := utf8.DecodeRuneInString(name)\n\treturn unicode.IsUpper(rune)\n}\n\nfunc isExportedOrBuiltinType(t reflect.Type) bool {\n\tfor t.Kind() == reflect.Ptr {\n\t\tt = t.Elem()\n\t}\n\treturn isExported(t.Name()) || t.PkgPath() == \"\"\n}\n"
  },
  {
    "path": "reflection/server_reflection_test.go",
    "content": "package reflection\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/kr/pretty\"\n\ttestutils \"github.com/smallnest/rpcx/_testutils\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype PBArith int\n\nfunc (t *PBArith) Mul(ctx context.Context, args *testutils.ProtoArgs, reply *testutils.ProtoReply) error {\n\treply.C = args.A * args.B\n\treturn nil\n}\n\nfunc TestReflection_Register(t *testing.T) {\n\tr := New()\n\tarith := PBArith(0)\n\terr := r.Register(\"Arith\", &arith, \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpretty.Println(r.Services[\"Arith\"].String())\n}\n\ntype Args struct {\n\tAa int\n\tB  string\n\tC  bool\n}\n\nfunc Test_generateJSON(t *testing.T) {\n\targsType := reflect.TypeOf(&Args{}).Elem()\n\tjsonData := generateJSON(argsType)\n\tassert.Equal(t, `{\"Aa\":0,\"B\":\"\",\"C\":false}`, jsonData)\n\n\tdef := generateTypeDefination(\"Args\", \"test\", jsonData)\n\n\tresult := \"type Args struct {\\n\\tAa int64  \\n\\tB  string \\n\\tC  bool   \\n}\\n\"\n\tassert.Equal(t, result, def)\n}\n"
  },
  {
    "path": "server/context.go",
    "content": "package server\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n)\n\n// Context represents a rpcx FastCall context.\ntype Context struct {\n\tconn net.Conn\n\treq  *protocol.Message\n\tctx  *share.Context\n\n\tasync bool\n}\n\n// NewContext creates a server.Context for Handler.\nfunc NewContext(ctx *share.Context, conn net.Conn, req *protocol.Message, async bool) *Context {\n\treturn &Context{conn: conn, req: req, ctx: ctx, async: async}\n}\n\n// Get returns value for key.\nfunc (ctx *Context) Get(key interface{}) interface{} {\n\treturn ctx.ctx.Value(key)\n}\n\n// SetValue sets the kv pair.\nfunc (ctx *Context) SetValue(key, val interface{}) {\n\tif key == nil || val == nil {\n\t\treturn\n\t}\n\tctx.ctx.SetValue(key, val)\n}\n\n// DeleteKey delete the kv pair by key.\nfunc (ctx *Context) DeleteKey(key interface{}) {\n\tif ctx.ctx == nil || key == nil {\n\t\treturn\n\t}\n\tctx.ctx.DeleteKey(key)\n}\n\n// Payload returns the  payload.\nfunc (ctx *Context) Payload() []byte {\n\treturn ctx.req.Payload\n}\n\n// Metadata returns the metadata.\nfunc (ctx *Context) Metadata() map[string]string {\n\treturn ctx.req.Metadata\n}\n\n// ServicePath returns the ServicePath.\nfunc (ctx *Context) ServicePath() string {\n\treturn ctx.req.ServicePath\n}\n\n// ServiceMethod returns the ServiceMethod.\nfunc (ctx *Context) ServiceMethod() string {\n\treturn ctx.req.ServiceMethod\n}\n\n// Bind parses the body data and stores the result to v.\nfunc (ctx *Context) Bind(v interface{}) error {\n\treq := ctx.req\n\tif v != nil {\n\t\tcodec := share.Codecs[req.SerializeType()]\n\t\tif codec == nil {\n\t\t\treturn fmt.Errorf(\"can not find codec for %d\", req.SerializeType())\n\t\t}\n\n\t\terr := codec.Decode(req.Payload, v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (ctx *Context) Write(v interface{}) error {\n\treq := ctx.req\n\n\tif req.IsOneway() { // no need to send response\n\t\treturn nil\n\t}\n\n\tcodec := share.Codecs[req.SerializeType()]\n\tif codec == nil {\n\t\treturn fmt.Errorf(\"can not find codec for %d\", req.SerializeType())\n\t}\n\n\tres := req.Clone()\n\tres.SetMessageType(protocol.Response)\n\n\tif v != nil {\n\t\tdata, err := codec.Encode(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tres.Payload = data\n\t}\n\n\tresMetadata := ctx.Get(share.ResMetaDataKey)\n\tif resMetadata != nil {\n\t\tresMetaInCtx := resMetadata.(map[string]string)\n\t\tmeta := res.Metadata\n\t\tif meta == nil {\n\t\t\tres.Metadata = resMetaInCtx\n\t\t} else {\n\t\t\tfor k, v := range resMetaInCtx {\n\t\t\t\tif meta[k] == \"\" {\n\t\t\t\t\tmeta[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(res.Payload) > 1024 && req.CompressType() != protocol.None {\n\t\tres.SetCompressType(req.CompressType())\n\t}\n\trespData := res.EncodeSlicePointer()\n\n\tvar err error\n\tif ctx.async {\n\t\tgo func() {\n\t\t\t_, err = ctx.conn.Write(*respData)\n\t\t\tprotocol.PutData(respData)\n\t\t}()\n\t} else {\n\t\t_, err = ctx.conn.Write(*respData)\n\t\tprotocol.PutData(respData)\n\t}\n\n\treturn err\n}\n\nfunc (ctx *Context) WriteError(err error) error {\n\treq := ctx.req\n\n\tif req.IsOneway() { // no need to send response\n\t\treturn nil\n\t}\n\n\tcodec := share.Codecs[req.SerializeType()]\n\tif codec == nil {\n\t\treturn fmt.Errorf(\"can not find codec for %d\", req.SerializeType())\n\t}\n\n\tres := req.Clone()\n\tres.SetMessageType(protocol.Response)\n\n\tresMetadata := ctx.Get(share.ResMetaDataKey)\n\tif resMetadata != nil {\n\t\tresMetaInCtx := resMetadata.(map[string]string)\n\t\tmeta := res.Metadata\n\t\tif meta == nil {\n\t\t\tres.Metadata = resMetaInCtx\n\t\t} else {\n\t\t\tfor k, v := range resMetaInCtx {\n\t\t\t\tif meta[k] == \"\" {\n\t\t\t\t\tmeta[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tres.SetMessageStatusType(protocol.Error)\n\tres.Metadata[protocol.ServiceError] = err.Error()\n\n\trespData := res.EncodeSlicePointer()\n\tctx.conn.Write(*respData)\n\tprotocol.PutData(respData)\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/converter.go",
    "content": "package server\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n)\n\nconst (\n\tXVersion           = \"X-RPCX-Version\"\n\tXMessageType       = \"X-RPCX-MessageType\"\n\tXHeartbeat         = \"X-RPCX-Heartbeat\"\n\tXOneway            = \"X-RPCX-Oneway\"\n\tXMessageStatusType = \"X-RPCX-MessageStatusType\"\n\tXSerializeType     = \"X-RPCX-SerializeType\"\n\tXCompressType      = \"X-RPCX-CompressType\"\n\tXMessageID         = \"X-RPCX-MessageID\"\n\tXServicePath       = \"X-RPCX-ServicePath\"\n\tXServiceMethod     = \"X-RPCX-ServiceMethod\"\n\tXMeta              = \"X-RPCX-Meta\"\n\tXErrorMessage      = \"X-RPCX-ErrorMessage\"\n)\n\n// HTTPRequest2RpcxRequest converts a http request to a rpcx request.\nfunc HTTPRequest2RpcxRequest(r *http.Request) (*protocol.Message, error) {\n\treq := protocol.NewMessage()\n\treq.SetMessageType(protocol.Request)\n\n\th := r.Header\n\tseq := h.Get(XMessageID)\n\tif seq != \"\" {\n\t\tid, err := strconv.ParseUint(seq, 10, 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treq.SetSeq(id)\n\t}\n\n\theartbeat := h.Get(XHeartbeat)\n\tif heartbeat != \"\" {\n\t\treq.SetHeartbeat(true)\n\t}\n\n\toneway := h.Get(XOneway)\n\tif oneway != \"\" {\n\t\treq.SetOneway(true)\n\t}\n\n\tst := h.Get(XSerializeType)\n\tif st != \"\" {\n\t\trst, err := strconv.Atoi(st)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treq.SetSerializeType(protocol.SerializeType(rst))\n\t}\n\n\tcompressType := h.Get(XCompressType)\n\tif compressType != \"\" {\n\t\tct, err := strconv.Atoi(compressType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treq.SetCompressType(protocol.CompressType(ct))\n\t}\n\n\tmeta := h.Get(XMeta)\n\tif meta != \"\" {\n\t\tmetadata, err := url.ParseQuery(meta)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmm := make(map[string]string)\n\t\tfor k, v := range metadata {\n\t\t\tif len(v) > 0 {\n\t\t\t\tmm[k] = v[0]\n\t\t\t}\n\t\t}\n\t\treq.Metadata = mm\n\t}\n\n\tauth := h.Get(\"Authorization\")\n\tif auth != \"\" {\n\t\tif req.Metadata == nil {\n\t\t\treq.Metadata = make(map[string]string)\n\t\t}\n\t\treq.Metadata[share.AuthKey] = auth\n\t}\n\n\treq.ServicePath = h.Get(XServicePath)\n\n\treq.ServiceMethod = h.Get(XServiceMethod)\n\n\tpayload, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Payload = payload\n\n\treturn req, nil\n}\n\n// func RpcxResponse2HttpResponse(res *protocol.Message) (url.Values, []byte, error) {\n// \tm := make(url.Values)\n// \tm.Set(XVersion, strconv.Itoa(int(res.Version())))\n// \tif res.IsHeartbeat() {\n// \t\tm.Set(XHeartbeat, \"true\")\n// \t}\n// \tif res.IsOneway() {\n// \t\tm.Set(XOneway, \"true\")\n// \t}\n// \tif res.MessageStatusType() == protocol.Error {\n// \t\tm.Set(XMessageStatusType, \"Error\")\n// \t} else {\n// \t\tm.Set(XMessageStatusType, \"Normal\")\n// \t}\n\n// \tif res.CompressType() == protocol.Gzip {\n// \t\tm.Set(\"Content-Encoding\", \"gzip\")\n// \t}\n\n// \tm.Set(XSerializeType, strconv.Itoa(int(res.SerializeType())))\n// \tm.Set(XMessageID, strconv.FormatUint(res.Seq(), 10))\n// \tm.Set(XServicePath, res.ServicePath)\n// \tm.Set(XServiceMethod, res.ServiceMethod)\n\n// \treturn m, res.Payload, nil\n// }\n"
  },
  {
    "path": "server/converter_test.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/smallnest/rpcx/codec\"\n\t\"github.com/smallnest/rpcx/share\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestHTTPRequest2RpcxRequest(t *testing.T) {\n\n\tcc := &codec.MsgpackCodec{}\n\n\targs := &Args{\n\t\tA: 10,\n\t\tB: 20,\n\t}\n\n\tdata, _ := cc.Encode(args)\n\n\treq, err := http.NewRequest(\"POST\", \"http://127.0.0.1:8972/\", bytes.NewReader(data))\n\tif err != nil {\n\t\tt.Fatal(\"failed to create request: \", err)\n\t\treturn\n\t}\n\n\th := req.Header\n\th.Set(XMessageID, \"10000\")\n\th.Set(XHeartbeat, \"0\")\n\th.Set(XOneway, \"0\")\n\th.Set(XSerializeType, \"3\")\n\th.Set(XMeta, \"Meta\")\n\th.Set(\"Authorization\", \"Authorization\")\n\th.Set(XServicePath, \"ProxyServer\")\n\th.Set(XServiceMethod, \"GetAdData\")\n\n\trpcxReq, err := HTTPRequest2RpcxRequest(req)\n\tif err != nil {\n\t\tt.Fatal(\"HTTPRequest2RpcxRequest() error\")\n\t}\n\n\tassert.NotNil(t, rpcxReq.Metadata)\n\n\tassert.Equal(t, h.Get(\"Authorization\"), rpcxReq.Metadata[share.AuthKey])\n\n\tassert.Equal(t, h.Get(XServicePath), rpcxReq.ServicePath)\n\n\tassert.Equal(t, h.Get(XServiceMethod), rpcxReq.ServiceMethod)\n}\n"
  },
  {
    "path": "server/file_transfer.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\tlru \"github.com/hashicorp/golang-lru\"\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/share\"\n)\n\n// FileTransferHandler handles uploading file. Must close the connection after it finished.\ntype FileTransferHandler func(conn net.Conn, args *share.FileTransferArgs)\n\n// DownloadFileHandler handles downloading file. Must close the connection after it finished.\ntype DownloadFileHandler func(conn net.Conn, args *share.DownloadFileArgs)\n\ntype tokenInfo struct {\n\ttoken []byte\n\targs  *share.FileTransferArgs\n}\n\ntype downloadTokenInfo struct {\n\ttoken []byte\n\targs  *share.DownloadFileArgs\n}\n\n// FileTransfer support transfer files from clients.\n// It registers a file transfer service and listens a on the given port.\n// Clients will invokes this service to get the token and send the token and the file to this port.\ntype FileTransfer struct {\n\tAddr                string\n\tAdvertiseAddr       string\n\thandler             FileTransferHandler\n\tdownloadFileHandler DownloadFileHandler\n\tcachedTokens        *lru.Cache\n\tservice             *FileTransferService\n\n\tstartOnce sync.Once\n\n\tln   net.Listener\n\tdone chan struct{}\n}\n\ntype FileTransferService struct {\n\tFileTransfer *FileTransfer\n}\n\n// NewFileTransfer creates a FileTransfer with given parameters.\nfunc NewFileTransfer(addr string, handler FileTransferHandler, downloadFileHandler DownloadFileHandler, waitNum int) *FileTransfer {\n\tcachedTokens, _ := lru.New(waitNum)\n\n\tfi := &FileTransfer{\n\t\tAddr:                addr,\n\t\thandler:             handler,\n\t\tdownloadFileHandler: downloadFileHandler,\n\t\tcachedTokens:        cachedTokens,\n\t}\n\n\tfi.service = &FileTransferService{\n\t\tFileTransfer: fi,\n\t}\n\n\treturn fi\n}\n\n// EnableFileTransfer supports filetransfer service in this server.\nfunc (s *Server) EnableFileTransfer(serviceName string, fileTransfer *FileTransfer) {\n\tif serviceName == \"\" {\n\t\tserviceName = share.SendFileServiceName\n\t}\n\t_ = fileTransfer.Start()\n\t_ = s.RegisterName(serviceName, fileTransfer.service, \"\")\n}\n\nfunc (s *FileTransferService) TransferFile(ctx context.Context, args *share.FileTransferArgs, reply *share.FileTransferReply) error {\n\ttoken := make([]byte, 32)\n\t_, err := rand.Read(token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*reply = share.FileTransferReply{\n\t\tToken: token,\n\t\tAddr:  s.FileTransfer.Addr,\n\t}\n\tif s.FileTransfer.AdvertiseAddr != \"\" {\n\t\treply.Addr = s.FileTransfer.AdvertiseAddr\n\t}\n\n\tcloned := args.Clone()\n\ts.FileTransfer.cachedTokens.Add(string(token), &tokenInfo{token, cloned})\n\n\treturn nil\n}\n\nfunc (s *FileTransferService) DownloadFile(ctx context.Context, args *share.DownloadFileArgs, reply *share.FileTransferReply) error {\n\ttoken := make([]byte, 32)\n\t_, err := rand.Read(token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*reply = share.FileTransferReply{\n\t\tToken: token,\n\t\tAddr:  s.FileTransfer.Addr,\n\t}\n\n\tif s.FileTransfer.AdvertiseAddr != \"\" {\n\t\treply.Addr = s.FileTransfer.AdvertiseAddr\n\t}\n\n\tcloned := args.Clone()\n\ts.FileTransfer.cachedTokens.Add(string(token), &downloadTokenInfo{token, cloned})\n\n\treturn nil\n}\n\nfunc (s *FileTransfer) Start() error {\n\ts.startOnce.Do(func() {\n\t\tgo s.start()\n\t})\n\n\treturn nil\n}\n\nfunc (s *FileTransfer) start() error {\n\tln, err := net.Listen(\"tcp\", s.Addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.ln = ln\n\n\tvar tempDelay time.Duration\n\n\tfor {\n\t\tselect {\n\t\tcase <-s.done:\n\t\t\treturn nil\n\t\tdefault:\n\t\t\tconn, e := ln.Accept()\n\t\t\tif e != nil {\n\t\t\t\tif ne, ok := e.(net.Error); ok && ne.Temporary() {\n\t\t\t\t\tif tempDelay == 0 {\n\t\t\t\t\t\ttempDelay = 5 * time.Millisecond\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttempDelay *= 2\n\t\t\t\t\t}\n\n\t\t\t\t\tif max := 1 * time.Second; tempDelay > max {\n\t\t\t\t\t\ttempDelay = max\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Errorf(\"filetransfer: accept error: %v; retrying in %v\", e, tempDelay)\n\t\t\t\t\ttime.Sleep(tempDelay)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn e\n\t\t\t}\n\t\t\ttempDelay = 0\n\n\t\t\tif tc, ok := conn.(*net.TCPConn); ok {\n\t\t\t\ttc.SetKeepAlive(true)\n\t\t\t\ttc.SetKeepAlivePeriod(3 * time.Minute)\n\t\t\t\ttc.SetLinger(10)\n\t\t\t}\n\n\t\t\ttoken := make([]byte, 32)\n\t\t\t_, err := io.ReadFull(conn, token)\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\tlog.Errorf(\"failed to read token from %s\", conn.RemoteAddr().String())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttokenStr := string(token)\n\t\t\tinfo, ok := s.cachedTokens.Get(tokenStr)\n\t\t\tif !ok {\n\t\t\t\tconn.Close()\n\t\t\t\tlog.Errorf(\"failed to read token from %s\", conn.RemoteAddr().String())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ts.cachedTokens.Remove(tokenStr)\n\n\t\t\tswitch ti := info.(type) {\n\t\t\tcase *tokenInfo:\n\t\t\t\tif s.handler == nil {\n\t\t\t\t\tconn.Close()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tgo s.handler(conn, ti.args)\n\t\t\tcase *downloadTokenInfo:\n\t\t\t\tif s.downloadFileHandler == nil {\n\t\t\t\t\tconn.Close()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tgo s.downloadFileHandler(conn, ti.args)\n\t\t\tdefault:\n\t\t\t\tconn.Close()\n\t\t\t}\n\n\t\t}\n\t}\n}\n\nfunc (s *FileTransfer) Stop() error {\n\tclose(s.done)\n\t// notify Accept() to return\n\tif s.ln != nil {\n\t\ts.ln.Close()\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/gateway.go",
    "content": "package server\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/rs/cors\"\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n\t\"github.com/soheilhy/cmux\"\n)\n\nfunc (s *Server) startGateway(network string, ln net.Listener) net.Listener {\n\tif network != \"tcp\" && network != \"tcp4\" && network != \"tcp6\" && network != \"reuseport\" {\n\t\t// log.Infof(\"network is not tcp/tcp4/tcp6 so can not start gateway\")\n\t\treturn ln\n\t}\n\n\tm := cmux.New(ln)\n\n\trpcxLn := m.Match(rpcxPrefixByteMatcher())\n\n\t// mux Plugins\n\tif s.Plugins != nil {\n\t\ts.Plugins.MuxMatch(m)\n\t}\n\n\tif !s.DisableJSONRPC {\n\t\tjsonrpc2Ln := m.Match(cmux.HTTP1HeaderField(\"X-JSONRPC-2.0\", \"true\"))\n\t\tgo s.startJSONRPC2(jsonrpc2Ln)\n\t}\n\n\tif !s.DisableHTTPGateway {\n\t\thttpLn := m.Match(cmux.HTTP1Fast()) // X-RPCX-MessageID\n\t\tgo s.startHTTP1APIGateway(httpLn)\n\t}\n\n\tgo m.Serve()\n\n\treturn rpcxLn\n}\n\nfunc http1Path(prefix string) cmux.Matcher {\n\treturn func(r io.Reader) bool {\n\t\treturn matchHTTP1Field(r, prefix, func(gotValue string) bool {\n\t\t\tbr := bufio.NewReader(&io.LimitedReader{R: r, N: 1024})\n\t\t\tl, part, err := br.ReadLine()\n\t\t\tif err != nil || part {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t_, uri, _, ok := parseRequestLine(string(l))\n\t\t\tif !ok {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif strings.HasPrefix(uri, prefix) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tu, err := url.Parse(uri)\n\t\t\tif err != nil {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn strings.HasPrefix(u.Path, prefix)\n\t\t})\n\t}\n}\n\n// grabbed from net/http.\nfunc parseRequestLine(line string) (method, uri, proto string, ok bool) {\n\ts1 := strings.Index(line, \" \")\n\ts2 := strings.Index(line[s1+1:], \" \")\n\tif s1 < 0 || s2 < 0 {\n\t\treturn\n\t}\n\ts2 += s1 + 1\n\treturn line[:s1], line[s1+1 : s2], line[s2+1:], true\n}\n\nfunc http1HeaderExist(name string) cmux.Matcher {\n\treturn func(r io.Reader) bool {\n\t\treturn matchHTTP1Field(r, name, func(gotValue string) bool {\n\t\t\treq, err := http.ReadRequest(bufio.NewReader(r))\n\t\t\tif err != nil {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn req.Header.Get(name) != \"\"\n\t\t})\n\t}\n}\n\nfunc matchHTTP1Field(r io.Reader, name string, matches func(string) bool) (matched bool) {\n\treq, err := http.ReadRequest(bufio.NewReader(r))\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn matches(req.Header.Get(name))\n}\n\nfunc rpcxPrefixByteMatcher() cmux.Matcher {\n\tmagic := protocol.MagicNumber()\n\treturn func(r io.Reader) bool {\n\t\tbuf := make([]byte, 1)\n\t\tn, _ := r.Read(buf)\n\t\treturn n == 1 && buf[0] == magic\n\t}\n}\n\nfunc (s *Server) startHTTP1APIGateway(ln net.Listener) {\n\trouter := httprouter.New()\n\trouter.POST(\"/*servicePath\", s.handleGatewayRequest)\n\trouter.GET(\"/*servicePath\", s.handleGatewayRequest)\n\trouter.PUT(\"/*servicePath\", s.handleGatewayRequest)\n\n\tif s.corsOptions != nil {\n\t\topt := cors.Options(*s.corsOptions)\n\t\tc := cors.New(opt)\n\t\tmux := c.Handler(router)\n\t\ts.mu.Lock()\n\t\ts.gatewayHTTPServer = &http.Server{Handler: mux}\n\t\ts.mu.Unlock()\n\t} else {\n\t\ts.mu.Lock()\n\t\ts.gatewayHTTPServer = &http.Server{Handler: router}\n\t\ts.mu.Unlock()\n\t}\n\n\tif err := s.gatewayHTTPServer.Serve(ln); err != nil {\n\t\tif errors.Is(err, ErrServerClosed) || errors.Is(err, cmux.ErrListenerClosed) || errors.Is(err, cmux.ErrServerClosed) {\n\t\t\tlog.Info(\"gateway server closed\")\n\t\t} else {\n\t\t\tlog.Warnf(\"error in gateway Serve: %T %s\", err, err)\n\t\t}\n\t}\n}\n\nfunc (s *Server) closeHTTP1APIGateway(ctx context.Context) error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif s.gatewayHTTPServer != nil {\n\t\treturn s.gatewayHTTPServer.Shutdown(ctx)\n\t}\n\n\treturn nil\n}\n\nfunc (s *Server) handleGatewayRequest(w http.ResponseWriter, r *http.Request, params httprouter.Params) {\n\tctx := share.WithValue(r.Context(), RemoteConnContextKey, r.RemoteAddr) // notice: It is a string, different with TCP (net.Conn)\n\terr := s.Plugins.DoPreReadRequest(ctx)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), 500)\n\t\treturn\n\t}\n\n\terr = s.Plugins.DoPostHTTPRequest(ctx, r, params)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), 500)\n\t\treturn\n\t}\n\n\tif r.Header.Get(XServicePath) == \"\" {\n\t\tservicePath := params.ByName(\"servicePath\")\n\t\tservicePath = strings.TrimPrefix(servicePath, \"/\")\n\t\tr.Header.Set(XServicePath, servicePath)\n\t}\n\tservicePath := r.Header.Get(XServicePath)\n\twh := w.Header()\n\treq, err := HTTPRequest2RpcxRequest(r)\n\n\t// set headers\n\twh.Set(XVersion, r.Header.Get(XVersion))\n\twh.Set(XMessageID, r.Header.Get(XMessageID))\n\n\tif err == nil && servicePath == \"\" {\n\t\terr = errors.New(\"empty servicepath\")\n\t} else {\n\t\twh.Set(XServicePath, servicePath)\n\t}\n\n\tif err == nil && r.Header.Get(XServiceMethod) == \"\" {\n\t\terr = errors.New(\"empty servicemethod\")\n\t} else {\n\t\twh.Set(XServiceMethod, r.Header.Get(XServiceMethod))\n\t}\n\n\tif err == nil && r.Header.Get(XSerializeType) == \"\" {\n\t\terr = errors.New(\"empty serialized type\")\n\t} else {\n\t\twh.Set(XSerializeType, r.Header.Get(XSerializeType))\n\t}\n\n\tif err != nil {\n\t\trh := r.Header\n\t\tfor k, v := range rh {\n\t\t\tif strings.HasPrefix(k, \"X-RPCX-\") && len(v) > 0 {\n\t\t\t\twh.Set(k, v[0])\n\t\t\t}\n\t\t}\n\n\t\twh.Set(XMessageStatusType, \"Error\")\n\t\twh.Set(XErrorMessage, err.Error())\n\t\treturn\n\t}\n\terr = s.Plugins.DoPostReadRequest(ctx, req, nil)\n\tif err != nil {\n\t\ts.Plugins.DoPreWriteResponse(ctx, req, nil, err)\n\t\thttp.Error(w, err.Error(), 500)\n\t\ts.Plugins.DoPostWriteResponse(ctx, req, req.Clone(), err)\n\t\treturn\n\t}\n\n\tctx.SetValue(StartRequestContextKey, time.Now().UnixNano())\n\terr = s.auth(ctx, req)\n\tif err != nil {\n\t\ts.Plugins.DoPreWriteResponse(ctx, req, nil, err)\n\t\twh.Set(XMessageStatusType, \"Error\")\n\t\twh.Set(XErrorMessage, err.Error())\n\t\tw.WriteHeader(401)\n\t\ts.Plugins.DoPostWriteResponse(ctx, req, req.Clone(), err)\n\t\treturn\n\t}\n\n\tresMetadata := make(map[string]string)\n\tnewCtx := share.WithLocalValue(share.WithLocalValue(ctx, share.ReqMetaDataKey, req.Metadata),\n\t\tshare.ResMetaDataKey, resMetadata)\n\n\tres, err := s.handleRequest(newCtx, req)\n\n\tif err != nil {\n\t\t// call DoPreWriteResponse\n\t\ts.Plugins.DoPreWriteResponse(ctx, req, nil, err)\n\t\tif s.HandleServiceError != nil {\n\t\t\ts.HandleServiceError(err)\n\t\t} else {\n\t\t\tlog.Warnf(\"rpcx:  gateway request: %v\", err)\n\t\t}\n\t\twh.Set(XMessageStatusType, \"Error\")\n\t\twh.Set(XErrorMessage, err.Error())\n\t\tw.WriteHeader(500)\n\t\t// call DoPostWriteResponse\n\t\ts.Plugins.DoPostWriteResponse(ctx, req, req.Clone(), err)\n\t\treturn\n\t}\n\n\t// will set res to call\n\ts.Plugins.DoPreWriteResponse(newCtx, req, res, nil)\n\tif len(resMetadata) > 0 { // copy meta in context to request\n\t\tmeta := res.Metadata\n\t\tif meta == nil {\n\t\t\tres.Metadata = resMetadata\n\t\t} else {\n\t\t\tfor k, v := range resMetadata {\n\t\t\t\tmeta[k] = v\n\t\t\t}\n\t\t}\n\t}\n\n\tmeta := url.Values{}\n\tfor k, v := range res.Metadata {\n\t\tmeta.Add(k, v)\n\t}\n\twh.Set(XMeta, meta.Encode())\n\n\tif res.CompressType() != protocol.None {\n\t\twh.Set(XCompressType, fmt.Sprintf(\"%d\", res.CompressType()))\n\t}\n\n\tw.Write(res.Payload)\n\ts.Plugins.DoPostWriteResponse(newCtx, req, res, err)\n}\n"
  },
  {
    "path": "server/jsonrpc2.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/rs/cors\"\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n\t\"github.com/soheilhy/cmux\"\n)\n\nfunc (s *Server) jsonrpcHandler(w http.ResponseWriter, r *http.Request) {\n\tdata, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tvar req = &jsonrpcRequest{}\n\n\terr = json.Unmarshal(data, req)\n\tif err != nil {\n\t\tvar res = &jsonrpcRespone{}\n\t\tres.Error = &JSONRPCError{\n\t\t\tCode:    CodeParseJSONRPCError,\n\t\t\tMessage: err.Error(),\n\t\t}\n\n\t\twriteResponse(w, res)\n\t\treturn\n\t}\n\tconn := r.Context().Value(HttpConnContextKey).(net.Conn)\n\n\tctx := share.WithValue(r.Context(), RemoteConnContextKey, conn)\n\n\tif req.ID != nil {\n\t\tres := s.handleJSONRPCRequest(ctx, req, r.Header)\n\t\twriteResponse(w, res)\n\t\treturn\n\t}\n\n\t// notification\n\tgo s.handleJSONRPCRequest(ctx, req, r.Header)\n}\n\nfunc (s *Server) handleJSONRPCRequest(ctx context.Context, r *jsonrpcRequest, header http.Header) *jsonrpcRespone {\n\ts.Plugins.DoPreReadRequest(ctx)\n\n\tvar res = &jsonrpcRespone{}\n\tres.ID = r.ID\n\n\treq := protocol.NewMessage()\n\tif req.Metadata == nil {\n\t\treq.Metadata = make(map[string]string)\n\t}\n\n\tif r.ID == nil {\n\t\treq.SetOneway(true)\n\t}\n\treq.SetMessageType(protocol.Request)\n\treq.SetSerializeType(protocol.JSON)\n\n\tlastDot := strings.LastIndex(r.Method, \".\")\n\tif lastDot <= 0 {\n\t\tres.Error = &JSONRPCError{\n\t\t\tCode:    CodeMethodNotFound,\n\t\t\tMessage: \"must contains servicepath and method\",\n\t\t}\n\t\treturn res\n\t}\n\treq.ServicePath = r.Method[:lastDot]\n\treq.ServiceMethod = r.Method[lastDot+1:]\n\treq.Payload = *r.Params\n\n\t// meta\n\tmeta := header.Get(XMeta)\n\tif meta != \"\" {\n\t\tmetadata, _ := url.ParseQuery(meta)\n\t\tfor k, v := range metadata {\n\t\t\tif len(v) > 0 {\n\t\t\t\treq.Metadata[k] = v[0]\n\t\t\t}\n\t\t}\n\t}\n\n\tauth := header.Get(\"Authorization\")\n\tif auth != \"\" {\n\t\treq.Metadata[share.AuthKey] = auth\n\t}\n\n\terr := s.Plugins.DoPostReadRequest(ctx, req, nil)\n\tif err != nil {\n\t\tres.Error = &JSONRPCError{\n\t\t\tCode:    CodeInternalJSONRPCError,\n\t\t\tMessage: err.Error(),\n\t\t}\n\t\treturn res\n\t}\n\n\terr = s.auth(ctx, req)\n\tif err != nil {\n\t\ts.Plugins.DoPreWriteResponse(ctx, req, nil, err)\n\t\tres.Error = &JSONRPCError{\n\t\t\tCode:    CodeInternalJSONRPCError,\n\t\t\tMessage: err.Error(),\n\t\t}\n\t\ts.Plugins.DoPostWriteResponse(ctx, req, req.Clone(), err)\n\t\treturn res\n\t}\n\n\tresp, err := s.handleRequest(ctx, req)\n\tif r.ID == nil {\n\t\treturn nil\n\t}\n\n\ts.Plugins.DoPreWriteResponse(ctx, req, nil, err)\n\tif err != nil {\n\t\tres.Error = &JSONRPCError{\n\t\t\tCode:    CodeInternalJSONRPCError,\n\t\t\tMessage: err.Error(),\n\t\t}\n\t\ts.Plugins.DoPostWriteResponse(ctx, req, req.Clone(), err)\n\t\treturn res\n\t}\n\n\tresult := json.RawMessage(resp.Payload)\n\tres.Result = &result\n\ts.Plugins.DoPostWriteResponse(ctx, req, req.Clone(), err)\n\treturn res\n}\n\nfunc writeResponse(w http.ResponseWriter, res *jsonrpcRespone) {\n\tdata, err := json.Marshal(res)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Context-Type\", \"application/json\")\n\tw.Write(data)\n}\n\ntype CORSOptions = cors.Options\n\n// AllowAllCORSOptions returns a option that allows access.\nfunc AllowAllCORSOptions() *CORSOptions {\n\treturn &CORSOptions{\n\t\tAllowedOrigins: []string{\"*\"},\n\t\tAllowedMethods: []string{\n\t\t\thttp.MethodHead,\n\t\t\thttp.MethodGet,\n\t\t\thttp.MethodPost,\n\t\t\thttp.MethodPut,\n\t\t\thttp.MethodPatch,\n\t\t\thttp.MethodDelete,\n\t\t},\n\t\tAllowedHeaders:   []string{\"*\"},\n\t\tAllowCredentials: false,\n\t}\n}\n\n// SetCORS sets CORS options.\n// for example:\n//\n//\tcors.Options{\n//\t\tAllowedOrigins:   []string{\"foo.com\"},\n//\t\tAllowedMethods:   []string{http.MethodGet, http.MethodPost, http.MethodDelete},\n//\t\tAllowCredentials: true,\n//\t}\nfunc (s *Server) SetCORS(options *CORSOptions) {\n\ts.corsOptions = options\n}\n\nfunc (s *Server) startJSONRPC2(ln net.Listener) {\n\tnewServer := http.NewServeMux()\n\tnewServer.HandleFunc(\"/\", s.jsonrpcHandler)\n\n\tsrv := http.Server{ConnContext: func(ctx context.Context, c net.Conn) context.Context {\n\t\treturn context.WithValue(ctx, HttpConnContextKey, c)\n\t}}\n\n\tif s.corsOptions != nil {\n\t\topt := cors.Options(*s.corsOptions)\n\t\tc := cors.New(opt)\n\t\tmux := c.Handler(newServer)\n\t\tsrv.Handler = mux\n\t} else {\n\t\tsrv.Handler = newServer\n\t}\n\n\ts.jsonrpcHTTPServerLock.Lock()\n\ts.jsonrpcHTTPServer = &srv\n\ts.jsonrpcHTTPServerLock.Unlock()\n\n\tif err := s.jsonrpcHTTPServer.Serve(ln); !errors.Is(err, cmux.ErrServerClosed) {\n\t\tlog.Errorf(\"error in JSONRPC server: %T %s\", err, err)\n\t}\n}\n\nfunc (s *Server) closeJSONRPC2(ctx context.Context) error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif s.jsonrpcHTTPServer != nil {\n\t\treturn s.jsonrpcHTTPServer.Shutdown(ctx)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/jsonrpc2_wire.go",
    "content": "// Copyright 2018 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage server\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n)\n\n// this file contains the go forms of the wire specification\n// see http://www.jsonrpc.org/specification for details\n\nconst (\n\t// CodeUnknownJSONRPCError should be used for all non coded errors.\n\tCodeUnknownJSONRPCError = -32001\n\t// CodeParseJSONRPCError is used when invalid JSON was received by the server.\n\tCodeParseJSONRPCError = -32700\n\t// CodeInvalidjsonrpcRequest is used when the JSON sent is not a valid jsonrpcRequest object.\n\tCodeInvalidjsonrpcRequest = -32600\n\t// CodeMethodNotFound should be returned by the handler when the method does\n\t// not exist / is not available.\n\tCodeMethodNotFound = -32601\n\t// CodeInvalidParams should be returned by the handler when method\n\t// parameter(s) were invalid.\n\tCodeInvalidParams = -32602\n\t// CodeInternalJSONRPCError is not currently returned but defined for completeness.\n\tCodeInternalJSONRPCError = -32603\n)\n\n// jsonrpcRequest is sent to a server to represent a Call or Notify operation.\ntype jsonrpcRequest struct {\n\t// VersionTag is always encoded as the string \"2.0\"\n\tVersionTag VersionTag `json:\"jsonrpc\"`\n\t// Method is a string containing the method name to invoke.\n\tMethod string `json:\"method\"`\n\t// Params is either a struct or an array with the parameters of the method.\n\tParams *json.RawMessage `json:\"params,omitempty\"`\n\t// The id of this request, used to tie the jsonrpcRespone back to the request.\n\t// Will be either a string or a number. If not set, the jsonrpcRequest is a notify,\n\t// and no response is possible.\n\tID *ID `json:\"id,omitempty\"`\n}\n\n// jsonrpcRespone is a reply to a jsonrpcRequest.\n// It will always have the ID field set to tie it back to a request, and will\n// have either the Result or JSONRPCError fields set depending on whether it is a\n// success or failure response.\ntype jsonrpcRespone struct {\n\t// VersionTag is always encoded as the string \"2.0\"\n\tVersionTag VersionTag `json:\"jsonrpc\"`\n\t// Result is the response value, and is required on success.\n\tResult *json.RawMessage `json:\"result,omitempty\"`\n\t// Error is a structured error response if the call fails.\n\tError *JSONRPCError `json:\"error,omitempty\"`\n\t// ID must be set and is the identifier of the jsonrpcRequest this is a response to.\n\tID *ID `json:\"id,omitempty\"`\n}\n\n// JSONRPCError represents a structured error in a jsonrpcRespone.\ntype JSONRPCError struct {\n\t// Code is an error code indicating the type of failure.\n\tCode int64 `json:\"code\"`\n\t// Message is a short description of the error.\n\tMessage string `json:\"message\"`\n\t// Data is optional structured data containing additional information about the error.\n\tData *json.RawMessage `json:\"data\"`\n}\n\n// VersionTag is a special 0 sized struct that encodes as the jsonrpc version\n// tag.\n// It will fail during decode if it is not the correct version tag in the\n// stream.\ntype VersionTag struct{}\n\n// ID is a jsonrpcRequest identifier.\n// Only one of either the Name or Number members will be set, using the\n// number form if the Name is the empty string.\ntype ID struct {\n\tName   string\n\tNumber int64\n}\n\n// IsNotify returns true if this request is a notification.\nfunc (r *jsonrpcRequest) IsNotify() bool {\n\treturn r.ID == nil\n}\n\nfunc (err *JSONRPCError) JSONRPCError() string {\n\tif err == nil {\n\t\treturn \"\"\n\t}\n\treturn err.Message\n}\n\nfunc (VersionTag) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(\"2.0\")\n}\n\nfunc (VersionTag) UnmarshalJSON(data []byte) error {\n\tversion := \"\"\n\tif err := json.Unmarshal(data, &version); err != nil {\n\t\treturn err\n\t}\n\tif version != \"2.0\" {\n\t\treturn fmt.Errorf(\"invalid RPC version %v\", version)\n\t}\n\treturn nil\n}\n\n// String returns a string representation of the ID.\n// The representation is non ambiguous, string forms are quoted, number forms\n// are preceded by a #\nfunc (id *ID) String() string {\n\tif id == nil {\n\t\treturn \"\"\n\t}\n\tif id.Name != \"\" {\n\t\treturn strconv.Quote(id.Name)\n\t}\n\treturn \"#\" + strconv.FormatInt(id.Number, 10)\n}\n\nfunc (id *ID) MarshalJSON() ([]byte, error) {\n\tif id.Name != \"\" {\n\t\treturn json.Marshal(id.Name)\n\t}\n\treturn json.Marshal(id.Number)\n}\n\nfunc (id *ID) UnmarshalJSON(data []byte) error {\n\t*id = ID{}\n\tif err := json.Unmarshal(data, &id.Number); err == nil {\n\t\treturn nil\n\t}\n\treturn json.Unmarshal(data, &id.Name)\n}\n"
  },
  {
    "path": "server/kcp.go",
    "content": "// +build kcp\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\tkcp \"github.com/xtaci/kcp-go\"\n)\n\nfunc init() {\n\tmakeListeners[\"kcp\"] = kcpMakeListener\n}\n\nfunc kcpMakeListener(s *Server, address string) (ln net.Listener, err error) {\n\tif s.options == nil || s.options[\"BlockCrypt\"] == nil {\n\t\treturn nil, errors.New(\"KCP BlockCrypt must be configured in server.Options\")\n\t}\n\n\treturn kcp.ListenWithOptions(address, s.options[\"BlockCrypt\"].(kcp.BlockCrypt), 10, 3)\n}\n\n// WithBlockCrypt sets kcp.BlockCrypt.\nfunc WithBlockCrypt(bc kcp.BlockCrypt) OptionFn {\n\treturn func(s *Server) {\n\t\ts.options[\"BlockCrypt\"] = bc\n\t}\n}\n"
  },
  {
    "path": "server/listener.go",
    "content": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n)\n\nvar makeListeners = make(map[string]MakeListener)\n\nfunc init() {\n\tmakeListeners[\"tcp\"] = tcpMakeListener(\"tcp\")\n\tmakeListeners[\"tcp4\"] = tcpMakeListener(\"tcp4\")\n\tmakeListeners[\"tcp6\"] = tcpMakeListener(\"tcp6\")\n\tmakeListeners[\"http\"] = tcpMakeListener(\"tcp\")\n\tmakeListeners[\"ws\"] = tcpMakeListener(\"tcp\")\n\tmakeListeners[\"wss\"] = tcpMakeListener(\"tcp\")\n}\n\n// RegisterMakeListener registers a MakeListener for network.\nfunc RegisterMakeListener(network string, ml MakeListener) {\n\tmakeListeners[network] = ml\n}\n\n// MakeListener defines a listener generator.\ntype MakeListener func(s *Server, address string) (ln net.Listener, err error)\n\n// block can be nil if the caller wishes to skip encryption in kcp.\n// tlsConfig can be nil iff we are not using network \"quic\".\nfunc (s *Server) makeListener(network, address string) (ln net.Listener, err error) {\n\tml := makeListeners[network]\n\tif ml == nil {\n\t\treturn nil, fmt.Errorf(\"can not make listener for %s\", network)\n\t}\n\n\tif network == \"wss\" && s.tlsConfig == nil {\n\t\treturn nil, errors.New(\"must set tlsconfig for wss\")\n\t}\n\n\treturn ml(s, address)\n}\n\nfunc tcpMakeListener(network string) MakeListener {\n\treturn func(s *Server, address string) (ln net.Listener, err error) {\n\t\tif s.tlsConfig == nil {\n\t\t\tln, err = net.Listen(network, address)\n\t\t} else {\n\t\t\tln, err = tls.Listen(network, address, s.tlsConfig)\n\t\t}\n\n\t\treturn ln, err\n\t}\n}\n"
  },
  {
    "path": "server/listener_linux.go",
    "content": "//go:build linux\n// +build linux\n\npackage server\n\nimport (\n\t\"net\"\n\t\"runtime\"\n\t\"time\"\n\n\turingnet \"github.com/godzie44/go-uring/net\"\n\t\"github.com/godzie44/go-uring/reactor\"\n\t\"github.com/godzie44/go-uring/uring\"\n\t\"github.com/smallnest/rpcx/log\"\n)\n\nfunc init() {\n\tmakeListeners[\"iouring\"] = iouringMakeListener\n}\n\n// iouringMakeListener creates a new listener using io_uring.\n// You can use RegisterMakeListener to register a customized iouring Listener creator.\n// experimental\nfunc iouringMakeListener(s *Server, address string) (ln net.Listener, err error) {\n\tn := runtime.GOMAXPROCS(-1)\n\n\tvar opts []uring.SetupOption\n\topts = append(opts, uring.WithSQPoll(time.Millisecond*100))\n\n\trings, closeRings, err := uring.CreateMany(n, uring.MaxEntries>>3, n, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnetReactor, err := reactor.NewNet(rings, reactor.WithLogger(&uringLogger{log.GetLogger()}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tln, err = uringnet.NewListener(net.ListenConfig{}, address, netReactor)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &uringnetListener{Listener: ln, closeRings: closeRings}, nil\n}\n\ntype uringnetListener struct {\n\tnet.Listener\n\tcloseRings uring.Defer\n}\n\nfunc (cl *uringnetListener) Close() error {\n\tcl.closeRings()\n\tcl.Listener.Close()\n\n\treturn nil\n}\n\ntype uringLogger struct {\n\tlog.Logger\n}\n\nfunc (l *uringLogger) Log(keyvals ...interface{}) {\n\tl.Logger.Info(keyvals...)\n}\n"
  },
  {
    "path": "server/listener_rdma.go",
    "content": "//go:build rdma\n// +build rdma\n\npackage server\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/smallnest/rsocket\"\n)\n\nfunc init() {\n\tmakeListeners[\"rdma\"] = rdmaMakeListener\n}\n\nfunc rdmaMakeListener(s *Server, address string) (ln net.Listener, err error) {\n\thost, port, err := net.SplitHostPort(address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tp, err := strconv.Atoi(port)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbacklog := os.Getenv(\"RDMA_BACKLOG\")\n\tif backlog == \"\" {\n\t\tbacklog = \"128\"\n\t}\n\tblog, _ := strconv.Atoi(backlog)\n\tif blog == 0 {\n\t\tblog = 128\n\t}\n\treturn rsocket.NewTCPListener(host, p, blog)\n}\n"
  },
  {
    "path": "server/listener_unix.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage server\n\nimport (\n\t\"net\"\n\n\treuseport \"github.com/kavu/go_reuseport\"\n)\n\nfunc init() {\n\tmakeListeners[\"reuseport\"] = reuseportMakeListener\n\tmakeListeners[\"unix\"] = unixMakeListener\n}\n\nfunc reuseportMakeListener(s *Server, address string) (ln net.Listener, err error) {\n\tvar network string\n\tif validIP6(address) {\n\t\tnetwork = \"tcp6\"\n\t} else {\n\t\tnetwork = \"tcp4\"\n\t}\n\n\treturn reuseport.NewReusablePortListener(network, address)\n}\n\nfunc unixMakeListener(s *Server, address string) (ln net.Listener, err error) {\n\tladdr, err := net.ResolveUnixAddr(\"unix\", address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn net.ListenUnix(\"unix\", laddr)\n}\n"
  },
  {
    "path": "server/memconn.go",
    "content": "package server\n\nimport (\n\t\"net\"\n\n\t\"github.com/akutz/memconn\"\n)\n\nfunc init() {\n\tmakeListeners[\"memu\"] = memconnMakeListener\n}\n\nfunc memconnMakeListener(s *Server, address string) (ln net.Listener, err error) {\n\treturn memconn.Listen(\"memu\", address)\n}\n"
  },
  {
    "path": "server/option.go",
    "content": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"time\"\n\n\t\"github.com/alitto/pond\"\n)\n\n// OptionFn configures options of server.\ntype OptionFn func(*Server)\n\n// // WithOptions sets multiple options.\n// func WithOptions(ops map[string]interface{}) OptionFn {\n// \treturn func(s *Server) {\n// \t\tfor k, v := range ops {\n// \t\t\ts.options[k] = v\n// \t\t}\n// \t}\n// }\n\n// WithTLSConfig sets tls.Config.\nfunc WithTLSConfig(cfg *tls.Config) OptionFn {\n\treturn func(s *Server) {\n\t\ts.tlsConfig = cfg\n\t}\n}\n\n// WithReadTimeout sets readTimeout.\nfunc WithReadTimeout(readTimeout time.Duration) OptionFn {\n\treturn func(s *Server) {\n\t\ts.readTimeout = readTimeout\n\t}\n}\n\n// WithWriteTimeout sets writeTimeout.\nfunc WithWriteTimeout(writeTimeout time.Duration) OptionFn {\n\treturn func(s *Server) {\n\t\ts.writeTimeout = writeTimeout\n\t}\n}\n\n// WithPool sets goroutine pool.\nfunc WithPool(maxWorkers, maxCapacity int, options ...pond.Option) OptionFn {\n\treturn func(s *Server) {\n\t\ts.pool = pond.New(maxWorkers, maxCapacity, options...)\n\t}\n}\n\n// WithCustomPool uses a custom goroutine pool.\nfunc WithCustomPool(pool WorkerPool) OptionFn {\n\treturn func(s *Server) {\n\t\ts.pool = pool\n\t}\n}\n\n// WithAsyncWrite sets AsyncWrite to true.\nfunc WithAsyncWrite() OptionFn {\n\treturn func(s *Server) {\n\t\ts.AsyncWrite = true\n\t}\n}\n"
  },
  {
    "path": "server/option_test.go",
    "content": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestOption(t *testing.T) {\n\tserver := NewServer()\n\n\tcert, _ := tls.LoadX509KeyPair(\"server.pem\", \"server.key\")\n\tconfig := &tls.Config{Certificates: []tls.Certificate{cert}}\n\n\to := WithTLSConfig(config)\n\to(server)\n\tassert.Equal(t, config, server.tlsConfig)\n\n\to = WithReadTimeout(time.Second)\n\to(server)\n\tassert.Equal(t, time.Second, server.readTimeout)\n\n\to = WithWriteTimeout(time.Second)\n\to(server)\n\tassert.Equal(t, time.Second, server.writeTimeout)\n}\n\n"
  },
  {
    "path": "server/options.go",
    "content": "package server\n\nimport \"time\"\n\n// WithTCPKeepAlivePeriod sets tcp keepalive period.\nfunc WithTCPKeepAlivePeriod(period time.Duration) OptionFn {\n\treturn func(s *Server) {\n\t\ts.options[\"TCPKeepAlivePeriod\"] = period\n\t}\n}\n"
  },
  {
    "path": "server/plugin.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/smallnest/rpcx/errors\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/soheilhy/cmux\"\n)\n\n// PluginContainer represents a plugin container that defines all methods to manage plugins.\n// And it also defines all extension points.\ntype PluginContainer interface {\n\tAdd(plugin Plugin)\n\tRemove(plugin Plugin)\n\tAll() []Plugin\n\n\tDoRegister(name string, rcvr interface{}, metadata string) error\n\tDoRegisterFunction(serviceName, fname string, fn interface{}, metadata string) error\n\tDoUnregister(name string) error\n\n\tDoPostConnAccept(net.Conn) (net.Conn, bool)\n\tDoPostConnClose(net.Conn) bool\n\n\tDoPreReadRequest(ctx context.Context) error\n\tDoPostReadRequest(ctx context.Context, r *protocol.Message, e error) error\n\tDoPostHTTPRequest(ctx context.Context, r *http.Request, params httprouter.Params) error\n\n\tDoPreHandleRequest(ctx context.Context, req *protocol.Message) error\n\tDoPreCall(ctx context.Context, serviceName, methodName string, args interface{}) (interface{}, error)\n\tDoPostCall(ctx context.Context, serviceName, methodName string, args, reply interface{}, err error) (interface{}, error)\n\n\tDoPreWriteResponse(context.Context, *protocol.Message, *protocol.Message, error) error\n\tDoPostWriteResponse(context.Context, *protocol.Message, *protocol.Message, error) error\n\n\tDoPreWriteRequest(ctx context.Context) error\n\tDoPostWriteRequest(ctx context.Context, r *protocol.Message, e error) error\n\n\tDoHeartbeatRequest(ctx context.Context, req *protocol.Message) error\n\n\tMuxMatch(m cmux.CMux)\n}\n\n// Plugin is the server plugin interface.\ntype Plugin interface{}\n\ntype (\n\t// RegisterPlugin is .\n\tRegisterPlugin interface {\n\t\tRegister(name string, rcvr interface{}, metadata string) error\n\t\tUnregister(name string) error\n\t}\n\n\t// RegisterFunctionPlugin is .\n\tRegisterFunctionPlugin interface {\n\t\tRegisterFunction(serviceName, fname string, fn interface{}, metadata string) error\n\t}\n\n\t// PostConnAcceptPlugin represents connection accept plugin.\n\t// if returns false, it means subsequent IPostConnAcceptPlugins should not continue to handle this conn\n\t// and this conn has been closed.\n\tPostConnAcceptPlugin interface {\n\t\tHandleConnAccept(net.Conn) (net.Conn, bool)\n\t}\n\n\t// PostConnClosePlugin represents client connection close plugin.\n\tPostConnClosePlugin interface {\n\t\tHandleConnClose(net.Conn) bool\n\t}\n\n\t// PreReadRequestPlugin represents .\n\tPreReadRequestPlugin interface {\n\t\tPreReadRequest(ctx context.Context) error\n\t}\n\n\t// PostReadRequestPlugin represents .\n\tPostReadRequestPlugin interface {\n\t\tPostReadRequest(ctx context.Context, r *protocol.Message, e error) error\n\t}\n\n\t// PostHTTPRequestPlugin represents .\n\tPostHTTPRequestPlugin interface {\n\t\tPostHTTPRequest(ctx context.Context, r *http.Request, params httprouter.Params) error\n\t}\n\n\t// PreHandleRequestPlugin represents .\n\tPreHandleRequestPlugin interface {\n\t\tPreHandleRequest(ctx context.Context, r *protocol.Message) error\n\t}\n\n\tPreCallPlugin interface {\n\t\tPreCall(ctx context.Context, serviceName, methodName string, args interface{}) (interface{}, error)\n\t}\n\n\tPostCallPlugin interface {\n\t\tPostCall(ctx context.Context, serviceName, methodName string, args, reply interface{}, err error) (interface{}, error)\n\t}\n\n\t// PreWriteResponsePlugin represents .\n\tPreWriteResponsePlugin interface {\n\t\tPreWriteResponse(context.Context, *protocol.Message, *protocol.Message, error) error\n\t}\n\n\t// PostWriteResponsePlugin represents .\n\tPostWriteResponsePlugin interface {\n\t\tPostWriteResponse(context.Context, *protocol.Message, *protocol.Message, error) error\n\t}\n\n\t// PreWriteRequestPlugin represents .\n\tPreWriteRequestPlugin interface {\n\t\tPreWriteRequest(ctx context.Context) error\n\t}\n\n\t// PostWriteRequestPlugin represents .\n\tPostWriteRequestPlugin interface {\n\t\tPostWriteRequest(ctx context.Context, r *protocol.Message, e error) error\n\t}\n\n\t// HeartbeatPlugin is .\n\tHeartbeatPlugin interface {\n\t\tHeartbeatRequest(ctx context.Context, req *protocol.Message) error\n\t}\n\n\tCMuxPlugin interface {\n\t\tMuxMatch(m cmux.CMux)\n\t}\n)\n\n// pluginContainer implements PluginContainer interface.\ntype pluginContainer struct {\n\tplugins []Plugin\n}\n\n// Add adds a plugin.\nfunc (p *pluginContainer) Add(plugin Plugin) {\n\tp.plugins = append(p.plugins, plugin)\n}\n\n// Remove removes a plugin by it's name.\nfunc (p *pluginContainer) Remove(plugin Plugin) {\n\tif p.plugins == nil {\n\t\treturn\n\t}\n\n\tplugins := make([]Plugin, 0, len(p.plugins))\n\tfor _, p := range p.plugins {\n\t\tif p != plugin {\n\t\t\tplugins = append(plugins, p)\n\t\t}\n\t}\n\n\tp.plugins = plugins\n}\n\nfunc (p *pluginContainer) All() []Plugin {\n\treturn p.plugins\n}\n\n// DoRegister invokes DoRegister plugin.\nfunc (p *pluginContainer) DoRegister(name string, rcvr interface{}, metadata string) error {\n\tvar es []error\n\tfor _, rp := range p.plugins {\n\t\tif plugin, ok := rp.(RegisterPlugin); ok {\n\t\t\terr := plugin.Register(name, rcvr, metadata)\n\t\t\tif err != nil {\n\t\t\t\tes = append(es, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(es) > 0 {\n\t\treturn errors.NewMultiError(es)\n\t}\n\treturn nil\n}\n\n// DoRegisterFunction invokes DoRegisterFunction plugin.\nfunc (p *pluginContainer) DoRegisterFunction(serviceName, fname string, fn interface{}, metadata string) error {\n\tvar es []error\n\tfor _, rp := range p.plugins {\n\t\tif plugin, ok := rp.(RegisterFunctionPlugin); ok {\n\t\t\terr := plugin.RegisterFunction(serviceName, fname, fn, metadata)\n\t\t\tif err != nil {\n\t\t\t\tes = append(es, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(es) > 0 {\n\t\treturn errors.NewMultiError(es)\n\t}\n\treturn nil\n}\n\n// DoUnregister invokes RegisterPlugin.\nfunc (p *pluginContainer) DoUnregister(name string) error {\n\tvar es []error\n\tfor _, rp := range p.plugins {\n\t\tif plugin, ok := rp.(RegisterPlugin); ok {\n\t\t\terr := plugin.Unregister(name)\n\t\t\tif err != nil {\n\t\t\t\tes = append(es, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(es) > 0 {\n\t\treturn errors.NewMultiError(es)\n\t}\n\treturn nil\n}\n\n// DoPostConnAccept handles accepted conn\nfunc (p *pluginContainer) DoPostConnAccept(conn net.Conn) (net.Conn, bool) {\n\tvar flag bool\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PostConnAcceptPlugin); ok {\n\t\t\tconn, flag = plugin.HandleConnAccept(conn)\n\t\t\tif !flag { // interrupt\n\t\t\t\tconn.Close()\n\t\t\t\treturn conn, false\n\t\t\t}\n\t\t}\n\t}\n\treturn conn, true\n}\n\n// DoPostConnClose handles closed conn\nfunc (p *pluginContainer) DoPostConnClose(conn net.Conn) bool {\n\tvar flag bool\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PostConnClosePlugin); ok {\n\t\t\tflag = plugin.HandleConnClose(conn)\n\t\t\tif !flag {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n// DoPreReadRequest invokes PreReadRequest plugin.\nfunc (p *pluginContainer) DoPreReadRequest(ctx context.Context) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PreReadRequestPlugin); ok {\n\t\t\terr := plugin.PreReadRequest(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoPostReadRequest invokes PostReadRequest plugin.\nfunc (p *pluginContainer) DoPostReadRequest(ctx context.Context, r *protocol.Message, e error) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PostReadRequestPlugin); ok {\n\t\t\terr := plugin.PostReadRequest(ctx, r, e)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoPostHTTPRequest invokes PostHTTPRequest plugin.\nfunc (p *pluginContainer) DoPostHTTPRequest(ctx context.Context, r *http.Request, params httprouter.Params) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PostHTTPRequestPlugin); ok {\n\t\t\terr := plugin.PostHTTPRequest(ctx, r, params)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoPreHandleRequest invokes PreHandleRequest plugin.\nfunc (p *pluginContainer) DoPreHandleRequest(ctx context.Context, r *protocol.Message) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PreHandleRequestPlugin); ok {\n\t\t\terr := plugin.PreHandleRequest(ctx, r)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoPreCall invokes PreCallPlugin plugin.\nfunc (p *pluginContainer) DoPreCall(ctx context.Context, serviceName, methodName string, args interface{}) (interface{}, error) {\n\tvar err error\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PreCallPlugin); ok {\n\t\t\targs, err = plugin.PreCall(ctx, serviceName, methodName, args)\n\t\t\tif err != nil {\n\t\t\t\treturn args, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn args, err\n}\n\n// DoPostCall invokes PostCallPlugin plugin.\nfunc (p *pluginContainer) DoPostCall(ctx context.Context, serviceName, methodName string, args, reply interface{}, err error) (interface{}, error) {\n\tvar e error\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PostCallPlugin); ok {\n\t\t\treply, e = plugin.PostCall(ctx, serviceName, methodName, args, reply, err)\n\t\t\tif e != nil {\n\t\t\t\treturn reply, e\n\t\t\t}\n\t\t}\n\t}\n\n\treturn reply, e\n}\n\n// DoPreWriteResponse invokes PreWriteResponse plugin.\nfunc (p *pluginContainer) DoPreWriteResponse(ctx context.Context, req *protocol.Message, res *protocol.Message, err error) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PreWriteResponsePlugin); ok {\n\t\t\terr := plugin.PreWriteResponse(ctx, req, res, err)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoPostWriteResponse invokes PostWriteResponse plugin.\nfunc (p *pluginContainer) DoPostWriteResponse(ctx context.Context, req *protocol.Message, resp *protocol.Message, e error) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PostWriteResponsePlugin); ok {\n\t\t\terr := plugin.PostWriteResponse(ctx, req, resp, e)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoPreWriteRequest invokes PreWriteRequest plugin.\nfunc (p *pluginContainer) DoPreWriteRequest(ctx context.Context) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PreWriteRequestPlugin); ok {\n\t\t\terr := plugin.PreWriteRequest(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoPostWriteRequest invokes PostWriteRequest plugin.\nfunc (p *pluginContainer) DoPostWriteRequest(ctx context.Context, r *protocol.Message, e error) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(PostWriteRequestPlugin); ok {\n\t\t\terr := plugin.PostWriteRequest(ctx, r, e)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DoHeartbeatRequest invokes HeartbeatRequest plugin.\nfunc (p *pluginContainer) DoHeartbeatRequest(ctx context.Context, r *protocol.Message) error {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(HeartbeatPlugin); ok {\n\t\t\terr := plugin.HeartbeatRequest(ctx, r)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// MuxMatch adds cmux Match.\nfunc (p *pluginContainer) MuxMatch(m cmux.CMux) {\n\tfor i := range p.plugins {\n\t\tif plugin, ok := p.plugins[i].(CMuxPlugin); ok {\n\t\t\tplugin.MuxMatch(m)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/plugin_test.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/smallnest/rpcx/client\"\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\ntype HeartbeatHandler struct{}\n\nfunc (h *HeartbeatHandler) HeartbeatRequest(ctx context.Context, req *protocol.Message) error {\n\tconn := ctx.Value(RemoteConnContextKey).(net.Conn)\n\tprintln(\"OnHeartbeat:\", conn.RemoteAddr().String())\n\treturn nil\n}\n\n// TestPluginHeartbeat: go test -v -test.run TestPluginHeartbeat\nfunc TestPluginHeartbeat(t *testing.T) {\n\th := &HeartbeatHandler{}\n\ts := NewServer(\n\t\tWithReadTimeout(time.Duration(5)*time.Second),\n\t\tWithWriteTimeout(time.Duration(5)*time.Second),\n\t)\n\ts.Plugins.Add(h)\n\ts.RegisterName(\"Arith\", new(Arith), \"\")\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\tgo func() {\n\t\t// server\n\t\tdefer wg.Done()\n\t\terr := s.Serve(\"tcp\", \"127.0.0.1:9001\")\n\t\tif err != nil {\n\t\t\tt.Log(err.Error())\n\t\t}\n\t}()\n\tgo func() {\n\t\t// wait for server start complete\n\t\ttime.Sleep(time.Second)\n\t\tdefer wg.Done()\n\t\t// client\n\t\topts := client.DefaultOption\n\t\topts.Heartbeat = true\n\t\topts.HeartbeatInterval = time.Second\n\t\topts.IdleTimeout = time.Duration(5) * time.Second\n\t\topts.ConnectTimeout = time.Duration(5) * time.Second\n\t\t// PeerDiscovery\n\t\td, err := client.NewPeer2PeerDiscovery(\"tcp@127.0.0.1:9001\", \"\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to NewPeer2PeerDiscovery: %v\", err)\n\t\t}\n\n\t\tc := client.NewXClient(\"Arith\", client.Failtry, client.RoundRobin, d, opts)\n\t\ti := 0\n\t\tfor {\n\t\t\ti++\n\t\t\tresp := &Reply{}\n\t\t\tc.Call(context.Background(), \"Mul\", &Args{A: 1, B: 5}, resp)\n\t\t\tt.Log(\"call Mul resp:\", resp.C)\n\t\t\ttime.Sleep(time.Second)\n\t\t\tif i > 10 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tc.Close()\n\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\ts.Shutdown(ctx)\n\t}()\n\twg.Wait()\n}\n"
  },
  {
    "path": "server/pool.go",
    "content": "package server\n\nimport (\n\t\"reflect\"\n\t\"sync\"\n)\n\n// Reset defines Reset method for pooled object.\ntype Reset interface {\n\tReset()\n}\n\nvar reflectTypePools = &typePools{\n\tpools: make(map[reflect.Type]*sync.Pool),\n\tNew: func(t reflect.Type) interface{} {\n\t\tvar argv reflect.Value\n\n\t\tif t.Kind() == reflect.Ptr { // reply must be ptr\n\t\t\targv = reflect.New(t.Elem())\n\t\t} else {\n\t\t\targv = reflect.New(t)\n\t\t}\n\n\t\treturn argv.Interface()\n\t},\n}\n\ntype typePools struct {\n\tmu    sync.RWMutex\n\tpools map[reflect.Type]*sync.Pool\n\tNew   func(t reflect.Type) interface{}\n}\n\nfunc (p *typePools) Init(t reflect.Type) {\n\ttp := &sync.Pool{}\n\ttp.New = func() interface{} {\n\t\treturn p.New(t)\n\t}\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\tp.pools[t] = tp\n}\n\nfunc (p *typePools) Put(t reflect.Type, x interface{}) {\n\tif o, ok := x.(Reset); ok {\n\t\to.Reset()\n\t\tp.mu.RLock()\n\t\tpool := p.pools[t]\n\t\tp.mu.RUnlock()\n\t\tpool.Put(x)\n\t}\n\n}\n\nfunc (p *typePools) Get(t reflect.Type) interface{} {\n\tp.mu.RLock()\n\tpool := p.pools[t]\n\tp.mu.RUnlock()\n\n\treturn pool.Get()\n}\n"
  },
  {
    "path": "server/pool_test.go",
    "content": "package server\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype Elem struct {\n\t// magicNumber the Magic Number\n\tmagicNumber int\n}\n\nfunc (e Elem) Reset() {\n\n}\n\nfunc TestPool(t *testing.T) {\n\telem := Elem{42}\n\telemType := reflect.TypeOf(elem)\n\t// init Elem pool\n\treflectTypePools.Init(elemType)\n\treflectTypePools.Put(elemType, elem)\n\t// Get() will remove element from pool\n\tassert.Equal(t, elem.magicNumber, reflectTypePools.Get(elemType).(Elem).magicNumber)\n}\n"
  },
  {
    "path": "server/quic.go",
    "content": "//go:build quic\n// +build quic\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\t\"github.com/smallnest/quick\"\n)\n\nfunc init() {\n\tmakeListeners[\"quic\"] = quicMakeListener\n}\n\nfunc quicMakeListener(s *Server, address string) (ln net.Listener, err error) {\n\tif s.tlsConfig == nil {\n\t\treturn nil, errors.New(\"TLSConfig must be configured in server.Options\")\n\t}\n\n\tif len(s.tlsConfig.NextProtos) == 0 {\n\t\ts.tlsConfig.NextProtos = []string{\"rpcx\"}\n\t}\n\n\treturn quick.Listen(\"udp\", address, s.tlsConfig, nil)\n}\n"
  },
  {
    "path": "server/router.go",
    "content": "package server\n\n// UpdateHandler 批量更新router\n// 服务器使用plugin热更时，批量替换特定接口\nfunc (s *Server) UpdateHandler(router map[string]Handler) {\n\tnewRouter := make(map[string]Handler)\n\tfor k, v := range s.router {\n\t\tnewRouter[k] = v\n\t}\n\tfor k, v := range router {\n\t\tnewRouter[k] = v\n\t}\n\ts.router = newRouter\n}\n"
  },
  {
    "path": "server/server.go",
    "content": "package server\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n\t\"github.com/soheilhy/cmux\"\n\t\"golang.org/x/net/websocket\"\n)\n\n// ErrServerClosed is returned by the Server's Serve, ListenAndServe after a call to Shutdown or Close.\nvar (\n\tErrServerClosed  = errors.New(\"http: Server closed\")\n\tErrReqReachLimit = errors.New(\"request reached rate limit\")\n)\n\nconst (\n\t// ReaderBuffsize is used for bufio reader.\n\tReaderBuffsize = 1024\n\t// WriterBuffsize is used for bufio writer.\n\tWriterBuffsize = 1024\n\n\t// // WriteChanSize is used for response.\n\t// WriteChanSize = 1024 * 1024\n)\n\n// contextKey is a value for use with context.WithValue. It's used as\n// a pointer so it fits in an interface{} without allocation.\ntype contextKey struct {\n\tname string\n}\n\nfunc (k *contextKey) String() string { return \"rpcx context value \" + k.name }\n\nvar (\n\t// RemoteConnContextKey is a context key. It can be used in\n\t// services with context.WithValue to access the connection arrived on.\n\t// The associated value will be of type net.Conn.\n\tRemoteConnContextKey = &contextKey{\"remote-conn\"}\n\t// StartRequestContextKey records the start time\n\tStartRequestContextKey = &contextKey{\"start-parse-request\"}\n\t// StartSendRequestContextKey records the start time\n\tStartSendRequestContextKey = &contextKey{\"start-send-request\"}\n\t// TagContextKey is used to record extra info in handling services. Its value is a map[string]interface{}\n\tTagContextKey = &contextKey{\"service-tag\"}\n\t// HttpConnContextKey is used to store http connection.\n\tHttpConnContextKey = &contextKey{\"http-conn\"}\n)\n\ntype Handler func(ctx *Context) error\n\ntype WorkerPool interface {\n\tSubmit(task func())\n\tStopAndWaitFor(deadline time.Duration)\n\tStop() context.Context\n\tStopAndWait()\n}\n\n// Server is rpcx server that use TCP or UDP.\ntype Server struct {\n\tln                net.Listener\n\treadTimeout       time.Duration\n\twriteTimeout      time.Duration\n\tgatewayHTTPServer *http.Server\n\n\tjsonrpcHTTPServerLock sync.Mutex\n\tjsonrpcHTTPServer     *http.Server\n\tDisableHTTPGateway    bool // disable http invoke or not.\n\tDisableJSONRPC        bool // disable json rpc or not.\n\tAsyncWrite            bool // set true if your server only serves few clients\n\tpool                  WorkerPool\n\n\tserviceMapMu sync.RWMutex\n\tserviceMap   map[string]*service\n\n\trouter map[string]Handler\n\n\tmu         sync.RWMutex\n\tactiveConn map[net.Conn]struct{}\n\tdoneChan   chan struct{}\n\tseq        atomic.Uint64\n\n\tinShutdown int32\n\tonShutdown []func(s *Server)\n\tonRestart  []func(s *Server)\n\n\t// TLSConfig for creating tls tcp connection.\n\ttlsConfig *tls.Config\n\t// BlockCrypt for kcp.BlockCrypt\n\toptions map[string]interface{}\n\t// CORS options\n\tcorsOptions *CORSOptions\n\n\tPlugins PluginContainer\n\n\t// AuthFunc can be used to auth.\n\tAuthFunc func(ctx context.Context, req *protocol.Message, token string) error\n\n\thandlerMsgNum int32\n\trequestCount  atomic.Uint64\n\n\t// HandleServiceError is used to get all service errors. You can use it write logs or others.\n\tHandleServiceError func(error)\n\n\t// ServerErrorFunc is a customized error handlers and you can use it to return customized error strings to clients.\n\t// If not set, it use err.Error()\n\tServerErrorFunc func(res *protocol.Message, err error) string\n\n\t// The server is started.\n\tStarted           chan struct{}\n\tunregisterAllOnce sync.Once\n}\n\n// NewServer returns a server.\nfunc NewServer(options ...OptionFn) *Server {\n\ts := &Server{\n\t\tPlugins:    &pluginContainer{},\n\t\toptions:    make(map[string]interface{}),\n\t\tactiveConn: make(map[net.Conn]struct{}),\n\t\tdoneChan:   make(chan struct{}),\n\t\tserviceMap: make(map[string]*service),\n\t\trouter:     make(map[string]Handler),\n\t\tAsyncWrite: false, // 除非你想做进一步的优化测试，否则建议你设置为false\n\t\tStarted:    make(chan struct{}),\n\t}\n\n\tfor _, op := range options {\n\t\top(s)\n\t}\n\n\tif s.options[\"TCPKeepAlivePeriod\"] == nil {\n\t\ts.options[\"TCPKeepAlivePeriod\"] = 3 * time.Minute\n\t}\n\n\treturn s\n}\n\n// Address returns listened address.\nfunc (s *Server) Address() net.Addr {\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\tif s.ln == nil {\n\t\treturn nil\n\t}\n\treturn s.ln.Addr()\n}\n\nfunc (s *Server) AddHandler(servicePath, serviceMethod string, handler func(*Context) error) {\n\ts.router[servicePath+\".\"+serviceMethod] = handler\n}\n\n// ActiveClientConn returns active connections.\nfunc (s *Server) ActiveClientConn() []net.Conn {\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\tresult := make([]net.Conn, 0, len(s.activeConn))\n\tfor clientConn := range s.activeConn {\n\t\tresult = append(result, clientConn)\n\t}\n\treturn result\n}\n\n// SendMessage a request to the specified client.\n// The client is designated by the conn.\n// conn can be gotten from context in services:\n//\n//\tctx.Value(RemoteConnContextKey)\n//\n// servicePath, serviceMethod, metadata can be set to zero values.\nfunc (s *Server) SendMessage(conn net.Conn, servicePath, serviceMethod string, metadata map[string]string, data []byte) error {\n\tctx := share.WithValue(context.Background(), StartSendRequestContextKey, time.Now().UnixNano())\n\ts.Plugins.DoPreWriteRequest(ctx)\n\n\treq := protocol.NewMessage()\n\treq.SetMessageType(protocol.Request)\n\n\tseq := s.seq.Add(1)\n\treq.SetSeq(seq)\n\treq.SetOneway(true)\n\treq.SetSerializeType(protocol.SerializeNone)\n\treq.ServicePath = servicePath\n\treq.ServiceMethod = serviceMethod\n\treq.Metadata = metadata\n\treq.Payload = data\n\n\tb := req.EncodeSlicePointer()\n\t_, err := conn.Write(*b)\n\tprotocol.PutData(b)\n\n\ts.Plugins.DoPostWriteRequest(ctx, req, err)\n\n\treturn err\n}\n\nfunc (s *Server) getDoneChan() <-chan struct{} {\n\treturn s.doneChan\n}\n\n// Serve starts and listens RPC requests.\n// It is blocked until receiving connections from clients.\nfunc (s *Server) Serve(network, address string) (err error) {\n\tvar ln net.Listener\n\tln, err = s.makeListener(network, address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer s.UnregisterAll()\n\n\tif network == \"http\" {\n\t\ts.serveByHTTP(ln, \"\")\n\t\treturn nil\n\t}\n\n\tif network == \"ws\" || network == \"wss\" {\n\t\ts.serveByWS(ln, \"\")\n\t\treturn nil\n\t}\n\n\t// try to start gateway\n\tln = s.startGateway(network, ln)\n\n\treturn s.serveListener(ln)\n}\n\n// ServeListener listens RPC requests.\n// It is blocked until receiving connections from clients.\nfunc (s *Server) ServeListener(network string, ln net.Listener) (err error) {\n\tdefer s.UnregisterAll()\n\n\tif network == \"http\" {\n\t\ts.serveByHTTP(ln, \"\")\n\t\treturn nil\n\t}\n\n\t// try to start gateway\n\tln = s.startGateway(network, ln)\n\n\treturn s.serveListener(ln)\n}\n\n// serveListener accepts incoming connections on the Listener ln,\n// creating a new service goroutine for each.\n// The service goroutines read requests and then call services to reply to them.\nfunc (s *Server) serveListener(ln net.Listener) error {\n\tvar tempDelay time.Duration\n\n\ts.mu.Lock()\n\ts.ln = ln\n\tclose(s.Started)\n\ts.mu.Unlock()\n\n\tfor {\n\t\tconn, e := ln.Accept()\n\t\tif e != nil {\n\t\t\tif s.isShutdown() {\n\t\t\t\t<-s.doneChan\n\t\t\t\treturn ErrServerClosed\n\t\t\t}\n\n\t\t\tif ne, ok := e.(net.Error); ok && ne.Temporary() {\n\t\t\t\tif tempDelay == 0 {\n\t\t\t\t\ttempDelay = 5 * time.Millisecond\n\t\t\t\t} else {\n\t\t\t\t\ttempDelay *= 2\n\t\t\t\t}\n\n\t\t\t\tif max := 1 * time.Second; tempDelay > max {\n\t\t\t\t\ttempDelay = max\n\t\t\t\t}\n\n\t\t\t\tlog.Errorf(\"rpcx: Accept error: %v; retrying in %v\", e, tempDelay)\n\t\t\t\ttime.Sleep(tempDelay)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif errors.Is(e, cmux.ErrListenerClosed) {\n\t\t\t\treturn ErrServerClosed\n\t\t\t}\n\t\t\treturn e\n\t\t}\n\t\ttempDelay = 0\n\n\t\tif tc, ok := conn.(*net.TCPConn); ok {\n\t\t\tperiod := s.options[\"TCPKeepAlivePeriod\"]\n\t\t\tif period != nil {\n\t\t\t\ttc.SetKeepAlive(true)\n\t\t\t\ttc.SetKeepAlivePeriod(period.(time.Duration))\n\t\t\t\ttc.SetLinger(10)\n\t\t\t}\n\t\t}\n\n\t\tconn, ok := s.Plugins.DoPostConnAccept(conn)\n\t\tif !ok {\n\t\t\tconn.Close()\n\t\t\tcontinue\n\t\t}\n\n\t\ts.mu.Lock()\n\t\ts.activeConn[conn] = struct{}{}\n\t\ts.mu.Unlock()\n\n\t\tif share.Trace {\n\t\t\tlog.Debugf(\"server accepted an conn: %v\", conn.RemoteAddr().String())\n\t\t}\n\n\t\tgo s.serveConn(conn)\n\t}\n}\n\n// serveByHTTP serves by HTTP.\n// if rpcPath is an empty string, use share.DefaultRPCPath.\nfunc (s *Server) serveByHTTP(ln net.Listener, rpcPath string) {\n\ts.ln = ln\n\n\tif rpcPath == \"\" {\n\t\trpcPath = share.DefaultRPCPath\n\t}\n\tmux := http.NewServeMux()\n\tmux.Handle(rpcPath, s)\n\tsrv := &http.Server{Handler: mux}\n\n\tsrv.Serve(ln)\n}\n\nfunc (s *Server) serveByWS(ln net.Listener, rpcPath string) {\n\ts.ln = ln\n\n\tif rpcPath == \"\" {\n\t\trpcPath = share.DefaultRPCPath\n\t}\n\tmux := http.NewServeMux()\n\tmux.Handle(rpcPath, websocket.Handler(s.ServeWS))\n\tsrv := &http.Server{Handler: mux}\n\n\tsrv.Serve(ln)\n}\n\nfunc (s *Server) sendResponse(ctx *share.Context, conn net.Conn, err error, req, res *protocol.Message) {\n\tif len(res.Payload) > 1024 && req.CompressType() != protocol.None {\n\t\tres.SetCompressType(req.CompressType())\n\t}\n\n\ts.Plugins.DoPreWriteResponse(ctx, req, res, err)\n\n\tdata := res.EncodeSlicePointer()\n\tif s.AsyncWrite {\n\t\tif s.pool != nil {\n\t\t\ts.pool.Submit(func() {\n\t\t\t\tif s.writeTimeout != 0 {\n\t\t\t\t\tconn.SetWriteDeadline(time.Now().Add(s.writeTimeout))\n\t\t\t\t}\n\t\t\t\tconn.Write(*data)\n\t\t\t\tprotocol.PutData(data)\n\t\t\t})\n\t\t} else {\n\t\t\tgo func() {\n\t\t\t\tif s.writeTimeout != 0 {\n\t\t\t\t\tconn.SetWriteDeadline(time.Now().Add(s.writeTimeout))\n\t\t\t\t}\n\t\t\t\tconn.Write(*data)\n\t\t\t\tprotocol.PutData(data)\n\t\t\t}()\n\t\t}\n\t} else {\n\t\tif s.writeTimeout != 0 {\n\t\t\tconn.SetWriteDeadline(time.Now().Add(s.writeTimeout))\n\t\t}\n\t\tconn.Write(*data)\n\t\tprotocol.PutData(data)\n\t}\n\ts.Plugins.DoPostWriteResponse(ctx, req, res, err)\n}\n\nfunc (s *Server) serveConn(conn net.Conn) {\n\tif s.isShutdown() {\n\t\ts.closeConn(conn)\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tconst size = 64 << 10\n\t\t\tbuf := make([]byte, size)\n\t\t\tss := runtime.Stack(buf, false)\n\t\t\tif ss > size {\n\t\t\t\tss = size\n\t\t\t}\n\t\t\tbuf = buf[:ss]\n\t\t\tlog.Errorf(\"serving %s panic error: %s, stack:\\n %s\", conn.RemoteAddr(), err, buf)\n\t\t}\n\n\t\tif share.Trace {\n\t\t\tlog.Debugf(\"server closed conn: %v\", conn.RemoteAddr().String())\n\t\t}\n\n\t\t// make sure all inflight requests are handled and all drained\n\t\tif s.isShutdown() {\n\t\t\t<-s.doneChan\n\t\t}\n\n\t\ts.closeConn(conn)\n\t}()\n\n\tif tlsConn, ok := conn.(*tls.Conn); ok {\n\t\tif d := s.readTimeout; d != 0 {\n\t\t\tconn.SetReadDeadline(time.Now().Add(d))\n\t\t}\n\t\tif d := s.writeTimeout; d != 0 {\n\t\t\tconn.SetWriteDeadline(time.Now().Add(d))\n\t\t}\n\t\tif err := tlsConn.Handshake(); err != nil {\n\t\t\tlog.Errorf(\"rpcx: TLS handshake error from %s: %v\", conn.RemoteAddr(), err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tr := bufio.NewReaderSize(conn, ReaderBuffsize)\n\n\t// read requests and handle it\n\tfor {\n\t\tif s.isShutdown() {\n\t\t\treturn\n\t\t}\n\n\t\tt0 := time.Now()\n\t\tif s.readTimeout != 0 {\n\t\t\tconn.SetReadDeadline(t0.Add(s.readTimeout))\n\t\t}\n\n\t\t// create a rpcx Context\n\t\tctx := share.WithValue(context.Background(), RemoteConnContextKey, conn)\n\n\t\t// read a request from the underlying connection\n\t\treq, err := s.readRequest(ctx, r)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tlog.Infof(\"client has closed this connection: %s\", conn.RemoteAddr().String())\n\t\t\t} else if errors.Is(err, net.ErrClosed) {\n\t\t\t\tlog.Infof(\"rpcx: connection %s is closed\", conn.RemoteAddr().String())\n\t\t\t} else if errors.Is(err, ErrReqReachLimit) {\n\t\t\t\tif !req.IsOneway() { // return a error response\n\t\t\t\t\tres := req.Clone()\n\t\t\t\t\tres.SetMessageType(protocol.Response)\n\n\t\t\t\t\ts.handleError(res, err)\n\t\t\t\t\ts.sendResponse(ctx, conn, err, req, res)\n\t\t\t\t} else { // Oneway and only call the plugins\n\t\t\t\t\ts.Plugins.DoPreWriteResponse(ctx, req, nil, err)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t} else { // wrong data\n\t\t\t\tlog.Warnf(\"rpcx: failed to read request: %v\", err)\n\t\t\t}\n\n\t\t\tif s.HandleServiceError != nil {\n\t\t\t\ts.HandleServiceError(err)\n\t\t\t}\n\n\t\t\treturn\n\t\t}\n\n\t\tif share.Trace {\n\t\t\tlog.Debugf(\"server received an request %+v from conn: %v\", req, conn.RemoteAddr().String())\n\t\t}\n\n\t\tctx = share.WithLocalValue(ctx, StartRequestContextKey, time.Now().UnixNano())\n\t\tcloseConn := false\n\t\tif !req.IsHeartbeat() {\n\t\t\terr = s.auth(ctx, req)\n\t\t\tcloseConn = err != nil\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif !req.IsOneway() { // return a error response\n\t\t\t\tres := req.Clone()\n\t\t\t\tres.SetMessageType(protocol.Response)\n\t\t\t\ts.handleError(res, err)\n\t\t\t\ts.sendResponse(ctx, conn, err, req, res)\n\t\t\t} else {\n\t\t\t\ts.Plugins.DoPreWriteResponse(ctx, req, nil, err)\n\t\t\t}\n\n\t\t\tif s.HandleServiceError != nil {\n\t\t\t\ts.HandleServiceError(err)\n\t\t\t}\n\n\t\t\t// auth failed, closed the connection\n\t\t\tif closeConn {\n\t\t\t\tlog.Infof(\"auth failed for conn %s: %v\", conn.RemoteAddr().String(), err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif s.pool != nil {\n\t\t\ts.pool.Submit(func() {\n\t\t\t\ts.processOneRequest(ctx, req, conn)\n\t\t\t})\n\t\t} else {\n\t\t\tgo s.processOneRequest(ctx, req, conn)\n\t\t}\n\t}\n}\n\nfunc (s *Server) processOneRequest(ctx *share.Context, req *protocol.Message, conn net.Conn) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tbuf := make([]byte, 1024)\n\t\t\tbuf = buf[:runtime.Stack(buf, true)]\n\t\t\tif s.HandleServiceError != nil {\n\t\t\t\ts.HandleServiceError(fmt.Errorf(\"%v\", r))\n\t\t\t} else {\n\t\t\t\tlog.Errorf(\"[handler internal error]: servicepath: %s, servicemethod: %s, err: %v，stacks: %s\", req.ServicePath, req.ServiceMethod, r, string(buf))\n\t\t\t}\n\t\t\tsctx := NewContext(ctx, conn, req, s.AsyncWrite)\n\t\t\tsctx.WriteError(fmt.Errorf(\"%v\", r))\n\t\t}\n\t}()\n\n\tatomic.AddInt32(&s.handlerMsgNum, 1)\n\tdefer atomic.AddInt32(&s.handlerMsgNum, -1)\n\n\t// 心跳请求，直接处理返回\n\tif req.IsHeartbeat() {\n\t\ts.Plugins.DoHeartbeatRequest(ctx, req)\n\t\treq.SetMessageType(protocol.Response)\n\t\tdata := req.EncodeSlicePointer()\n\n\t\tif s.writeTimeout != 0 {\n\t\t\tconn.SetWriteDeadline(time.Now().Add(s.writeTimeout))\n\t\t}\n\t\tconn.Write(*data)\n\n\t\tprotocol.PutData(data)\n\n\t\treturn\n\t}\n\n\tcancelFunc := parseServerTimeout(ctx, req)\n\tif cancelFunc != nil {\n\t\tdefer cancelFunc()\n\t}\n\n\tresMetadata := make(map[string]string)\n\tif req.Metadata == nil {\n\t\treq.Metadata = make(map[string]string)\n\t}\n\tctx = share.WithLocalValue(share.WithLocalValue(ctx, share.ReqMetaDataKey, req.Metadata),\n\t\tshare.ResMetaDataKey, resMetadata)\n\n\ts.Plugins.DoPreHandleRequest(ctx, req)\n\n\tif share.Trace {\n\t\tlog.Debugf(\"server handle request %+v from conn: %v\", req, conn.RemoteAddr().String())\n\t}\n\n\t// use handlers first\n\tif handler, ok := s.router[req.ServicePath+\".\"+req.ServiceMethod]; ok {\n\t\tsctx := NewContext(ctx, conn, req, s.AsyncWrite)\n\t\terr := handler(sctx)\n\t\tif err != nil {\n\t\t\tif s.HandleServiceError != nil {\n\t\t\t\ts.HandleServiceError(err)\n\t\t\t} else {\n\t\t\t\tlog.Errorf(\"[handler internal error]: servicepath: %s, servicemethod, err: %v\", req.ServicePath, req.ServiceMethod, err)\n\t\t\t}\n\t\t\tsctx.WriteError(err)\n\t\t}\n\n\t\treturn\n\t}\n\n\tres, err := s.handleRequest(ctx, req)\n\tif err != nil {\n\t\tif s.HandleServiceError != nil {\n\t\t\ts.HandleServiceError(err)\n\t\t} else {\n\t\t\tlog.Warnf(\"rpcx: failed to handle request: %v\", err)\n\t\t}\n\t}\n\n\tif !req.IsOneway() {\n\t\tif len(resMetadata) > 0 { // copy meta in context to responses\n\t\t\tmeta := res.Metadata\n\t\t\tif meta == nil {\n\t\t\t\tres.Metadata = resMetadata\n\t\t\t} else {\n\t\t\t\tfor k, v := range resMetadata {\n\t\t\t\t\tif meta[k] == \"\" {\n\t\t\t\t\t\tmeta[k] = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ts.sendResponse(ctx, conn, err, req, res)\n\t}\n\n\tif share.Trace {\n\t\tlog.Debugf(\"server write response %+v for an request %+v from conn: %v\", res, req, conn.RemoteAddr().String())\n\t}\n}\n\nfunc parseServerTimeout(ctx *share.Context, req *protocol.Message) context.CancelFunc {\n\tif req == nil || req.Metadata == nil {\n\t\treturn nil\n\t}\n\n\tst := req.Metadata[share.ServerTimeout]\n\tif st == \"\" {\n\t\treturn nil\n\t}\n\n\ttimeout, err := strconv.ParseInt(st, 10, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tnewCtx, cancel := context.WithTimeout(ctx.Context, time.Duration(timeout)*time.Millisecond)\n\tctx.Context = newCtx\n\treturn cancel\n}\n\nfunc (s *Server) isShutdown() bool {\n\treturn atomic.LoadInt32(&s.inShutdown) == 1\n}\n\nfunc (s *Server) closeConn(conn net.Conn) {\n\ts.mu.Lock()\n\tdelete(s.activeConn, conn)\n\ts.mu.Unlock()\n\n\tconn.Close()\n\n\ts.Plugins.DoPostConnClose(conn)\n}\n\nfunc (s *Server) readRequest(ctx context.Context, r io.Reader) (req *protocol.Message, err error) {\n\terr = s.Plugins.DoPreReadRequest(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// pool req?\n\treq = protocol.NewMessage()\n\terr = req.Decode(r)\n\tif err == io.EOF {\n\t\treturn req, err\n\t}\n\tperr := s.Plugins.DoPostReadRequest(ctx, req, err)\n\tif err == nil {\n\t\terr = perr\n\t}\n\treturn req, err\n}\n\nfunc (s *Server) auth(ctx context.Context, req *protocol.Message) error {\n\tif s.AuthFunc != nil {\n\t\ttoken := req.Metadata[share.AuthKey]\n\t\treturn s.AuthFunc(ctx, req, token)\n\t}\n\n\treturn nil\n}\n\nfunc (s *Server) handleRequest(ctx context.Context, req *protocol.Message) (res *protocol.Message, err error) {\n\tserviceName := req.ServicePath\n\tmethodName := req.ServiceMethod\n\n\tres = req.Clone()\n\n\tres.SetMessageType(protocol.Response)\n\ts.serviceMapMu.RLock()\n\tservice := s.serviceMap[serviceName]\n\n\tif share.Trace {\n\t\tlog.Debugf(\"server get service %+v for an request %+v\", service, req)\n\t}\n\n\ts.serviceMapMu.RUnlock()\n\tif service == nil {\n\t\terr = errors.New(\"rpcx: can't find service \" + serviceName)\n\t\treturn s.handleError(res, err)\n\t}\n\tmtype := service.method[methodName]\n\tif mtype == nil {\n\t\tif service.function[methodName] != nil { // check raw functions\n\t\t\treturn s.handleRequestForFunction(ctx, req)\n\t\t}\n\t\terr = errors.New(\"rpcx: can't find method \" + methodName)\n\t\treturn s.handleError(res, err)\n\t}\n\n\t// get a argv object from object pool\n\targv := reflectTypePools.Get(mtype.ArgType)\n\n\tcodec := share.Codecs[req.SerializeType()]\n\tif codec == nil {\n\t\terr = fmt.Errorf(\"can not find codec for %d\", req.SerializeType())\n\t\treturn s.handleError(res, err)\n\t}\n\n\terr = codec.Decode(req.Payload, argv)\n\tif err != nil {\n\t\treturn s.handleError(res, err)\n\t}\n\n\t// and get a reply object from object pool\n\treplyv := reflectTypePools.Get(mtype.ReplyType)\n\n\targv, err = s.Plugins.DoPreCall(ctx, serviceName, methodName, argv)\n\tif err != nil {\n\t\t// return reply to object pool\n\t\treflectTypePools.Put(mtype.ReplyType, replyv)\n\t\treturn s.handleError(res, err)\n\t}\n\n\tif mtype.ArgType.Kind() != reflect.Ptr {\n\t\terr = service.call(ctx, mtype, reflect.ValueOf(argv).Elem(), reflect.ValueOf(replyv))\n\t} else {\n\t\terr = service.call(ctx, mtype, reflect.ValueOf(argv), reflect.ValueOf(replyv))\n\t}\n\n\treplyv, err1 := s.Plugins.DoPostCall(ctx, serviceName, methodName, argv, replyv, err)\n\tif err == nil {\n\t\terr = err1\n\t}\n\n\t// return argc to object pool\n\treflectTypePools.Put(mtype.ArgType, argv)\n\n\tif err != nil {\n\t\tif replyv != nil {\n\t\t\tdata, err := codec.Encode(replyv)\n\t\t\t// return reply to object pool\n\t\t\treflectTypePools.Put(mtype.ReplyType, replyv)\n\t\t\tif err != nil {\n\t\t\t\treturn s.handleError(res, err)\n\t\t\t}\n\t\t\tres.Payload = data\n\t\t}\n\t\treturn s.handleError(res, err)\n\t}\n\n\tif !req.IsOneway() {\n\t\tdata, err := codec.Encode(replyv)\n\t\t// return reply to object pool\n\t\treflectTypePools.Put(mtype.ReplyType, replyv)\n\t\tif err != nil {\n\t\t\treturn s.handleError(res, err)\n\t\t}\n\t\tres.Payload = data\n\t} else if replyv != nil {\n\t\treflectTypePools.Put(mtype.ReplyType, replyv)\n\t}\n\n\tif share.Trace {\n\t\tlog.Debugf(\"server called service %+v for an request %+v\", service, req)\n\t}\n\n\treturn res, nil\n}\n\nfunc (s *Server) handleRequestForFunction(ctx context.Context, req *protocol.Message) (res *protocol.Message, err error) {\n\tres = req.Clone()\n\n\tres.SetMessageType(protocol.Response)\n\n\tserviceName := req.ServicePath\n\tmethodName := req.ServiceMethod\n\ts.serviceMapMu.RLock()\n\tservice := s.serviceMap[serviceName]\n\ts.serviceMapMu.RUnlock()\n\tif service == nil {\n\t\terr = errors.New(\"rpcx: can't find service  for func raw function\")\n\t\treturn s.handleError(res, err)\n\t}\n\tmtype := service.function[methodName]\n\tif mtype == nil {\n\t\terr = errors.New(\"rpcx: can't find method \" + methodName)\n\t\treturn s.handleError(res, err)\n\t}\n\n\targv := reflectTypePools.Get(mtype.ArgType)\n\n\tcodec := share.Codecs[req.SerializeType()]\n\tif codec == nil {\n\t\terr = fmt.Errorf(\"can not find codec for %d\", req.SerializeType())\n\t\treturn s.handleError(res, err)\n\t}\n\n\terr = codec.Decode(req.Payload, argv)\n\tif err != nil {\n\t\treturn s.handleError(res, err)\n\t}\n\n\treplyv := reflectTypePools.Get(mtype.ReplyType)\n\targv, err = s.Plugins.DoPreCall(ctx, serviceName, methodName, argv)\n\tif err != nil {\n\t\t// return reply to object pool\n\t\treflectTypePools.Put(mtype.ReplyType, replyv)\n\t\treturn s.handleError(res, err)\n\t}\n\n\tif mtype.ArgType.Kind() != reflect.Ptr {\n\t\terr = service.callForFunction(ctx, mtype, reflect.ValueOf(argv).Elem(), reflect.ValueOf(replyv))\n\t} else {\n\t\terr = service.callForFunction(ctx, mtype, reflect.ValueOf(argv), reflect.ValueOf(replyv))\n\t}\n\n\treplyv, err1 := s.Plugins.DoPostCall(ctx, serviceName, methodName, argv, replyv, err)\n\tif err == nil {\n\t\terr = err1\n\t}\n\n\treflectTypePools.Put(mtype.ArgType, argv)\n\tif err != nil {\n\t\treflectTypePools.Put(mtype.ReplyType, replyv)\n\t\treturn s.handleError(res, err)\n\t}\n\n\tif !req.IsOneway() {\n\t\tdata, err := codec.Encode(replyv)\n\t\treflectTypePools.Put(mtype.ReplyType, replyv)\n\t\tif err != nil {\n\t\t\treturn s.handleError(res, err)\n\t\t}\n\t\tres.Payload = data\n\t} else if replyv != nil {\n\t\treflectTypePools.Put(mtype.ReplyType, replyv)\n\t}\n\n\treturn res, nil\n}\n\nfunc (s *Server) handleError(res *protocol.Message, err error) (*protocol.Message, error) {\n\tres.SetMessageStatusType(protocol.Error)\n\tif res.Metadata == nil {\n\t\tres.Metadata = make(map[string]string)\n\t}\n\n\tif s.ServerErrorFunc != nil {\n\t\tres.Metadata[protocol.ServiceError] = s.ServerErrorFunc(res, err)\n\t} else {\n\t\tres.Metadata[protocol.ServiceError] = err.Error()\n\t}\n\n\treturn res, err\n}\n\n// Can connect to RPC service using HTTP CONNECT to rpcPath.\nvar connected = \"200 Connected to rpcx\"\n\n// ServeHTTP implements an http.Handler that answers RPC requests.\nfunc (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tif req.Method != http.MethodConnect {\n\t\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\tw.WriteHeader(http.StatusMethodNotAllowed)\n\t\tio.WriteString(w, \"405 must CONNECT\\n\")\n\t\treturn\n\t}\n\tconn, _, err := w.(http.Hijacker).Hijack()\n\tif err != nil {\n\t\tlog.Info(\"rpc hijacking \", req.RemoteAddr, \": \", err.Error())\n\t\treturn\n\t}\n\tio.WriteString(conn, \"HTTP/1.0 \"+connected+\"\\n\\n\")\n\n\ts.mu.Lock()\n\ts.activeConn[conn] = struct{}{}\n\ts.mu.Unlock()\n\n\ts.serveConn(conn)\n}\n\nfunc (s *Server) ServeWS(conn *websocket.Conn) {\n\ts.mu.Lock()\n\ts.activeConn[conn] = struct{}{}\n\ts.mu.Unlock()\n\n\tconn.PayloadType = websocket.BinaryFrame\n\ts.serveConn(conn)\n}\n\n// Close immediately closes all active net.Listeners.\nfunc (s *Server) Close() error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tvar err error\n\tif s.ln != nil {\n\t\terr = s.ln.Close()\n\t}\n\tfor c := range s.activeConn {\n\t\tc.Close()\n\t\tdelete(s.activeConn, c)\n\t\ts.Plugins.DoPostConnClose(c)\n\t}\n\ts.closeDoneChanLocked()\n\n\tif s.pool != nil {\n\t\ts.pool.StopAndWaitFor(10 * time.Second)\n\t}\n\n\treturn err\n}\n\n// RegisterOnShutdown registers a function to call on Shutdown.\n// This can be used to gracefully shutdown connections.\nfunc (s *Server) RegisterOnShutdown(f func(s *Server)) {\n\ts.mu.Lock()\n\ts.onShutdown = append(s.onShutdown, f)\n\ts.mu.Unlock()\n}\n\n// RegisterOnRestart registers a function to call on Restart.\nfunc (s *Server) RegisterOnRestart(f func(s *Server)) {\n\ts.mu.Lock()\n\ts.onRestart = append(s.onRestart, f)\n\ts.mu.Unlock()\n}\n\nvar shutdownPollInterval = 1000 * time.Millisecond\n\n// Shutdown gracefully shuts down the server without interrupting any\n// active connections. Shutdown works by first closing the\n// listener, then closing all idle connections, and then waiting\n// indefinitely for connections to return to idle and then shut down.\n// If the provided context expires before the shutdown is complete,\n// Shutdown returns the context's error, otherwise it returns any\n// error returned from closing the Server's underlying Listener.\nfunc (s *Server) Shutdown(ctx context.Context) error {\n\tvar err error\n\tif atomic.CompareAndSwapInt32(&s.inShutdown, 0, 1) {\n\t\tlog.Info(\"shutdown begin\")\n\n\t\ts.mu.Lock()\n\n\t\t// 主动注销注册的服务\n\t\ts.UnregisterAll()\n\n\t\tif s.ln != nil {\n\t\t\ts.ln.Close()\n\t\t}\n\t\tfor conn := range s.activeConn {\n\t\t\tif tcpConn, ok := conn.(*net.TCPConn); ok {\n\t\t\t\ttcpConn.CloseRead()\n\t\t\t}\n\t\t}\n\t\ts.mu.Unlock()\n\n\t\t// wait all in-processing requests finish.\n\t\tticker := time.NewTicker(shutdownPollInterval)\n\t\tdefer ticker.Stop()\n\touter:\n\t\tfor {\n\t\t\tif s.checkProcessMsg() {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\terr = ctx.Err()\n\t\t\t\tbreak outer\n\t\t\tcase <-ticker.C:\n\t\t\t}\n\t\t}\n\n\t\ts.jsonrpcHTTPServerLock.Lock()\n\t\tif s.gatewayHTTPServer != nil {\n\t\t\tif err := s.closeHTTP1APIGateway(ctx); err != nil {\n\t\t\t\tlog.Warnf(\"failed to close gateway: %v\", err)\n\t\t\t} else {\n\t\t\t\tlog.Info(\"closed gateway\")\n\t\t\t}\n\t\t}\n\t\ts.jsonrpcHTTPServerLock.Unlock()\n\n\t\tif s.jsonrpcHTTPServer != nil {\n\t\t\tif err := s.closeJSONRPC2(ctx); err != nil {\n\t\t\t\tlog.Warnf(\"failed to close JSONRPC: %v\", err)\n\t\t\t} else {\n\t\t\t\tlog.Info(\"closed JSONRPC\")\n\t\t\t}\n\t\t}\n\n\t\ts.mu.Lock()\n\t\tfor conn := range s.activeConn {\n\t\t\tconn.Close()\n\t\t\tdelete(s.activeConn, conn)\n\t\t\ts.Plugins.DoPostConnClose(conn)\n\t\t}\n\t\ts.closeDoneChanLocked()\n\n\t\ts.mu.Unlock()\n\n\t\tlog.Info(\"shutdown end\")\n\n\t}\n\treturn err\n}\n\n// Restart restarts this server gracefully.\n// It starts a new rpcx server with the same port with SO_REUSEPORT socket option,\n// and shutdown this rpcx server gracefully.\nfunc (s *Server) Restart(ctx context.Context) error {\n\tpid, err := s.startProcess()\n\tif err != nil {\n\t\treturn err\n\t}\n\tlog.Infof(\"restart a new rpcx server: %d\", pid)\n\n\t// TODO: is it necessary?\n\ttime.Sleep(3 * time.Second)\n\treturn s.Shutdown(ctx)\n}\n\nfunc (s *Server) startProcess() (int, error) {\n\targv0, err := exec.LookPath(os.Args[0])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\t// Pass on the environment and replace the old count key with the new one.\n\tvar env []string\n\tenv = append(env, os.Environ()...)\n\n\toriginalWD, _ := os.Getwd()\n\tallFiles := []*os.File{os.Stdin, os.Stdout, os.Stderr}\n\tprocess, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{\n\t\tDir:   originalWD,\n\t\tEnv:   env,\n\t\tFiles: allFiles,\n\t})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn process.Pid, nil\n}\n\nfunc (s *Server) checkProcessMsg() bool {\n\tsize := atomic.LoadInt32(&s.handlerMsgNum)\n\tlog.Info(\"need handle in-processing msg size:\", size)\n\treturn size == 0\n}\n\nfunc (s *Server) closeDoneChanLocked() {\n\tselect {\n\tcase <-s.doneChan:\n\t\t// Already closed. Don't close again.\n\tdefault:\n\t\t// Safe to close here. We're the only closer, guarded\n\t\t// by s.mu.RegisterName\n\t\tclose(s.doneChan)\n\t}\n}\n\nvar ip4Reg = regexp.MustCompile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`)\n\nfunc validIP4(ipAddress string) bool {\n\tipAddress = strings.Trim(ipAddress, \" \")\n\ti := strings.LastIndex(ipAddress, \":\")\n\tipAddress = ipAddress[:i] // remove port\n\n\treturn ip4Reg.MatchString(ipAddress)\n}\n\nfunc validIP6(ipAddress string) bool {\n\tipAddress = strings.Trim(ipAddress, \" \")\n\ti := strings.LastIndex(ipAddress, \":\")\n\tipAddress = ipAddress[:i] // remove port\n\tipAddress = strings.TrimPrefix(ipAddress, \"[\")\n\tipAddress = strings.TrimSuffix(ipAddress, \"]\")\n\tip := net.ParseIP(ipAddress)\n\tif ip != nil && ip.To4() == nil {\n\t\treturn true\n\t} else {\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "server/server_test.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\ttestutils \"github.com/smallnest/rpcx/_testutils\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype Args struct {\n\tA int\n\tB int\n}\n\ntype Reply struct {\n\tC int\n}\n\ntype Arith int\n\nfunc (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) error {\n\treply.C = args.A * args.B\n\treturn nil\n}\n\nfunc (t *Arith) ThriftMul(ctx context.Context, args *testutils.ThriftArgs_, reply *testutils.ThriftReply) error {\n\treply.C = args.A * args.B\n\treturn nil\n}\n\nfunc (t *Arith) ConsumingOperation(ctx context.Context, args *testutils.ThriftArgs_, reply *testutils.ThriftReply) error {\n\treply.C = args.A * args.B\n\ttime.Sleep(10 * time.Second)\n\treturn nil\n}\n\nfunc TestShutdownHook(t *testing.T) {\n\ts := NewServer()\n\tvar cancel1 context.CancelFunc\n\ts.RegisterOnShutdown(func(s *Server) {\n\t\tvar ctx context.Context\n\t\tctx, cancel1 = context.WithTimeout(context.Background(), 155*time.Second)\n\t\ts.Shutdown(ctx)\n\t})\n\ts.RegisterName(\"Arith2\", new(Arith), \"\")\n\ts.Register(new(Arith), \"\")\n\tgo s.Serve(\"tcp\", \":0\")\n\n\ttime.Sleep(time.Second)\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\ts.Shutdown(ctx)\n\tcancel()\n\tif cancel1 != nil {\n\t\tcancel1()\n\t}\n}\n\nfunc TestHandleRequest(t *testing.T) {\n\t// use jsoncodec\n\n\treq := protocol.NewMessage()\n\treq.SetVersion(0)\n\treq.SetMessageType(protocol.Request)\n\treq.SetHeartbeat(false)\n\treq.SetOneway(false)\n\treq.SetCompressType(protocol.None)\n\treq.SetMessageStatusType(protocol.Normal)\n\treq.SetSerializeType(protocol.JSON)\n\treq.SetSeq(1234567890)\n\n\treq.ServicePath = \"Arith\"\n\treq.ServiceMethod = \"Mul\"\n\n\targv := &Args{\n\t\tA: 10,\n\t\tB: 20,\n\t}\n\n\tdata, err := json.Marshal(argv)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq.Payload = data\n\n\tserver := NewServer()\n\tserver.RegisterName(\"Arith\", new(Arith), \"\")\n\tres, err := server.handleRequest(context.Background(), req)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to hand request: %v\", err)\n\t}\n\n\tif res.Payload == nil {\n\t\tt.Fatalf(\"expect reply but got %s\", res.Payload)\n\t}\n\n\treply := &Reply{}\n\n\tcodec := share.Codecs[res.SerializeType()]\n\tif codec == nil {\n\t\tt.Fatalf(\"can not find codec %c\", codec)\n\t}\n\n\terr = codec.Decode(res.Payload, reply)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to decode response: %v\", err)\n\t}\n\n\tif reply.C != 200 {\n\t\tt.Fatalf(\"expect 200 but got %d\", reply.C)\n\t}\n}\n\nfunc TestHandler(t *testing.T) {\n\t// use jsoncodec\n\n\treq := protocol.NewMessage()\n\treq.SetVersion(0)\n\treq.SetMessageType(protocol.Request)\n\treq.SetHeartbeat(false)\n\treq.SetOneway(false)\n\treq.SetCompressType(protocol.None)\n\treq.SetMessageStatusType(protocol.Normal)\n\treq.SetSerializeType(protocol.JSON)\n\treq.SetSeq(1234567890)\n\n\treq.ServicePath = \"Arith\"\n\treq.ServiceMethod = \"Mul\"\n\n\targv := &Args{\n\t\tA: 10,\n\t\tB: 20,\n\t}\n\n\tdata, err := json.Marshal(argv)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq.Payload = data\n\n\tserverConn, clientConn := net.Pipe()\n\n\thandler := func(ctx *Context) error {\n\t\treq := &Args{}\n\t\tres := &Reply{}\n\t\tctx.Bind(req)\n\t\tres.C = req.A * req.B\n\n\t\treturn ctx.Write(res)\n\t}\n\n\tgo func() {\n\t\tctx := NewContext(share.NewContext(context.Background()), serverConn, req, false)\n\t\terr = handler(ctx)\n\t\tassert.NoError(t, err)\n\n\t\tserverConn.Close()\n\t}()\n\n\tdata, err = io.ReadAll(clientConn)\n\tassert.NoError(t, err)\n\n\tresp, err := protocol.Read(bytes.NewReader(data))\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, \"Arith\", resp.ServicePath)\n\tassert.Equal(t, \"Mul\", resp.ServiceMethod)\n\tassert.Equal(t, req.Seq(), resp.Seq())\n\n\tassert.Equal(t, \"{\\\"C\\\":200}\", string(resp.Payload))\n}\n\nfunc Test_validIP6(t *testing.T) {\n\ttype args struct {\n\t\tipAddress string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\"1\", args{ipAddress: \"[CDCD:910A:2222:5498:8475:1111:3900:2020]:8080\"}, true},\n\t\t{\"2\", args{ipAddress: \"[1030::C9B4:FF12:48AA:1A2B]:8080\"}, true},\n\t\t{\"3\", args{ipAddress: \"[2000:0:0:0:0:0:0:1]:8080\"}, true},\n\t\t{\"4\", args{ipAddress: \"127.0.0.1:8080\"}, false},\n\t\t{\"5\", args{ipAddress: \"localhost:8080\"}, false},\n\t\t{\"6\", args{ipAddress: \"127.1:8080\"}, false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, tt.want, validIP6(tt.args.ipAddress), \"validIP6(%v)\", tt.args.ipAddress)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/service.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\trerrors \"github.com/smallnest/rpcx/errors\"\n\t\"github.com/smallnest/rpcx/log\"\n)\n\n// RpcServiceError represents an error that is case by service implementation.\ntype RpcServiceInternalError struct {\n\tErr    string\n\tMethod string\n\tArgv   interface{}\n\tstack  string\n}\n\n// Error returns the error message.\nfunc (e RpcServiceInternalError) Error() string {\n\treturn fmt.Sprintf(\"[service internal error]: %v, method: %s, argv: %+v, stack: %s\", e.Err, e.Method, e.Argv, e.stack)\n}\n\n// String returns the error message.\nfunc (e RpcServiceInternalError) String() string {\n\treturn e.Error()\n}\n\n// Precompute the reflect type for error. Can't use error directly\n// because Typeof takes an empty interface value. This is annoying.\nvar typeOfError = reflect.TypeFor[error]()\n\n// Precompute the reflect type for context.\nvar typeOfContext = reflect.TypeFor[context.Context]()\n\ntype methodType struct {\n\tsync.Mutex // protects counters\n\tmethod     reflect.Method\n\tArgType    reflect.Type\n\tReplyType  reflect.Type\n\t// numCalls   uint\n}\n\ntype functionType struct {\n\tsync.Mutex // protects counters\n\tfn         reflect.Value\n\tArgType    reflect.Type\n\tReplyType  reflect.Type\n}\n\ntype service struct {\n\tname     string                   // name of service\n\trcvr     reflect.Value            // receiver of methods for the service\n\ttyp      reflect.Type             // type of the receiver\n\tmethod   map[string]*methodType   // registered methods\n\tfunction map[string]*functionType // registered functions\n}\n\nfunc isExported(name string) bool {\n\trune, _ := utf8.DecodeRuneInString(name)\n\treturn unicode.IsUpper(rune)\n}\n\nfunc isExportedOrBuiltinType(t reflect.Type) bool {\n\tfor t.Kind() == reflect.Ptr {\n\t\tt = t.Elem()\n\t}\n\t// PkgPath will be non-empty even for an exported type,\n\t// so we need to check the type name as well.\n\treturn isExported(t.Name()) || t.PkgPath() == \"\"\n}\n\nfunc (s *Server) ListServices() []string {\n\ts.serviceMapMu.RLock()\n\tdefer s.serviceMapMu.RUnlock()\n\tvar arr []string\n\tfor name := range s.serviceMap {\n\t\tarr = append(arr, name)\n\t}\n\treturn arr\n}\n\n// Register publishes in the server the set of methods of the\n// receiver value that satisfy the following conditions:\n//   - exported method of exported type\n//   - three arguments, the first is of context.Context, both of exported type for three arguments\n//   - the third argument is a pointer\n//   - one return value, of type error\n//\n// It returns an error if the receiver is not an exported type or has\n// no suitable methods. It also logs the error.\n// The client accesses each method using a string of the form \"Type.Method\",\n// where Type is the receiver's concrete type.\nfunc (s *Server) Register(rcvr interface{}, metadata string) error {\n\tsname, err := s.register(rcvr, \"\", false)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.Plugins.DoRegister(sname, rcvr, metadata)\n}\n\n// RegisterName is like Register but uses the provided name for the type\n// instead of the receiver's concrete type.\nfunc (s *Server) RegisterName(name string, rcvr interface{}, metadata string) error {\n\t_, err := s.register(rcvr, name, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif s.Plugins == nil {\n\t\ts.Plugins = &pluginContainer{}\n\t}\n\treturn s.Plugins.DoRegister(name, rcvr, metadata)\n}\n\n// RegisterFunction publishes a function that satisfy the following conditions:\n//   - three arguments, the first is of context.Context, both of exported type for three arguments\n//   - the third argument is a pointer\n//   - one return value, of type error\n//\n// The client accesses function using a string of the form \"servicePath.Method\".\nfunc (s *Server) RegisterFunction(servicePath string, fn interface{}, metadata string) error {\n\tfname, err := s.registerFunction(servicePath, fn, \"\", false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn s.Plugins.DoRegisterFunction(servicePath, fname, fn, metadata)\n}\n\n// RegisterFunctionName is like RegisterFunction but uses the provided name for the function\n// instead of the function's concrete type.\nfunc (s *Server) RegisterFunctionName(servicePath string, name string, fn interface{}, metadata string) error {\n\t_, err := s.registerFunction(servicePath, fn, name, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn s.Plugins.DoRegisterFunction(servicePath, name, fn, metadata)\n}\n\nfunc (s *Server) register(rcvr interface{}, name string, useName bool) (string, error) {\n\ts.serviceMapMu.Lock()\n\tdefer s.serviceMapMu.Unlock()\n\n\tservice := new(service)\n\tservice.typ = reflect.TypeOf(rcvr)\n\tservice.rcvr = reflect.ValueOf(rcvr)\n\tsname := reflect.Indirect(service.rcvr).Type().Name() // Type\n\tif useName {\n\t\tsname = name\n\t}\n\tif sname == \"\" {\n\t\terrorStr := \"rpcx.Register: no service name for type \" + service.typ.String()\n\t\tlog.Error(errorStr)\n\t\treturn sname, errors.New(errorStr)\n\t}\n\tif !useName && !isExported(sname) {\n\t\terrorStr := \"rpcx.Register: type \" + sname + \" is not exported\"\n\t\tlog.Error(errorStr)\n\t\treturn sname, errors.New(errorStr)\n\t}\n\tservice.name = sname\n\n\t// Install the methods\n\tservice.method = suitableMethods(service.typ, true)\n\n\tif len(service.method) == 0 {\n\t\tvar errorStr string\n\n\t\t// To help the user, see if a pointer receiver would work.\n\t\tmethod := suitableMethods(reflect.PtrTo(service.typ), false)\n\t\tif len(method) != 0 {\n\t\t\terrorStr = \"rpcx.Register: type \" + sname + \" has no exported methods of suitable type (hint: pass a pointer to value of that type)\"\n\t\t} else {\n\t\t\terrorStr = \"rpcx.Register: type \" + sname + \" has no exported methods of suitable type\"\n\t\t}\n\t\tlog.Error(errorStr)\n\t\treturn sname, errors.New(errorStr)\n\t}\n\ts.serviceMap[service.name] = service\n\treturn sname, nil\n}\n\nfunc (s *Server) registerFunction(servicePath string, fn interface{}, name string, useName bool) (string, error) {\n\ts.serviceMapMu.Lock()\n\tdefer s.serviceMapMu.Unlock()\n\n\tss := s.serviceMap[servicePath]\n\tif ss == nil {\n\t\tss = new(service)\n\t\tss.name = servicePath\n\t\tss.function = make(map[string]*functionType)\n\t}\n\n\tf, ok := fn.(reflect.Value)\n\tif !ok {\n\t\tf = reflect.ValueOf(fn)\n\t}\n\tif f.Kind() != reflect.Func {\n\t\treturn \"\", errors.New(\"function must be func or bound method\")\n\t}\n\n\tfname := runtime.FuncForPC(reflect.Indirect(f).Pointer()).Name()\n\tif fname != \"\" {\n\t\ti := strings.LastIndex(fname, \".\")\n\t\tif i >= 0 {\n\t\t\tfname = fname[i+1:]\n\t\t}\n\t}\n\tif useName {\n\t\tfname = name\n\t}\n\tif fname == \"\" {\n\t\terrorStr := \"rpcx.registerFunction: no func name for type \" + f.Type().String()\n\t\tlog.Error(errorStr)\n\t\treturn fname, errors.New(errorStr)\n\t}\n\n\tt := f.Type()\n\tif t.NumIn() != 3 {\n\t\treturn fname, fmt.Errorf(\"rpcx.registerFunction: has wrong number of ins: %s\", f.Type().String())\n\t}\n\tif t.NumOut() != 1 {\n\t\treturn fname, fmt.Errorf(\"rpcx.registerFunction: has wrong number of outs: %s\", f.Type().String())\n\t}\n\n\t// First arg must be context.Context\n\tctxType := t.In(0)\n\tif !ctxType.Implements(typeOfContext) {\n\t\treturn fname, fmt.Errorf(\"function %s must use context as  the first parameter\", f.Type().String())\n\t}\n\n\targType := t.In(1)\n\tif !isExportedOrBuiltinType(argType) {\n\t\treturn fname, fmt.Errorf(\"function %s parameter type not exported: %v\", f.Type().String(), argType)\n\t}\n\n\treplyType := t.In(2)\n\tif replyType.Kind() != reflect.Ptr {\n\t\treturn fname, fmt.Errorf(\"function %s reply type not a pointer: %s\", f.Type().String(), replyType)\n\t}\n\tif !isExportedOrBuiltinType(replyType) {\n\t\treturn fname, fmt.Errorf(\"function %s reply type not exported: %v\", f.Type().String(), replyType)\n\t}\n\n\t// The return type of the method must be error.\n\tif returnType := t.Out(0); returnType != typeOfError {\n\t\treturn fname, fmt.Errorf(\"function %s returns %s, not error\", f.Type().String(), returnType.String())\n\t}\n\n\t// Install the methods\n\tss.function[fname] = &functionType{fn: f, ArgType: argType, ReplyType: replyType}\n\ts.serviceMap[servicePath] = ss\n\n\t// init pool for reflect.Type of args and reply\n\treflectTypePools.Init(argType)\n\treflectTypePools.Init(replyType)\n\n\treturn fname, nil\n}\n\n// suitableMethods returns suitable Rpc methods of typ, it will report\n// error using log if reportErr is true.\nfunc suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType {\n\tmethods := make(map[string]*methodType)\n\tfor m := 0; m < typ.NumMethod(); m++ {\n\t\tmethod := typ.Method(m)\n\t\tmtype := method.Type\n\t\tmname := method.Name\n\t\t// Method must be exported.\n\t\tif method.PkgPath != \"\" {\n\t\t\tcontinue\n\t\t}\n\t\t// Method needs four ins: receiver, context.Context, *args, *reply.\n\t\tif mtype.NumIn() != 4 {\n\t\t\tif reportErr {\n\t\t\t\tlog.Debug(\"method \", mname, \" has wrong number of ins:\", mtype.NumIn())\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\t// First arg must be context.Context\n\t\tctxType := mtype.In(1)\n\t\tif !ctxType.Implements(typeOfContext) {\n\t\t\tif reportErr {\n\t\t\t\tlog.Debug(\"method \", mname, \" must use context.Context as the first parameter\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Second arg need not be a pointer.\n\t\targType := mtype.In(2)\n\t\tif !isExportedOrBuiltinType(argType) {\n\t\t\tif reportErr {\n\t\t\t\tlog.Info(mname, \" parameter type not exported: \", argType)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\t// Third arg must be a pointer.\n\t\treplyType := mtype.In(3)\n\t\tif replyType.Kind() != reflect.Ptr {\n\t\t\tif reportErr {\n\t\t\t\tlog.Info(\"method\", mname, \" reply type not a pointer:\", replyType)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\t// Reply type must be exported.\n\t\tif !isExportedOrBuiltinType(replyType) {\n\t\t\tif reportErr {\n\t\t\t\tlog.Info(\"method\", mname, \" reply type not exported:\", replyType)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\t// Method needs one out.\n\t\tif mtype.NumOut() != 1 {\n\t\t\tif reportErr {\n\t\t\t\tlog.Info(\"method\", mname, \" has wrong number of outs:\", mtype.NumOut())\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\t// The return type of the method must be error.\n\t\tif returnType := mtype.Out(0); returnType != typeOfError {\n\t\t\tif reportErr {\n\t\t\t\tlog.Info(\"method\", mname, \" returns \", returnType.String(), \" not error\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tmethods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}\n\n\t\t// init pool for reflect.Type of args and reply\n\t\treflectTypePools.Init(argType)\n\t\treflectTypePools.Init(replyType)\n\t}\n\treturn methods\n}\n\n// UnregisterAll unregisters all services.\n// You can call this method when you want to shutdown/upgrade this node.\nfunc (s *Server) UnregisterAll() error {\n\tvar err error\n\ts.unregisterAllOnce.Do(func() {\n\t\terr = s.unregisterAll()\n\t})\n\n\treturn err\n}\n\nfunc (s *Server) unregisterAll() error {\n\ts.serviceMapMu.RLock()\n\tdefer s.serviceMapMu.RUnlock()\n\tvar es []error\n\tfor k := range s.serviceMap {\n\t\terr := s.Plugins.DoUnregister(k)\n\t\tif err != nil {\n\t\t\tes = append(es, err)\n\t\t}\n\t}\n\n\tif len(es) > 0 {\n\t\treturn rerrors.NewMultiError(es)\n\t}\n\treturn nil\n}\n\nfunc (s *service) call(ctx context.Context, mtype *methodType, argv, replyv reflect.Value) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tbuf := make([]byte, 4096)\n\t\t\tn := runtime.Stack(buf, false)\n\t\t\tbuf = buf[:n]\n\n\t\t\terr = &RpcServiceInternalError{\n\t\t\t\tErr:    fmt.Sprintf(\"%v\", r),\n\t\t\t\tMethod: mtype.method.Name,\n\t\t\t\tArgv:   argv.Interface(),\n\t\t\t\tstack:  string(buf),\n\t\t\t}\n\t\t\tlog.Error(err)\n\t\t}\n\t}()\n\n\tfunction := mtype.method.Func\n\t// Invoke the method, providing a new value for the reply.\n\treturnValues := function.Call([]reflect.Value{s.rcvr, reflect.ValueOf(ctx), argv, replyv})\n\t// The return value for the method is an error.\n\terrInter := returnValues[0].Interface()\n\tif errInter != nil {\n\t\treturn errInter.(error)\n\t}\n\n\treturn nil\n}\n\nfunc (s *service) callForFunction(ctx context.Context, ft *functionType, argv, replyv reflect.Value) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tbuf := make([]byte, 4096)\n\t\t\tn := runtime.Stack(buf, false)\n\t\t\tbuf = buf[:n]\n\n\t\t\terr = &RpcServiceInternalError{\n\t\t\t\tErr:    fmt.Sprintf(\"%v\", r),\n\t\t\t\tMethod: fmt.Sprintf(\"%s\", runtime.FuncForPC(ft.fn.Pointer())),\n\t\t\t\tArgv:   argv.Interface(),\n\t\t\t\tstack:  string(buf),\n\t\t\t}\n\t\t\tlog.Error(err)\n\t\t}\n\t}()\n\n\t// Invoke the function, providing a new value for the reply.\n\treturnValues := ft.fn.Call([]reflect.Value{reflect.ValueOf(ctx), argv, replyv})\n\t// The return value for the method is an error.\n\terrInter := returnValues[0].Interface()\n\tif errInter != nil {\n\t\treturn errInter.(error)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/service_test.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_isExported(t *testing.T) {\n\n\tassert.Equal(t, true, isExported(\"IsExported\"))\n\n\tassert.Equal(t, false, isExported(\"isExported\"))\n\n\tassert.Equal(t, false, isExported(\"_isExported\"))\n\n\tassert.Equal(t, false, isExported(\"123_isExported\"))\n\n\tassert.Equal(t, false, isExported(\"[]_isExported\"))\n\n\tassert.Equal(t, false, isExported(\"&_isExported\"))\n}\n\n\nfunc Mul(ctx context.Context, args *Args, reply *Reply) error {\n\treply.C = args.A * args.B\n\treturn nil\n}\n\nfunc Test_isExportedOrBuiltinType(t *testing.T) {\n\ttypeOfMul := reflect.TypeOf(Mul)\n\tassert.Equal(t, true, isExportedOrBuiltinType(typeOfMul))\n}"
  },
  {
    "path": "server/stream.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\tlru \"github.com/hashicorp/golang-lru\"\n\t\"github.com/smallnest/rpcx/log\"\n\t\"github.com/smallnest/rpcx/share\"\n)\n\nvar ErrNotAccept = errors.New(\"server refused the connection\")\n\n// StreamHandler handles a streaming connection with client.\ntype StreamHandler func(conn net.Conn, args *share.StreamServiceArgs)\n\n// StreamAcceptor accepts connection from clients or not.\n// You can use it to validate clients and determine if accept or drop the connection.\ntype StreamAcceptor func(ctx context.Context, args *share.StreamServiceArgs) bool\n\ntype streamTokenInfo struct {\n\ttoken []byte\n\targs  *share.StreamServiceArgs\n}\n\n// StreamService support streaming between clients and server.\n// It registers a streaming service and listens on the given port.\n// Clients will invokes this service to get the token and send the token and begin to stream.\ntype StreamService struct {\n\tAddr          string\n\tAdvertiseAddr string\n\thandler       StreamHandler\n\tacceptor      StreamAcceptor\n\tcachedTokens  *lru.Cache\n\n\tstartOnce sync.Once\n\n\tln   net.Listener\n\tdone chan struct{}\n}\n\n// NewStreamService creates a stream service.\nfunc NewStreamService(addr string, streamHandler StreamHandler, acceptor StreamAcceptor, waitNum int) *StreamService {\n\tcachedTokens, _ := lru.New(waitNum)\n\n\tfi := &StreamService{\n\t\tAddr:         addr,\n\t\thandler:      streamHandler,\n\t\tcachedTokens: cachedTokens,\n\t\tacceptor:     acceptor,\n\t}\n\n\treturn fi\n}\n\n// EnableStreamService supports stream service in this server.\nfunc (s *Server) EnableStreamService(serviceName string, streamService *StreamService) {\n\tif serviceName == \"\" {\n\t\tserviceName = share.StreamServiceName\n\t}\n\t_ = streamService.Start()\n\t_ = s.RegisterName(serviceName, streamService, \"\")\n}\n\nfunc (s *StreamService) Stream(ctx context.Context, args *share.StreamServiceArgs, reply *share.StreamServiceReply) error {\n\t// clientConn := ctx.Value(server.RemoteConnContextKey).(net.Conn)\n\n\tif s.acceptor != nil && !s.acceptor(ctx, args) {\n\t\treturn ErrNotAccept\n\t}\n\n\ttoken := make([]byte, 32)\n\t_, err := rand.Read(token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*reply = share.StreamServiceReply{\n\t\tToken: token,\n\t\tAddr:  s.Addr,\n\t}\n\tif s.AdvertiseAddr != \"\" {\n\t\treply.Addr = s.AdvertiseAddr\n\t}\n\n\ts.cachedTokens.Add(string(token), &streamTokenInfo{token, args})\n\treturn nil\n}\n\nfunc (s *StreamService) Start() error {\n\ts.startOnce.Do(func() {\n\t\tgo s.start()\n\t})\n\n\treturn nil\n}\n\nfunc (s *StreamService) start() error {\n\tln, err := net.Listen(\"tcp\", s.Addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.ln = ln\n\n\tvar tempDelay time.Duration\n\n\tfor {\n\t\tselect {\n\t\tcase <-s.done:\n\t\t\treturn nil\n\t\tdefault:\n\t\t\tconn, e := ln.Accept()\n\t\t\tif e != nil {\n\t\t\t\tif ne, ok := e.(net.Error); ok && ne.Temporary() {\n\t\t\t\t\tif tempDelay == 0 {\n\t\t\t\t\t\ttempDelay = 5 * time.Millisecond\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttempDelay *= 2\n\t\t\t\t\t}\n\n\t\t\t\t\tif max := 1 * time.Second; tempDelay > max {\n\t\t\t\t\t\ttempDelay = max\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Errorf(\"filetransfer: accept error: %v; retrying in %v\", e, tempDelay)\n\t\t\t\t\ttime.Sleep(tempDelay)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn e\n\t\t\t}\n\t\t\ttempDelay = 0\n\n\t\t\tif tc, ok := conn.(*net.TCPConn); ok {\n\t\t\t\ttc.SetKeepAlive(true)\n\t\t\t\ttc.SetKeepAlivePeriod(3 * time.Minute)\n\t\t\t\ttc.SetLinger(10)\n\t\t\t}\n\n\t\t\ttoken := make([]byte, 32)\n\t\t\t_, err := io.ReadFull(conn, token)\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\tlog.Errorf(\"failed to read token from %s\", conn.RemoteAddr().String())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttokenStr := string(token)\n\t\t\tinfo, ok := s.cachedTokens.Get(tokenStr)\n\t\t\tif !ok {\n\t\t\t\tconn.Close()\n\t\t\t\tlog.Errorf(\"failed to read token from %s\", conn.RemoteAddr().String())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ts.cachedTokens.Remove(tokenStr)\n\n\t\t\tswitch ti := info.(type) {\n\t\t\tcase *streamTokenInfo:\n\t\t\t\tif s.handler == nil {\n\t\t\t\t\tconn.Close()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tgo s.handler(conn, ti.args)\n\t\t\tdefault:\n\t\t\t\tconn.Close()\n\t\t\t}\n\n\t\t}\n\t}\n}\n\nfunc (s *StreamService) Stop() error {\n\tclose(s.done)\n\tif s.ln != nil {\n\t\ts.ln.Close()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "serverplugin/alias.go",
    "content": "package serverplugin\n\nimport (\n\t\"context\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\nvar aliasAppliedKey = \"__aliasAppliedKey\"\n\ntype aliasPair struct {\n\tservicePath, serviceMethod string\n}\n\n// AliasPlugin can be used to set aliases for services\ntype AliasPlugin struct {\n\tAliases          map[string]*aliasPair\n\tReseverseAliases map[string]*aliasPair\n}\n\n// Alias sets a alias for the serviceMethod.\n// For example Alias(\"anewpath&method\", \"Arith.mul\")\nfunc (p *AliasPlugin) Alias(aliasServicePath, aliasServiceMethod string, servicePath, serviceMethod string) {\n\tp.Aliases[aliasServicePath+\".\"+aliasServiceMethod] = &aliasPair{\n\t\tservicePath:   servicePath,\n\t\tserviceMethod: serviceMethod,\n\t}\n\tp.ReseverseAliases[servicePath+\".\"+serviceMethod] = &aliasPair{\n\t\tservicePath:   aliasServicePath,\n\t\tserviceMethod: aliasServiceMethod,\n\t}\n}\n\n// NewAliasPlugin creates a new NewAliasPlugin\nfunc NewAliasPlugin() *AliasPlugin {\n\treturn &AliasPlugin{\n\t\tAliases:          make(map[string]*aliasPair),\n\t\tReseverseAliases: make(map[string]*aliasPair),\n\t}\n}\n\n// PostReadRequest converts the alias of this service.\nfunc (p *AliasPlugin) PostReadRequest(ctx context.Context, r *protocol.Message, e error) error {\n\tvar sp = r.ServicePath\n\tvar sm = r.ServiceMethod\n\n\tk := sp + \".\" + sm\n\tif p.Aliases != nil {\n\t\tif pm := p.Aliases[k]; pm != nil {\n\t\t\tr.ServicePath = pm.servicePath\n\t\t\tr.ServiceMethod = pm.serviceMethod\n\t\t\tif r.Metadata == nil {\n\t\t\t\tr.Metadata = make(map[string]string)\n\t\t\t}\n\t\t\tr.Metadata[aliasAppliedKey] = \"true\"\n\t\t}\n\t}\n\treturn nil\n}\n\n// PreWriteResponse restore servicePath and serviceMethod.\nfunc (p *AliasPlugin) PreWriteResponse(ctx context.Context, r *protocol.Message, res *protocol.Message) error {\n\tif r.Metadata[aliasAppliedKey] != \"true\" {\n\t\treturn nil\n\t}\n\tvar sp = r.ServicePath\n\tvar sm = r.ServiceMethod\n\n\tk := sp + \".\" + sm\n\tif p.ReseverseAliases != nil {\n\t\tif pm := p.ReseverseAliases[k]; pm != nil {\n\t\t\tr.ServicePath = pm.servicePath\n\t\t\tr.ServiceMethod = pm.serviceMethod\n\t\t\tdelete(r.Metadata, aliasAppliedKey)\n\t\t\tif res != nil {\n\t\t\t\tres.ServicePath = pm.servicePath\n\t\t\t\tres.ServiceMethod = pm.serviceMethod\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "serverplugin/blacklist.go",
    "content": "package serverplugin\n\nimport \"net\"\n\n// BlacklistPlugin is a plugin that control only ip addresses in blacklist can **NOT** access services.\ntype BlacklistPlugin struct {\n\tBlacklist     map[string]bool\n\tBlacklistMask []*net.IPNet // net.ParseCIDR(\"172.17.0.0/16\") to get *net.IPNet\n}\n\n// HandleConnAccept check ip.\nfunc (plugin *BlacklistPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) {\n\tip, _, err := net.SplitHostPort(conn.RemoteAddr().String())\n\tif err != nil {\n\t\treturn conn, true\n\t}\n\tif plugin.Blacklist[ip] {\n\t\treturn conn, false\n\t}\n\n\tremoteIP := net.ParseIP(ip)\n\tfor _, mask := range plugin.BlacklistMask {\n\t\tif mask.Contains(remoteIP) {\n\t\t\treturn conn, false\n\t\t}\n\t}\n\n\treturn conn, true\n}\n"
  },
  {
    "path": "serverplugin/mdns.go",
    "content": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/grandcat/zeroconf\"\n\tmetrics \"github.com/rcrowley/go-metrics\"\n)\n\ntype serviceMeta struct {\n\tService        string `json:\"service,omitempty\"`\n\tMeta           string `json:\"meta,omitempty\"`\n\tServiceAddress string `json:\"service_address,omitempty\"`\n}\n\n// MDNSRegisterPlugin implements mdns/dns-sd registry.\ntype MDNSRegisterPlugin struct {\n\t// service address, for example, tcp@127.0.0.1:8972, quic@127.0.0.1:1234\n\tServiceAddress string\n\tport           int\n\tMetrics        metrics.Registry\n\t// Registered services\n\tServices       []*serviceMeta\n\tUpdateInterval time.Duration\n\n\tserver *zeroconf.Server\n\tdomain string\n\n\tdying chan struct{}\n\tdone  chan struct{}\n}\n\n// NewMDNSRegisterPlugin return a new MDNSRegisterPlugin.\n// If domain is empty, use \"local.\" in default.\nfunc NewMDNSRegisterPlugin(serviceAddress string, port int, m metrics.Registry, updateInterval time.Duration, domain string) *MDNSRegisterPlugin {\n\tif domain == \"\" {\n\t\tdomain = \"local.\"\n\t}\n\treturn &MDNSRegisterPlugin{\n\t\tServiceAddress: serviceAddress,\n\t\tport:           port,\n\t\tMetrics:        m,\n\t\tUpdateInterval: updateInterval,\n\t\tdomain:         domain,\n\t\tdying:          make(chan struct{}),\n\t\tdone:           make(chan struct{}),\n\t}\n}\n\n// Start starts the mdns loop.\nfunc (p *MDNSRegisterPlugin) Start() error {\n\n\tif p.server == nil && len(p.Services) != 0 {\n\t\tp.initMDNS()\n\t}\n\n\tif p.UpdateInterval > 0 {\n\t\tgo func() {\n\t\t\tticker := time.NewTicker(p.UpdateInterval)\n\n\t\t\tdefer ticker.Stop()\n\t\t\tdefer p.server.Shutdown()\n\n\t\t\tfor {\n\t\t\t\t// refresh service TTL\n\t\t\t\tselect {\n\t\t\t\tcase <-p.dying:\n\t\t\t\t\tclose(p.done)\n\t\t\t\t\treturn\n\t\t\t\tcase <-ticker.C:\n\t\t\t\t\tif p.server == nil && len(p.Services) == 0 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\textra := make(map[string]string)\n\t\t\t\t\tif p.Metrics != nil {\n\t\t\t\t\t\textra[\"calls\"] = fmt.Sprintf(\"%.2f\", metrics.GetOrRegisterMeter(\"calls\", p.Metrics).RateMean())\n\t\t\t\t\t\textra[\"connections\"] = fmt.Sprintf(\"%.2f\", metrics.GetOrRegisterMeter(\"connections\", p.Metrics).RateMean())\n\t\t\t\t\t}\n\n\t\t\t\t\t// set this same metrics for all services at this server\n\t\t\t\t\tfor _, sm := range p.Services {\n\t\t\t\t\t\tv, _ := url.ParseQuery(string(sm.Meta))\n\t\t\t\t\t\tfor key, value := range extra {\n\t\t\t\t\t\t\tv.Set(key, value)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsm.Meta = v.Encode()\n\t\t\t\t\t}\n\t\t\t\t\tss, _ := json.Marshal(p.Services)\n\t\t\t\t\ts := url.QueryEscape(string(ss))\n\t\t\t\t\tp.server.SetText([]string{s})\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn nil\n}\n\n// Stop unregister all services.\nfunc (p *MDNSRegisterPlugin) Stop() error {\n\tp.server.Shutdown()\n\n\tclose(p.dying)\n\t<-p.done\n\n\treturn nil\n}\nfunc (p *MDNSRegisterPlugin) initMDNS() {\n\tdata, _ := json.Marshal(p.Services)\n\ts := url.QueryEscape(string(data))\n\thost, _ := os.Hostname()\n\n\taddr := p.ServiceAddress\n\ti := strings.Index(addr, \"@\")\n\tif i > 0 {\n\t\taddr = addr[i+1:]\n\t}\n\t_, portStr, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tp.port, err = strconv.Atoi(portStr)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tserver, err := zeroconf.Register(host, \"_rpcxservices\", p.domain, p.port, []string{s}, nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tp.server = server\n}\n\n// HandleConnAccept handles connections from clients\nfunc (p *MDNSRegisterPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) {\n\tif p.Metrics != nil {\n\t\tmetrics.GetOrRegisterMeter(\"connections\", p.Metrics).Mark(1)\n\t}\n\treturn conn, true\n}\n\n// PreCall handles rpc call from clients\nfunc (p *MDNSRegisterPlugin) PreCall(_ context.Context, _, _ string, args interface{}) (interface{}, error) {\n\tif p.Metrics != nil {\n\t\tmetrics.GetOrRegisterMeter(\"calls\", p.Metrics).Mark(1)\n\t}\n\treturn args, nil\n}\n\n// Register handles registering event.\n// this service is registered at BASE/serviceName/thisIpAddress node\nfunc (p *MDNSRegisterPlugin) Register(name string, rcvr interface{}, metadata string) (err error) {\n\tif strings.TrimSpace(name) == \"\" {\n\t\terr = errors.New(\"Register service `name` can't be empty\")\n\t\treturn\n\t}\n\n\tsm := &serviceMeta{\n\t\tService:        name,\n\t\tMeta:           metadata,\n\t\tServiceAddress: p.ServiceAddress,\n\t}\n\n\tp.Services = append(p.Services, sm)\n\n\tif p.server == nil {\n\t\tp.initMDNS()\n\t\treturn\n\t}\n\n\tss, _ := json.Marshal(p.Services)\n\ts := url.QueryEscape(string(ss))\n\tp.server.SetText([]string{s})\n\treturn\n}\n\nfunc (p *MDNSRegisterPlugin) RegisterFunction(serviceName, fname string, fn interface{}, metadata string) error {\n\treturn p.Register(serviceName, fn, metadata)\n}\n\nfunc (p *MDNSRegisterPlugin) Unregister(name string) (err error) {\n\tif len(p.Services) == 0 {\n\t\treturn nil\n\t}\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\terr = errors.New(\"Register service `name` can't be empty\")\n\t\treturn\n\t}\n\n\tvar services = make([]*serviceMeta, 0, len(p.Services)-1)\n\tfor _, meta := range p.Services {\n\t\tif meta.Service != name {\n\t\t\tservices = append(services, meta)\n\t\t}\n\t}\n\tp.Services = services\n\n\tss, _ := json.Marshal(p.Services)\n\ts := url.QueryEscape(string(ss))\n\tp.server.SetText([]string{s})\n\n\t// if p.server != nil {\n\t// \tp.server.Shutdown()\n\t// \treturn\n\t// }\n\n\treturn\n}\n"
  },
  {
    "path": "serverplugin/metrics.go",
    "content": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/rcrowley/go-metrics/exp\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/server\"\n)\n\n// MetricsPlugin has an issue. It changes seq of requests and it is wrong!!!!\n// we should use other methods to map requests and responses not but seq.\n\n// MetricsPlugin collects metrics of a rpc server.\n// You can report metrics to log, syslog, Graphite, InfluxDB or others to display them in Dashboard such as grafana, Graphite.\ntype MetricsPlugin struct {\n\tRegistry metrics.Registry\n\tPrefix   string\n}\n\n// NewMetricsPlugin creates a new MetricsPlugin\nfunc NewMetricsPlugin(registry metrics.Registry) *MetricsPlugin {\n\treturn &MetricsPlugin{Registry: registry}\n}\n\nfunc (p *MetricsPlugin) withPrefix(m string) string {\n\treturn p.Prefix + m\n}\n\n// Register handles registering event.\nfunc (p *MetricsPlugin) Register(name string, rcvr interface{}, metadata string) error {\n\tserviceCounter := metrics.GetOrRegisterCounter(p.withPrefix(\"serviceCounter\"), p.Registry)\n\tserviceCounter.Inc(1)\n\treturn nil\n}\n\n// HandleConnAccept handles connections from clients\nfunc (p *MetricsPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) {\n\tclientMeter := metrics.GetOrRegisterMeter(p.withPrefix(\"clientMeter\"), p.Registry)\n\tclientMeter.Mark(1)\n\treturn conn, true\n}\n\n// PreReadRequest marks start time of calling service\nfunc (p *MetricsPlugin) PreReadRequest(ctx context.Context) error {\n\treturn nil\n}\n\n// PostReadRequest counts read\nfunc (p *MetricsPlugin) PostReadRequest(ctx context.Context, r *protocol.Message, e error) error {\n\tsp := r.ServicePath\n\tsm := r.ServiceMethod\n\n\tif sp == \"\" {\n\t\treturn nil\n\t}\n\tm := metrics.GetOrRegisterMeter(p.withPrefix(\"service.\"+sp+\".\"+sm+\".Read_Qps\"), p.Registry)\n\tm.Mark(1)\n\treturn nil\n}\n\n// PostWriteResponse count write\nfunc (p *MetricsPlugin) PostWriteResponse(ctx context.Context, req *protocol.Message, res *protocol.Message, e error) error {\n\tsp := res.ServicePath\n\tsm := res.ServiceMethod\n\n\tif sp == \"\" {\n\t\treturn nil\n\t}\n\n\tm := metrics.GetOrRegisterMeter(p.withPrefix(\"service.\"+sp+\".\"+sm+\".Write_Qps\"), p.Registry)\n\tm.Mark(1)\n\n\tt := ctx.Value(server.StartRequestContextKey).(int64)\n\n\tif t > 0 {\n\t\tt = time.Now().UnixNano() - t\n\t\tif t < 30*time.Minute.Nanoseconds() { // it is impossible that calltime exceeds 30 minute\n\t\t\t// Historgram\n\t\t\th := metrics.GetOrRegisterHistogram(p.withPrefix(\"service.\"+sp+\".\"+sm+\".CallTime\"), p.Registry,\n\t\t\t\tmetrics.NewExpDecaySample(1028, 0.015))\n\t\t\th.Update(t)\n\t\t}\n\t}\n\treturn nil\n}\n\n// Log reports metrics into logs.\n//\n// p.Log( 5 * time.Second, log.New(os.Stderr, \"metrics: \", log.Lmicroseconds))\nfunc (p *MetricsPlugin) Log(freq time.Duration, l metrics.Logger) {\n\tgo metrics.Log(p.Registry, freq, l)\n}\n\n// Graphite reports metrics into graphite.\n//\n//\taddr, _ := net.ResolveTCPAddr(\"tcp\", \"127.0.0.1:2003\")\n//\tp.Graphite(10e9, \"metrics\", addr)\nfunc (p *MetricsPlugin) Graphite(freq time.Duration, prefix string, addr *net.TCPAddr) {\n\tgo metrics.Graphite(p.Registry, freq, prefix, addr)\n}\n\n// // InfluxDB reports metrics into influxdb.\n// //\n// // \tp.InfluxDB(10e9, \"http://127.0.0.1:8086\",\"metrics\", \"test\",\"test\"})\n// //\n// func (p *MetricsPlugin) InfluxDB(freq time.Duration, url, database, username, password string) {\n// \tgo InfluxDB(p.Registry, freq, url, database, username, password)\n// }\n\n// // InfluxDBWithTags reports metrics into influxdb with tags.\n// // you can set node info into tags.\n// //\n// // \tp.InfluxDBWithTags(10e9, \"http://127.0.0.1:8086\",\"metrics\", \"test\",\"test\", map[string]string{\"host\":\"127.0.0.1\"})\n// //\n// func (p *MetricsPlugin) InfluxDBWithTags(freq time.Duration, url, database, username, password string, tags map[string]string) {\n// \tgo InfluxDBWithTags(p.Registry, freq, url, database, username, password, tags)\n// }\n\n// Exp uses the same mechanism as the official expvar but exposed under /debug/metrics,\n// which shows a json representation of all your usual expvars as well as all your go-metrics.\nfunc (p *MetricsPlugin) Exp() {\n\texp.Exp(metrics.DefaultRegistry)\n}\n"
  },
  {
    "path": "serverplugin/rate_limiting.go",
    "content": "package serverplugin\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/juju/ratelimit\"\n)\n\n// RateLimitingPlugin can limit connecting per unit time\ntype RateLimitingPlugin struct {\n\tFillInterval time.Duration\n\tCapacity     int64\n\tbucket       *ratelimit.Bucket\n}\n\n// NewRateLimitingPlugin creates a new RateLimitingPlugin\nfunc NewRateLimitingPlugin(fillInterval time.Duration, capacity int64) *RateLimitingPlugin {\n\ttb := ratelimit.NewBucket(fillInterval, capacity)\n\n\treturn &RateLimitingPlugin{\n\t\tFillInterval: fillInterval,\n\t\tCapacity:     capacity,\n\t\tbucket:       tb}\n}\n\n// HandleConnAccept can limit connecting rate\nfunc (plugin *RateLimitingPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) {\n\treturn conn, plugin.bucket.TakeAvailable(1) > 0\n}\n"
  },
  {
    "path": "serverplugin/redis.go",
    "content": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tmetrics \"github.com/rcrowley/go-metrics\"\n\t\"github.com/rpcxio/libkv\"\n\t\"github.com/rpcxio/libkv/store\"\n\t\"github.com/rpcxio/libkv/store/redis\"\n\t\"github.com/smallnest/rpcx/log\"\n)\n\nfunc init() {\n\tredis.Register()\n}\n\n// RedisRegisterPlugin implements redis registry.\ntype RedisRegisterPlugin struct {\n\t// service address, for example, tcp@127.0.0.1:8972, quic@127.0.0.1:1234\n\tServiceAddress string\n\t// redis addresses\n\tRedisServers []string\n\t// base path for rpcx server, for example com/example/rpcx\n\tBasePath string\n\tMetrics  metrics.Registry\n\t// Registered services\n\tServices       []string\n\tmetasLock      sync.RWMutex\n\tmetas          map[string]string\n\tUpdateInterval time.Duration\n\n\tOptions *store.Config\n\tkv      store.Store\n\n\tdying chan struct{}\n\tdone  chan struct{}\n}\n\n// Start starts to connect redis cluster\nfunc (p *RedisRegisterPlugin) Start() error {\n\tif p.done == nil {\n\t\tp.done = make(chan struct{})\n\t}\n\tif p.dying == nil {\n\t\tp.dying = make(chan struct{})\n\t}\n\n\tif p.kv == nil {\n\t\tkv, err := libkv.NewStore(store.REDIS, p.RedisServers, p.Options)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"cannot create redis registry: %v\", err)\n\t\t\tclose(p.done)\n\t\t\treturn err\n\t\t}\n\t\tp.kv = kv\n\t}\n\n\terr := p.kv.Put(p.BasePath, []byte(\"rpcx_path\"), &store.WriteOptions{IsDir: true})\n\tif err != nil && !strings.Contains(err.Error(), \"Not a file\") {\n\t\tlog.Errorf(\"cannot create redis path %s: %v\", p.BasePath, err)\n\t\tclose(p.done)\n\t\treturn err\n\t}\n\n\tif p.UpdateInterval > 0 {\n\t\tgo func() {\n\t\t\tticker := time.NewTicker(p.UpdateInterval)\n\n\t\t\tdefer ticker.Stop()\n\t\t\tdefer p.kv.Close()\n\n\t\t\t// refresh service TTL\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-p.dying:\n\t\t\t\t\tclose(p.done)\n\t\t\t\t\treturn\n\t\t\t\tcase <-ticker.C:\n\t\t\t\t\textra := make(map[string]string)\n\t\t\t\t\tif p.Metrics != nil {\n\t\t\t\t\t\textra[\"calls\"] = fmt.Sprintf(\"%.2f\", metrics.GetOrRegisterMeter(\"calls\", p.Metrics).RateMean())\n\t\t\t\t\t\textra[\"connections\"] = fmt.Sprintf(\"%.2f\", metrics.GetOrRegisterMeter(\"connections\", p.Metrics).RateMean())\n\t\t\t\t\t}\n\t\t\t\t\t// set this same metrics for all services at this server\n\t\t\t\t\tfor _, name := range p.Services {\n\t\t\t\t\t\tnodePath := fmt.Sprintf(\"%s/%s/%s\", p.BasePath, name, p.ServiceAddress)\n\t\t\t\t\t\tkvPair, err := p.kv.Get(nodePath)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Infof(\"can't get data of node: %s, because of %v\", nodePath, err.Error())\n\n\t\t\t\t\t\t\tp.metasLock.RLock()\n\t\t\t\t\t\t\tmeta := p.metas[name]\n\t\t\t\t\t\t\tp.metasLock.RUnlock()\n\n\t\t\t\t\t\t\terr = p.kv.Put(nodePath, []byte(meta), &store.WriteOptions{TTL: p.UpdateInterval * 2})\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tlog.Errorf(\"cannot re-create redis path %s: %v\", nodePath, err)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tv, _ := url.ParseQuery(string(kvPair.Value))\n\t\t\t\t\t\t\tfor key, value := range extra {\n\t\t\t\t\t\t\t\tv.Set(key, value)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tp.kv.Put(nodePath, []byte(v.Encode()), &store.WriteOptions{TTL: p.UpdateInterval * 2})\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn nil\n}\n\n// Stop unregister all services.\nfunc (p *RedisRegisterPlugin) Stop() error {\n\tif p.kv == nil {\n\t\tkv, err := libkv.NewStore(store.REDIS, p.RedisServers, p.Options)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"cannot create redis registry: %v\", err)\n\t\t\treturn err\n\t\t}\n\t\tp.kv = kv\n\t}\n\n\tfor _, name := range p.Services {\n\t\tnodePath := fmt.Sprintf(\"%s/%s/%s\", p.BasePath, name, p.ServiceAddress)\n\t\texist, err := p.kv.Exists(nodePath)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"cannot delete path %s: %v\", nodePath, err)\n\t\t\tcontinue\n\t\t}\n\t\tif exist {\n\t\t\tp.kv.Delete(nodePath)\n\t\t\tlog.Infof(\"delete path %s\", nodePath, err)\n\t\t}\n\t}\n\n\tclose(p.dying)\n\t<-p.done\n\n\treturn nil\n}\n\n// HandleConnAccept handles connections from clients\nfunc (p *RedisRegisterPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) {\n\tif p.Metrics != nil {\n\t\tmetrics.GetOrRegisterMeter(\"connections\", p.Metrics).Mark(1)\n\t}\n\treturn conn, true\n}\n\n// PreCall handles rpc call from clients\nfunc (p *RedisRegisterPlugin) PreCall(_ context.Context, _, _ string, args interface{}) (interface{}, error) {\n\tif p.Metrics != nil {\n\t\tmetrics.GetOrRegisterMeter(\"calls\", p.Metrics).Mark(1)\n\t}\n\treturn args, nil\n}\n\n// Register handles registering event.\n// this service is registered at BASE/serviceName/thisIpAddress node\nfunc (p *RedisRegisterPlugin) Register(name string, rcvr interface{}, metadata string) (err error) {\n\tif strings.TrimSpace(name) == \"\" {\n\t\terr = errors.New(\"Register service `name` can't be empty\")\n\t\treturn\n\t}\n\n\tif p.kv == nil {\n\t\tredis.Register()\n\t\tkv, err := libkv.NewStore(store.REDIS, p.RedisServers, p.Options)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"cannot create redis registry: %v\", err)\n\t\t\treturn err\n\t\t}\n\t\tp.kv = kv\n\t}\n\n\terr = p.kv.Put(p.BasePath, []byte(\"rpcx_path\"), &store.WriteOptions{IsDir: true})\n\tif err != nil && !strings.Contains(err.Error(), \"Not a file\") {\n\t\tlog.Errorf(\"cannot create redis path %s: %v\", p.BasePath, err)\n\t\treturn err\n\t}\n\n\tnodePath := fmt.Sprintf(\"%s/%s\", p.BasePath, name)\n\terr = p.kv.Put(nodePath, []byte(name), &store.WriteOptions{IsDir: true})\n\tif err != nil && !strings.Contains(err.Error(), \"Not a file\") {\n\t\tlog.Errorf(\"cannot create redis path %s: %v\", nodePath, err)\n\t\treturn err\n\t}\n\n\tnodePath = fmt.Sprintf(\"%s/%s/%s\", p.BasePath, name, p.ServiceAddress)\n\terr = p.kv.Put(nodePath, []byte(metadata), &store.WriteOptions{TTL: p.UpdateInterval * 2})\n\tif err != nil {\n\t\tlog.Errorf(\"cannot create redis path %s: %v\", nodePath, err)\n\t\treturn err\n\t}\n\n\tp.Services = append(p.Services, name)\n\n\tp.metasLock.Lock()\n\tif p.metas == nil {\n\t\tp.metas = make(map[string]string)\n\t}\n\tp.metas[name] = metadata\n\tp.metasLock.Unlock()\n\treturn\n}\n\nfunc (p *RedisRegisterPlugin) Unregister(name string) (err error) {\n\tif len(p.Services) == 0 {\n\t\treturn nil\n\t}\n\n\tif strings.TrimSpace(name) == \"\" {\n\t\terr = errors.New(\"Register service `name` can't be empty\")\n\t\treturn\n\t}\n\n\tif p.kv == nil {\n\t\tredis.Register()\n\t\tkv, err := libkv.NewStore(store.REDIS, p.RedisServers, p.Options)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"cannot create redis registry: %v\", err)\n\t\t\treturn err\n\t\t}\n\t\tp.kv = kv\n\t}\n\n\terr = p.kv.Put(p.BasePath, []byte(\"rpcx_path\"), &store.WriteOptions{IsDir: true})\n\tif err != nil && !strings.Contains(err.Error(), \"Not a file\") {\n\t\tlog.Errorf(\"cannot create redis path %s: %v\", p.BasePath, err)\n\t\treturn err\n\t}\n\n\tnodePath := fmt.Sprintf(\"%s/%s\", p.BasePath, name)\n\terr = p.kv.Put(nodePath, []byte(name), &store.WriteOptions{IsDir: true})\n\tif err != nil && !strings.Contains(err.Error(), \"Not a file\") {\n\t\tlog.Errorf(\"cannot create redis path %s: %v\", nodePath, err)\n\t\treturn err\n\t}\n\n\tnodePath = fmt.Sprintf(\"%s/%s/%s\", p.BasePath, name, p.ServiceAddress)\n\n\terr = p.kv.Delete(nodePath)\n\tif err != nil {\n\t\tlog.Errorf(\"cannot remove redis path %s: %v\", nodePath, err)\n\t\treturn err\n\t}\n\n\tvar services = make([]string, 0, len(p.Services)-1)\n\tfor _, s := range p.Services {\n\t\tif s != name {\n\t\t\tservices = append(services, s)\n\t\t}\n\t}\n\tp.Services = services\n\n\tp.metasLock.Lock()\n\tif p.metas == nil {\n\t\tp.metas = make(map[string]string)\n\t}\n\tdelete(p.metas, name)\n\tp.metasLock.Unlock()\n\treturn\n}\n"
  },
  {
    "path": "serverplugin/registry_test.go",
    "content": "package serverplugin\n\nimport \"context\"\n\ntype Args struct {\n\tA int\n\tB int\n}\n\ntype Reply struct {\n\tC int\n}\n\ntype Arith int\n\nfunc (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) error {\n\treply.C = args.A * args.B\n\treturn nil\n}\n"
  },
  {
    "path": "serverplugin/req_rate_limiting.go",
    "content": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/juju/ratelimit\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/server\"\n)\n\n// ReqRateLimitingPlugin can limit requests per unit time\ntype ReqRateLimitingPlugin struct {\n\tFillInterval time.Duration\n\tCapacity     int64\n\tbucket       *ratelimit.Bucket\n\tblock        bool // blocks or return error if reach the limit\n}\n\n// NewReqRateLimitingPlugin creates a new RateLimitingPlugin\nfunc NewReqRateLimitingPlugin(fillInterval time.Duration, capacity int64, block bool) *ReqRateLimitingPlugin {\n\ttb := ratelimit.NewBucket(fillInterval, capacity)\n\n\treturn &ReqRateLimitingPlugin{\n\t\tFillInterval: fillInterval,\n\t\tCapacity:     capacity,\n\t\tbucket:       tb,\n\t\tblock:        block,\n\t}\n}\n\n// PostReadRequest can limit request processing.\nfunc (plugin *ReqRateLimitingPlugin) PostReadRequest(ctx context.Context, r *protocol.Message, e error) error {\n\tif plugin.block {\n\t\tplugin.bucket.Wait(1)\n\t\treturn nil\n\t}\n\n\tcount := plugin.bucket.TakeAvailable(1)\n\tif count == 1 {\n\t\treturn nil\n\t}\n\treturn server.ErrReqReachLimit\n}\n"
  },
  {
    "path": "serverplugin/req_rate_limiting_redis.go",
    "content": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/go-redis/redis_rate/v10\"\n\t\"github.com/redis/go-redis/v9\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/server\"\n)\n\nvar _ server.PostReadRequestPlugin = (*RedisRateLimitingPlugin)(nil)\n\n// RedisRateLimitingPlugin can limit requests per unit time\ntype RedisRateLimitingPlugin struct {\n\taddrs   []string\n\tlimiter redis_rate.Limiter\n\tlimit   redis_rate.Limit\n}\n\n// NewRedisRateLimitingPlugin creates a new RateLimitingPlugin\nfunc NewRedisRateLimitingPlugin(addrs []string, rate int, burst int, period time.Duration) *RedisRateLimitingPlugin {\n\tlimit := redis_rate.Limit{\n\t\tRate:   rate,\n\t\tBurst:  burst,\n\t\tPeriod: period,\n\t}\n\trdb := redis.NewClusterClient(&redis.ClusterOptions{\n\t\tAddrs: addrs,\n\t})\n\n\tlimiter := redis_rate.NewLimiter(rdb)\n\n\treturn &RedisRateLimitingPlugin{\n\t\taddrs:   addrs,\n\t\tlimiter: *limiter,\n\t\tlimit:   limit,\n\t}\n}\n\n// PostReadRequest can limit request processing.\nfunc (plugin *RedisRateLimitingPlugin) PostReadRequest(ctx context.Context, r *protocol.Message, e error) error {\n\tres, err := plugin.limiter.Allow(ctx, r.ServicePath+\"/\"+r.ServiceMethod, plugin.limit)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif res.Allowed > 0 {\n\t\treturn nil\n\t}\n\treturn server.ErrReqReachLimit\n}\n"
  },
  {
    "path": "serverplugin/tee.go",
    "content": "package serverplugin\n\nimport (\n\t\"io\"\n\t\"net\"\n)\n\n// TeeConnPlugin is a plugin that copy requests from clients and send to a io.Writer.\ntype TeeConnPlugin struct {\n\tw io.Writer\n}\n\nfunc NewTeeConnPlugin(w io.Writer) *TeeConnPlugin {\n\treturn &TeeConnPlugin{w: w}\n}\n\n// Update can start a stream copy by setting a non-nil w.\n// If you set a nil w, it doesn't copy stream.\nfunc (plugin *TeeConnPlugin) Update(w io.Writer) {\n\tplugin.w = w\n}\n\n// HandleConnAccept check ip.\nfunc (plugin *TeeConnPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) {\n\ttc := &teeConn{conn, plugin.w}\n\treturn tc, true\n}\n\ntype teeConn struct {\n\tnet.Conn\n\tw io.Writer\n}\n\nfunc (t *teeConn) Read(p []byte) (n int, err error) {\n\tn, err = t.Conn.Read(p)\n\tif n > 0 && t.w != nil {\n\t\tt.w.Write(p[:n])\n\t\t// if _, err := t.w.Write(p[:n]); err != nil {\n\t\t// return n, err //discard error\n\t\t// }\n\t}\n\treturn\n}\n"
  },
  {
    "path": "serverplugin/whitelist.go",
    "content": "package serverplugin\n\nimport \"net\"\n\n// WhitelistPlugin is a plugin that control only ip addresses in whitelist can access services.\ntype WhitelistPlugin struct {\n\tWhitelist     map[string]bool\n\tWhitelistMask []*net.IPNet // net.ParseCIDR(\"172.17.0.0/16\") to get *net.IPNet\n}\n\n// HandleConnAccept check ip.\nfunc (plugin *WhitelistPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) {\n\tip, _, err := net.SplitHostPort(conn.RemoteAddr().String())\n\tif err != nil {\n\t\treturn conn, false\n\t}\n\tif plugin.Whitelist[ip] {\n\t\treturn conn, true\n\t}\n\n\tremoteIP := net.ParseIP(ip)\n\tfor _, mask := range plugin.WhitelistMask {\n\t\tif mask.Contains(remoteIP) {\n\t\t\treturn conn, true\n\t\t}\n\t}\n\n\treturn conn, false\n}\n"
  },
  {
    "path": "share/context.go",
    "content": "package share\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"sync\"\n)\n\n// var _ context.Context = &Context{}\n\n// Context is a rpcx customized Context that can contains multiple values.\ntype Context struct {\n\ttagsLock *sync.Mutex\n\ttags     map[interface{}]interface{}\n\tcontext.Context\n}\n\nfunc NewContext(ctx context.Context) *Context {\n\ttagsLock := &sync.Mutex{}\n\tctx = context.WithValue(ctx, ContextTagsLock, tagsLock)\n\treturn &Context{\n\t\ttagsLock: tagsLock,\n\t\tContext:  ctx,\n\t\ttags:     map[interface{}]interface{}{isShareContext: true},\n\t}\n}\n\nfunc (c *Context) Lock() {\n\tc.tagsLock.Lock()\n}\n\nfunc (c *Context) Unlock() {\n\tc.tagsLock.Unlock()\n}\n\nfunc (c *Context) Value(key interface{}) interface{} {\n\tc.tagsLock.Lock()\n\tdefer c.tagsLock.Unlock()\n\tif c.tags == nil {\n\t\tc.tags = make(map[interface{}]interface{})\n\t}\n\n\tif v, ok := c.tags[key]; ok {\n\t\treturn v\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc (c *Context) SetValue(key, val interface{}) {\n\tc.tagsLock.Lock()\n\tdefer c.tagsLock.Unlock()\n\n\tif c.tags == nil {\n\t\tc.tags = make(map[interface{}]interface{})\n\t}\n\tc.tags[key] = val\n}\n\n// DeleteKey delete the kv pair by key.\nfunc (c *Context) DeleteKey(key interface{}) {\n\tc.tagsLock.Lock()\n\tdefer c.tagsLock.Unlock()\n\n\tif c.tags == nil || key == nil {\n\t\treturn\n\t}\n\tdelete(c.tags, key)\n}\n\nfunc (c *Context) String() string {\n\treturn fmt.Sprintf(\"%v.WithValue(%v)\", c.Context, c.tags)\n}\n\nfunc WithValue(parent context.Context, key, val interface{}) *Context {\n\tif key == nil {\n\t\tpanic(\"nil key\")\n\t}\n\tif !reflect.TypeOf(key).Comparable() {\n\t\tpanic(\"key is not comparable\")\n\t}\n\n\ttags := make(map[interface{}]interface{})\n\ttags[key] = val\n\treturn &Context{Context: parent, tags: tags, tagsLock: &sync.Mutex{}}\n}\n\nfunc WithLocalValue(ctx *Context, key, val interface{}) *Context {\n\tif key == nil {\n\t\tpanic(\"nil key\")\n\t}\n\tif !reflect.TypeOf(key).Comparable() {\n\t\tpanic(\"key is not comparable\")\n\t}\n\n\tif ctx.tags == nil {\n\t\tctx.tags = make(map[interface{}]interface{})\n\t}\n\n\tctx.tags[key] = val\n\treturn ctx\n}\n\n// IsShareContext checks whether a context is share.Context.\nfunc IsShareContext(ctx context.Context) bool {\n\tok := ctx.Value(isShareContext)\n\treturn ok != nil\n}\n"
  },
  {
    "path": "share/context_test.go",
    "content": "package share\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar (\n\tTheAnswer   = \"Answer to the Ultimate Question of Life, the Universe, and Everything\"\n\tMagicNumber = 42\n)\n\nfunc TestContext(t *testing.T) {\n\trpcxContext := NewContext(context.Background())\n\tassert.NotNil(t, rpcxContext.Context)\n\tassert.NotNil(t, rpcxContext.tags)\n\n\trpcxContext.SetValue(\"string\", TheAnswer)\n\trpcxContext.SetValue(42, MagicNumber)\n\tassert.Equal(t, MagicNumber, rpcxContext.Value(42))\n\tassert.Equal(t, TheAnswer, rpcxContext.Value(\"string\"))\n\n\trpcxContext.SetValue(\"string\", TheAnswer)\n\tt.Log(rpcxContext.String())\n}\n\nfunc TestWithValue(t *testing.T) {\n\tctx := WithValue(context.Background(), \"key\", \"value\")\n\tassert.NotNil(t, ctx.tags)\n}\n\nfunc TestWithLocalValue(t *testing.T) {\n\tvar c = NewContext(context.Background())\n\tc.SetValue(\"key\", \"value\")\n\n\tctx := WithLocalValue(c, \"MagicNumber\", \"42\")\n\n\tassert.Equal(t, \"value\", ctx.tags[\"key\"])\n\tassert.Equal(t, \"42\", ctx.tags[\"MagicNumber\"])\n}\n"
  },
  {
    "path": "share/share.go",
    "content": "package share\n\nimport (\n\t\"github.com/smallnest/rpcx/codec\"\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\nconst (\n\t// DefaultRPCPath is used by ServeHTTP.\n\tDefaultRPCPath = \"/_rpcx_\"\n\n\t// AuthKey is used in metadata.\n\tAuthKey = \"__AUTH\"\n\n\t// ServerAddress is used to get address of the server by client\n\tServerAddress = \"__ServerAddress\"\n\n\t// ServerTimeout timeout value passed from client to control timeout of server\n\tServerTimeout = \"__ServerTimeout\"\n\n\t// SendFileServiceName is name of the file transfer service.\n\tSendFileServiceName = \"_filetransfer\"\n\n\t// StreamServiceName is name of the stream service.\n\tStreamServiceName = \"_streamservice\"\n\n\t// ContextTagsLock is name of the Context TagsLock.\n\tContextTagsLock = \"_tagsLock\"\n\t// _isShareContext indicates this context is share.Contex.\n\tisShareContext = \"_isShareContext\"\n)\n\n// Trace is a flag to write a trace log or not.\n// You should not enable this flag for product environment and enable it only for test.\n// It writes trace log with logger Debug level.\nvar Trace bool\n\n// Codecs are codecs supported by rpcx. You can add customized codecs in Codecs.\nvar Codecs = map[protocol.SerializeType]codec.Codec{\n\tprotocol.SerializeNone: &codec.ByteCodec{},\n\tprotocol.JSON:          &codec.JSONCodec{},\n\tprotocol.ProtoBuffer:   &codec.PBCodec{},\n\tprotocol.MsgPack:       &codec.MsgpackCodec{},\n\tprotocol.Thrift:        &codec.ThriftCodec{},\n}\n\n// RegisterCodec register customized codec.\nfunc RegisterCodec(t protocol.SerializeType, c codec.Codec) {\n\tCodecs[t] = c\n}\n\n// ContextKey defines key type in context.\ntype ContextKey string\n\n// ReqMetaDataKey is used to set metadata in context of requests.\nvar ReqMetaDataKey = ContextKey(\"__req_metadata\")\n\n// ResMetaDataKey is used to set metadata in context of responses.\nvar ResMetaDataKey = ContextKey(\"__res_metadata\")\n\n// FileTransferArgs args from clients.\ntype FileTransferArgs struct {\n\tFileName string            `json:\"file_name,omitempty\"`\n\tFileSize int64             `json:\"file_size,omitempty\"`\n\tMeta     map[string]string `json:\"meta,omitempty\"`\n}\n\n// Clone clones this DownloadFileArgs.\nfunc (args FileTransferArgs) Clone() *FileTransferArgs {\n\tmeta := make(map[string]string)\n\tfor k, v := range args.Meta {\n\t\tmeta[k] = v\n\t}\n\n\treturn &FileTransferArgs{\n\t\tFileName: args.FileName,\n\t\tFileSize: args.FileSize,\n\t\tMeta:     meta,\n\t}\n}\n\n// FileTransferReply response to token and addr to clients.\ntype FileTransferReply struct {\n\tToken []byte `json:\"token,omitempty\"`\n\tAddr  string `json:\"addr,omitempty\"`\n}\n\n// DownloadFileArgs args from clients.\ntype DownloadFileArgs struct {\n\tFileName string            `json:\"file_name,omitempty\"`\n\tMeta     map[string]string `json:\"meta,omitempty\"`\n}\n\n// Clone clones this DownloadFileArgs.\nfunc (args DownloadFileArgs) Clone() *DownloadFileArgs {\n\tmeta := make(map[string]string)\n\tfor k, v := range args.Meta {\n\t\tmeta[k] = v\n\t}\n\n\treturn &DownloadFileArgs{\n\t\tFileName: args.FileName,\n\t\tMeta:     meta,\n\t}\n}\n\n// StreamServiceArgs is the request type for stream service.\ntype StreamServiceArgs struct {\n\tMeta map[string]string `json:\"meta,omitempty\"`\n}\n\n// StreamServiceReply is the reply type for stream service.\ntype StreamServiceReply struct {\n\tToken []byte `json:\"token,omitempty\"`\n\tAddr  string `json:\"addr,omitempty\"`\n}\n"
  },
  {
    "path": "share/share_test.go",
    "content": "package share\n\nimport (\n\t\"testing\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype MockCodec struct {}\n\nfunc (codec MockCodec) Encode(i interface{}) ([]byte, error) {\n\treturn nil, nil\n}\n\nfunc (codec MockCodec) Decode(data []byte, i interface{}) error {\n\treturn nil\n}\n\nfunc TestShare(t *testing.T) {\n\tregisteredCodecNum := len(Codecs)\n\tcodec := MockCodec{}\n\n\tmockCodecType := 127\n\tRegisterCodec(protocol.SerializeType(mockCodecType), codec)\n\tassert.Equal(t, registeredCodecNum + 1, len(Codecs))\n}\n"
  },
  {
    "path": "tool/xgen/README.md",
    "content": "# xgen\n\n`xgen` isn a tool that can help you generate a server stub for rpcx services.\n\nIt search structs in your specified files and add them as services. Currently it doesn't support registring functions.\n\n## Usage\n\n```sh\n# install\ngo get -u github.com/smallnest/rpcx/tool/xgen/...\n\n# run\nxgen -o server.go <file>.go\n```\n\nThe above will generate server.go containing a rpcx which registers all exported struct types contained in `<file>.go`.\n\n\n## Options\n\n```\n  -o string\n    \tspecify the filename of the output\n  -pkg\n    \tprocess the whole package instead of just the given file\n  -r string\n    \tregistry type. support etcd, consul, zookeeper, mdns (default \"etcd\")\n  -tags string\n    \tbuild tags to add to generated file\n```\n\nYou can run as:\n\n```sh\nxgen [options] <file1>.go <file2>.go <file3>.go \n```\n\nfor example, `xgen -o server.go a.go b.go /User/abc/go/src/github.com/abc/aaa/c.go`\n\nor\n\n```sh\nxgen [options] <package1> <package1> <package1>\n```\n\nfor example, `xgen -o server.go github.com/abc/aaa github.com/abc/bbb github.com/abc/ccc`"
  },
  {
    "path": "tool/xgen/parser/parser.go",
    "content": "// Package parser parses Go code and keeps track of all the types defined\n// and provides access to all the constants defined for an int type.\npackage parser\n\nimport (\n\t\"go/ast\"\n\t\"go/build\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\ntype Parser struct {\n\tPkgPath     string\n\tPkgName     string\n\tPkgFullName string\n\tStructNames map[string]bool\n}\n\ntype visitor struct {\n\t*Parser\n\n\tname     string\n\texplicit bool\n}\n\nfunc isExported(name string) bool {\n\trune, _ := utf8.DecodeRuneInString(name)\n\treturn unicode.IsUpper(rune)\n}\n\nfunc (v *visitor) Visit(n ast.Node) (w ast.Visitor) {\n\tswitch n := n.(type) {\n\tcase *ast.Package:\n\t\treturn v\n\tcase *ast.File:\n\t\tv.PkgName = n.Name.String()\n\t\treturn v\n\n\tcase *ast.GenDecl:\n\t\treturn v\n\tcase *ast.TypeSpec:\n\t\tv.name = n.Name.String()\n\n\t\t// Allow to specify non-structs explicitly independent of '-all' flag.\n\t\tif v.explicit {\n\t\t\tv.StructNames[v.name] = true\n\t\t\treturn nil\n\t\t}\n\t\treturn v\n\tcase *ast.StructType:\n\t\treturn nil\n\tcase *ast.FuncDecl:\n\t\tif isExported(v.name) {\n\t\t\tif n.Type.Params.NumFields() == 3 && n.Type.Results.NumFields() == 1 {\n\t\t\t\tparams := n.Type.Params.List\n\t\t\t\tresult := n.Type.Results.List[0]\n\n\t\t\t\tif result.Type.(*ast.Ident).Name != \"error\" {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tif p, ok := params[0].Type.(*ast.SelectorExpr); ok {\n\t\t\t\t\tx := p.X.(*ast.Ident)\n\t\t\t\t\ts := p.Sel\n\n\t\t\t\t\tif x.Name+\".\"+s.Name == \"context.Context\" {\n\t\t\t\t\t\tv.StructNames[v.name] = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\treturn nil\n\t}\n\treturn nil\n}\n\nfunc (p *Parser) Parse(fname string, isDir bool) error {\n\tp.PkgPath = build.Default.GOPATH\n\n\tfset := token.NewFileSet()\n\tif isDir {\n\t\tpackages, err := parser.ParseDir(fset, fname, nil, parser.ParseComments)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, pckg := range packages {\n\t\t\tast.Walk(&visitor{Parser: p}, pckg)\n\t\t}\n\t} else {\n\t\tf, err := parser.ParseFile(fset, fname, nil, parser.ParseComments)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tast.Walk(&visitor{Parser: p}, f)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "tool/xgen/xgen.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"go/build\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/smallnest/rpcx/tool/xgen/parser\"\n)\n\nvar (\n\tprocessPkg    = flag.Bool(\"pkg\", false, \"process the whole package instead of just the given file\")\n\tspecifiedName = flag.String(\"o\", \"\", \"specify the filename of the output\")\n\tbuildTags     = flag.String(\"tags\", \"\", \"build tags to add to generated file\")\n\tregistry      = flag.String(\"r\", \"\", \"registry type. support etcd, consul, zookeeper, mdns\")\n)\n\nfunc main() {\n\tflag.Parse()\n\n\tfiles := flag.Args()\n\n\tif *processPkg {\n\t\tif len(flag.Args()) == 0 {\n\t\t\tlog.Fatalf(\"not set packages\")\n\t\t}\n\t\tvar pkgFiles []string\n\t\tfor _, f := range files {\n\t\t\tpkgFiles = append(pkgFiles, filepath.Join(filepath.Join(build.Default.GOPATH, \"src\", f)))\n\t\t}\n\t\tfiles = pkgFiles\n\t}\n\n\tif len(files) == 0 {\n\t\tflag.Usage()\n\t\tos.Exit(1)\n\t}\n\n\tvar parsers []*parser.Parser\n\n\tfor _, fname := range files {\n\t\tfInfo, err := os.Stat(fname)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif !filepath.IsAbs(fname) {\n\t\t\tfname, err = filepath.Abs(fname)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}\n\n\t\trelDir, err := filepath.Rel(filepath.Join(build.Default.GOPATH, \"src\"), fname)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"provided directory not under GOPATH (%s): %v\",\n\t\t\t\tbuild.Default.GOPATH, err)\n\t\t\treturn\n\t\t}\n\n\t\tp := &parser.Parser{PkgFullName: relDir, StructNames: make(map[string]bool)}\n\t\tif err := p.Parse(fname, fInfo.IsDir()); err != nil {\n\t\t\tfmt.Printf(\"Error parsing %v: %v\", fname, err)\n\t\t\treturn\n\t\t}\n\n\t\tparsers = append(parsers, p)\n\t\t_ = generate(parsers)\n\t}\n\n}\n\nfunc generate(parsers []*parser.Parser) error {\n\tvar w io.Writer\n\tif *specifiedName == \"\" {\n\t\tw = os.Stdout\n\t} else {\n\t\tf, err := os.Create(*specifiedName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f.Close()\n\t\tw = f\n\t}\n\n\tif *buildTags != \"\" {\n\t\tfmt.Fprintln(w, \"// +build \", *buildTags)\n\t\tfmt.Fprintln(w)\n\t}\n\tfmt.Fprintln(w, \"// AUTOGENERATED FILE: rpcx server stub code\")\n\tfmt.Fprintln(w, \"// compilable during generation.\")\n\tfmt.Fprintln(w)\n\tfmt.Fprintln(w, \"package main\")\n\n\tfmt.Fprintln(w)\n\tfmt.Fprintln(w, \"import (\")\n\tfmt.Fprintln(w, `  \"flag\"`)\n\tfmt.Fprintln(w, `  \"time\"`)\n\tfmt.Fprintln(w)\n\tfmt.Fprintln(w, `  metrics \"github.com/rcrowley/go-metrics\"`)\n\tfmt.Fprintln(w, `  \"github.com/smallnest/rpcx/server\"`)\n\tfmt.Fprintln(w, `  \"github.com/smallnest/rpcx/serverplugin\"`)\n\n\tvar importedPackages = make(map[string]bool)\n\tfor _, p := range parsers {\n\t\tif !importedPackages[p.PkgFullName] {\n\t\t\tfmt.Fprintln(w, `  \"`+p.PkgFullName+`\"`)\n\t\t\timportedPackages[p.PkgFullName] = true\n\t\t}\n\t}\n\tfmt.Fprintln(w, \")\")\n\tfmt.Fprintln(w)\n\n\tvar mainFn = `\nvar (\n\taddr     = flag.String(\"addr\", \"localhost:8972\", \"server address\")`\n\tif *registry == \"etcd\" || *registry == \"consul\" || *registry == \"zookeeper\" {\n\t\tmainFn = mainFn + `\n\trAddr = flag.String(\"rAddr\", \"localhost:2379\", \"register address\")`\n\t}\n\n\tmainFn = mainFn + `\n\tbasePath = flag.String(\"base\", \"/rpcx\", \"prefix path\")\n)\n\t\nfunc main() {\n\tflag.Parse()\n\n\t_ = time.Second\n\t_ = metrics.UseNilMetrics\n\t_ = serverplugin.GetFunctionName\n\n\ts := server.NewServer()\n\taddRegistryPlugin(s)\n\n\tregisterServices(s)\n\n\ts.Serve(\"tcp\", *addr)\n}`\n\tfmt.Fprintln(w, mainFn)\n\n\tfmt.Fprintln(w, `func registerServices(s *server.Server) {`)\n\tfor _, p := range parsers {\n\t\tfor n := range p.StructNames {\n\t\t\tfmt.Fprintln(w, `\ts.Register(new(`+p.PkgName+\".\"+n+`), \"\")`)\n\t\t}\n\t}\n\n\tfmt.Fprintln(w, `}`)\n\n\tuseRegistry := false\n\n\tfmt.Fprintln(w, `func addRegistryPlugin(s *server.Server) {`)\n\tswitch *registry {\n\tcase \"etcd\":\n\t\tfmt.Fprintln(w, `\t// add registery\n\tr := &serverplugin.EtcdRegisterPlugin{\n\t\tServiceAddress: \"tcp@\" + *addr,\n\t\tEtcdServers:    []string{*rAddr},\n\t\tBasePath:       *basePath,\n\t\tMetrics:        metrics.NewRegistry(),\n\t\tUpdateInterval: time.Minute,\n\t}`)\n\t\tuseRegistry = true\n\tcase \"consul\":\n\t\tfmt.Fprintln(w, `\t// add registery\n\tr := &serverplugin.ConsulRegisterPlugin{\n\t\tServiceAddress: \"tcp@\" + *addr,\n\t\tConsulServers:  []string{*rAddr},\n\t\tBasePath:       *basePath,\n\t\tMetrics:        metrics.NewRegistry(),\n\t\tUpdateInterval: time.Minute,\n\t}`)\n\t\tuseRegistry = true\n\tcase \"zookeeper\":\n\t\tfmt.Fprintln(w, `\t// add registery\n\tr := &serverplugin.ZooKeeperRegisterPlugin{\n\t\tServiceAddress:   \"tcp@\" + *addr,\n\t\tZooKeeperServers: []string{*rAddr},\n\t\tBasePath:         *basePath,\n\t\tMetrics:          metrics.NewRegistry(),\n\t\tUpdateInterval:   time.Minute,\n\t}`)\n\t\tuseRegistry = true\n\tcase \"mdns\":\n\t\tfmt.Fprintln(w, `\n\t\t\tr := serverplugin.NewMDNSRegisterPlugin(\"tcp@\"+*addr, 8972, metrics.NewRegistry(), time.Minute, \"\")`)\n\t\tuseRegistry = true\n\tdefault:\n\t\tfmt.Fprintln(w, `\n\t\t`)\n\t}\n\n\tif useRegistry {\n\t\tfmt.Fprintln(w, `\n\terr := r.Start()\n\tif err != nil {\n\t\t//log.Fatal(err)\n\t}\n\ts.Plugins.Add(r)`)\n\t}\n\n\tfmt.Fprintln(w, `}`)\n\treturn nil\n}\n"
  },
  {
    "path": "util/buffer_pool.go",
    "content": "package util\n\nimport (\n\t\"math\"\n\t\"sync\"\n)\n\ntype levelPool struct {\n\tsize int\n\tpool sync.Pool\n}\n\nfunc newLevelPool(size int) *levelPool {\n\treturn &levelPool{\n\t\tsize: size,\n\t\tpool: sync.Pool{\n\t\t\tNew: func() interface{} {\n\t\t\t\tdata := make([]byte, size)\n\t\t\t\treturn &data\n\t\t\t},\n\t\t},\n\t}\n}\n\ntype LimitedPool struct {\n\tminSize int\n\tmaxSize int\n\tpools   []*levelPool\n}\n\nfunc NewLimitedPool(minSize, maxSize int) *LimitedPool {\n\tif maxSize < minSize {\n\t\tpanic(\"maxSize can't be less than minSize\")\n\t}\n\tconst multiplier = 2\n\tvar pools []*levelPool\n\tcurSize := minSize\n\tfor curSize < maxSize {\n\t\tpools = append(pools, newLevelPool(curSize))\n\t\tcurSize *= multiplier\n\t}\n\tpools = append(pools, newLevelPool(maxSize))\n\treturn &LimitedPool{\n\t\tminSize: minSize,\n\t\tmaxSize: maxSize,\n\t\tpools:   pools,\n\t}\n}\n\nfunc (p *LimitedPool) findPool(size int) *levelPool {\n\tif size > p.maxSize {\n\t\treturn nil\n\t}\n\tidx := int(math.Ceil(math.Log2(float64(size) / float64(p.minSize))))\n\tif idx < 0 {\n\t\tidx = 0\n\t}\n\tif idx > len(p.pools)-1 {\n\t\treturn nil\n\t}\n\treturn p.pools[idx]\n}\n\nfunc (p *LimitedPool) findPutPool(size int) *levelPool {\n\tif size > p.maxSize {\n\t\treturn nil\n\t}\n\tif size < p.minSize {\n\t\treturn nil\n\t}\n\n\tidx := int(math.Floor(math.Log2(float64(size) / float64(p.minSize))))\n\tif idx < 0 {\n\t\tidx = 0\n\t}\n\tif idx > len(p.pools)-1 {\n\t\treturn nil\n\t}\n\treturn p.pools[idx]\n}\n\nfunc (p *LimitedPool) Get(size int) *[]byte {\n\tsp := p.findPool(size)\n\tif sp == nil {\n\t\tdata := make([]byte, size)\n\t\treturn &data\n\t}\n\tbuf := sp.pool.Get().(*[]byte)\n\t*buf = (*buf)[:size]\n\treturn buf\n}\n\nfunc (p *LimitedPool) Put(b *[]byte) {\n\tsp := p.findPutPool(cap(*b))\n\tif sp == nil {\n\t\treturn\n\t}\n\t*b = (*b)[:cap(*b)]\n\tsp.pool.Put(b)\n}\n"
  },
  {
    "path": "util/buffer_pool_test.go",
    "content": "package util\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestLimitedPool_findPool(t *testing.T) {\n\tpool := NewLimitedPool(512, 4096)\n\n\ttests := []struct {\n\t\targs int\n\t\twant int\n\t}{\n\t\t{200, 512},\n\t\t{512, 512},\n\t\t{1000, 1024},\n\t\t{2000, 2048},\n\t\t{2048, 2048},\n\t\t{4095, 4096},\n\t\t{4096, 4096},\n\t\t{4097, -1},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"bytes-%d\", tt.args), func(t *testing.T) {\n\t\t\tgot := pool.findPool(tt.args)\n\t\t\tif got == nil {\n\t\t\t\tif tt.want > 0 {\n\t\t\t\t\tfmt.Printf(\"expect %d pool but got nil\", tt.want)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got.size != tt.want {\n\t\t\t\tfmt.Printf(\"expect %d pool but got %d pool\", tt.want, got.size)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLimitedPool_findPutPool(t *testing.T) {\n\tpool := NewLimitedPool(512, 4096)\n\n\ttests := []struct {\n\t\targs int\n\t\twant int\n\t}{\n\t\t{200, -1}, //too small so we discard it\n\t\t{512, 512},\n\t\t{1000, 512},\n\t\t{2000, 1024},\n\t\t{2048, 2048},\n\t\t{4095, 2048},\n\t\t{4096, 4096},\n\t\t{4097, -1}, // too big so we discard it\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"bytes-%d\", tt.args), func(t *testing.T) {\n\t\t\tgot := pool.findPutPool(tt.args)\n\t\t\tif got == nil {\n\t\t\t\tif tt.want > 0 {\n\t\t\t\t\tfmt.Printf(\"expect %d pool but got nil\", tt.want)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got.size != tt.want {\n\t\t\t\tfmt.Printf(\"expect %d pool but got %d pool\", tt.want, got.size)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "util/compress.go",
    "content": "package util\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"io\"\n\t\"sync\"\n)\n\nvar (\n\tspWriter sync.Pool\n\tspReader sync.Pool\n\tspBuffer sync.Pool\n)\n\nfunc init() {\n\tspWriter = sync.Pool{New: func() interface{} {\n\t\treturn gzip.NewWriter(nil)\n\t}}\n\tspReader = sync.Pool{New: func() interface{} {\n\t\treturn new(gzip.Reader)\n\t}}\n\tspBuffer = sync.Pool{New: func() interface{} {\n\t\treturn bytes.NewBuffer(nil)\n\t}}\n}\n\n// Unzip unzips data.\nfunc Unzip(data []byte) ([]byte, error) {\n\tbuf := bytes.NewBuffer(data)\n\n\tgr := spReader.Get().(*gzip.Reader)\n\tdefer func() {\n\t\tspReader.Put(gr)\n\t}()\n\terr := gr.Reset(buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer gr.Close()\n\n\tdata, err = io.ReadAll(gr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn data, err\n}\n\n// Zip zips data.\nfunc Zip(data []byte) ([]byte, error) {\n\tbuf := spBuffer.Get().(*bytes.Buffer)\n\tw := spWriter.Get().(*gzip.Writer)\n\tw.Reset(buf)\n\n\tdefer func() {\n\t\tbuf.Reset()\n\t\tspBuffer.Put(buf)\n\t\tw.Close()\n\t\tspWriter.Put(w)\n\t}()\n\t_, err := w.Write(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = w.Flush()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = w.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdec := buf.Bytes()\n\treturn dec, nil\n}\n"
  },
  {
    "path": "util/compress_test.go",
    "content": "package util\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestZip(t *testing.T) {\n\ts := \"%5B%7B%22service%22%3A%22AttrDict%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%2C%7B%22service%22%3A%22BrasInfo%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%5D\"\n\tt.Logf(\"origin len: %d\", len(s))\n\tdata, err := Zip([]byte(s))\n\tif err != nil {\n\t\tt.Fatalf(\"failed to zip: %v\", err)\n\t}\n\tt.Logf(\"zipped len: %d\", len(data))\n\ts2, err := Unzip(data)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to unzip: %v\", err)\n\t}\n\n\tif string(s2) != s {\n\t\tt.Fatalf(\"unzip data is wrong\")\n\t}\n}\n\nfunc BenchmarkZip(b *testing.B) {\n\ts := \"%5B%7B%22service%22%3A%22AttrDict%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%2C%7B%22service%22%3A%22BrasInfo%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%5D\"\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\tdata, err := Zip([]byte(s))\n\t\t\tif err != nil {\n\t\t\t\tb.Errorf(\"failed to zip: %v\", err)\n\t\t\t}\n\t\t\t_ = data\n\t\t}\n\t})\n}\n\nfunc BenchmarkUnzip(b *testing.B) {\n\ts := \"%5B%7B%22service%22%3A%22AttrDict%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%2C%7B%22service%22%3A%22BrasInfo%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%5D\"\n\tdata, err := Zip([]byte(s))\n\tif err != nil {\n\t\tb.Fatalf(\"failed to zip: %v\", err)\n\t}\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\ts2, err := Unzip(data)\n\t\t\tif err != nil {\n\t\t\t\tb.Errorf(\"failed to zip: %v\", err)\n\t\t\t}\n\t\t\t_ = s2\n\t\t}\n\t})\n}\n\nfunc oldUnzip(data []byte) ([]byte, error) {\n\tbuf := spBuffer.Get().(*bytes.Buffer)\n\tdefer func() {\n\t\tbuf.Reset()\n\t\tspBuffer.Put(buf)\n\t}()\n\n\t_, err := buf.Write(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgr := spReader.Get().(*gzip.Reader)\n\tdefer func() {\n\t\tspReader.Put(gr)\n\t}()\n\terr = gr.Reset(buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer gr.Close()\n\n\tdata, err = io.ReadAll(gr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn data, err\n}\n\nfunc BenchmarkUnzip_Old(b *testing.B) {\n\ts := \"%5B%7B%22service%22%3A%22AttrDict%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%2C%7B%22service%22%3A%22BrasInfo%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%5D\"\n\tdata, err := Zip([]byte(s))\n\tif err != nil {\n\t\tb.Fatalf(\"failed to zip: %v\", err)\n\t}\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\ts2, err := oldUnzip(data)\n\t\t\tif err != nil {\n\t\t\t\tb.Errorf(\"failed to zip: %v\", err)\n\t\t\t}\n\t\t\t_ = s2\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "util/converter.go",
    "content": "package util\n\nimport (\n\t\"unsafe\"\n)\n\nfunc SliceByteToString(b []byte) string {\n\treturn *(*string)(unsafe.Pointer(&b))\n}\n\nfunc StringToSliceByte(s string) []byte {\n\tx := (*[2]uintptr)(unsafe.Pointer(&s))\n\th := [3]uintptr{x[0], x[1], x[1]}\n\treturn *(*[]byte)(unsafe.Pointer(&h))\n}\n\nfunc CopyMeta(src, dst map[string]string) {\n\tif dst == nil {\n\t\treturn\n\t}\n\tfor k, v := range src {\n\t\tdst[k] = v\n\t}\n}\n"
  },
  {
    "path": "util/net.go",
    "content": "package util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// GetFreePort gets a free port.\nfunc GetFreePort() (port int, err error) {\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer listener.Close()\n\n\treturn listener.Addr().(*net.TCPAddr).Port, nil\n}\n\n// ParseRpcxAddress parses rpcx address such as tcp@127.0.0.1:8972  quic@192.168.1.1:9981\nfunc ParseRpcxAddress(addr string) (network string, ip string, port int, err error) {\n\tati := strings.Index(addr, \"@\")\n\tif ati <= 0 {\n\t\treturn \"\", \"\", 0, fmt.Errorf(\"invalid rpcx address: %s\", addr)\n\t}\n\n\tnetwork = addr[:ati]\n\taddr = addr[ati+1:]\n\n\tvar portstr string\n\tip, portstr, err = net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn \"\", \"\", 0, err\n\t}\n\n\tport, err = strconv.Atoi(portstr)\n\treturn network, ip, port, err\n}\n\nfunc ConvertMeta2Map(meta string) map[string]string {\n\tvar rt = make(map[string]string)\n\n\tif meta == \"\" {\n\t\treturn rt\n\t}\n\n\tv, err := url.ParseQuery(meta)\n\tif err != nil {\n\t\treturn rt\n\t}\n\n\tfor key := range v {\n\t\trt[key] = v.Get(key)\n\t}\n\treturn rt\n}\n\nfunc ConvertMap2String(meta map[string]string) string {\n\tvar buf strings.Builder\n\tkeys := make([]string, 0, len(meta))\n\tfor k := range meta {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tfor _, k := range keys {\n\t\tvs := meta[k]\n\t\tkeyEscaped := url.QueryEscape(k)\n\t\tif buf.Len() > 0 {\n\t\t\tbuf.WriteByte('&')\n\t\t}\n\t\tbuf.WriteString(keyEscaped)\n\t\tbuf.WriteByte('=')\n\t\tbuf.WriteString(url.QueryEscape(vs))\n\t}\n\treturn buf.String()\n}\n\n// ExternalIPV4 gets external IPv4 address of this server.\nfunc ExternalIPV4() (string, error) {\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfor _, iface := range ifaces {\n\t\tif iface.Flags&net.FlagUp == 0 {\n\t\t\tcontinue // interface down\n\t\t}\n\t\tif iface.Flags&net.FlagLoopback != 0 {\n\t\t\tcontinue // loopback interface\n\t\t}\n\t\taddrs, err := iface.Addrs()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tfor _, addr := range addrs {\n\t\t\tvar ip net.IP\n\t\t\tswitch v := addr.(type) {\n\t\t\tcase *net.IPNet:\n\t\t\t\tip = v.IP\n\t\t\tcase *net.IPAddr:\n\t\t\t\tip = v.IP\n\t\t\t}\n\t\t\tif ip == nil || ip.IsLoopback() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tip = ip.To4()\n\t\t\tif ip == nil {\n\t\t\t\tcontinue // not an ipv4 address\n\t\t\t}\n\t\t\treturn ip.String(), nil\n\t\t}\n\t}\n\treturn \"\", errors.New(\"are you connected to the network?\")\n}\n\n// ExternalIPV6 gets external IPv6 address of this server.\nfunc ExternalIPV6() (string, error) {\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfor _, iface := range ifaces {\n\t\tif iface.Flags&net.FlagUp == 0 {\n\t\t\tcontinue // interface down\n\t\t}\n\t\tif iface.Flags&net.FlagLoopback != 0 {\n\t\t\tcontinue // loopback interface\n\t\t}\n\t\taddrs, err := iface.Addrs()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tfor _, addr := range addrs {\n\t\t\tvar ip net.IP\n\t\t\tswitch v := addr.(type) {\n\t\t\tcase *net.IPNet:\n\t\t\t\tip = v.IP\n\t\t\tcase *net.IPAddr:\n\t\t\t\tip = v.IP\n\t\t\t}\n\t\t\tif ip == nil || ip.IsLoopback() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tip = ip.To16()\n\t\t\tif ip == nil {\n\t\t\t\tcontinue // not an ipv4 address\n\t\t\t}\n\t\t\treturn ip.String(), nil\n\t\t}\n\t}\n\treturn \"\", errors.New(\"are you connected to the network?\")\n}\n"
  },
  {
    "path": "util/net_test.go",
    "content": "package util\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestGetFreePort(t *testing.T) {\n\tfor i := 0; i < 1000; i++ {\n\t\tport, err := GetFreePort()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif port == 0 {\n\t\t\tt.Error(\"GetFreePort() return 0\")\n\t\t}\n\t}\n}\n\nvar oldGetFreePort = func() (port int, err error) {\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer listener.Close()\n\n\taddr := listener.Addr().String()\n\t_, portString, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn strconv.Atoi(portString)\n}\n\nfunc BenchmarkGetFreePort_Old(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\toldGetFreePort()\n\t}\n}\n\nfunc BenchmarkGetFreePort_New(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tGetFreePort()\n\t}\n}\n\nfunc TestExternalIPV4(t *testing.T) {\n\tip, err := ExternalIPV4()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif ip == \"\" {\n\t\tt.Fatal(\"expect an IP but got empty\")\n\t}\n\tt.Log(ip)\n}\n\nfunc TestExternalIPV6(t *testing.T) {\n\tip, err := ExternalIPV6()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif ip == \"\" {\n\t\tt.Fatal(\"expect an IP but got empty\")\n\t}\n\tt.Log(ip)\n}\n"
  }
]