├── .gitignore ├── LICENSE ├── README.md ├── conn.go ├── example ├── client │ └── main.go ├── pb │ ├── example.pb.go │ └── example.proto └── server │ └── main.go ├── go.mod ├── go.sum ├── options.go ├── pool.go └── pool_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pool 2 | [![GoDoc](https://godoc.org/github.com/shimingyah/pool?status.svg)](https://godoc.org/github.com/shimingyah/pool) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/shimingyah/pool?style=flat-square)](https://goreportcard.com/report/github.com/shimingyah/pool) 4 | [![LICENSE](https://img.shields.io/badge/licence-Apache%202.0-brightgreen.svg?style=flat-square)](https://github.com/shimingyah/pool/blob/master/LICENSE) 5 | 6 | Connection pool for Go's grpc client that supports connection reuse. 7 | 8 | Pool provides additional features: 9 | 10 | * `Connection reuse` supported by specific MaxConcurrentStreams param. 11 | * `Failure reconnection` supported by grpc's keepalive. 12 | 13 | # Getting started 14 | 15 | ## Install 16 | 17 | Import package: 18 | 19 | ``` 20 | import ( 21 | "github.com/shimingyah/pool" 22 | ) 23 | ``` 24 | 25 | ``` 26 | go get github.com/shimingyah/pool 27 | ``` 28 | 29 | # Usage 30 | 31 | ``` 32 | p, err := pool.New("127.0.0.1:8080", pool.DefaultOptions) 33 | if err != nil { 34 | log.Fatalf("failed to new pool: %v", err) 35 | } 36 | defer p.Close() 37 | 38 | conn, err := p.Get() 39 | if err != nil { 40 | log.Fatalf("failed to get conn: %v", err) 41 | } 42 | defer conn.Close() 43 | 44 | // cc := conn.Value() 45 | // client := pb.NewClient(conn.Value()) 46 | ``` 47 | See the complete example: [https://github.com/shimingyah/pool/tree/master/example](https://github.com/shimingyah/pool/tree/master/example) 48 | 49 | # Reference 50 | * [https://github.com/fatih/pool](https://github.com/fatih/pool) 51 | * [https://github.com/silenceper/pool](https://github.com/silenceper/pool) 52 | 53 | # License 54 | 55 | Pool is under the Apache 2.0 license. See the [LICENSE](https://github.com/shimingyah/pool/blob/master/LICENSE) file for details. -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 shimingyah. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // ee the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pool 16 | 17 | import ( 18 | "google.golang.org/grpc" 19 | ) 20 | 21 | // Conn single grpc connection inerface 22 | type Conn interface { 23 | // Value return the actual grpc connection type *grpc.ClientConn. 24 | Value() *grpc.ClientConn 25 | 26 | // Close decrease the reference of grpc connection, instead of close it. 27 | // if the pool is full, just close it. 28 | Close() error 29 | } 30 | 31 | // Conn is wrapped grpc.ClientConn. to provide close and value method. 32 | type conn struct { 33 | cc *grpc.ClientConn 34 | pool *pool 35 | once bool 36 | } 37 | 38 | // Value see Conn interface. 39 | func (c *conn) Value() *grpc.ClientConn { 40 | return c.cc 41 | } 42 | 43 | // Close see Conn interface. 44 | func (c *conn) Close() error { 45 | c.pool.decrRef() 46 | if c.once { 47 | return c.reset() 48 | } 49 | return nil 50 | } 51 | 52 | func (c *conn) reset() error { 53 | cc := c.cc 54 | c.cc = nil 55 | c.once = false 56 | if cc != nil { 57 | return cc.Close() 58 | } 59 | return nil 60 | } 61 | 62 | func (p *pool) wrapConn(cc *grpc.ClientConn, once bool) *conn { 63 | return &conn{ 64 | cc: cc, 65 | pool: p, 66 | once: once, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 shimingyah. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // ee the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "fmt" 21 | "log" 22 | "time" 23 | 24 | "github.com/shimingyah/pool" 25 | "github.com/shimingyah/pool/example/pb" 26 | ) 27 | 28 | var addr = flag.String("addr", "127.0.0.1:50000", "the address to connect to") 29 | 30 | func main() { 31 | flag.Parse() 32 | 33 | p, err := pool.New(*addr, pool.DefaultOptions) 34 | if err != nil { 35 | log.Fatalf("failed to new pool: %v", err) 36 | } 37 | defer p.Close() 38 | 39 | conn, err := p.Get() 40 | if err != nil { 41 | log.Fatalf("failed to get conn: %v", err) 42 | } 43 | defer conn.Close() 44 | 45 | client := pb.NewEchoClient(conn.Value()) 46 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 47 | defer cancel() 48 | 49 | res, err := client.Say(ctx, &pb.EchoRequest{Message: []byte("hi")}) 50 | if err != nil { 51 | log.Fatalf("unexpected error from Say: %v", err) 52 | } 53 | fmt.Println("rpc response:", res) 54 | } 55 | -------------------------------------------------------------------------------- /example/pb/example.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: example.proto 3 | 4 | package pb 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | import ( 11 | context "golang.org/x/net/context" 12 | grpc "google.golang.org/grpc" 13 | ) 14 | 15 | // Reference imports to suppress errors if they are not otherwise used. 16 | var _ = proto.Marshal 17 | var _ = fmt.Errorf 18 | var _ = math.Inf 19 | 20 | // This is a compile-time assertion to ensure that this generated file 21 | // is compatible with the proto package it is being compiled against. 22 | // A compilation error at this line likely means your copy of the 23 | // proto package needs to be updated. 24 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 25 | 26 | // EchoRequest is the request for echo. 27 | type EchoRequest struct { 28 | Message []byte `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 29 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 30 | XXX_unrecognized []byte `json:"-"` 31 | XXX_sizecache int32 `json:"-"` 32 | } 33 | 34 | func (m *EchoRequest) Reset() { *m = EchoRequest{} } 35 | func (m *EchoRequest) String() string { return proto.CompactTextString(m) } 36 | func (*EchoRequest) ProtoMessage() {} 37 | func (*EchoRequest) Descriptor() ([]byte, []int) { 38 | return fileDescriptor_example_d72c2831d52b7689, []int{0} 39 | } 40 | func (m *EchoRequest) XXX_Unmarshal(b []byte) error { 41 | return xxx_messageInfo_EchoRequest.Unmarshal(m, b) 42 | } 43 | func (m *EchoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 44 | return xxx_messageInfo_EchoRequest.Marshal(b, m, deterministic) 45 | } 46 | func (dst *EchoRequest) XXX_Merge(src proto.Message) { 47 | xxx_messageInfo_EchoRequest.Merge(dst, src) 48 | } 49 | func (m *EchoRequest) XXX_Size() int { 50 | return xxx_messageInfo_EchoRequest.Size(m) 51 | } 52 | func (m *EchoRequest) XXX_DiscardUnknown() { 53 | xxx_messageInfo_EchoRequest.DiscardUnknown(m) 54 | } 55 | 56 | var xxx_messageInfo_EchoRequest proto.InternalMessageInfo 57 | 58 | func (m *EchoRequest) GetMessage() []byte { 59 | if m != nil { 60 | return m.Message 61 | } 62 | return nil 63 | } 64 | 65 | // EchoResponse is the response for echo. 66 | type EchoResponse struct { 67 | Message []byte `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 68 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 69 | XXX_unrecognized []byte `json:"-"` 70 | XXX_sizecache int32 `json:"-"` 71 | } 72 | 73 | func (m *EchoResponse) Reset() { *m = EchoResponse{} } 74 | func (m *EchoResponse) String() string { return proto.CompactTextString(m) } 75 | func (*EchoResponse) ProtoMessage() {} 76 | func (*EchoResponse) Descriptor() ([]byte, []int) { 77 | return fileDescriptor_example_d72c2831d52b7689, []int{1} 78 | } 79 | func (m *EchoResponse) XXX_Unmarshal(b []byte) error { 80 | return xxx_messageInfo_EchoResponse.Unmarshal(m, b) 81 | } 82 | func (m *EchoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 83 | return xxx_messageInfo_EchoResponse.Marshal(b, m, deterministic) 84 | } 85 | func (dst *EchoResponse) XXX_Merge(src proto.Message) { 86 | xxx_messageInfo_EchoResponse.Merge(dst, src) 87 | } 88 | func (m *EchoResponse) XXX_Size() int { 89 | return xxx_messageInfo_EchoResponse.Size(m) 90 | } 91 | func (m *EchoResponse) XXX_DiscardUnknown() { 92 | xxx_messageInfo_EchoResponse.DiscardUnknown(m) 93 | } 94 | 95 | var xxx_messageInfo_EchoResponse proto.InternalMessageInfo 96 | 97 | func (m *EchoResponse) GetMessage() []byte { 98 | if m != nil { 99 | return m.Message 100 | } 101 | return nil 102 | } 103 | 104 | func init() { 105 | proto.RegisterType((*EchoRequest)(nil), "pb.EchoRequest") 106 | proto.RegisterType((*EchoResponse)(nil), "pb.EchoResponse") 107 | } 108 | 109 | // Reference imports to suppress errors if they are not otherwise used. 110 | var _ context.Context 111 | var _ grpc.ClientConn 112 | 113 | // This is a compile-time assertion to ensure that this generated file 114 | // is compatible with the grpc package it is being compiled against. 115 | const _ = grpc.SupportPackageIsVersion4 116 | 117 | // EchoClient is the client API for Echo service. 118 | // 119 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 120 | type EchoClient interface { 121 | // Say is simple request. 122 | Say(ctx context.Context, in *EchoRequest, opts ...grpc.CallOption) (*EchoResponse, error) 123 | } 124 | 125 | type echoClient struct { 126 | cc *grpc.ClientConn 127 | } 128 | 129 | func NewEchoClient(cc *grpc.ClientConn) EchoClient { 130 | return &echoClient{cc} 131 | } 132 | 133 | func (c *echoClient) Say(ctx context.Context, in *EchoRequest, opts ...grpc.CallOption) (*EchoResponse, error) { 134 | out := new(EchoResponse) 135 | err := c.cc.Invoke(ctx, "/pb.Echo/Say", in, out, opts...) 136 | if err != nil { 137 | return nil, err 138 | } 139 | return out, nil 140 | } 141 | 142 | // EchoServer is the server API for Echo service. 143 | type EchoServer interface { 144 | // Say is simple request. 145 | Say(context.Context, *EchoRequest) (*EchoResponse, error) 146 | } 147 | 148 | func RegisterEchoServer(s *grpc.Server, srv EchoServer) { 149 | s.RegisterService(&_Echo_serviceDesc, srv) 150 | } 151 | 152 | func _Echo_Say_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 153 | in := new(EchoRequest) 154 | if err := dec(in); err != nil { 155 | return nil, err 156 | } 157 | if interceptor == nil { 158 | return srv.(EchoServer).Say(ctx, in) 159 | } 160 | info := &grpc.UnaryServerInfo{ 161 | Server: srv, 162 | FullMethod: "/pb.Echo/Say", 163 | } 164 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 165 | return srv.(EchoServer).Say(ctx, req.(*EchoRequest)) 166 | } 167 | return interceptor(ctx, in, info, handler) 168 | } 169 | 170 | var _Echo_serviceDesc = grpc.ServiceDesc{ 171 | ServiceName: "pb.Echo", 172 | HandlerType: (*EchoServer)(nil), 173 | Methods: []grpc.MethodDesc{ 174 | { 175 | MethodName: "Say", 176 | Handler: _Echo_Say_Handler, 177 | }, 178 | }, 179 | Streams: []grpc.StreamDesc{}, 180 | Metadata: "example.proto", 181 | } 182 | 183 | func init() { proto.RegisterFile("example.proto", fileDescriptor_example_d72c2831d52b7689) } 184 | 185 | var fileDescriptor_example_d72c2831d52b7689 = []byte{ 186 | // 127 bytes of a gzipped FileDescriptorProto 187 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4d, 0xad, 0x48, 0xcc, 188 | 0x2d, 0xc8, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0x52, 0xe7, 189 | 0xe2, 0x76, 0x4d, 0xce, 0xc8, 0x0f, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x92, 0xe0, 0x62, 190 | 0xcf, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x82, 0x71, 191 | 0x95, 0x34, 0xb8, 0x78, 0x20, 0x0a, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x71, 0xab, 0x34, 0x32, 192 | 0xe2, 0x62, 0x01, 0xa9, 0x14, 0xd2, 0xe2, 0x62, 0x0e, 0x4e, 0xac, 0x14, 0xe2, 0xd7, 0x2b, 0x48, 193 | 0xd2, 0x43, 0xb2, 0x43, 0x4a, 0x00, 0x21, 0x00, 0x31, 0x4b, 0x89, 0x21, 0x89, 0x0d, 0xec, 0x22, 194 | 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xba, 0x45, 0x75, 0x9d, 0xa2, 0x00, 0x00, 0x00, 195 | } 196 | -------------------------------------------------------------------------------- /example/pb/example.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2019 shimingyah. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // ee the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package pb; 18 | 19 | // EchoRequest is the request for echo. 20 | message EchoRequest { 21 | bytes message = 1; 22 | } 23 | 24 | // EchoResponse is the response for echo. 25 | message EchoResponse { 26 | bytes message = 1; 27 | } 28 | 29 | // Echo is the echo service. 30 | service Echo { 31 | // Say is simple request. 32 | rpc Say(EchoRequest) returns (EchoResponse) {} 33 | } -------------------------------------------------------------------------------- /example/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 shimingyah. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // ee the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "fmt" 21 | "log" 22 | "net" 23 | 24 | "google.golang.org/grpc" 25 | "google.golang.org/grpc/keepalive" 26 | 27 | "github.com/shimingyah/pool" 28 | "github.com/shimingyah/pool/example/pb" 29 | ) 30 | 31 | var port = flag.Int("port", 50000, "port number") 32 | 33 | // server implements EchoServer. 34 | type server struct{} 35 | 36 | func (s *server) Say(context.Context, *pb.EchoRequest) (*pb.EchoResponse, error) { 37 | return &pb.EchoResponse{Message: []byte("hello world")}, nil 38 | } 39 | 40 | func main() { 41 | flag.Parse() 42 | 43 | listen, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%v", *port)) 44 | if err != nil { 45 | log.Fatalf("failed to listen: %v", err) 46 | } 47 | 48 | s := grpc.NewServer( 49 | grpc.InitialWindowSize(pool.InitialWindowSize), 50 | grpc.InitialConnWindowSize(pool.InitialConnWindowSize), 51 | grpc.MaxSendMsgSize(pool.MaxSendMsgSize), 52 | grpc.MaxRecvMsgSize(pool.MaxRecvMsgSize), 53 | grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ 54 | PermitWithoutStream: true, 55 | }), 56 | grpc.KeepaliveParams(keepalive.ServerParameters{ 57 | Time: pool.KeepAliveTime, 58 | Timeout: pool.KeepAliveTimeout, 59 | }), 60 | ) 61 | pb.RegisterEchoServer(s, &server{}) 62 | 63 | if err := s.Serve(listen); err != nil { 64 | log.Fatalf("failed to serve: %v", err) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shimingyah/pool 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/golang/protobuf v1.2.0 7 | github.com/stretchr/testify v1.3.0 8 | golang.org/x/net v0.0.0-20190311183353-d8887717615a 9 | google.golang.org/grpc v1.22.0 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 4 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 8 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 9 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 10 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 16 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 17 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 18 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 19 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 20 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 21 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 22 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 23 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 25 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 26 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 27 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 28 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 29 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 30 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 31 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= 32 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 33 | google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= 34 | google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 35 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 36 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 shimingyah. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // ee the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pool 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "google.golang.org/grpc" 22 | "google.golang.org/grpc/keepalive" 23 | ) 24 | 25 | const ( 26 | // DialTimeout the timeout of create connection 27 | DialTimeout = 5 * time.Second 28 | 29 | // BackoffMaxDelay provided maximum delay when backing off after failed connection attempts. 30 | BackoffMaxDelay = 3 * time.Second 31 | 32 | // KeepAliveTime is the duration of time after which if the client doesn't see 33 | // any activity it pings the server to see if the transport is still alive. 34 | KeepAliveTime = time.Duration(10) * time.Second 35 | 36 | // KeepAliveTimeout is the duration of time for which the client waits after having 37 | // pinged for keepalive check and if no activity is seen even after that the connection 38 | // is closed. 39 | KeepAliveTimeout = time.Duration(3) * time.Second 40 | 41 | // InitialWindowSize we set it 1GB is to provide system's throughput. 42 | InitialWindowSize = 1 << 30 43 | 44 | // InitialConnWindowSize we set it 1GB is to provide system's throughput. 45 | InitialConnWindowSize = 1 << 30 46 | 47 | // MaxSendMsgSize set max gRPC request message size sent to server. 48 | // If any request message size is larger than current value, an error will be reported from gRPC. 49 | MaxSendMsgSize = 4 << 30 50 | 51 | // MaxRecvMsgSize set max gRPC receive message size received from server. 52 | // If any message size is larger than current value, an error will be reported from gRPC. 53 | MaxRecvMsgSize = 4 << 30 54 | ) 55 | 56 | // Options are params for creating grpc connect pool. 57 | type Options struct { 58 | // Dial is an application supplied function for creating and configuring a connection. 59 | Dial func(address string) (*grpc.ClientConn, error) 60 | 61 | // Maximum number of idle connections in the pool. 62 | MaxIdle int 63 | 64 | // Maximum number of connections allocated by the pool at a given time. 65 | // When zero, there is no limit on the number of connections in the pool. 66 | MaxActive int 67 | 68 | // MaxConcurrentStreams limit on the number of concurrent streams to each single connection 69 | MaxConcurrentStreams int 70 | 71 | // If Reuse is true and the pool is at the MaxActive limit, then Get() reuse 72 | // the connection to return, If Reuse is false and the pool is at the MaxActive limit, 73 | // create a one-time connection to return. 74 | Reuse bool 75 | } 76 | 77 | // DefaultOptions sets a list of recommended options for good performance. 78 | // Feel free to modify these to suit your needs. 79 | var DefaultOptions = Options{ 80 | Dial: Dial, 81 | MaxIdle: 8, 82 | MaxActive: 64, 83 | MaxConcurrentStreams: 64, 84 | Reuse: true, 85 | } 86 | 87 | // Dial return a grpc connection with defined configurations. 88 | func Dial(address string) (*grpc.ClientConn, error) { 89 | ctx, cancel := context.WithTimeout(context.Background(), DialTimeout) 90 | defer cancel() 91 | return grpc.DialContext(ctx, address, grpc.WithInsecure(), 92 | grpc.WithBackoffMaxDelay(BackoffMaxDelay), 93 | grpc.WithInitialWindowSize(InitialWindowSize), 94 | grpc.WithInitialConnWindowSize(InitialConnWindowSize), 95 | grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(MaxSendMsgSize)), 96 | grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxRecvMsgSize)), 97 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 98 | Time: KeepAliveTime, 99 | Timeout: KeepAliveTimeout, 100 | PermitWithoutStream: true, 101 | })) 102 | } 103 | 104 | // DialTest return a simple grpc connection with defined configurations. 105 | func DialTest(address string) (*grpc.ClientConn, error) { 106 | ctx, cancel := context.WithTimeout(context.Background(), DialTimeout) 107 | defer cancel() 108 | return grpc.DialContext(ctx, address, grpc.WithInsecure()) 109 | } 110 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 shimingyah. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // ee the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pool 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "log" 21 | "math" 22 | "sync" 23 | "sync/atomic" 24 | ) 25 | 26 | // ErrClosed is the error resulting if the pool is closed via pool.Close(). 27 | var ErrClosed = errors.New("pool is closed") 28 | 29 | // Pool interface describes a pool implementation. 30 | // An ideal pool is threadsafe and easy to use. 31 | type Pool interface { 32 | // Get returns a new connection from the pool. Closing the connections puts 33 | // it back to the Pool. Closing it when the pool is destroyed or full will 34 | // be counted as an error. we guarantee the conn.Value() isn't nil when conn isn't nil. 35 | Get() (Conn, error) 36 | 37 | // Close closes the pool and all its connections. After Close() the pool is 38 | // no longer usable. You can't make concurrent calls Close and Get method. 39 | // It will be cause panic. 40 | Close() error 41 | 42 | // Status returns the current status of the pool. 43 | Status() string 44 | } 45 | 46 | type pool struct { 47 | // atomic, used to get connection random 48 | index uint32 49 | 50 | // atomic, the current physical connection of pool 51 | current int32 52 | 53 | // atomic, the using logic connection of pool 54 | // logic connection = physical connection * MaxConcurrentStreams 55 | ref int32 56 | 57 | // pool options 58 | opt Options 59 | 60 | // all of created physical connections 61 | conns []*conn 62 | 63 | // the server address is to create connection. 64 | address string 65 | 66 | // closed set true when Close is called. 67 | closed int32 68 | 69 | // control the atomic var current's concurrent read write. 70 | sync.RWMutex 71 | } 72 | 73 | // New return a connection pool. 74 | func New(address string, option Options) (Pool, error) { 75 | if address == "" { 76 | return nil, errors.New("invalid address settings") 77 | } 78 | if option.Dial == nil { 79 | return nil, errors.New("invalid dial settings") 80 | } 81 | if option.MaxIdle <= 0 || option.MaxActive <= 0 || option.MaxIdle > option.MaxActive { 82 | return nil, errors.New("invalid maximum settings") 83 | } 84 | if option.MaxConcurrentStreams <= 0 { 85 | return nil, errors.New("invalid maximun settings") 86 | } 87 | 88 | p := &pool{ 89 | index: 0, 90 | current: int32(option.MaxIdle), 91 | ref: 0, 92 | opt: option, 93 | conns: make([]*conn, option.MaxActive), 94 | address: address, 95 | closed: 0, 96 | } 97 | 98 | for i := 0; i < p.opt.MaxIdle; i++ { 99 | c, err := p.opt.Dial(address) 100 | if err != nil { 101 | p.Close() 102 | return nil, fmt.Errorf("dial is not able to fill the pool: %s", err) 103 | } 104 | p.conns[i] = p.wrapConn(c, false) 105 | } 106 | log.Printf("new pool success: %v\n", p.Status()) 107 | 108 | return p, nil 109 | } 110 | 111 | func (p *pool) incrRef() int32 { 112 | newRef := atomic.AddInt32(&p.ref, 1) 113 | if newRef == math.MaxInt32 { 114 | panic(fmt.Sprintf("overflow ref: %d", newRef)) 115 | } 116 | return newRef 117 | } 118 | 119 | func (p *pool) decrRef() { 120 | newRef := atomic.AddInt32(&p.ref, -1) 121 | if newRef < 0 && atomic.LoadInt32(&p.closed) == 0 { 122 | panic(fmt.Sprintf("negative ref: %d", newRef)) 123 | } 124 | if newRef == 0 && atomic.LoadInt32(&p.current) > int32(p.opt.MaxIdle) { 125 | p.Lock() 126 | if atomic.LoadInt32(&p.ref) == 0 { 127 | log.Printf("shrink pool: %d ---> %d, decrement: %d, maxActive: %d\n", 128 | p.current, p.opt.MaxIdle, p.current-int32(p.opt.MaxIdle), p.opt.MaxActive) 129 | atomic.StoreInt32(&p.current, int32(p.opt.MaxIdle)) 130 | p.deleteFrom(p.opt.MaxIdle) 131 | } 132 | p.Unlock() 133 | } 134 | } 135 | 136 | func (p *pool) reset(index int) { 137 | conn := p.conns[index] 138 | if conn == nil { 139 | return 140 | } 141 | conn.reset() 142 | p.conns[index] = nil 143 | } 144 | 145 | func (p *pool) deleteFrom(begin int) { 146 | for i := begin; i < p.opt.MaxActive; i++ { 147 | p.reset(i) 148 | } 149 | } 150 | 151 | // Get see Pool interface. 152 | func (p *pool) Get() (Conn, error) { 153 | // the first selected from the created connections 154 | nextRef := p.incrRef() 155 | p.RLock() 156 | current := atomic.LoadInt32(&p.current) 157 | p.RUnlock() 158 | if current == 0 { 159 | return nil, ErrClosed 160 | } 161 | if nextRef <= current*int32(p.opt.MaxConcurrentStreams) { 162 | next := atomic.AddUint32(&p.index, 1) % uint32(current) 163 | return p.conns[next], nil 164 | } 165 | 166 | // the number connection of pool is reach to max active 167 | if current == int32(p.opt.MaxActive) { 168 | // the second if reuse is true, select from pool's connections 169 | if p.opt.Reuse { 170 | next := atomic.AddUint32(&p.index, 1) % uint32(current) 171 | return p.conns[next], nil 172 | } 173 | // the third create one-time connection 174 | c, err := p.opt.Dial(p.address) 175 | return p.wrapConn(c, true), err 176 | } 177 | 178 | // the fourth create new connections given back to pool 179 | p.Lock() 180 | current = atomic.LoadInt32(&p.current) 181 | if current < int32(p.opt.MaxActive) && nextRef > current*int32(p.opt.MaxConcurrentStreams) { 182 | // 2 times the incremental or the remain incremental 183 | increment := current 184 | if current+increment > int32(p.opt.MaxActive) { 185 | increment = int32(p.opt.MaxActive) - current 186 | } 187 | var i int32 188 | var err error 189 | for i = 0; i < increment; i++ { 190 | c, er := p.opt.Dial(p.address) 191 | if er != nil { 192 | err = er 193 | break 194 | } 195 | p.reset(int(current + i)) 196 | p.conns[current+i] = p.wrapConn(c, false) 197 | } 198 | current += i 199 | log.Printf("grow pool: %d ---> %d, increment: %d, maxActive: %d\n", 200 | p.current, current, increment, p.opt.MaxActive) 201 | atomic.StoreInt32(&p.current, current) 202 | if err != nil { 203 | p.Unlock() 204 | return nil, err 205 | } 206 | } 207 | p.Unlock() 208 | next := atomic.AddUint32(&p.index, 1) % uint32(current) 209 | return p.conns[next], nil 210 | } 211 | 212 | // Close see Pool interface. 213 | func (p *pool) Close() error { 214 | atomic.StoreInt32(&p.closed, 1) 215 | atomic.StoreUint32(&p.index, 0) 216 | atomic.StoreInt32(&p.current, 0) 217 | atomic.StoreInt32(&p.ref, 0) 218 | p.deleteFrom(0) 219 | log.Printf("close pool success: %v\n", p.Status()) 220 | return nil 221 | } 222 | 223 | // Status see Pool interface. 224 | func (p *pool) Status() string { 225 | return fmt.Sprintf("address:%s, index:%d, current:%d, ref:%d. option:%v", 226 | p.address, p.index, p.current, p.ref, p.opt) 227 | } 228 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 shimingyah. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // ee the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pool 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "sync" 21 | "sync/atomic" 22 | "testing" 23 | "time" 24 | 25 | "github.com/shimingyah/pool/example/pb" 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | var endpoint = flag.String("endpoint", "127.0.0.1:50000", "grpc server endpoint") 30 | 31 | func newPool(op *Options) (Pool, *pool, Options, error) { 32 | opt := DefaultOptions 33 | opt.Dial = DialTest 34 | if op != nil { 35 | opt = *op 36 | } 37 | p, err := New(*endpoint, opt) 38 | return p, p.(*pool), opt, err 39 | } 40 | 41 | func TestNew(t *testing.T) { 42 | p, nativePool, opt, err := newPool(nil) 43 | require.NoError(t, err) 44 | defer p.Close() 45 | 46 | require.EqualValues(t, 0, nativePool.index) 47 | require.EqualValues(t, 0, nativePool.ref) 48 | require.EqualValues(t, opt.MaxIdle, nativePool.current) 49 | require.EqualValues(t, opt.MaxActive, len(nativePool.conns)) 50 | } 51 | 52 | func TestNew2(t *testing.T) { 53 | opt := DefaultOptions 54 | 55 | _, err := New("", opt) 56 | require.Error(t, err) 57 | 58 | opt.Dial = nil 59 | _, err = New("127.0.0.1:8080", opt) 60 | require.Error(t, err) 61 | 62 | opt = DefaultOptions 63 | opt.MaxConcurrentStreams = 0 64 | _, err = New("127.0.0.1:8080", opt) 65 | require.Error(t, err) 66 | 67 | opt = DefaultOptions 68 | opt.MaxIdle = 0 69 | _, err = New("127.0.0.1:8080", opt) 70 | require.Error(t, err) 71 | 72 | opt = DefaultOptions 73 | opt.MaxActive = 0 74 | _, err = New("127.0.0.1:8080", opt) 75 | require.Error(t, err) 76 | 77 | opt = DefaultOptions 78 | opt.MaxIdle = 2 79 | opt.MaxActive = 1 80 | _, err = New("127.0.0.1:8080", opt) 81 | require.Error(t, err) 82 | } 83 | 84 | func TestClose(t *testing.T) { 85 | p, nativePool, opt, err := newPool(nil) 86 | require.NoError(t, err) 87 | p.Close() 88 | 89 | require.EqualValues(t, 0, nativePool.index) 90 | require.EqualValues(t, 0, nativePool.ref) 91 | require.EqualValues(t, 0, nativePool.current) 92 | require.EqualValues(t, true, nativePool.conns[0] == nil) 93 | require.EqualValues(t, true, nativePool.conns[opt.MaxIdle-1] == nil) 94 | } 95 | 96 | func TestReset(t *testing.T) { 97 | p, nativePool, opt, err := newPool(nil) 98 | require.NoError(t, err) 99 | defer p.Close() 100 | 101 | nativePool.reset(0) 102 | require.EqualValues(t, true, nativePool.conns[0] == nil) 103 | nativePool.reset(opt.MaxIdle + 1) 104 | require.EqualValues(t, true, nativePool.conns[opt.MaxIdle+1] == nil) 105 | } 106 | 107 | func TestBasicGet(t *testing.T) { 108 | p, nativePool, _, err := newPool(nil) 109 | require.NoError(t, err) 110 | defer p.Close() 111 | 112 | conn, err := p.Get() 113 | require.NoError(t, err) 114 | require.EqualValues(t, true, conn.Value() != nil) 115 | 116 | require.EqualValues(t, 1, nativePool.index) 117 | require.EqualValues(t, 1, nativePool.ref) 118 | 119 | conn.Close() 120 | 121 | require.EqualValues(t, 1, nativePool.index) 122 | require.EqualValues(t, 0, nativePool.ref) 123 | } 124 | 125 | func TestGetAfterClose(t *testing.T) { 126 | p, _, _, err := newPool(nil) 127 | require.NoError(t, err) 128 | p.Close() 129 | 130 | _, err = p.Get() 131 | require.EqualError(t, err, "pool is closed") 132 | } 133 | 134 | func TestBasicGet2(t *testing.T) { 135 | opt := DefaultOptions 136 | opt.Dial = DialTest 137 | opt.MaxIdle = 1 138 | opt.MaxActive = 2 139 | opt.MaxConcurrentStreams = 2 140 | opt.Reuse = true 141 | 142 | p, nativePool, _, err := newPool(&opt) 143 | require.NoError(t, err) 144 | defer p.Close() 145 | 146 | conn1, err := p.Get() 147 | require.NoError(t, err) 148 | defer conn1.Close() 149 | 150 | conn2, err := p.Get() 151 | require.NoError(t, err) 152 | defer conn2.Close() 153 | 154 | require.EqualValues(t, 2, nativePool.index) 155 | require.EqualValues(t, 2, nativePool.ref) 156 | require.EqualValues(t, 1, nativePool.current) 157 | 158 | // create new connections push back to pool 159 | conn3, err := p.Get() 160 | require.NoError(t, err) 161 | defer conn3.Close() 162 | 163 | require.EqualValues(t, 3, nativePool.index) 164 | require.EqualValues(t, 3, nativePool.ref) 165 | require.EqualValues(t, 2, nativePool.current) 166 | 167 | conn4, err := p.Get() 168 | require.NoError(t, err) 169 | defer conn4.Close() 170 | 171 | // reuse exists connections 172 | conn5, err := p.Get() 173 | require.NoError(t, err) 174 | defer conn5.Close() 175 | 176 | nativeConn := conn5.(*conn) 177 | require.EqualValues(t, false, nativeConn.once) 178 | } 179 | 180 | func TestBasicGet3(t *testing.T) { 181 | opt := DefaultOptions 182 | opt.Dial = DialTest 183 | opt.MaxIdle = 1 184 | opt.MaxActive = 1 185 | opt.MaxConcurrentStreams = 1 186 | opt.Reuse = false 187 | 188 | p, _, _, err := newPool(&opt) 189 | require.NoError(t, err) 190 | defer p.Close() 191 | 192 | conn1, err := p.Get() 193 | require.NoError(t, err) 194 | defer conn1.Close() 195 | 196 | // create new connections doesn't push back to pool 197 | conn2, err := p.Get() 198 | require.NoError(t, err) 199 | defer conn2.Close() 200 | 201 | nativeConn := conn2.(*conn) 202 | require.EqualValues(t, true, nativeConn.once) 203 | } 204 | 205 | func TestConcurrentGet(t *testing.T) { 206 | opt := DefaultOptions 207 | opt.Dial = DialTest 208 | opt.MaxIdle = 8 209 | opt.MaxActive = 64 210 | opt.MaxConcurrentStreams = 2 211 | opt.Reuse = false 212 | 213 | p, nativePool, _, err := newPool(&opt) 214 | require.NoError(t, err) 215 | defer p.Close() 216 | 217 | var wg sync.WaitGroup 218 | wg.Add(500) 219 | 220 | for i := 0; i < 500; i++ { 221 | go func(i int) { 222 | conn, err := p.Get() 223 | require.NoError(t, err) 224 | require.EqualValues(t, true, conn != nil) 225 | conn.Close() 226 | wg.Done() 227 | t.Logf("goroutine: %v, index: %v, ref: %v, current: %v", i, 228 | atomic.LoadUint32(&nativePool.index), 229 | atomic.LoadInt32(&nativePool.ref), 230 | atomic.LoadInt32(&nativePool.current)) 231 | }(i) 232 | } 233 | wg.Wait() 234 | 235 | require.EqualValues(t, 0, nativePool.ref) 236 | require.EqualValues(t, opt.MaxIdle, nativePool.current) 237 | require.EqualValues(t, true, nativePool.conns[0] != nil) 238 | require.EqualValues(t, true, nativePool.conns[opt.MaxIdle] == nil) 239 | } 240 | 241 | var size = 4 * 1024 * 1024 242 | 243 | func BenchmarkPoolRPC(b *testing.B) { 244 | opt := DefaultOptions 245 | p, err := New(*endpoint, opt) 246 | if err != nil { 247 | b.Fatalf("failed to new pool: %v", err) 248 | } 249 | defer p.Close() 250 | 251 | testFunc := func() { 252 | conn, err := p.Get() 253 | if err != nil { 254 | b.Fatalf("failed to get conn: %v", err) 255 | } 256 | defer conn.Close() 257 | 258 | client := pb.NewEchoClient(conn.Value()) 259 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 260 | defer cancel() 261 | 262 | data := make([]byte, size) 263 | _, err = client.Say(ctx, &pb.EchoRequest{Message: data}) 264 | if err != nil { 265 | b.Fatalf("unexpected error from Say: %v", err) 266 | } 267 | } 268 | 269 | b.ResetTimer() 270 | b.RunParallel(func(tpb *testing.PB) { 271 | for tpb.Next() { 272 | testFunc() 273 | } 274 | }) 275 | } 276 | 277 | func BenchmarkSingleRPC(b *testing.B) { 278 | testFunc := func() { 279 | cc, err := Dial(*endpoint) 280 | if err != nil { 281 | b.Fatalf("failed to create grpc conn: %v", err) 282 | } 283 | 284 | client := pb.NewEchoClient(cc) 285 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 286 | defer cancel() 287 | 288 | data := make([]byte, size) 289 | _, err = client.Say(ctx, &pb.EchoRequest{Message: data}) 290 | if err != nil { 291 | b.Fatalf("unexpected error from Say: %v", err) 292 | } 293 | } 294 | 295 | b.RunParallel(func(tpb *testing.PB) { 296 | for tpb.Next() { 297 | testFunc() 298 | } 299 | }) 300 | } 301 | --------------------------------------------------------------------------------