├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── collections ├── collections.go ├── collections_test.go ├── queue.go └── queue_test.go ├── concurrent ├── atomic.go ├── atomic_test.go ├── cond.go └── cond_test.go ├── config.go ├── config_test.go ├── example__simple_test.go ├── example_customFactory_test.go ├── example_multipleBorrowers_test.go ├── factory.go ├── factory_test.go ├── go.mod ├── go.sum ├── object.go ├── object_test.go ├── pool.go ├── pool_abandoned_test.go ├── pool_perf_test.go └── pool_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "21:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | # only build master 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**.md' 9 | pull_request: 10 | paths-ignore: 11 | - '**.md' 12 | 13 | jobs: 14 | 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | steps: 19 | 20 | - name: Set up Go 1.13 21 | uses: actions/setup-go@v1 22 | with: 23 | go-version: 1.13 24 | id: go 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v1 28 | 29 | - name: Test 30 | run: go test ./... -timeout 5m -race -coverprofile=coverage.txt -covermode=atomic 31 | 32 | - name: Sumbit code cov 33 | if: success() 34 | run: bash <(curl -s https://codecov.io/bash) 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .idea 26 | *.coverprofile 27 | coverage.txt 28 | profile.out -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go Commons Pool 2 | ===== 3 | 4 | [![Build Status](https://github.com/jolestar/go-commons-pool/workflows/Build/badge.svg)](https://github.com/jolestar/go-commons-pool/actions) 5 | [![CodeCov](https://codecov.io/gh/jolestar/go-commons-pool/branch/master/graph/badge.svg)](https://codecov.io/gh/jolestar/go-commons-pool) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/jolestar/go-commons-pool)](https://goreportcard.com/report/github.com/jolestar/go-commons-pool) 7 | [![GoDoc](https://godoc.org/github.com/jolestar/go-commons-pool?status.svg)](https://godoc.org/github.com/jolestar/go-commons-pool) 8 | 9 | The Go Commons Pool is a generic object pool for [Golang](https://golang.org/), direct rewrite from [Apache Commons Pool](https://commons.apache.org/proper/commons-pool/). 10 | 11 | Features 12 | ------- 13 | 14 | 1. Support custom [PooledObjectFactory](https://godoc.org/github.com/jolestar/go-commons-pool#PooledObjectFactory). 15 | 1. Rich pool configuration option, can precise control pooled object lifecycle. See [ObjectPoolConfig](https://godoc.org/github.com/jolestar/go-commons-pool#ObjectPoolConfig). 16 | * Pool LIFO (last in, first out) or FIFO (first in, first out) 17 | * Pool cap config 18 | * Pool object validate config 19 | * Pool object borrow block and max waiting time config 20 | * Pool object eviction config 21 | * Pool object abandon config 22 | 23 | Pool Configuration Option 24 | ------- 25 | 26 | Configuration option table, more detail description see [ObjectPoolConfig](https://godoc.org/github.com/jolestar/go-commons-pool#ObjectPoolConfig) 27 | 28 | | Option | Default | Description | 29 | | ------------------------------|:--------------:| :------------| 30 | | LIFO | true |If pool is LIFO (last in, first out)| 31 | | MaxTotal | 8 |The cap of pool| 32 | | MaxIdle | 8 |Max "idle" instances in the pool | 33 | | MinIdle | 0 |Min "idle" instances in the pool | 34 | | TestOnCreate | false |Validate when object is created| 35 | | TestOnBorrow | false |Validate when object is borrowed| 36 | | TestOnReturn | false |Validate when object is returned| 37 | | TestWhileIdle | false |Validate when object is idle, see TimeBetweenEvictionRuns | 38 | | BlockWhenExhausted | true |Whether to block when the pool is exhausted | 39 | | MinEvictableIdleTime | 30m |Eviction configuration,see DefaultEvictionPolicy | 40 | | SoftMinEvictableIdleTime | math.MaxInt64 |Eviction configuration,see DefaultEvictionPolicy | 41 | | NumTestsPerEvictionRun | 3 |The maximum number of objects to examine during each run evictor goroutine | 42 | | TimeBetweenEvictionRuns | 0 |The number of milliseconds to sleep between runs of the evictor goroutine, less than 1 mean not run | 43 | 44 | Usage 45 | ------- 46 | 47 | ### Use Simple Factory 48 | 49 | ```go 50 | import ( 51 | "context" 52 | "fmt" 53 | "strconv" 54 | "sync/atomic" 55 | 56 | "github.com/jolestar/go-commons-pool/v2" 57 | ) 58 | 59 | func Example_simple() { 60 | type myPoolObject struct { 61 | s string 62 | } 63 | 64 | v := uint64(0) 65 | factory := pool.NewPooledObjectFactorySimple( 66 | func(context.Context) (interface{}, error) { 67 | return &myPoolObject{ 68 | s: strconv.FormatUint(atomic.AddUint64(&v, 1), 10), 69 | }, 70 | nil 71 | }) 72 | 73 | ctx := context.Background() 74 | p := pool.NewObjectPoolWithDefaultConfig(ctx, factory) 75 | 76 | obj, err := p.BorrowObject(ctx) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | o := obj.(*myPoolObject) 82 | fmt.Println(o.s) 83 | 84 | err = p.ReturnObject(ctx, obj) 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | // Output: 1 90 | } 91 | ``` 92 | 93 | ### Use Custom Factory 94 | 95 | ```go 96 | import ( 97 | "context" 98 | "fmt" 99 | "strconv" 100 | "sync/atomic" 101 | 102 | "github.com/jolestar/go-commons-pool/v2" 103 | ) 104 | 105 | type MyPoolObject struct { 106 | s string 107 | } 108 | 109 | type MyCustomFactory struct { 110 | v uint64 111 | } 112 | 113 | func (f *MyCustomFactory) MakeObject(ctx context.Context) (*pool.PooledObject, error) { 114 | return pool.NewPooledObject( 115 | &MyPoolObject{ 116 | s: strconv.FormatUint(atomic.AddUint64(&f.v, 1), 10), 117 | }), 118 | nil 119 | } 120 | 121 | func (f *MyCustomFactory) DestroyObject(ctx context.Context, object *pool.PooledObject) error { 122 | // do destroy 123 | return nil 124 | } 125 | 126 | func (f *MyCustomFactory) ValidateObject(ctx context.Context, object *pool.PooledObject) bool { 127 | // do validate 128 | return true 129 | } 130 | 131 | func (f *MyCustomFactory) ActivateObject(ctx context.Context, object *pool.PooledObject) error { 132 | // do activate 133 | return nil 134 | } 135 | 136 | func (f *MyCustomFactory) PassivateObject(ctx context.Context, object *pool.PooledObject) error { 137 | // do passivate 138 | return nil 139 | } 140 | 141 | func Example_customFactory() { 142 | ctx := context.Background() 143 | p := pool.NewObjectPoolWithDefaultConfig(ctx, &MyCustomFactory{}) 144 | p.Config.MaxTotal = 100 145 | 146 | obj1, err := p.BorrowObject(ctx) 147 | if err != nil { 148 | panic(err) 149 | } 150 | 151 | o := obj1.(*MyPoolObject) 152 | fmt.Println(o.s) 153 | 154 | err = p.ReturnObject(ctx, obj1) 155 | if err != nil { 156 | panic(err) 157 | } 158 | 159 | // Output: 1 160 | } 161 | ``` 162 | 163 | For more examples please see `pool_test.go` and `example_simple_test.go`, `example_customFactory_test.go`. 164 | 165 | Note 166 | ------- 167 | 168 | PooledObjectFactory.MakeObject must return a pointer, not value. 169 | The following code will complain error. 170 | 171 | ```golang 172 | p := pool.NewObjectPoolWithDefaultConfig(ctx, pool.NewPooledObjectFactorySimple( 173 | func(context.Context) (interface{}, error) { 174 | return "hello", nil 175 | })) 176 | obj, _ := p.BorrowObject() 177 | p.ReturnObject(obj) 178 | ``` 179 | 180 | The right way is: 181 | 182 | ```golang 183 | p := pool.NewObjectPoolWithDefaultConfig(ctx, pool.NewPooledObjectFactorySimple( 184 | func(context.Context) (interface{}, error) { 185 | s := "hello" 186 | return &s, nil 187 | })) 188 | ``` 189 | 190 | For more examples please see `example_simple_test.go`. 191 | 192 | Dependency 193 | ------- 194 | 195 | * [testify](https://github.com/stretchr/testify) for test 196 | 197 | PerformanceTest 198 | ------- 199 | 200 | The results of running the pool_perf_test is almost equal to the java version [PerformanceTest](https://github.com/apache/commons-pool/blob/trunk/src/test/java/org/apache/commons/pool2/performance/PerformanceTest.java) 201 | 202 | go test --perf=true 203 | 204 | For Apache commons pool user 205 | ------- 206 | 207 | * Direct use pool.Config.xxx to change pool config 208 | * Default config value is same as java version 209 | * If TimeBetweenEvictionRuns changed after ObjectPool created, should call **ObjectPool.StartEvictor** to take effect. Java version do this on set method. 210 | * No KeyedObjectPool (TODO) 211 | * No ProxiedObjectPool 212 | * No pool stats (TODO) 213 | 214 | FAQ 215 | ------- 216 | 217 | [FAQ](https://github.com/jolestar/go-commons-pool/wiki/FAQ) 218 | 219 | How to contribute 220 | ------- 221 | 222 | * Choose one open issue you want to solve, if not create one and describe what you want to change. 223 | * Fork the repository on GitHub. 224 | * Write code to solve the issue. 225 | * Create PR and link to the issue. 226 | * Make sure test and coverage pass. 227 | * Wait maintainers to merge. 228 | 229 | 中文文档 230 | ------- 231 | 232 | * [Go Commons Pool发布以及Golang多线程编程问题总结](http://jolestar.com/go-commons-pool-and-go-concurrent/) 233 | * [Go Commons Pool 1.0 发布](http://jolestar.com/go-commons-pool-v1-release/) 234 | 235 | License 236 | ------- 237 | 238 | Go Commons Pool is available under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). 239 | -------------------------------------------------------------------------------- /collections/collections.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | // Iterator interface for collection 9 | // see LinkedBlockDequeIterator 10 | type Iterator interface { 11 | HasNext() bool 12 | Next() interface{} 13 | Remove() 14 | } 15 | 16 | // SyncIdentityMap is a concurrent safe map 17 | // use key's pointer as map key 18 | type SyncIdentityMap struct { 19 | sync.RWMutex 20 | m map[uintptr]interface{} 21 | } 22 | 23 | // NewSyncMap return a new SyncIdentityMap 24 | func NewSyncMap() *SyncIdentityMap { 25 | return &SyncIdentityMap{m: make(map[uintptr]interface{})} 26 | } 27 | 28 | // Get by key 29 | func (m *SyncIdentityMap) Get(key interface{}) interface{} { 30 | m.RLock() 31 | keyPtr := genKey(key) 32 | value := m.m[keyPtr] 33 | m.RUnlock() 34 | return value 35 | } 36 | 37 | func genKey(key interface{}) uintptr { 38 | keyValue := reflect.ValueOf(key) 39 | return keyValue.Pointer() 40 | } 41 | 42 | // Put key and value to map 43 | func (m *SyncIdentityMap) Put(key interface{}, value interface{}) { 44 | m.Lock() 45 | keyPtr := genKey(key) 46 | m.m[keyPtr] = value 47 | m.Unlock() 48 | } 49 | 50 | // Remove value by key 51 | func (m *SyncIdentityMap) Remove(key interface{}) { 52 | m.Lock() 53 | keyPtr := genKey(key) 54 | delete(m.m, keyPtr) 55 | m.Unlock() 56 | } 57 | 58 | // Size return map len, and is concurrent safe 59 | func (m *SyncIdentityMap) Size() int { 60 | m.RLock() 61 | defer m.RUnlock() 62 | return len(m.m) 63 | } 64 | 65 | // Values copy all map's value to slice 66 | func (m *SyncIdentityMap) Values() []interface{} { 67 | m.RLock() 68 | defer m.RUnlock() 69 | list := make([]interface{}, len(m.m)) 70 | i := 0 71 | for _, v := range m.m { 72 | list[i] = v 73 | i++ 74 | } 75 | return list 76 | } 77 | -------------------------------------------------------------------------------- /collections/collections_test.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type HashableObject struct { 11 | str string 12 | i int 13 | b bool 14 | } 15 | 16 | type UnhashableObject struct { 17 | str string 18 | i int 19 | b bool 20 | strs []string 21 | m map[string]int 22 | } 23 | 24 | func TestSyncMapString(t *testing.T) { 25 | t.Parallel() 26 | 27 | m := NewSyncMap() 28 | key := "key" 29 | //var key *interface{} 30 | //key = &"key1" 31 | m.Put(&key, "value1") 32 | assert.Equal(t, "value1", m.Get(&key)) 33 | m.Remove(&key) 34 | assert.Nil(t, m.Get(&key)) 35 | } 36 | 37 | func TestSyncMapValues(t *testing.T) { 38 | t.Parallel() 39 | 40 | m := NewSyncMap() 41 | key := "key" 42 | key2 := "key2" 43 | m.Put(&key, "value1") 44 | m.Put(&key2, "value2") 45 | values := m.Values() 46 | assert.Equal(t, 2, len(values)) 47 | } 48 | 49 | func TestSyncMapHashableObject(t *testing.T) { 50 | t.Parallel() 51 | 52 | m := NewSyncMap() 53 | o1 := HashableObject{} 54 | m.Put(&o1, "value1") 55 | assert.Equal(t, "value1", m.Get(&o1)) 56 | //change object 57 | o1.str = "str" 58 | o1.i = 6 59 | assert.Equal(t, "value1", m.Get(&o1)) 60 | } 61 | 62 | func TestSyncMapHashableObject2(t *testing.T) { 63 | t.Parallel() 64 | 65 | m := NewSyncMap() 66 | o1 := HashableObject{} 67 | m.Put(&o1, "value1") 68 | assert.Equal(t, "value1", m.Get(&o1)) 69 | 70 | o2 := HashableObject{} 71 | assert.Nil(t, m.Get(&o2)) 72 | } 73 | 74 | func TestSyncMapHashableObject3(t *testing.T) { 75 | t.Parallel() 76 | 77 | m := NewSyncMap() 78 | o1 := HashableObject{} 79 | m.Put(&o1, &o1) 80 | o1.str = "h" 81 | assert.Equal(t, &o1, m.Get(&o1)) 82 | } 83 | 84 | func TestSyncMapUnhashableObject(t *testing.T) { 85 | t.Parallel() 86 | 87 | m := NewSyncMap() 88 | o1 := UnhashableObject{} 89 | m.Put(&o1, "value1") 90 | assert.Equal(t, "value1", m.Get(&o1)) 91 | //change object 92 | o1.str = "str" 93 | o1.i = 6 94 | assert.Equal(t, "value1", m.Get(&o1)) 95 | } 96 | 97 | func TestMultiThread(t *testing.T) { 98 | t.Parallel() 99 | 100 | m := NewSyncMap() 101 | wait := sync.WaitGroup{} 102 | wait.Add(1000) 103 | for i := 0; i < 1000; i++ { 104 | go func() { 105 | o1 := HashableObject{} 106 | m.Put(&o1, &o1) 107 | wait.Done() 108 | }() 109 | } 110 | wait.Wait() 111 | assert.Equal(t, 1000, m.Size()) 112 | } 113 | -------------------------------------------------------------------------------- /collections/queue.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "sync" 7 | 8 | "github.com/jolestar/go-commons-pool/v2/concurrent" 9 | ) 10 | 11 | // InterruptedErr when deque block method bean interrupted will return this err 12 | type InterruptedErr struct { 13 | } 14 | 15 | // NewInterruptedErr return new error instance 16 | func NewInterruptedErr() *InterruptedErr { 17 | return &InterruptedErr{} 18 | } 19 | 20 | func (err *InterruptedErr) Error() string { 21 | return "Interrupted" 22 | } 23 | 24 | // Node is LinkedBlockingDeque's element 25 | type Node struct { 26 | 27 | //The item, or nil if this node has been removed. 28 | item interface{} 29 | 30 | //One of: 31 | //- the real predecessor Node 32 | //- this Node, meaning the predecessor is tail 33 | //- nil, meaning there is no predecessor 34 | prev *Node 35 | 36 | //One of: 37 | //- the real successor Node 38 | //- this Node, meaning the successor is head 39 | //- null, meaning there is no successor 40 | next *Node 41 | } 42 | 43 | func newNode(item interface{}, prev *Node, next *Node) *Node { 44 | return &Node{item: item, prev: prev, next: next} 45 | } 46 | 47 | // LinkedBlockingDeque is a concurrent safe blocking deque 48 | type LinkedBlockingDeque struct { 49 | 50 | //Pointer to first node. 51 | //Invariant: (first == nil && last == nil) || 52 | // (first.prev == nil && first.item != nil) 53 | first *Node 54 | 55 | // Pointer to last node. 56 | // Invariant: (first == null && last == null) || 57 | // (last.next == null && last.item != null) 58 | last *Node 59 | 60 | // Number of items in the deque 61 | count int 62 | 63 | // Maximum number of items in the deque 64 | capacity int 65 | 66 | // Main lock guarding all access 67 | lock *sync.Mutex 68 | 69 | // Condition for waiting takes 70 | notEmpty *concurrent.TimeoutCond 71 | 72 | //Condition for waiting puts 73 | notFull *concurrent.TimeoutCond 74 | } 75 | 76 | // NewDeque return a LinkedBlockingDeque with init capacity 77 | func NewDeque(capacity int) *LinkedBlockingDeque { 78 | if capacity < 0 { 79 | panic(errors.New("capacity must > 0")) 80 | } 81 | lock := new(sync.Mutex) 82 | return &LinkedBlockingDeque{capacity: capacity, lock: lock, notEmpty: concurrent.NewTimeoutCond(lock), notFull: concurrent.NewTimeoutCond(lock)} 83 | } 84 | 85 | //Links provided element as first element, or returns false if full. 86 | //return true if successful, otherwise false 87 | func (q *LinkedBlockingDeque) linkFirst(e interface{}) bool { 88 | if q.count >= q.capacity { 89 | return false 90 | } 91 | f := q.first 92 | x := newNode(e, nil, f) 93 | q.first = x 94 | if q.last == nil { 95 | q.last = x 96 | } else { 97 | f.prev = x 98 | } 99 | q.count = q.count + 1 100 | q.notEmpty.Signal() 101 | return true 102 | } 103 | 104 | //Links provided element as last element, or returns false if full. 105 | //return true} if successful, otherwise false 106 | func (q *LinkedBlockingDeque) linkLast(e interface{}) bool { 107 | // assert lock.isHeldByCurrentThread(); 108 | if q.count >= q.capacity { 109 | return false 110 | } 111 | l := q.last 112 | x := newNode(e, l, nil) 113 | q.last = x 114 | if q.first == nil { 115 | q.first = x 116 | } else { 117 | l.next = x 118 | } 119 | q.count = q.count + 1 120 | q.notEmpty.Signal() 121 | return true 122 | } 123 | 124 | //Removes and returns the first element, or nil if empty. 125 | func (q *LinkedBlockingDeque) unlinkFirst() interface{} { 126 | // assert lock.isHeldByCurrentThread(); 127 | f := q.first 128 | if f == nil { 129 | return nil 130 | } 131 | n := f.next 132 | item := f.item 133 | f.item = nil 134 | f.next = f //help GC 135 | q.first = n 136 | if n == nil { 137 | q.last = nil 138 | } else { 139 | n.prev = nil 140 | } 141 | q.count = q.count - 1 142 | q.notFull.Signal() 143 | return item 144 | } 145 | 146 | //Removes and returns the last element, or nil if empty. 147 | func (q *LinkedBlockingDeque) unlinkLast() interface{} { 148 | l := q.last 149 | if l == nil { 150 | return nil 151 | } 152 | p := l.prev 153 | item := l.item 154 | l.item = nil 155 | l.prev = l // help GC 156 | q.last = p 157 | if p == nil { 158 | q.first = nil 159 | } else { 160 | p.next = nil 161 | } 162 | q.count = q.count - 1 163 | q.notFull.Signal() 164 | return item 165 | } 166 | 167 | //Unlink the provided node. 168 | func (q *LinkedBlockingDeque) unlink(x *Node) { 169 | // assert lock.isHeldByCurrentThread(); 170 | p := x.prev 171 | n := x.next 172 | if p == nil { 173 | q.unlinkFirst() 174 | } else if n == nil { 175 | q.unlinkLast() 176 | } else { 177 | p.next = n 178 | n.prev = p 179 | x.item = nil 180 | // Don't mess with x's links. They may still be in use by 181 | // an iterator. 182 | q.count = q.count - 1 183 | q.notFull.Signal() 184 | } 185 | } 186 | 187 | // AddFirst inserts the specified element at the front of this deque if it is 188 | // possible to do so immediately without violating capacity restrictions, 189 | // return error if no space is currently available. 190 | func (q *LinkedBlockingDeque) AddFirst(e interface{}) error { 191 | if e == nil { 192 | return errors.New("e is nil") 193 | } 194 | if !q.OfferFirst(e) { 195 | return errors.New("Deque full") 196 | } 197 | return nil 198 | } 199 | 200 | // AddLast inserts the specified element at the end of this deque if it is 201 | // possible to do so immediately without violating capacity restrictions, 202 | // return error if no space is currently available. 203 | func (q *LinkedBlockingDeque) AddLast(e interface{}) error { 204 | if e == nil { 205 | return errors.New("e is nil") 206 | } 207 | if !q.OfferLast(e) { 208 | return errors.New("Deque full") 209 | } 210 | return nil 211 | } 212 | 213 | // OfferFirst inserts the specified element at the front of this deque unless it would violate capacity restrictions. 214 | // return if the element was added to this deque 215 | func (q *LinkedBlockingDeque) OfferFirst(e interface{}) bool { 216 | if e == nil { 217 | return false 218 | } 219 | q.lock.Lock() 220 | result := q.linkFirst(e) 221 | q.lock.Unlock() 222 | return result 223 | } 224 | 225 | // OfferLast inserts the specified element at the end of this deque unless it would violate capacity restrictions. 226 | // return if the element was added to this deque 227 | func (q *LinkedBlockingDeque) OfferLast(e interface{}) bool { 228 | if e == nil { 229 | return false 230 | } 231 | q.lock.Lock() 232 | result := q.linkLast(e) 233 | q.lock.Unlock() 234 | return result 235 | } 236 | 237 | // PutFirst link the provided element as the first in the queue, waiting until there 238 | // is space to do so if the queue is full. 239 | func (q *LinkedBlockingDeque) PutFirst(ctx context.Context, e interface{}) { 240 | if e == nil { 241 | return 242 | } 243 | q.lock.Lock() 244 | defer q.lock.Unlock() 245 | for !q.linkFirst(e) { 246 | q.notFull.Wait(ctx) 247 | } 248 | } 249 | 250 | // PutLast Link the provided element as the last in the queue, waiting until there 251 | // is space to do so if the queue is full. 252 | func (q *LinkedBlockingDeque) PutLast(ctx context.Context, e interface{}) { 253 | if e == nil { 254 | return 255 | } 256 | q.lock.Lock() 257 | defer q.lock.Unlock() 258 | for !q.linkLast(e) { 259 | q.notFull.Wait(ctx) 260 | } 261 | } 262 | 263 | // PollFirst retrieves and removes the first element of this deque, 264 | // or returns nil if this deque is empty. 265 | func (q *LinkedBlockingDeque) PollFirst() (e interface{}) { 266 | q.lock.Lock() 267 | result := q.unlinkFirst() 268 | q.lock.Unlock() 269 | return result 270 | } 271 | 272 | // PollFirstWithContext retrieves and removes the first element of this deque, waiting 273 | // until the context is done if necessary for an element to become available. 274 | // return NewInterruptedErr when waiting bean interrupted 275 | func (q *LinkedBlockingDeque) PollFirstWithContext(ctx context.Context) (interface{}, error) { 276 | q.lock.Lock() 277 | defer q.lock.Unlock() 278 | var x interface{} 279 | interrupt := false 280 | for x = q.unlinkFirst(); x == nil; x = q.unlinkFirst() { 281 | if interrupt { 282 | return nil, NewInterruptedErr() 283 | } 284 | select { 285 | case <-ctx.Done(): 286 | return nil, nil 287 | default: 288 | } 289 | interrupt = q.notEmpty.Wait(ctx) 290 | } 291 | return x, nil 292 | } 293 | 294 | // PollLast retrieves and removes the last element of this deque, 295 | // or returns nil if this deque is empty. 296 | func (q *LinkedBlockingDeque) PollLast() interface{} { 297 | q.lock.Lock() 298 | result := q.unlinkLast() 299 | q.lock.Unlock() 300 | return result 301 | } 302 | 303 | // PollLastWithContext retrieves and removes the last element of this deque, waiting 304 | // until the context is done if necessary for an element to become available. 305 | // return NewInterruptedErr when waiting bean interrupted 306 | func (q *LinkedBlockingDeque) PollLastWithContext(ctx context.Context) (interface{}, error) { 307 | q.lock.Lock() 308 | defer q.lock.Unlock() 309 | var x interface{} 310 | interrupt := false 311 | for x = q.unlinkLast(); x == nil; x = q.unlinkLast() { 312 | if interrupt { 313 | return nil, NewInterruptedErr() 314 | } 315 | select { 316 | case <-ctx.Done(): 317 | return nil, nil 318 | default: 319 | } 320 | interrupt = q.notEmpty.Wait(ctx) 321 | } 322 | return x, nil 323 | } 324 | 325 | // TakeFirst unlink the first element in the queue, waiting until there is an element 326 | // to unlink if the queue is empty. 327 | // return NewInterruptedErr if wait condition is interrupted 328 | func (q *LinkedBlockingDeque) TakeFirst(ctx context.Context) (interface{}, error) { 329 | q.lock.Lock() 330 | defer q.lock.Unlock() 331 | var x interface{} 332 | interrupt := false 333 | for x = q.unlinkFirst(); x == nil; x = q.unlinkFirst() { 334 | if interrupt { 335 | return nil, NewInterruptedErr() 336 | } 337 | interrupt = q.notEmpty.Wait(ctx) 338 | } 339 | return x, nil 340 | } 341 | 342 | // TakeLast unlink the last element in the queue, waiting until there is an element 343 | // to unlink if the queue is empty. 344 | // return NewInterruptedErr if wait condition is interrupted 345 | func (q *LinkedBlockingDeque) TakeLast(ctx context.Context) (interface{}, error) { 346 | q.lock.Lock() 347 | defer q.lock.Unlock() 348 | var x interface{} 349 | interrupt := false 350 | for x = q.unlinkLast(); x == nil; x = q.unlinkLast() { 351 | if interrupt { 352 | return nil, NewInterruptedErr() 353 | } 354 | interrupt = q.notEmpty.Wait(ctx) 355 | } 356 | return x, nil 357 | } 358 | 359 | // PeekFirst retrieves, but does not remove, the first element of this deque, 360 | // or returns nil if this deque is empty. 361 | func (q *LinkedBlockingDeque) PeekFirst() interface{} { 362 | var result interface{} 363 | q.lock.Lock() 364 | if q.first == nil { 365 | result = nil 366 | } else { 367 | result = q.first.item 368 | } 369 | q.lock.Unlock() 370 | return result 371 | } 372 | 373 | // PeekLast retrieves, but does not remove, the last element of this deque, 374 | // or returns nil if this deque is empty. 375 | func (q *LinkedBlockingDeque) PeekLast() interface{} { 376 | var result interface{} 377 | q.lock.Lock() 378 | if q.last == nil { 379 | result = nil 380 | } else { 381 | result = q.last.item 382 | } 383 | q.lock.Unlock() 384 | return result 385 | } 386 | 387 | // RemoveFirstOccurrence removes the first occurrence of the specified element from this deque. 388 | // If the deque does not contain the element, it is unchanged. 389 | // More formally, removes the first element item such that 390 | // o == item 391 | // (if such an element exists). 392 | // Returns true if this deque contained the specified element 393 | // (or equivalently, if this deque changed as a result of the call). 394 | func (q *LinkedBlockingDeque) RemoveFirstOccurrence(item interface{}) bool { 395 | if item == nil { 396 | return false 397 | } 398 | q.lock.Lock() 399 | defer q.lock.Unlock() 400 | for p := q.first; p != nil; p = p.next { 401 | if item == p.item { 402 | q.unlink(p) 403 | return true 404 | } 405 | } 406 | return false 407 | } 408 | 409 | // RemoveLastOccurrence removes the last occurrence of the specified element from this deque. 410 | // If the deque does not contain the element, it is unchanged. 411 | // More formally, removes the last element item such that 412 | // o == item 413 | // (if such an element exists). 414 | // Returns true if this deque contained the specified element 415 | // (or equivalently, if this deque changed as a result of the call). 416 | func (q *LinkedBlockingDeque) RemoveLastOccurrence(item interface{}) bool { 417 | if item == nil { 418 | return false 419 | } 420 | q.lock.Lock() 421 | defer q.lock.Unlock() 422 | for p := q.last; p != nil; p = p.prev { 423 | if item == p.item { 424 | q.unlink(p) 425 | return true 426 | } 427 | } 428 | return false 429 | } 430 | 431 | // InterruptTakeWaiters interrupts the goroutine currently waiting to take an object from the pool. 432 | func (q *LinkedBlockingDeque) InterruptTakeWaiters() { 433 | q.notEmpty.Interrupt() 434 | } 435 | 436 | // HasTakeWaiters returns true if there are goroutine waiting to take instances from this deque. 437 | // See disclaimer on accuracy in TimeoutCond.HasWaiters() 438 | func (q *LinkedBlockingDeque) HasTakeWaiters() bool { 439 | q.lock.Lock() 440 | defer q.lock.Unlock() 441 | return q.notEmpty.HasWaiters() 442 | } 443 | 444 | // ToSlice returns an slice containing all of the elements in this deque, in 445 | // proper sequence (from first to last element). 446 | func (q *LinkedBlockingDeque) ToSlice() []interface{} { 447 | q.lock.Lock() 448 | defer q.lock.Unlock() 449 | a := make([]interface{}, q.count) 450 | for p, k := q.first, 0; p != nil; p, k = p.next, k+1 { 451 | a[k] = p.item 452 | } 453 | return a 454 | } 455 | 456 | // Size return this LinkedBlockingDeque current elements len, is concurrent safe 457 | func (q *LinkedBlockingDeque) Size() int { 458 | q.lock.Lock() 459 | defer q.lock.Unlock() 460 | return q.size() 461 | } 462 | 463 | func (q *LinkedBlockingDeque) size() int { 464 | return q.count 465 | } 466 | 467 | // Iterator return a asc iterator of this deque 468 | func (q *LinkedBlockingDeque) Iterator() Iterator { 469 | return newIterator(q, false) 470 | } 471 | 472 | // DescendingIterator return a desc iterator of this deque 473 | func (q *LinkedBlockingDeque) DescendingIterator() Iterator { 474 | return newIterator(q, true) 475 | } 476 | 477 | func newIterator(q *LinkedBlockingDeque, desc bool) *LinkedBlockingDequeIterator { 478 | q.lock.Lock() 479 | defer q.lock.Unlock() 480 | iterator := LinkedBlockingDequeIterator{q: q, desc: desc} 481 | iterator.next = iterator.firstNode() 482 | if iterator.next == nil { 483 | iterator.nextItem = nil 484 | } else { 485 | iterator.nextItem = iterator.next.item 486 | } 487 | return &iterator 488 | } 489 | 490 | // LinkedBlockingDequeIterator is iterator implements for LinkedBlockingDeque 491 | type LinkedBlockingDequeIterator struct { 492 | q *LinkedBlockingDeque 493 | next *Node 494 | nextItem interface{} 495 | lastRet *Node 496 | desc bool 497 | } 498 | 499 | func (iterator *LinkedBlockingDequeIterator) firstNode() *Node { 500 | if iterator.desc { 501 | return iterator.q.last 502 | } 503 | return iterator.q.first 504 | } 505 | 506 | func (iterator *LinkedBlockingDequeIterator) nextNode(node *Node) *Node { 507 | if iterator.desc { 508 | return node.prev 509 | } 510 | return node.next 511 | } 512 | 513 | // HasNext return is exist next element 514 | func (iterator *LinkedBlockingDequeIterator) HasNext() bool { 515 | return iterator.next != nil 516 | } 517 | 518 | // Next return next element, if not exist will return nil 519 | func (iterator *LinkedBlockingDequeIterator) Next() interface{} { 520 | if iterator.next == nil { 521 | //TODO error or nil ? 522 | //panic(errors.New("NoSuchElement")) 523 | return nil 524 | } 525 | iterator.lastRet = iterator.next 526 | x := iterator.nextItem 527 | iterator.advance() 528 | return x 529 | } 530 | 531 | func (iterator *LinkedBlockingDequeIterator) advance() { 532 | lock := iterator.q.lock 533 | lock.Lock() 534 | defer lock.Unlock() 535 | iterator.next = iterator.succ(iterator.next) 536 | if iterator.next == nil { 537 | iterator.nextItem = nil 538 | } else { 539 | iterator.nextItem = iterator.next.item 540 | } 541 | } 542 | 543 | func (iterator *LinkedBlockingDequeIterator) succ(n *Node) *Node { 544 | for { 545 | s := iterator.nextNode(n) 546 | if s == nil { 547 | return nil 548 | } else if s.item != nil { 549 | return s 550 | } else if s == n { 551 | return iterator.firstNode() 552 | } 553 | n = s 554 | } 555 | } 556 | 557 | // Remove current element from dequeue 558 | func (iterator *LinkedBlockingDequeIterator) Remove() { 559 | n := iterator.lastRet 560 | if n == nil { 561 | panic(errors.New("IllegalStateException")) 562 | } 563 | iterator.lastRet = nil 564 | lock := iterator.q.lock 565 | lock.Lock() 566 | if n.item != nil { 567 | iterator.q.unlink(n) 568 | } 569 | lock.Unlock() 570 | } 571 | -------------------------------------------------------------------------------- /collections/queue_test.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "reflect" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "github.com/jolestar/go-commons-pool/v2/concurrent" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/suite" 15 | ) 16 | 17 | var ONE = 1 18 | var TWO = 2 19 | var THREE = 3 20 | 21 | type LinkedBlockDequeTestSuite struct { 22 | suite.Suite 23 | deque *LinkedBlockingDeque 24 | } 25 | 26 | func (suit *LinkedBlockDequeTestSuite) NoErrorWithResult(object interface{}, err error) interface{} { 27 | //suit.NotNil(object) 28 | suit.Nil(err) 29 | return object 30 | } 31 | 32 | func (suit *LinkedBlockDequeTestSuite) ErrorWithResult(object interface{}, err error) error { 33 | suit.Nil(object) 34 | suit.NotNil(err) 35 | return err 36 | } 37 | 38 | func TestLinkedBlockQueueTestSuite(t *testing.T) { 39 | t.Parallel() 40 | 41 | suite.Run(t, new(LinkedBlockDequeTestSuite)) 42 | } 43 | 44 | func (suit *LinkedBlockDequeTestSuite) SetupTest() { 45 | suit.deque = NewDeque(2) 46 | } 47 | 48 | func (suit *LinkedBlockDequeTestSuite) TestAddFirst() { 49 | suit.deque.AddFirst(ONE) 50 | suit.deque.AddFirst(TWO) 51 | //fmt.Println(deque.Size()) 52 | suit.Equal(2, suit.deque.Size(), "deque size != 2") 53 | e := suit.deque.AddFirst(THREE) 54 | assert.NotNil(suit.T(), e, "deque can not add three element") 55 | suit.Equal(TWO, suit.deque.PollFirst()) 56 | } 57 | 58 | func (suit *LinkedBlockDequeTestSuite) TestAddLast() { 59 | suit.deque.AddLast(ONE) 60 | suit.deque.AddLast(TWO) 61 | suit.Equal(2, suit.deque.Size()) 62 | e := suit.deque.AddLast(THREE) 63 | assert.NotNil(suit.T(), e, "deque can not add three element") 64 | suit.Equal(ONE, suit.deque.PollFirst()) 65 | } 66 | 67 | func (suit *LinkedBlockDequeTestSuite) TestOfferFirst() { 68 | suit.deque.OfferFirst(ONE) 69 | suit.deque.OfferFirst(TWO) 70 | suit.Equal(2, suit.deque.Size()) 71 | suit.deque.OfferFirst(nil) 72 | suit.Equal(TWO, suit.deque.PollFirst()) 73 | } 74 | 75 | func (suit *LinkedBlockDequeTestSuite) TestOfferLast() { 76 | suit.deque.OfferLast(ONE) 77 | suit.deque.OfferLast(TWO) 78 | suit.Equal(2, suit.deque.Size()) 79 | suit.deque.OfferLast(nil) 80 | suit.Equal(ONE, suit.deque.PollFirst()) 81 | } 82 | 83 | func (suit *LinkedBlockDequeTestSuite) TestPutFirst() { 84 | ctx := context.Background() 85 | suit.deque.PutFirst(ctx, nil) 86 | suit.deque.PutFirst(ctx, ONE) 87 | suit.deque.PutFirst(ctx, TWO) 88 | suit.Equal(2, suit.deque.Size()) 89 | suit.Equal(TWO, suit.deque.PollFirst()) 90 | } 91 | 92 | func (suit *LinkedBlockDequeTestSuite) TestPutLast() { 93 | ctx := context.Background() 94 | suit.deque.PutLast(ctx, nil) 95 | suit.deque.PutLast(ctx, ONE) 96 | suit.deque.PutLast(ctx, TWO) 97 | suit.Equal(2, suit.deque.Size()) 98 | suit.Equal(ONE, suit.deque.PollFirst()) 99 | } 100 | 101 | func (suit *LinkedBlockDequeTestSuite) TestPutLastWait() { 102 | ctx := context.Background() 103 | suit.deque.PutLast(ctx, ONE) 104 | suit.deque.PutLast(ctx, TWO) 105 | wait := sync.WaitGroup{} 106 | wait.Add(1) 107 | go func() { 108 | suit.deque.PutLast(ctx, THREE) 109 | wait.Done() 110 | }() 111 | time.Sleep(100 * time.Millisecond) 112 | suit.Equal(TWO, suit.deque.PollLast()) 113 | wait.Wait() 114 | suit.Equal(2, suit.deque.Size()) 115 | suit.Equal(THREE, suit.deque.PollLast()) 116 | } 117 | 118 | func (suit *LinkedBlockDequeTestSuite) TestPutFirstWait() { 119 | ctx := context.Background() 120 | suit.deque.PutFirst(ctx, ONE) 121 | suit.deque.PutFirst(ctx, TWO) 122 | wait := sync.WaitGroup{} 123 | wait.Add(1) 124 | go func() { 125 | suit.deque.PutFirst(ctx, THREE) 126 | wait.Done() 127 | }() 128 | time.Sleep(100 * time.Millisecond) 129 | suit.Equal(TWO, suit.deque.PollFirst()) 130 | wait.Wait() 131 | suit.Equal(2, suit.deque.Size()) 132 | suit.Equal(THREE, suit.deque.PollFirst()) 133 | } 134 | 135 | func (suit *LinkedBlockDequeTestSuite) TestPollFirst() { 136 | assert.Nil(suit.T(), suit.deque.PollFirst()) 137 | assert.True(suit.T(), suit.deque.OfferFirst(ONE)) 138 | assert.True(suit.T(), suit.deque.OfferFirst(TWO)) 139 | suit.Equal(TWO, suit.deque.PollFirst()) 140 | } 141 | 142 | func (suit *LinkedBlockDequeTestSuite) TestPollLast() { 143 | assert.Nil(suit.T(), suit.deque.PollLast()) 144 | assert.True(suit.T(), suit.deque.OfferFirst(ONE)) 145 | assert.True(suit.T(), suit.deque.OfferFirst(TWO)) 146 | suit.Equal(ONE, suit.deque.PollLast()) 147 | } 148 | 149 | func (suit *LinkedBlockDequeTestSuite) TestTakeFirst() { 150 | ctx := context.Background() 151 | assert.True(suit.T(), suit.deque.OfferFirst(ONE)) 152 | assert.True(suit.T(), suit.deque.OfferFirst(TWO)) 153 | suit.Equal(TWO, suit.NoErrorWithResult(suit.deque.TakeFirst(ctx))) 154 | } 155 | 156 | func (suit *LinkedBlockDequeTestSuite) TestTakeFirstWait() { 157 | ctx := context.Background() 158 | ch := make(chan interface{}, 1) 159 | go func() { 160 | o, _ := suit.deque.TakeFirst(ctx) 161 | ch <- o 162 | }() 163 | time.Sleep(100 * time.Millisecond) 164 | suit.True(suit.deque.OfferFirst(ONE)) 165 | o := <-ch 166 | close(ch) 167 | suit.Equal(ONE, o) 168 | } 169 | 170 | func (suit *LinkedBlockDequeTestSuite) TestTakeLastWait() { 171 | ctx := context.Background() 172 | ch := make(chan interface{}, 1) 173 | go func() { 174 | o, _ := suit.deque.TakeLast(ctx) 175 | ch <- o 176 | }() 177 | time.Sleep(100 * time.Millisecond) 178 | suit.True(suit.deque.OfferFirst(ONE)) 179 | o := <-ch 180 | close(ch) 181 | suit.Equal(ONE, o) 182 | } 183 | 184 | func (suit *LinkedBlockDequeTestSuite) TestTakeLast() { 185 | ctx := context.Background() 186 | assert.True(suit.T(), suit.deque.OfferFirst(ONE)) 187 | assert.True(suit.T(), suit.deque.OfferFirst(TWO)) 188 | suit.Equal(ONE, suit.NoErrorWithResult(suit.deque.TakeLast(ctx))) 189 | } 190 | 191 | func (suit *LinkedBlockDequeTestSuite) TestRemoveFirstOccurence() { 192 | suit.deque = NewDeque(3) 193 | assert.False(suit.T(), suit.deque.RemoveFirstOccurrence(nil)) 194 | assert.False(suit.T(), suit.deque.RemoveFirstOccurrence(ONE)) 195 | suit.deque.AddLast(ONE) 196 | suit.deque.AddLast(TWO) 197 | suit.deque.AddLast(ONE) 198 | assert.True(suit.T(), suit.deque.RemoveFirstOccurrence(ONE)) 199 | assert.True(suit.T(), suit.deque.Size() == 2) 200 | assert.True(suit.T(), reflect.DeepEqual(suit.deque.ToSlice(), []interface{}{TWO, ONE})) 201 | } 202 | 203 | func (suit *LinkedBlockDequeTestSuite) TestRemoveLastOccurence() { 204 | suit.deque = NewDeque(3) 205 | assert.False(suit.T(), suit.deque.RemoveLastOccurrence(nil)) 206 | assert.False(suit.T(), suit.deque.RemoveLastOccurrence(ONE)) 207 | suit.deque.AddLast(ONE) 208 | suit.deque.AddLast(TWO) 209 | suit.deque.AddLast(ONE) 210 | assert.True(suit.T(), suit.deque.RemoveLastOccurrence(ONE)) 211 | assert.True(suit.T(), suit.deque.Size() == 2) 212 | assert.True(suit.T(), reflect.DeepEqual(suit.deque.ToSlice(), []interface{}{ONE, TWO})) 213 | } 214 | 215 | func (suit *LinkedBlockDequeTestSuite) TestPeek() { 216 | suit.deque.AddLast(ONE) 217 | suit.deque.AddLast(TWO) 218 | suit.Equal(2, suit.deque.Size()) 219 | suit.Equal(ONE, suit.deque.PeekFirst()) 220 | suit.Equal(TWO, suit.deque.PeekLast()) 221 | suit.Equal(2, suit.deque.Size()) 222 | } 223 | 224 | func (suit *LinkedBlockDequeTestSuite) TestPollFirstWithTimeout() { 225 | assert.Nil(suit.T(), suit.deque.PollFirst()) 226 | 227 | ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) 228 | defer cancel() 229 | assert.Nil(suit.T(), suit.NoErrorWithResult(suit.deque.PollFirstWithContext(ctx))) 230 | } 231 | 232 | func (suit *LinkedBlockDequeTestSuite) TestPollLastWithTimeout() { 233 | assert.Nil(suit.T(), suit.deque.PollLast()) 234 | 235 | ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) 236 | defer cancel() 237 | assert.Nil(suit.T(), suit.NoErrorWithResult(suit.deque.PollLastWithContext(ctx))) 238 | } 239 | 240 | func (suit *LinkedBlockDequeTestSuite) TestInterrupt() { 241 | ctx := context.Background() 242 | wait := sync.WaitGroup{} 243 | wait.Add(2) 244 | go func() { 245 | for i := 0; i < 2; i++ { 246 | time.Sleep(1 * time.Second) 247 | suit.deque.InterruptTakeWaiters() 248 | fmt.Println("TestInterrupt suit.deque.InterruptTakeWaiters") 249 | wait.Done() 250 | } 251 | }() 252 | for i := 0; i < 2; i++ { 253 | _, e := suit.deque.TakeFirst(ctx) 254 | _, ok := e.(*InterruptedErr) 255 | suit.True(ok, "expect InterruptedErr but get %v", reflect.TypeOf(e)) 256 | suit.NotNil(e.Error()) 257 | } 258 | wait.Wait() 259 | } 260 | 261 | func (suit *LinkedBlockDequeTestSuite) TestIterator() { 262 | suit.deque.AddLast(ONE) 263 | suit.deque.AddLast(TWO) 264 | iterator := suit.deque.Iterator() 265 | var list []int 266 | for iterator.HasNext() { 267 | item := iterator.Next().(int) 268 | list = append(list, item) 269 | } 270 | //fmt.Println("list:",list) 271 | assert.True(suit.T(), reflect.DeepEqual(list, []int{ONE, TWO})) 272 | } 273 | 274 | func (suit *LinkedBlockDequeTestSuite) TestDescendingIterator() { 275 | suit.deque.AddLast(ONE) 276 | suit.deque.AddLast(TWO) 277 | iterator := suit.deque.DescendingIterator() 278 | var list []int 279 | for iterator.HasNext() { 280 | item := iterator.Next().(int) 281 | list = append(list, item) 282 | } 283 | //fmt.Println("list:",list) 284 | assert.True(suit.T(), reflect.DeepEqual(list, []int{TWO, ONE})) 285 | } 286 | 287 | func (suit *LinkedBlockDequeTestSuite) TestIteratorRemove() { 288 | count := 100 289 | suit.deque = NewDeque(count) 290 | 291 | for i := 0; i < count; i++ { 292 | suit.deque.AddFirst(i) 293 | } 294 | suit.Equal(count, suit.deque.Size()) 295 | startWait := sync.WaitGroup{} 296 | startWait.Add(1) 297 | 298 | endWait := sync.WaitGroup{} 299 | endWait.Add(count + 1) 300 | 301 | counts := make(map[int]concurrent.AtomicInteger, count) 302 | hasErr := concurrent.AtomicInteger(0) 303 | for i := 0; i < count; i++ { 304 | go func(idx int) { 305 | startWait.Wait() 306 | iterator := suit.deque.Iterator() 307 | for iterator.HasNext() { 308 | item := iterator.Next() 309 | if item == nil { 310 | hasErr.IncrementAndGet() 311 | } else { 312 | c := counts[idx] 313 | c.IncrementAndGet() 314 | } 315 | } 316 | endWait.Done() 317 | }(i) 318 | } 319 | go func() { 320 | startWait.Wait() 321 | iterator := suit.deque.Iterator() 322 | c := 0 323 | for iterator.HasNext() { 324 | iterator.Next() 325 | if c%2 == 1 { 326 | iterator.Remove() 327 | } 328 | c = c + 1 329 | } 330 | endWait.Done() 331 | }() 332 | startWait.Done() 333 | endWait.Wait() 334 | iterator := suit.deque.Iterator() 335 | var list []int 336 | for iterator.HasNext() { 337 | item := iterator.Next().(int) 338 | list = append(list, item) 339 | } 340 | //fmt.Println("list:",list) 341 | //fmt.Println("counts:", counts) 342 | suit.Equal(count/2, suit.deque.Size()) 343 | suit.Equal(count/2, len(list)) 344 | suit.Equal(int32(0), hasErr.Get()) 345 | } 346 | 347 | func (suit *LinkedBlockDequeTestSuite) TestQueueLock() { 348 | ctx := context.Background() 349 | suit.deque = NewDeque(1) 350 | ch := make(chan int) 351 | go func() { 352 | ch <- suit.NoErrorWithResult(suit.deque.TakeFirst(ctx)).(int) 353 | fmt.Printf("TestQueueLock take finish.\n") 354 | }() 355 | //time.Sleep(1*time.Second) 356 | go func() { 357 | suit.deque.PutFirst(ctx, 1) 358 | fmt.Printf("TestQueueLock put finish.\n") 359 | }() 360 | val := <-ch 361 | close(ch) 362 | suit.Equal(1, val) 363 | } 364 | 365 | func (suit *LinkedBlockDequeTestSuite) TestQueueConcurrent() { 366 | ctx := context.Background() 367 | count := 100 368 | suit.deque = NewDeque(count) 369 | ch := make(chan int, count) 370 | for i := 0; i < count; i++ { 371 | go func() { 372 | ch <- suit.NoErrorWithResult(suit.deque.TakeFirst(ctx)).(int) 373 | }() 374 | } 375 | time.Sleep(100 * time.Millisecond) 376 | for i := 0; i < count; i++ { 377 | go func(val int) { 378 | suit.deque.AddFirst(val) 379 | }(i) 380 | } 381 | values := make([]int, count) 382 | valueset := make(map[int]int) 383 | for i := 0; i < count; i++ { 384 | val := <-ch 385 | values[i] = val 386 | valueset[val] = val 387 | } 388 | fmt.Println("TestQueueConcurrent", values) 389 | suit.Equal(count, len(valueset)) 390 | close(ch) 391 | //suit.Equal(1, val) 392 | } 393 | 394 | func (suit *LinkedBlockDequeTestSuite) TestQueueConcurrentTimeout() { 395 | suit.deque = NewDeque(10) 396 | count := 20 397 | ch := make(chan int, count/2) 398 | timeoutChan := make(chan time.Duration, count/2) 399 | timeout := 1 * time.Second 400 | for i := 0; i < count; i++ { 401 | go func() { 402 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 403 | defer cancel() 404 | 405 | startWait := time.Now() 406 | r := suit.NoErrorWithResult(suit.deque.PollFirstWithContext(ctx)) 407 | waitElapsed := time.Since(startWait) 408 | //timeout 409 | if r == nil { 410 | timeoutChan <- waitElapsed 411 | } else { 412 | ch <- r.(int) 413 | } 414 | }() 415 | } 416 | for i := 0; i < count/2; i++ { 417 | go func(val int) { 418 | time.Sleep(time.Duration(rand.Int63n(int64(timeout)))) 419 | suit.deque.AddFirst(val) 420 | }(i) 421 | } 422 | for i := 0; i < count/2; i++ { 423 | t := <-timeoutChan 424 | //fmt.Println(t) 425 | if t < timeout { 426 | suit.Fail(fmt.Sprintf("%v timeout %v < %v", t, i, timeout)) 427 | } 428 | } 429 | close(timeoutChan) 430 | valueset := make(map[int]int) 431 | for i := 0; i < count/2; i++ { 432 | val := <-ch 433 | valueset[val] = val 434 | } 435 | close(ch) 436 | suit.Equal(count/2, len(valueset)) 437 | } 438 | 439 | func (suit *LinkedBlockDequeTestSuite) TestQueuePutAndPullTimeout() { 440 | ctx := context.Background() 441 | suit.deque = NewDeque(1) 442 | ch := make(chan int) 443 | timeout := 500 * time.Millisecond 444 | go func() { 445 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 446 | defer cancel() 447 | ch <- suit.NoErrorWithResult(suit.deque.PollFirstWithContext(ctx)).(int) 448 | fmt.Printf("TestQueueLock take finish.\n") 449 | }() 450 | time.Sleep(100 * time.Millisecond) 451 | go func() { 452 | suit.deque.PutFirst(ctx, 1) 453 | fmt.Printf("TestQueueLock put finish.\n") 454 | }() 455 | val := <-ch 456 | close(ch) 457 | suit.Equal(1, val) 458 | } 459 | 460 | func (suit *LinkedBlockDequeTestSuite) TestHasTakeWaitersWithTimeout() { 461 | ctx := context.Background() 462 | suit.deque = NewDeque(1) 463 | suit.False(suit.deque.HasTakeWaiters()) 464 | timeout := 500 * time.Millisecond 465 | ch := make(chan int) 466 | go func() { 467 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 468 | defer cancel() 469 | 470 | ch <- suit.NoErrorWithResult(suit.deque.PollFirstWithContext(ctx)).(int) 471 | fmt.Printf("TestQueueLock take finish.\n") 472 | }() 473 | time.Sleep(50 * time.Millisecond) 474 | suit.True(suit.deque.HasTakeWaiters()) 475 | go func() { 476 | suit.deque.PutFirst(ctx, 1) 477 | fmt.Printf("TestQueueLock put finish.\n") 478 | }() 479 | val := <-ch 480 | close(ch) 481 | suit.Equal(1, val) 482 | suit.False(suit.deque.HasTakeWaiters()) 483 | } 484 | 485 | func (suit *LinkedBlockDequeTestSuite) TestHasTakeWaiters() { 486 | ctx := context.Background() 487 | suit.deque = NewDeque(1) 488 | suit.False(suit.deque.HasTakeWaiters()) 489 | ch := make(chan int) 490 | go func() { 491 | ch <- suit.NoErrorWithResult(suit.deque.TakeFirst(ctx)).(int) 492 | fmt.Printf("TestQueueLock take finish.\n") 493 | }() 494 | time.Sleep(50 * time.Millisecond) 495 | suit.True(suit.deque.HasTakeWaiters()) 496 | go func() { 497 | suit.deque.PutFirst(ctx, 1) 498 | fmt.Printf("TestQueueLock put finish.\n") 499 | }() 500 | val := <-ch 501 | close(ch) 502 | suit.Equal(1, val) 503 | suit.False(suit.deque.HasTakeWaiters()) 504 | } 505 | 506 | // https://github.com/jolestar/go-commons-pool/issues/44 507 | func (suit *LinkedBlockDequeTestSuite) TestDeadLock() { 508 | ctx := context.Background() 509 | suit.deque = NewDeque(1) 510 | suit.deque.PutFirst(ctx, 1) 511 | count := 1000000 512 | testWG := sync.WaitGroup{} 513 | testWG.Add(count) 514 | for i := 0; i < count; i++ { 515 | o := suit.NoErrorWithResult(suit.deque.PollFirstWithContext(ctx)) 516 | go func() { 517 | suit.deque.PutFirst(ctx, o) 518 | testWG.Done() 519 | }() 520 | } 521 | testWG.Wait() 522 | } 523 | -------------------------------------------------------------------------------- /concurrent/atomic.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import "sync/atomic" 4 | 5 | // AtomicInteger is a int32 wrapper fo atomic 6 | type AtomicInteger int32 7 | 8 | // IncrementAndGet increment wrapped int32 with 1 and return new value. 9 | func (i *AtomicInteger) IncrementAndGet() int32 { 10 | return atomic.AddInt32((*int32)(i), int32(1)) 11 | } 12 | 13 | // GetAndIncrement increment wrapped int32 with 1 and return old value. 14 | func (i *AtomicInteger) GetAndIncrement() int32 { 15 | ret := atomic.LoadInt32((*int32)(i)) 16 | atomic.AddInt32((*int32)(i), int32(1)) 17 | return ret 18 | } 19 | 20 | // DecrementAndGet decrement wrapped int32 with 1 and return new value. 21 | func (i *AtomicInteger) DecrementAndGet() int32 { 22 | return atomic.AddInt32((*int32)(i), int32(-1)) 23 | } 24 | 25 | // GetAndDecrement decrement wrapped int32 with 1 and return old value. 26 | func (i *AtomicInteger) GetAndDecrement() int32 { 27 | ret := atomic.LoadInt32((*int32)(i)) 28 | atomic.AddInt32((*int32)(i), int32(-1)) 29 | return ret 30 | } 31 | 32 | // Get current value 33 | func (i *AtomicInteger) Get() int32 { 34 | return atomic.LoadInt32((*int32)(i)) 35 | } 36 | -------------------------------------------------------------------------------- /concurrent/atomic_test.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAtomicIncrement(t *testing.T) { 11 | t.Parallel() 12 | 13 | i := AtomicInteger(int32(0)) 14 | v := i.IncrementAndGet() 15 | assert.Equal(t, int32(1), v) 16 | assert.Equal(t, int32(1), i.Get()) 17 | v = i.GetAndIncrement() 18 | assert.Equal(t, int32(1), v) 19 | assert.Equal(t, int32(2), i.Get()) 20 | } 21 | 22 | func TestAtomicDecrement(t *testing.T) { 23 | t.Parallel() 24 | 25 | i := AtomicInteger(int32(2)) 26 | v := i.DecrementAndGet() 27 | assert.Equal(t, int32(1), v) 28 | assert.Equal(t, int32(1), i.Get()) 29 | v = i.GetAndDecrement() 30 | assert.Equal(t, int32(1), v) 31 | assert.Equal(t, int32(0), i.Get()) 32 | } 33 | 34 | func TestAtomicConcurrentIncrement(t *testing.T) { 35 | t.Parallel() 36 | 37 | integer := AtomicInteger(int32(0)) 38 | count := 100 39 | wait := sync.WaitGroup{} 40 | wait.Add(count) 41 | start := sync.WaitGroup{} 42 | start.Add(1) 43 | for i := 0; i < count; i++ { 44 | go func() { 45 | start.Wait() 46 | integer.IncrementAndGet() 47 | wait.Done() 48 | }() 49 | } 50 | start.Done() 51 | wait.Wait() 52 | assert.Equal(t, int32(count), integer.Get()) 53 | } 54 | 55 | func TestAtomicConcurrentDecrement(t *testing.T) { 56 | t.Parallel() 57 | 58 | count := 100 59 | integer := AtomicInteger(int32(count)) 60 | wait := sync.WaitGroup{} 61 | wait.Add(count) 62 | start := sync.WaitGroup{} 63 | start.Add(1) 64 | for i := 0; i < count; i++ { 65 | go func() { 66 | start.Wait() 67 | integer.DecrementAndGet() 68 | wait.Done() 69 | }() 70 | } 71 | start.Done() 72 | wait.Wait() 73 | assert.Equal(t, int32(0), integer.Get()) 74 | } 75 | 76 | func TestAtomicConcurrentIncrementAndDecrementAndGet(t *testing.T) { 77 | t.Parallel() 78 | 79 | count := 100 80 | integer := AtomicInteger(0) 81 | wait := sync.WaitGroup{} 82 | wait.Add(count) 83 | start := sync.WaitGroup{} 84 | start.Add(1) 85 | for i := 0; i < count; i++ { 86 | go func(idx int) { 87 | start.Wait() 88 | if idx%2 == 0 { 89 | integer.IncrementAndGet() 90 | } else { 91 | integer.DecrementAndGet() 92 | } 93 | integer.Get() 94 | wait.Done() 95 | }(i) 96 | } 97 | start.Done() 98 | wait.Wait() 99 | assert.Equal(t, int32(0), integer.Get()) 100 | } 101 | 102 | func TestAtomicConcurrentGetAndIncrementAndDecrement(t *testing.T) { 103 | t.Parallel() 104 | 105 | count := 100 106 | integer := AtomicInteger(0) 107 | wait := sync.WaitGroup{} 108 | wait.Add(count) 109 | start := sync.WaitGroup{} 110 | start.Add(1) 111 | for i := 0; i < count; i++ { 112 | go func(idx int) { 113 | start.Wait() 114 | if idx%2 == 0 { 115 | integer.GetAndIncrement() 116 | } else { 117 | integer.GetAndDecrement() 118 | } 119 | integer.Get() 120 | wait.Done() 121 | }(i) 122 | } 123 | start.Done() 124 | wait.Wait() 125 | assert.Equal(t, int32(0), integer.Get()) 126 | } 127 | -------------------------------------------------------------------------------- /concurrent/cond.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "strconv" 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | // TimeoutCond is a sync.Cond improve for support wait timeout. 12 | type TimeoutCond struct { 13 | hasWaiters uint64 14 | L sync.Locker 15 | signal chan int 16 | condL sync.RWMutex 17 | } 18 | 19 | // NewTimeoutCond return a new TimeoutCond 20 | func NewTimeoutCond(l sync.Locker) *TimeoutCond { 21 | cond := TimeoutCond{L: l, signal: make(chan int, 1), condL: sync.RWMutex{}} 22 | return &cond 23 | } 24 | 25 | func (cond *TimeoutCond) addWaiter() { 26 | v := atomic.AddUint64(&cond.hasWaiters, 1) 27 | if v == 0 { 28 | panic("too many waiters; max is " + strconv.FormatUint(math.MaxUint64, 10)) 29 | } 30 | } 31 | 32 | func (cond *TimeoutCond) removeWaiter() { 33 | // Decrement. See notes here: https://godoc.org/sync/atomic#AddUint64 34 | v := atomic.AddUint64(&cond.hasWaiters, ^uint64(0)) 35 | 36 | if v == math.MaxUint64 { 37 | panic("removeWaiter called more than once after addWaiter") 38 | } 39 | } 40 | 41 | // HasWaiters queries whether any goroutine are waiting on this condition 42 | func (cond *TimeoutCond) HasWaiters() bool { 43 | return atomic.LoadUint64(&cond.hasWaiters) > 0 44 | } 45 | 46 | // Wait waits for a signal, or for the context do be done. Returns true if signaled. 47 | func (cond *TimeoutCond) Wait(ctx context.Context) bool { 48 | cond.addWaiter() 49 | 50 | cond.condL.RLock() 51 | //copy signal in lock, avoid data race with Interrupt 52 | ch := cond.signal 53 | cond.condL.RUnlock() 54 | //wait should unlock mutex, if not will cause deadlock 55 | cond.L.Unlock() 56 | defer cond.removeWaiter() 57 | defer cond.L.Lock() 58 | 59 | select { 60 | case _, ok := <-ch: 61 | return !ok 62 | case <-ctx.Done(): 63 | return false 64 | } 65 | } 66 | 67 | // Signal wakes one goroutine waiting on c, if there is any. 68 | func (cond *TimeoutCond) Signal() { 69 | cond.condL.RLock() 70 | select { 71 | case cond.signal <- 1: 72 | default: 73 | } 74 | cond.condL.RUnlock() 75 | } 76 | 77 | // Interrupt goroutine wait on this TimeoutCond 78 | func (cond *TimeoutCond) Interrupt() { 79 | cond.condL.Lock() 80 | defer cond.condL.Unlock() 81 | close(cond.signal) 82 | cond.signal = make(chan int, 0) 83 | } 84 | -------------------------------------------------------------------------------- /concurrent/cond_test.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | type LockTestObject struct { 16 | t *testing.T 17 | lock *sync.Mutex 18 | cond *TimeoutCond 19 | } 20 | 21 | func NewLockTestObject(t *testing.T) *LockTestObject { 22 | lock := new(sync.Mutex) 23 | return &LockTestObject{t: t, lock: lock, cond: NewTimeoutCond(lock)} 24 | } 25 | 26 | func (o *LockTestObject) lockAndWaitWithTimeout(timeout time.Duration) bool { 27 | o.lock.Lock() 28 | defer o.lock.Unlock() 29 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 30 | defer cancel() 31 | return o.cond.Wait(ctx) 32 | } 33 | 34 | func (o *LockTestObject) lockAndWait() bool { 35 | o.lock.Lock() 36 | defer o.lock.Unlock() 37 | o.t.Log("lockAndWait") 38 | return o.cond.Wait(context.Background()) 39 | } 40 | 41 | func (o *LockTestObject) lockAndSignal() { 42 | o.lock.Lock() 43 | defer o.lock.Unlock() 44 | o.t.Log("lockAndNotify") 45 | o.cond.Signal() 46 | } 47 | 48 | func (o *LockTestObject) hasWaiters() bool { 49 | return o.cond.HasWaiters() 50 | } 51 | 52 | func TestTimeoutCondWait(t *testing.T) { 53 | t.Parallel() 54 | 55 | t.Log("TestTimeoutCondWait") 56 | obj := NewLockTestObject(t) 57 | wait := sync.WaitGroup{} 58 | wait.Add(2) 59 | go func() { 60 | obj.lockAndWait() 61 | wait.Done() 62 | }() 63 | time.Sleep(50 * time.Millisecond) 64 | go func() { 65 | obj.lockAndSignal() 66 | wait.Done() 67 | }() 68 | wait.Wait() 69 | } 70 | 71 | func TestTimeoutCondWaitTimeout(t *testing.T) { 72 | t.Parallel() 73 | 74 | t.Log("TestTimeoutCondWaitTimeout") 75 | obj := NewLockTestObject(t) 76 | wait := sync.WaitGroup{} 77 | wait.Add(1) 78 | go func() { 79 | obj.lockAndWaitWithTimeout(2 * time.Second) 80 | wait.Done() 81 | }() 82 | wait.Wait() 83 | } 84 | 85 | func TestTimeoutCondWaitTimeoutNotify(t *testing.T) { 86 | t.Parallel() 87 | 88 | t.Log("TestTimeoutCondWaitTimeoutNotify") 89 | obj := NewLockTestObject(t) 90 | wait := sync.WaitGroup{} 91 | wait.Add(2) 92 | ch := make(chan time.Duration, 1) 93 | timeout := 5 * time.Second 94 | go func() { 95 | begin := time.Now() 96 | obj.lockAndWaitWithTimeout(timeout * time.Millisecond) 97 | elapsed := time.Since(begin) 98 | ch <- elapsed 99 | wait.Done() 100 | }() 101 | time.Sleep(200 * time.Millisecond) 102 | go func() { 103 | obj.lockAndSignal() 104 | wait.Done() 105 | }() 106 | wait.Wait() 107 | elapsed := <-ch 108 | close(ch) 109 | assert.True(t, elapsed < timeout) 110 | assert.True(t, elapsed >= 200*time.Millisecond) 111 | } 112 | 113 | func TestTimeoutCondWaitTimeoutRemain(t *testing.T) { 114 | t.Parallel() 115 | 116 | t.Log("TestTimeoutCondWaitTimeoutRemain") 117 | obj := NewLockTestObject(t) 118 | wait := sync.WaitGroup{} 119 | wait.Add(2) 120 | ch := make(chan bool, 1) 121 | timeout := 2 * time.Second 122 | go func() { 123 | interrupted := obj.lockAndWaitWithTimeout(timeout) 124 | ch <- interrupted 125 | wait.Done() 126 | }() 127 | time.Sleep(200 * time.Millisecond) 128 | go func() { 129 | obj.lockAndSignal() 130 | wait.Done() 131 | }() 132 | wait.Wait() 133 | interrupted := <-ch 134 | close(ch) 135 | assert.False(t, interrupted, "should not have been interrupted (timed out?)") 136 | } 137 | 138 | func TestTimeoutCondHasWaiters(t *testing.T) { 139 | t.Parallel() 140 | 141 | t.Log("TestTimeoutCondHasWaiters") 142 | obj := NewLockTestObject(t) 143 | waitersCount := 2 144 | ch := make(chan struct{}, waitersCount) 145 | for i := 0; i < 2; i++ { 146 | go func() { 147 | obj.lockAndWait() 148 | ch <- struct{}{} 149 | }() 150 | } 151 | time.Sleep(50 * time.Millisecond) 152 | assert.True(t, obj.hasWaiters(), "Should have waiters") 153 | 154 | obj.lockAndSignal() 155 | <-ch 156 | assert.True(t, obj.hasWaiters(), "Should still have waiters") 157 | 158 | obj.lockAndSignal() 159 | <-ch 160 | assert.False(t, obj.hasWaiters(), "Should no longer have waiters") 161 | } 162 | 163 | func TestTooManyWaiters(t *testing.T) { 164 | t.Parallel() 165 | 166 | obj := NewLockTestObject(t) 167 | obj.cond.hasWaiters = math.MaxUint64 168 | 169 | require.Panics(t, func() { obj.lockAndWait() }) 170 | } 171 | 172 | func TestRemoveWaiterUsedIncorrectly(t *testing.T) { 173 | t.Parallel() 174 | 175 | cond := NewTimeoutCond(&sync.Mutex{}) 176 | require.Panics(t, cond.removeWaiter) 177 | } 178 | 179 | func TestInterrupted(t *testing.T) { 180 | t.Parallel() 181 | 182 | t.Log("TestInterrupted") 183 | obj := NewLockTestObject(t) 184 | wait := sync.WaitGroup{} 185 | count := 5 186 | wait.Add(5) 187 | ch := make(chan bool, 5) 188 | for i := 0; i < count; i++ { 189 | go func() { 190 | ch <- obj.lockAndWait() 191 | wait.Done() 192 | }() 193 | } 194 | time.Sleep(100 * time.Millisecond) 195 | go func() { obj.cond.Interrupt() }() 196 | wait.Wait() 197 | for i := 0; i < count; i++ { 198 | b := <-ch 199 | assert.True(t, b, "expect %v interrupted but get false", i) 200 | } 201 | } 202 | 203 | func TestInterruptedWithTimeout(t *testing.T) { 204 | t.Parallel() 205 | 206 | t.Log("TestInterruptedWithTimeout") 207 | obj := NewLockTestObject(t) 208 | wait := sync.WaitGroup{} 209 | count := 5 210 | wait.Add(5) 211 | ch := make(chan bool, 5) 212 | timeout := 1000 * time.Millisecond 213 | for i := 0; i < count; i++ { 214 | go func() { 215 | interrupted := obj.lockAndWaitWithTimeout(timeout) 216 | ch <- interrupted 217 | wait.Done() 218 | }() 219 | } 220 | time.Sleep(100 * time.Millisecond) 221 | go func() { obj.cond.Interrupt() }() 222 | wait.Wait() 223 | for i := 0; i < count; i++ { 224 | b := <-ch 225 | assert.True(t, b, "expect %v interrupted but get false", i) 226 | } 227 | } 228 | 229 | func TestSignalNoWait(t *testing.T) { 230 | t.Parallel() 231 | 232 | obj := NewLockTestObject(t) 233 | obj.cond.Signal() 234 | } 235 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "math" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | const ( 12 | // DefaultMaxTotal is the default value of ObjectPoolConfig.MaxTotal 13 | DefaultMaxTotal = 8 14 | // DefaultMaxIdle is the default value of ObjectPoolConfig.MaxIdle 15 | DefaultMaxIdle = 8 16 | // DefaultMinIdle is the default value of ObjectPoolConfig.MinIdle 17 | DefaultMinIdle = 0 18 | // DefaultLIFO is the default value of ObjectPoolConfig.LIFO 19 | DefaultLIFO = true 20 | 21 | // TODO 22 | // DEFAULT_FAIRNESS = false 23 | 24 | // DefaultMinEvictableIdleTime is the default value of ObjectPoolConfig.MinEvictableIdleTime 25 | DefaultMinEvictableIdleTime = 30 * time.Minute 26 | // DefaultSoftMinEvictableIdleTime is the default value of ObjectPoolConfig.SoftMinEvictableIdleTime 27 | DefaultSoftMinEvictableIdleTime = time.Duration(math.MaxInt64) 28 | // DefaultNumTestsPerEvictionRun is the default value of ObjectPoolConfig.NumTestsPerEvictionRun 29 | DefaultNumTestsPerEvictionRun = 3 30 | // DefaultTestOnCreate is the default value of ObjectPoolConfig.TestOnCreate 31 | DefaultTestOnCreate = false 32 | // DefaultTestOnBorrow is the default value of ObjectPoolConfig.TestOnBorrow 33 | DefaultTestOnBorrow = false 34 | // DefaultTestOnReturn is the default value of ObjectPoolConfig.TestOnReturn 35 | DefaultTestOnReturn = false 36 | // DefaultTestWhileIdle is the default value of ObjectPoolConfig.TestWhileIdle 37 | DefaultTestWhileIdle = false 38 | // DefaultTimeBetweenEvictionRuns is the default value of ObjectPoolConfig.TimeBetweenEvictionRuns 39 | DefaultTimeBetweenEvictionRuns = time.Duration(0) 40 | // DefaultBlockWhenExhausted is the default value of ObjectPoolConfig.BlockWhenExhausted 41 | DefaultBlockWhenExhausted = true 42 | // DefaultEvictionPolicyName is the default value of ObjectPoolConfig.EvictionPolicyName 43 | DefaultEvictionPolicyName = "github.com/jolestar/go-commons-pool/DefaultEvictionPolicy" 44 | ) 45 | 46 | // ObjectPoolConfig is ObjectPool config, include cap, block, valid strategy, evict strategy etc. 47 | type ObjectPoolConfig struct { 48 | /** 49 | * Whether the pool has LIFO (last in, first out) behaviour with 50 | * respect to idle objects - always returning the most recently used object 51 | * from the pool, or as a FIFO (first in, first out) queue, where the pool 52 | * always returns the oldest object in the idle object pool. 53 | */ 54 | LIFO bool 55 | 56 | /** 57 | * The cap on the number of objects that can be allocated by the pool 58 | * (checked out to clients, or idle awaiting checkout) at a given time. Use 59 | * a negative value for no limit. 60 | */ 61 | MaxTotal int 62 | 63 | /** 64 | * The cap on the number of "idle" instances in the pool. Use a 65 | * negative value to indicate an unlimited number of idle instances. 66 | * If MaxIdle 67 | * is set too low on heavily loaded systems it is possible you will see 68 | * objects being destroyed and almost immediately new objects being created. 69 | * This is a result of the active goroutines momentarily returning objects 70 | * faster than they are requesting them them, causing the number of idle 71 | * objects to rise above maxIdle. The best value for maxIdle for heavily 72 | * loaded system will vary but the default is a good starting point. 73 | */ 74 | MaxIdle int 75 | 76 | /** 77 | * The minimum number of idle objects to maintain in 78 | * the pool. This setting only has an effect if 79 | * TimeBetweenEvictionRuns is greater than zero. If this 80 | * is the case, an attempt is made to ensure that the pool has the required 81 | * minimum number of instances during idle object eviction runs. 82 | * 83 | * If the configured value of MinIdle is greater than the configured value 84 | * for MaxIdle then the value of MaxIdle will be used instead. 85 | * 86 | */ 87 | MinIdle int 88 | 89 | /** 90 | * Whether objects created for the pool will be validated before 91 | * being returned from the ObjectPool.BorrowObject() method. Validation is 92 | * performed by the ValidateObject() method of the factory 93 | * associated with the pool. If the object fails to validate, then 94 | * ObjectPool.BorrowObject() will fail. 95 | */ 96 | TestOnCreate bool 97 | 98 | /** 99 | * Whether objects borrowed from the pool will be validated before 100 | * being returned from the ObjectPool.BorrowObject() method. Validation is 101 | * performed by the ValidateObject() method of the factory 102 | * associated with the pool. If the object fails to validate, it will be 103 | * removed from the pool and destroyed, and a new attempt will be made to 104 | * borrow an object from the pool. 105 | */ 106 | TestOnBorrow bool 107 | 108 | /** 109 | * Whether objects borrowed from the pool will be validated when 110 | * they are returned to the pool via the ObjectPool.ReturnObject() method. 111 | * Validation is performed by the ValidateObject() method of 112 | * the factory associated with the pool. Returning objects that fail validation 113 | * are destroyed rather then being returned the pool. 114 | */ 115 | TestOnReturn bool 116 | 117 | /** 118 | * Whether objects sitting idle in the pool will be validated by the 119 | * idle object evictor (if any - see 120 | * TimeBetweenEvictionRuns ). Validation is performed 121 | * by the ValidateObject() method of the factory associated 122 | * with the pool. If the object fails to validate, it will be removed from 123 | * the pool and destroyed. Note that setting this property has no effect 124 | * unless the idle object evictor is enabled by setting 125 | * TimeBetweenEvictionRuns to a positive value. 126 | */ 127 | TestWhileIdle bool 128 | 129 | /** 130 | * Whether to block when the ObjectPool.BorrowObject() method is 131 | * invoked when the pool is exhausted (the maximum number of "active" 132 | * objects has been reached). 133 | */ 134 | BlockWhenExhausted bool 135 | 136 | //TODO support fairness config 137 | //Fairness bool 138 | 139 | /** 140 | * The minimum amount of time an object may sit idle in the pool 141 | * before it is eligible for eviction by the idle object evictor (if any - 142 | * see TimeBetweenEvictionRuns. When non-positive, 143 | * no objects will be evicted from the pool due to idle time alone. 144 | */ 145 | MinEvictableIdleTime time.Duration 146 | 147 | /** 148 | * The minimum amount of time an object may sit idle in the pool 149 | * before it is eligible for eviction by the idle object evictor (if any - 150 | * see TimeBetweenEvictionRuns), 151 | * with the extra condition that at least MinIdle object 152 | * instances remain in the pool. This setting is overridden by 153 | * MinEvictableIdleTime (that is, if 154 | * MinEvictableIdleTime is positive, then 155 | * SoftMinEvictableIdleTime is ignored). 156 | */ 157 | SoftMinEvictableIdleTime time.Duration 158 | 159 | /** 160 | * The maximum number of objects to examine during each run (if any) 161 | * of the idle object evictor goroutine. When positive, the number of tests 162 | * performed for a run will be the minimum of the configured value and the 163 | * number of idle instances in the pool. When negative, the number of tests 164 | * performed will be math.Ceil(ObjectPool.GetNumIdle()/math. 165 | * Abs(PoolConfig.NumTestsPerEvictionRun)) which means that when the 166 | * value is -n roughly one nth of the idle objects will be 167 | * tested per run. 168 | */ 169 | NumTestsPerEvictionRun int 170 | 171 | /** 172 | * The name of the EvictionPolicy implementation that is 173 | * used by this pool. Please register policy by RegistryEvictionPolicy(name, policy) 174 | */ 175 | EvictionPolicyName string 176 | 177 | /** 178 | * The amount of time sleep between runs of the idle 179 | * object evictor goroutine. When non-positive, no idle object evictor goroutine 180 | * will be run. 181 | * if this value changed after ObjectPool created, should call ObjectPool.StartEvictor to take effect. 182 | */ 183 | TimeBetweenEvictionRuns time.Duration 184 | 185 | /** 186 | * The context.Context to use when the evictor runs in the background. 187 | */ 188 | EvictionContext context.Context 189 | } 190 | 191 | // NewDefaultPoolConfig return a ObjectPoolConfig instance init with default value. 192 | func NewDefaultPoolConfig() *ObjectPoolConfig { 193 | return &ObjectPoolConfig{ 194 | LIFO: DefaultLIFO, 195 | MaxTotal: DefaultMaxTotal, 196 | MaxIdle: DefaultMaxIdle, 197 | MinIdle: DefaultMinIdle, 198 | MinEvictableIdleTime: DefaultMinEvictableIdleTime, 199 | SoftMinEvictableIdleTime: DefaultSoftMinEvictableIdleTime, 200 | NumTestsPerEvictionRun: DefaultNumTestsPerEvictionRun, 201 | EvictionPolicyName: DefaultEvictionPolicyName, 202 | EvictionContext: context.Background(), 203 | TestOnCreate: DefaultTestOnCreate, 204 | TestOnBorrow: DefaultTestOnBorrow, 205 | TestOnReturn: DefaultTestOnReturn, 206 | TestWhileIdle: DefaultTestWhileIdle, 207 | TimeBetweenEvictionRuns: DefaultTimeBetweenEvictionRuns, 208 | BlockWhenExhausted: DefaultBlockWhenExhausted} 209 | } 210 | 211 | // AbandonedConfig ObjectPool abandoned strategy config 212 | type AbandonedConfig struct { 213 | RemoveAbandonedOnBorrow bool 214 | RemoveAbandonedOnMaintenance bool 215 | // Timeout before an abandoned object can be removed. 216 | RemoveAbandonedTimeout time.Duration 217 | } 218 | 219 | // NewDefaultAbandonedConfig return a new AbandonedConfig instance init with default. 220 | func NewDefaultAbandonedConfig() *AbandonedConfig { 221 | return &AbandonedConfig{RemoveAbandonedOnBorrow: false, RemoveAbandonedOnMaintenance: false, RemoveAbandonedTimeout: 5 * time.Minute} 222 | } 223 | 224 | // EvictionConfig is config for ObjectPool EvictionPolicy 225 | type EvictionConfig struct { 226 | IdleEvictTime time.Duration 227 | IdleSoftEvictTime time.Duration 228 | MinIdle int 229 | Context context.Context 230 | } 231 | 232 | // EvictionPolicy is a interface support custom EvictionPolicy 233 | type EvictionPolicy interface { 234 | // Evict do evict by config 235 | Evict(config *EvictionConfig, underTest *PooledObject, idleCount int) bool 236 | } 237 | 238 | // DefaultEvictionPolicy is a default EvictionPolicy impl 239 | type DefaultEvictionPolicy struct { 240 | } 241 | 242 | // Evict do evict by config 243 | func (p *DefaultEvictionPolicy) Evict(config *EvictionConfig, underTest *PooledObject, idleCount int) bool { 244 | idleTime := underTest.GetIdleTime() 245 | 246 | if (config.IdleSoftEvictTime < idleTime && 247 | config.MinIdle < idleCount) || 248 | config.IdleEvictTime < idleTime { 249 | return true 250 | } 251 | return false 252 | } 253 | 254 | var ( 255 | policiesMutex sync.Mutex 256 | policies = make(map[string]EvictionPolicy) 257 | ) 258 | 259 | // RegistryEvictionPolicy registry a custom EvictionPolicy with gaven name. 260 | func RegistryEvictionPolicy(name string, policy EvictionPolicy) { 261 | if name == "" || policy == nil { 262 | panic(errors.New("invalid argument")) 263 | } 264 | policiesMutex.Lock() 265 | policies[name] = policy 266 | policiesMutex.Unlock() 267 | } 268 | 269 | // GetEvictionPolicy return a EvictionPolicy by gaven name 270 | func GetEvictionPolicy(name string) EvictionPolicy { 271 | policiesMutex.Lock() 272 | defer policiesMutex.Unlock() 273 | return policies[name] 274 | 275 | } 276 | 277 | func init() { 278 | RegistryEvictionPolicy(DefaultEvictionPolicyName, new(DefaultEvictionPolicy)) 279 | } 280 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPoolConfig(t *testing.T) { 11 | t.Parallel() 12 | 13 | config := NewDefaultPoolConfig() 14 | if debugTest { 15 | fmt.Println(config) 16 | } 17 | assert.NotNil(t, config) 18 | } 19 | 20 | func TestGetEvictionPolicy(t *testing.T) { 21 | t.Parallel() 22 | 23 | policy := GetEvictionPolicy(DefaultEvictionPolicyName) 24 | assert.NotNil(t, policy) 25 | } 26 | 27 | func TestDefaultConfig(t *testing.T) { 28 | t.Parallel() 29 | 30 | config := NewDefaultPoolConfig() 31 | assert.Equal(t, DefaultBlockWhenExhausted, config.BlockWhenExhausted) 32 | assert.Equal(t, DefaultEvictionPolicyName, config.EvictionPolicyName) 33 | assert.Equal(t, DefaultLIFO, config.LIFO) 34 | assert.Equal(t, DefaultMaxIdle, config.MaxIdle) 35 | assert.Equal(t, DefaultMaxTotal, config.MaxTotal) 36 | assert.Equal(t, DefaultMinEvictableIdleTime, config.MinEvictableIdleTime) 37 | assert.Equal(t, DefaultMinIdle, config.MinIdle) 38 | assert.Equal(t, DefaultNumTestsPerEvictionRun, config.NumTestsPerEvictionRun) 39 | assert.Equal(t, DefaultSoftMinEvictableIdleTime, config.SoftMinEvictableIdleTime) 40 | assert.Equal(t, DefaultTestOnBorrow, config.TestOnBorrow) 41 | assert.Equal(t, DefaultTestOnCreate, config.TestOnCreate) 42 | assert.Equal(t, DefaultTestOnReturn, config.TestOnReturn) 43 | assert.Equal(t, DefaultTestWhileIdle, config.TestWhileIdle) 44 | assert.Equal(t, DefaultTimeBetweenEvictionRuns, config.TimeBetweenEvictionRuns) 45 | } 46 | -------------------------------------------------------------------------------- /example__simple_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "sync/atomic" 8 | 9 | "github.com/jolestar/go-commons-pool/v2" 10 | ) 11 | 12 | func Example_simple() { 13 | type myPoolObject struct { 14 | s string 15 | } 16 | 17 | v := uint64(0) 18 | factory := pool.NewPooledObjectFactorySimple( 19 | func(context.Context) (interface{}, error) { 20 | return &myPoolObject{ 21 | s: strconv.FormatUint(atomic.AddUint64(&v, 1), 10), 22 | }, 23 | nil 24 | }) 25 | 26 | ctx := context.Background() 27 | p := pool.NewObjectPoolWithDefaultConfig(ctx, factory) 28 | 29 | obj, err := p.BorrowObject(ctx) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | o := obj.(*myPoolObject) 35 | fmt.Println(o.s) 36 | 37 | err = p.ReturnObject(ctx, obj) 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | // Output: 1 43 | } 44 | -------------------------------------------------------------------------------- /example_customFactory_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "sync/atomic" 8 | 9 | "github.com/jolestar/go-commons-pool/v2" 10 | ) 11 | 12 | type MyPoolObject struct { 13 | s string 14 | } 15 | 16 | type MyCustomFactory struct { 17 | v uint64 18 | } 19 | 20 | func (f *MyCustomFactory) MakeObject(ctx context.Context) (*pool.PooledObject, error) { 21 | return pool.NewPooledObject( 22 | &MyPoolObject{ 23 | s: strconv.FormatUint(atomic.AddUint64(&f.v, 1), 10), 24 | }), 25 | nil 26 | } 27 | 28 | func (f *MyCustomFactory) DestroyObject(ctx context.Context, object *pool.PooledObject) error { 29 | // do destroy 30 | return nil 31 | } 32 | 33 | func (f *MyCustomFactory) ValidateObject(ctx context.Context, object *pool.PooledObject) bool { 34 | // do validate 35 | return true 36 | } 37 | 38 | func (f *MyCustomFactory) ActivateObject(ctx context.Context, object *pool.PooledObject) error { 39 | // do activate 40 | return nil 41 | } 42 | 43 | func (f *MyCustomFactory) PassivateObject(ctx context.Context, object *pool.PooledObject) error { 44 | // do passivate 45 | return nil 46 | } 47 | 48 | func Example_customFactory() { 49 | ctx := context.Background() 50 | p := pool.NewObjectPoolWithDefaultConfig(ctx, &MyCustomFactory{}) 51 | 52 | obj1, err := p.BorrowObject(ctx) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | o := obj1.(*MyPoolObject) 58 | fmt.Println(o.s) 59 | 60 | err = p.ReturnObject(ctx, obj1) 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | // Output: 1 66 | } 67 | -------------------------------------------------------------------------------- /example_multipleBorrowers_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "sync/atomic" 8 | 9 | "github.com/jolestar/go-commons-pool/v2" 10 | ) 11 | 12 | func Example_multipleBorrowers() { 13 | type myPoolObject struct { 14 | s string 15 | } 16 | 17 | var v uint64 18 | factory := pool.NewPooledObjectFactorySimple( 19 | func(context.Context) (interface{}, error) { 20 | return &myPoolObject{ 21 | s: strconv.FormatUint(atomic.AddUint64(&v, 1), 10), 22 | }, 23 | nil 24 | }) 25 | 26 | ctx := context.Background() 27 | p := pool.NewObjectPoolWithDefaultConfig(ctx, factory) 28 | 29 | // Borrows #1 30 | obj1, err := p.BorrowObject(ctx) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | o := obj1.(*myPoolObject) 36 | fmt.Println(o.s) 37 | 38 | // Borrowing again while the first object is borrowed will cause a new object to be made, if 39 | // the pool configuration allows it. If the pull is full, this will block until the context 40 | // is cancelled or an object is returned to the pool. 41 | // 42 | // Borrows #2 43 | obj2, err := p.BorrowObject(ctx) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | // Returning the object to the pool makes it available to another borrower. 49 | err = p.ReturnObject(ctx, obj1) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | // Since there's an object available in the pool, this gets that rather than creating a new one. 55 | // 56 | // Borrows #1 again (since it was returned earlier) 57 | obj3, err := p.BorrowObject(ctx) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | o = obj2.(*myPoolObject) 63 | fmt.Println(o.s) 64 | 65 | err = p.ReturnObject(ctx, obj2) 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | o = obj3.(*myPoolObject) 71 | fmt.Println(o.s) 72 | 73 | err = p.ReturnObject(ctx, obj3) 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | // Output: 79 | // 1 80 | // 2 81 | // 1 82 | } 83 | -------------------------------------------------------------------------------- /factory.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | // PooledObjectFactory is factory interface for ObjectPool 9 | type PooledObjectFactory interface { 10 | 11 | /** 12 | * Create a pointer to an instance that can be served by the 13 | * pool and wrap it in a PooledObject to be managed by the pool. 14 | * 15 | * return error if there is a problem creating a new instance, 16 | * this will be propagated to the code requesting an object. 17 | */ 18 | MakeObject(ctx context.Context) (*PooledObject, error) 19 | 20 | /** 21 | * Destroys an instance no longer needed by the pool. 22 | */ 23 | DestroyObject(ctx context.Context, object *PooledObject) error 24 | 25 | /** 26 | * Ensures that the instance is safe to be returned by the pool. 27 | * 28 | * return false if object is not valid and should 29 | * be dropped from the pool, true otherwise. 30 | */ 31 | ValidateObject(ctx context.Context, object *PooledObject) bool 32 | 33 | /** 34 | * Reinitialize an instance to be returned by the pool. 35 | * 36 | * return error if there is a problem activating object, 37 | * this error may be swallowed by the pool. 38 | */ 39 | ActivateObject(ctx context.Context, object *PooledObject) error 40 | 41 | /** 42 | * Uninitialize an instance to be returned to the idle object pool. 43 | * 44 | * return error if there is a problem passivating obj, 45 | * this exception may be swallowed by the pool. 46 | */ 47 | PassivateObject(ctx context.Context, object *PooledObject) error 48 | } 49 | 50 | // DefaultPooledObjectFactory is a default PooledObjectFactory impl, support init by func 51 | type DefaultPooledObjectFactory struct { 52 | make func(ctx context.Context) (*PooledObject, error) 53 | destroy func(ctx context.Context, object *PooledObject) error 54 | validate func(ctx context.Context, object *PooledObject) bool 55 | activate func(ctx context.Context, object *PooledObject) error 56 | passivate func(ctx context.Context, object *PooledObject) error 57 | } 58 | 59 | // NewPooledObjectFactorySimple return a DefaultPooledObjectFactory, only custom MakeObject func 60 | func NewPooledObjectFactorySimple( 61 | create func(context.Context) (interface{}, error)) PooledObjectFactory { 62 | return NewPooledObjectFactory(create, nil, nil, nil, nil) 63 | } 64 | 65 | // NewPooledObjectFactory return a DefaultPooledObjectFactory, init with gaven func. 66 | func NewPooledObjectFactory( 67 | create func(context.Context) (interface{}, error), 68 | destroy func(ctx context.Context, object *PooledObject) error, 69 | validate func(ctx context.Context, object *PooledObject) bool, 70 | activate func(ctx context.Context, object *PooledObject) error, 71 | passivate func(ctx context.Context, object *PooledObject) error) PooledObjectFactory { 72 | if create == nil { 73 | panic(errors.New("make function can not be nil")) 74 | } 75 | return &DefaultPooledObjectFactory{ 76 | make: func(ctx context.Context) (*PooledObject, error) { 77 | o, err := create(ctx) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return NewPooledObject(o), nil 82 | }, 83 | destroy: destroy, 84 | validate: validate, 85 | activate: activate, 86 | passivate: passivate} 87 | } 88 | 89 | // MakeObject see PooledObjectFactory.MakeObject 90 | func (f *DefaultPooledObjectFactory) MakeObject(ctx context.Context) (*PooledObject, error) { 91 | return f.make(ctx) 92 | } 93 | 94 | // DestroyObject see PooledObjectFactory.DestroyObject 95 | func (f *DefaultPooledObjectFactory) DestroyObject(ctx context.Context, object *PooledObject) error { 96 | if f.destroy != nil { 97 | return f.destroy(ctx, object) 98 | } 99 | return nil 100 | } 101 | 102 | // ValidateObject see PooledObjectFactory.ValidateObject 103 | func (f *DefaultPooledObjectFactory) ValidateObject(ctx context.Context, object *PooledObject) bool { 104 | if f.validate != nil { 105 | return f.validate(ctx, object) 106 | } 107 | return true 108 | } 109 | 110 | // ActivateObject see PooledObjectFactory.ActivateObject 111 | func (f *DefaultPooledObjectFactory) ActivateObject(ctx context.Context, object *PooledObject) error { 112 | if f.activate != nil { 113 | return f.activate(ctx, object) 114 | } 115 | return nil 116 | } 117 | 118 | // PassivateObject see PooledObjectFactory.PassivateObject 119 | func (f *DefaultPooledObjectFactory) PassivateObject(ctx context.Context, object *PooledObject) error { 120 | if f.passivate != nil { 121 | return f.passivate(ctx, object) 122 | } 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /factory_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type TestFactoryObject struct { 12 | status string 13 | } 14 | 15 | func TestDefaultPooledObjectFactorySimple(t *testing.T) { 16 | t.Parallel() 17 | 18 | ctx := context.Background() 19 | factory := NewPooledObjectFactorySimple( 20 | func(context.Context) (interface{}, error) { 21 | return &TestFactoryObject{status: "make"}, nil 22 | }) 23 | 24 | assert.NotNil(t, factory) 25 | o, _ := factory.MakeObject(ctx) 26 | if debugTest { 27 | fmt.Println("object:", o.Object) 28 | } 29 | assert.NotNil(t, o) 30 | assert.Nil(t, factory.ActivateObject(ctx, o)) 31 | assert.Nil(t, factory.PassivateObject(ctx, o)) 32 | assert.True(t, factory.ValidateObject(ctx, o)) 33 | assert.Nil(t, factory.DestroyObject(ctx, o)) 34 | } 35 | 36 | func TestDefaultPooledObjectFactory(t *testing.T) { 37 | t.Parallel() 38 | 39 | factory := NewPooledObjectFactory( 40 | func(context.Context) (interface{}, error) { 41 | return &TestFactoryObject{status: "make"}, nil 42 | }, 43 | func(ctx context.Context, object *PooledObject) error { 44 | object.Object.(*TestFactoryObject).status = "destroy" 45 | return nil 46 | }, 47 | func(ctx context.Context, object *PooledObject) bool { 48 | object.Object.(*TestFactoryObject).status = "validate" 49 | return true 50 | }, 51 | func(ctx context.Context, object *PooledObject) error { 52 | object.Object.(*TestFactoryObject).status = "activate" 53 | return nil 54 | }, 55 | func(ctx context.Context, object *PooledObject) error { 56 | object.Object.(*TestFactoryObject).status = "passivate" 57 | return nil 58 | }) 59 | 60 | assert.NotNil(t, factory) 61 | 62 | ctx := context.Background() 63 | o, _ := factory.MakeObject(ctx) 64 | assert.NotNil(t, o) 65 | obj := o.Object.(*TestFactoryObject) 66 | assert.Equal(t, "make", obj.status) 67 | assert.Nil(t, factory.ActivateObject(ctx, o)) 68 | assert.Equal(t, "activate", obj.status) 69 | assert.Nil(t, factory.PassivateObject(ctx, o)) 70 | assert.Equal(t, "passivate", obj.status) 71 | assert.True(t, factory.ValidateObject(ctx, o)) 72 | assert.Equal(t, "validate", obj.status) 73 | assert.Nil(t, factory.DestroyObject(ctx, o)) 74 | assert.Equal(t, "destroy", obj.status) 75 | } 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jolestar/go-commons-pool/v2 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/fortytw2/leaktest v1.3.0 7 | github.com/stretchr/testify v1.8.2 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 5 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 9 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 10 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 11 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 12 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 13 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 14 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /object.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/jolestar/go-commons-pool/v2/collections" 9 | ) 10 | 11 | // PooledObjectState is PooledObjectState enum const 12 | type PooledObjectState int 13 | 14 | const ( 15 | // StateIdle in the queue, not in use. default value. 16 | StateIdle PooledObjectState = iota 17 | // StateAllocated in use. 18 | StateAllocated 19 | // StateEviction in the queue, currently being tested for possible eviction. 20 | StateEviction 21 | // StateEvictionReturnToHead not in the queue, currently being tested for possible eviction. An 22 | // attempt to borrow the object was made while being tested which removed it 23 | // from the queue. It should be returned to the head of the queue once 24 | // eviction testing completes. 25 | StateEvictionReturnToHead 26 | // StateInvalid failed maintenance (e.g. eviction test or validation) and will be / has 27 | // been destroyed 28 | StateInvalid 29 | // StateAbandoned Deemed abandoned, to be invalidated. 30 | StateAbandoned 31 | // StateReturning Returning to the pool 32 | StateReturning 33 | ) 34 | 35 | // TrackedUse allows pooled objects to make information available about when 36 | // and how they were used available to the object pool. The object pool may, but 37 | // is not required, to use this information to make more informed decisions when 38 | // determining the state of a pooled object - for instance whether or not the 39 | // object has been abandoned. 40 | type TrackedUse interface { 41 | // GetLastUsed Get the last time o object was used in ms. 42 | GetLastUsed() time.Time 43 | } 44 | 45 | // PooledObject is the wrapper of origin object that is used to track the additional information, 46 | // such as state, for the pooled objects. 47 | type PooledObject struct { 48 | // Object must be a pointer 49 | Object interface{} 50 | CreateTime time.Time 51 | LastBorrowTime time.Time 52 | LastReturnTime time.Time 53 | //init equals CreateTime 54 | LastUseTime time.Time 55 | state PooledObjectState 56 | BorrowedCount int32 57 | lock sync.Mutex 58 | } 59 | 60 | // NewPooledObject return new init PooledObject 61 | func NewPooledObject(object interface{}) *PooledObject { 62 | time := time.Now() 63 | return &PooledObject{Object: object, state: StateIdle, CreateTime: time, LastUseTime: time, LastBorrowTime: time, LastReturnTime: time} 64 | } 65 | 66 | // GetActiveTime return the time that this object last spent in the the active state 67 | func (o *PooledObject) GetActiveTime() time.Duration { 68 | // Take copies to avoid concurrent issues 69 | rTime := o.LastReturnTime 70 | bTime := o.LastBorrowTime 71 | 72 | if rTime.After(bTime) { 73 | return rTime.Sub(bTime) 74 | } 75 | return time.Since(bTime) 76 | } 77 | 78 | // GetIdleTime the time that this object last spend in the the idle state 79 | func (o *PooledObject) GetIdleTime() time.Duration { 80 | elapsed := time.Since(o.LastReturnTime) 81 | // elapsed may be negative if: 82 | // - another goroutine updates lastReturnTime during the calculation window 83 | if elapsed >= 0 { 84 | return elapsed 85 | } 86 | return 0 87 | } 88 | 89 | // GetLastUsedTime return an estimate of the last time this object was used. 90 | func (o *PooledObject) GetLastUsedTime() time.Time { 91 | trackedUse, ok := o.Object.(TrackedUse) 92 | if ok && trackedUse.GetLastUsed().After(o.LastUseTime) { 93 | return trackedUse.GetLastUsed() 94 | } 95 | return o.LastUseTime 96 | } 97 | 98 | func (o *PooledObject) doAllocate() bool { 99 | if o.state == StateIdle { 100 | o.state = StateAllocated 101 | o.LastBorrowTime = time.Now() 102 | o.LastUseTime = o.LastBorrowTime 103 | o.BorrowedCount++ 104 | //if (logAbandoned) { 105 | //borrowedBy = new AbandonedObjectCreatedException(); 106 | //} 107 | return true 108 | } else if o.state == StateEviction { 109 | // TODO Allocate anyway and ignore eviction test 110 | o.state = StateEvictionReturnToHead 111 | return false 112 | } 113 | // TODO if validating and testOnBorrow == true then pre-allocate for 114 | // performance 115 | return false 116 | } 117 | 118 | // Allocate this object 119 | func (o *PooledObject) Allocate() bool { 120 | o.lock.Lock() 121 | result := o.doAllocate() 122 | o.lock.Unlock() 123 | return result 124 | } 125 | 126 | func (o *PooledObject) doDeallocate() bool { 127 | if o.state == StateAllocated || 128 | o.state == StateReturning { 129 | o.state = StateIdle 130 | o.LastReturnTime = time.Now() 131 | //borrowedBy = nil; 132 | return true 133 | } 134 | return false 135 | } 136 | 137 | // Deallocate this object 138 | func (o *PooledObject) Deallocate() bool { 139 | o.lock.Lock() 140 | result := o.doDeallocate() 141 | o.lock.Unlock() 142 | return result 143 | } 144 | 145 | // Invalidate this object 146 | func (o *PooledObject) Invalidate() { 147 | o.lock.Lock() 148 | o.invalidate() 149 | o.lock.Unlock() 150 | } 151 | 152 | func (o *PooledObject) invalidate() { 153 | o.state = StateInvalid 154 | } 155 | 156 | // GetState return current state of this object 157 | func (o *PooledObject) GetState() PooledObjectState { 158 | o.lock.Lock() 159 | defer o.lock.Unlock() 160 | return o.state 161 | } 162 | 163 | // MarkAbandoned mark this object to Abandoned state 164 | func (o *PooledObject) MarkAbandoned() { 165 | o.lock.Lock() 166 | o.markAbandoned() 167 | o.lock.Unlock() 168 | } 169 | 170 | func (o *PooledObject) markAbandoned() { 171 | o.state = StateAbandoned 172 | } 173 | 174 | // MarkReturning mark this object to Returning state 175 | func (o *PooledObject) MarkReturning() { 176 | o.lock.Lock() 177 | o.markReturning() 178 | o.lock.Unlock() 179 | } 180 | 181 | func (o *PooledObject) markReturning() { 182 | o.state = StateReturning 183 | } 184 | 185 | // StartEvictionTest attempt to place the pooled object in the EVICTION state 186 | func (o *PooledObject) StartEvictionTest() bool { 187 | o.lock.Lock() 188 | defer o.lock.Unlock() 189 | if o.state == StateIdle { 190 | o.state = StateEviction 191 | return true 192 | } 193 | 194 | return false 195 | } 196 | 197 | // EndEvictionTest called to inform the object that the eviction test has ended. 198 | func (o *PooledObject) EndEvictionTest(idleQueue *collections.LinkedBlockingDeque) bool { 199 | o.lock.Lock() 200 | defer o.lock.Unlock() 201 | if o.state == StateEviction { 202 | o.state = StateIdle 203 | return true 204 | } else if o.state == StateEvictionReturnToHead { 205 | o.state = StateIdle 206 | if !idleQueue.OfferFirst(o) { 207 | // TODO - Should never happen 208 | panic(fmt.Errorf("Should never happen")) 209 | } 210 | } 211 | 212 | return false 213 | } 214 | -------------------------------------------------------------------------------- /object_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPooledObject(t *testing.T) { 11 | t.Parallel() 12 | 13 | object := &TestObject{Num: 1} 14 | pooledObject := NewPooledObject(object) 15 | pooledObject.MarkReturning() 16 | assert.Equal(t, StateReturning, pooledObject.GetState()) 17 | 18 | pooledObject.MarkAbandoned() 19 | assert.Equal(t, StateAbandoned, pooledObject.GetState()) 20 | } 21 | 22 | type TrackedUseObject struct { 23 | lastUsed time.Time 24 | } 25 | 26 | func (o *TrackedUseObject) GetLastUsed() time.Time { 27 | return o.lastUsed 28 | } 29 | 30 | func TestTrackedUse(t *testing.T) { 31 | t.Parallel() 32 | 33 | now := time.Now() 34 | object := &TrackedUseObject{lastUsed: now} 35 | trackedUse := TrackedUse(object) 36 | assert.Equal(t, now, trackedUse.GetLastUsed()) 37 | 38 | pooledObject := NewPooledObject(object) 39 | time.Sleep(20 * time.Millisecond) 40 | pooledObject.Allocate() 41 | time2 := pooledObject.GetLastUsedTime() 42 | assert.True(t, now != time2) 43 | object.lastUsed = time.Now() 44 | time3 := pooledObject.GetLastUsedTime() 45 | assert.Equal(t, object.lastUsed, time3) 46 | } 47 | 48 | func TestActiveTime(t *testing.T) { 49 | t.Parallel() 50 | 51 | object := &TrackedUseObject{} 52 | pooledObject := NewPooledObject(object) 53 | pooledObject.Allocate() 54 | time.Sleep(20 * time.Millisecond) 55 | pooledObject.Deallocate() 56 | assert.True(t, pooledObject.GetActiveTime() >= 20*time.Millisecond) 57 | } 58 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "math" 7 | "sync" 8 | "time" 9 | 10 | "github.com/jolestar/go-commons-pool/v2/collections" 11 | "github.com/jolestar/go-commons-pool/v2/concurrent" 12 | ) 13 | 14 | type baseErr struct { 15 | msg string 16 | } 17 | 18 | func (err *baseErr) Error() string { 19 | return err.msg 20 | } 21 | 22 | // IllegalStateErr when use pool in a illegal way, return this err 23 | type IllegalStateErr struct { 24 | baseErr 25 | } 26 | 27 | // NewIllegalStateErr return new IllegalStateErr 28 | func NewIllegalStateErr(msg string) *IllegalStateErr { 29 | return &IllegalStateErr{baseErr{msg}} 30 | } 31 | 32 | // NoSuchElementErr when no available object in pool, return this err 33 | type NoSuchElementErr struct { 34 | baseErr 35 | } 36 | 37 | // NewNoSuchElementErr return new NoSuchElementErr 38 | func NewNoSuchElementErr(msg string) *NoSuchElementErr { 39 | return &NoSuchElementErr{baseErr{msg}} 40 | } 41 | 42 | // ObjectPool is a generic object pool 43 | type ObjectPool struct { 44 | AbandonedConfig *AbandonedConfig 45 | Config *ObjectPoolConfig 46 | closed bool 47 | closeLock sync.Mutex 48 | evictionLock sync.Mutex 49 | idleObjects *collections.LinkedBlockingDeque 50 | allObjects *collections.SyncIdentityMap 51 | factory PooledObjectFactory 52 | createCount concurrent.AtomicInteger 53 | destroyedByEvictorCount concurrent.AtomicInteger 54 | destroyedCount concurrent.AtomicInteger 55 | destroyedByBorrowValidationCount concurrent.AtomicInteger 56 | evictor *time.Ticker 57 | evictorStopChan chan struct{} 58 | evictorStopWG sync.WaitGroup 59 | evictionIterator collections.Iterator 60 | } 61 | 62 | // NewObjectPool return new ObjectPool, init with PooledObjectFactory and ObjectPoolConfig 63 | func NewObjectPool(ctx context.Context, factory PooledObjectFactory, config *ObjectPoolConfig) *ObjectPool { 64 | return NewObjectPoolWithAbandonedConfig(ctx, factory, config, nil) 65 | } 66 | 67 | // NewObjectPoolWithDefaultConfig return new ObjectPool init with PooledObjectFactory and default config 68 | func NewObjectPoolWithDefaultConfig(ctx context.Context, factory PooledObjectFactory) *ObjectPool { 69 | return NewObjectPool(ctx, factory, NewDefaultPoolConfig()) 70 | } 71 | 72 | // NewObjectPoolWithAbandonedConfig return new ObjectPool init with PooledObjectFactory, ObjectPoolConfig, and AbandonedConfig 73 | func NewObjectPoolWithAbandonedConfig(ctx context.Context, factory PooledObjectFactory, config *ObjectPoolConfig, abandonedConfig *AbandonedConfig) *ObjectPool { 74 | pool := ObjectPool{factory: factory, Config: config, 75 | idleObjects: collections.NewDeque(math.MaxInt32), 76 | allObjects: collections.NewSyncMap(), 77 | createCount: concurrent.AtomicInteger(0), 78 | destroyedByEvictorCount: concurrent.AtomicInteger(0), 79 | destroyedCount: concurrent.AtomicInteger(0), 80 | AbandonedConfig: abandonedConfig} 81 | pool.StartEvictor() 82 | return &pool 83 | } 84 | 85 | // AddObject create an object using the PooledObjectFactory factory, passivate it, and then place it in 86 | // the idle object pool. AddObject is useful for "pre-loading" 87 | // a pool with idle objects. (Optional operation). 88 | func (pool *ObjectPool) AddObject(ctx context.Context) error { 89 | if pool.IsClosed() { 90 | return NewIllegalStateErr("Pool not open") 91 | } 92 | if pool.factory == nil { 93 | return NewIllegalStateErr("Cannot add objects without a factory.") 94 | } 95 | p, e := pool.create(ctx) 96 | if e != nil { 97 | return e 98 | } 99 | e = pool.addIdleObject(ctx, p) 100 | if e != nil { 101 | pool.destroy(ctx, p) 102 | return e 103 | } 104 | return nil 105 | } 106 | 107 | func (pool *ObjectPool) addIdleObject(ctx context.Context, p *PooledObject) error { 108 | if p != nil { 109 | err := pool.factory.PassivateObject(ctx, p) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | if pool.Config.LIFO { 115 | err = pool.idleObjects.AddFirst(p) 116 | } else { 117 | err = pool.idleObjects.AddLast(p) 118 | } 119 | 120 | if err != nil { 121 | return err 122 | } 123 | } 124 | 125 | return nil 126 | } 127 | 128 | // BorrowObject obtains an instance from pool. 129 | // Instances returned from pool method will have been either newly created 130 | // with PooledObjectFactory.MakeObject or will be a previously 131 | // idle object and have been activated with 132 | // PooledObjectFactory.ActivateObject and then validated with 133 | // PooledObjectFactory.ValidateObject. 134 | // If the pool is full (based on the number of objects in the pool and the 135 | // value of the MaxTotal configuration field), this method will block until 136 | // an object is returned to the pool or the context is done. 137 | // 138 | // By contract, clients must return the borrowed instance 139 | // using ReturnObject, InvalidateObject 140 | func (pool *ObjectPool) BorrowObject(ctx context.Context) (interface{}, error) { 141 | return pool.borrowObject(ctx) 142 | } 143 | 144 | // GetNumIdle return the number of instances currently idle in pool. This may be 145 | // considered an approximation of the number of objects that can be 146 | // BorrowObject borrowed without creating any new instances. 147 | func (pool *ObjectPool) GetNumIdle() int { 148 | return pool.idleObjects.Size() 149 | } 150 | 151 | // GetNumActive return the number of instances currently borrowed from pool. 152 | func (pool *ObjectPool) GetNumActive() int { 153 | return pool.allObjects.Size() - pool.idleObjects.Size() 154 | } 155 | 156 | // GetDestroyedCount return destroyed object count of this pool 157 | func (pool *ObjectPool) GetDestroyedCount() int { 158 | return int(pool.destroyedCount.Get()) 159 | } 160 | 161 | // GetDestroyedByBorrowValidationCount return destroyed object count when borrow validation 162 | func (pool *ObjectPool) GetDestroyedByBorrowValidationCount() int { 163 | return int(pool.destroyedByBorrowValidationCount.Get()) 164 | } 165 | 166 | func (pool *ObjectPool) removeAbandoned(ctx context.Context, config *AbandonedConfig) { 167 | // Generate a list of abandoned objects to remove 168 | var remove []*PooledObject 169 | objects := pool.allObjects.Values() 170 | for _, o := range objects { 171 | pooledObject := o.(*PooledObject) 172 | pooledObject.lock.Lock() 173 | if pooledObject.state == StateAllocated && 174 | time.Since(pooledObject.GetLastUsedTime()) > config.RemoveAbandonedTimeout { 175 | pooledObject.markAbandoned() 176 | remove = append(remove, pooledObject) 177 | } 178 | pooledObject.lock.Unlock() 179 | } 180 | 181 | // Now remove the abandoned objects 182 | for _, pooledObject := range remove { 183 | //if (config.getLogAbandoned()) { 184 | //pooledObject.printStackTrace(ac.getLogWriter()); 185 | //} 186 | pool.InvalidateObject(ctx, pooledObject.Object) 187 | } 188 | } 189 | 190 | func (pool *ObjectPool) create(ctx context.Context) (*PooledObject, error) { 191 | localMaxTotal := pool.Config.MaxTotal 192 | newCreateCount := pool.createCount.IncrementAndGet() 193 | if localMaxTotal > -1 && int(newCreateCount) > localMaxTotal || 194 | newCreateCount >= math.MaxInt32 { 195 | pool.createCount.DecrementAndGet() 196 | return nil, nil 197 | } 198 | 199 | p, e := pool.factory.MakeObject(ctx) 200 | if e != nil { 201 | pool.createCount.DecrementAndGet() 202 | return nil, e 203 | } 204 | 205 | // ac := pool.abandonedConfig; 206 | // if (ac != null && ac.getLogAbandoned()) { 207 | // p.setLogAbandoned(true); 208 | // } 209 | pool.allObjects.Put(p.Object, p) 210 | return p, nil 211 | } 212 | 213 | func (pool *ObjectPool) destroy(ctx context.Context, toDestroy *PooledObject) { 214 | pool.doDestroy(ctx, toDestroy, false) 215 | } 216 | 217 | func (pool *ObjectPool) doDestroy(ctx context.Context, toDestroy *PooledObject, inLock bool) { 218 | //golang has not recursive lock, so ... 219 | if inLock { 220 | toDestroy.invalidate() 221 | } else { 222 | toDestroy.Invalidate() 223 | } 224 | pool.idleObjects.RemoveFirstOccurrence(toDestroy) 225 | pool.allObjects.Remove(toDestroy.Object) 226 | pool.factory.DestroyObject(ctx, toDestroy) 227 | pool.destroyedCount.IncrementAndGet() 228 | pool.createCount.DecrementAndGet() 229 | } 230 | 231 | func (pool *ObjectPool) updateStatsBorrow(object *PooledObject, time time.Duration) { 232 | //TODO 233 | } 234 | 235 | func (pool *ObjectPool) updateStatsReturn(activeTime time.Duration) { 236 | //TODO 237 | //returnedCount.incrementAndGet(); 238 | //activeTimes.add(activeTime); 239 | } 240 | 241 | func (pool *ObjectPool) borrowObject(ctx context.Context) (interface{}, error) { 242 | if pool.IsClosed() { 243 | return nil, NewIllegalStateErr("Pool not open") 244 | } 245 | ac := pool.AbandonedConfig 246 | if ac != nil && ac.RemoveAbandonedOnBorrow && 247 | (pool.GetNumIdle() < 2) && 248 | (pool.GetNumActive() > pool.Config.MaxTotal-3) { 249 | pool.removeAbandoned(ctx, ac) 250 | } 251 | 252 | var p *PooledObject 253 | var e error 254 | // Get local copy of current config so it is consistent for entire 255 | // method execution 256 | blockWhenExhausted := pool.Config.BlockWhenExhausted 257 | 258 | var create bool 259 | waitTime := time.Now() 260 | var ok bool 261 | for p == nil { 262 | create = false 263 | if blockWhenExhausted { 264 | p, ok = pool.idleObjects.PollFirst().(*PooledObject) 265 | if !ok { 266 | p, e = pool.create(ctx) 267 | if e != nil { 268 | return nil, e 269 | } 270 | if p != nil { 271 | create = true 272 | ok = true 273 | } 274 | } 275 | if p == nil { 276 | obj, err := pool.idleObjects.PollFirstWithContext(ctx) 277 | if err != nil { 278 | return nil, err 279 | } 280 | p, ok = obj.(*PooledObject) 281 | } 282 | if !ok { 283 | return nil, NewNoSuchElementErr("Timeout waiting for idle object") 284 | } 285 | if !p.Allocate() { 286 | p = nil 287 | } 288 | } else { 289 | p, ok = pool.idleObjects.PollFirst().(*PooledObject) 290 | if !ok { 291 | p, e = pool.create(ctx) 292 | if e != nil { 293 | return nil, e 294 | } 295 | if p != nil { 296 | create = true 297 | } 298 | } 299 | if p == nil { 300 | return nil, NewNoSuchElementErr("Pool exhausted") 301 | } 302 | if !p.Allocate() { 303 | p = nil 304 | } 305 | } 306 | 307 | if p != nil { 308 | e := pool.factory.ActivateObject(ctx, p) 309 | if e != nil { 310 | pool.destroy(ctx, p) 311 | p = nil 312 | if create { 313 | return nil, NewNoSuchElementErr("Unable to activate object") 314 | } 315 | } 316 | } 317 | if p != nil && (pool.Config.TestOnBorrow || create && pool.Config.TestOnCreate) { 318 | validate := pool.factory.ValidateObject(ctx, p) 319 | if !validate { 320 | pool.destroy(ctx, p) 321 | pool.destroyedByBorrowValidationCount.IncrementAndGet() 322 | p = nil 323 | if create { 324 | return nil, NewNoSuchElementErr("Unable to validate object") 325 | } 326 | } 327 | } 328 | } 329 | 330 | pool.updateStatsBorrow(p, time.Since(waitTime)) 331 | return p.Object, nil 332 | } 333 | 334 | func (pool *ObjectPool) isAbandonedConfig() bool { 335 | return pool.AbandonedConfig != nil 336 | } 337 | 338 | func (pool *ObjectPool) ensureIdle(ctx context.Context, idleCount int, always bool) { 339 | if idleCount < 1 || pool.IsClosed() || (!always && !pool.idleObjects.HasTakeWaiters()) { 340 | return 341 | } 342 | 343 | for pool.idleObjects.Size() < idleCount { 344 | //just ignore create error 345 | p, _ := pool.create(ctx) 346 | if p == nil { 347 | // Can't create objects, no reason to think another call to 348 | // create will work. Give up. 349 | break 350 | } 351 | if pool.Config.LIFO { 352 | pool.idleObjects.AddFirst(p) 353 | } else { 354 | pool.idleObjects.AddLast(p) 355 | } 356 | } 357 | if pool.IsClosed() { 358 | // Pool closed while object was being added to idle objects. 359 | // Make sure the returned object is destroyed rather than left 360 | // in the idle object pool (which would effectively be a leak) 361 | pool.Clear(ctx) 362 | } 363 | } 364 | 365 | // IsClosed return this pool is closed 366 | func (pool *ObjectPool) IsClosed() bool { 367 | return pool.getClose() 368 | } 369 | 370 | // ReturnObject return an instance to the pool. By contract, object 371 | // must have been obtained using BorrowObject() 372 | func (pool *ObjectPool) ReturnObject(ctx context.Context, object interface{}) error { 373 | if object == nil { 374 | return errors.New("object is nil") 375 | } 376 | p, ok := pool.allObjects.Get(object).(*PooledObject) 377 | 378 | if !ok { 379 | if !pool.isAbandonedConfig() { 380 | return NewIllegalStateErr( 381 | "Returned object not currently part of pool") 382 | } 383 | return nil // Object was abandoned and removed 384 | } 385 | p.lock.Lock() 386 | 387 | state := p.state 388 | if state != StateAllocated { 389 | p.lock.Unlock() 390 | return NewIllegalStateErr( 391 | "Object has already been returned to pool or is invalid") 392 | } 393 | //use unlock method markReturning() not MarkReturning 394 | // because go lock is not recursive 395 | p.markReturning() // Keep from being marked abandoned 396 | p.lock.Unlock() 397 | activeTime := p.GetActiveTime() 398 | 399 | if pool.Config.TestOnReturn { 400 | if !pool.factory.ValidateObject(ctx, p) { 401 | pool.destroy(ctx, p) 402 | pool.ensureIdle(ctx, 1, false) 403 | pool.updateStatsReturn(activeTime) 404 | // swallowException(e); 405 | return nil 406 | } 407 | } 408 | 409 | err := pool.factory.PassivateObject(ctx, p) 410 | if err != nil { 411 | //swallowException(e1); 412 | pool.destroy(ctx, p) 413 | pool.ensureIdle(ctx, 1, false) 414 | pool.updateStatsReturn(activeTime) 415 | // swallowException(e); 416 | return nil 417 | } 418 | 419 | if !p.Deallocate() { 420 | return NewIllegalStateErr("Object has already been returned to pool or is invalid") 421 | } 422 | 423 | maxIdleSave := pool.Config.MaxIdle 424 | if pool.IsClosed() || maxIdleSave > -1 && maxIdleSave <= pool.idleObjects.Size() { 425 | pool.destroy(ctx, p) 426 | } else { 427 | if pool.Config.LIFO { 428 | pool.idleObjects.AddFirst(p) 429 | } else { 430 | pool.idleObjects.AddLast(p) 431 | } 432 | if pool.IsClosed() { 433 | // Pool closed while object was being added to idle objects. 434 | // Make sure the returned object is destroyed rather than left 435 | // in the idle object pool (which would effectively be a leak) 436 | pool.Clear(ctx) 437 | } 438 | } 439 | pool.updateStatsReturn(activeTime) 440 | return nil 441 | } 442 | 443 | // Clear clears any objects sitting idle in the pool, releasing any associated 444 | // resources (optional operation). Idle objects cleared must be 445 | // PooledObjectFactory.DestroyObject(PooledObject) . 446 | func (pool *ObjectPool) Clear(ctx context.Context) { 447 | p, ok := pool.idleObjects.PollFirst().(*PooledObject) 448 | 449 | for ok { 450 | pool.destroy(ctx, p) 451 | p, ok = pool.idleObjects.PollFirst().(*PooledObject) 452 | } 453 | } 454 | 455 | // InvalidateObject invalidates an object from the pool. 456 | // By contract, object must have been obtained 457 | // using BorrowObject. 458 | // This method should be used when an object that has been borrowed is 459 | // determined (due to an exception or other problem) to be invalid. 460 | func (pool *ObjectPool) InvalidateObject(ctx context.Context, object interface{}) error { 461 | p, ok := pool.allObjects.Get(object).(*PooledObject) 462 | if !ok { 463 | if pool.isAbandonedConfig() { 464 | return nil 465 | } 466 | return NewIllegalStateErr( 467 | "Invalidated object not currently part of pool") 468 | } 469 | p.lock.Lock() 470 | if p.state != StateInvalid { 471 | pool.doDestroy(ctx, p, true) 472 | } 473 | p.lock.Unlock() 474 | pool.ensureIdle(ctx, 1, false) 475 | return nil 476 | } 477 | 478 | // Close pool, and free any resources associated with it. 479 | func (pool *ObjectPool) Close(ctx context.Context) { 480 | if pool.IsClosed() { 481 | return 482 | } 483 | 484 | if pool.getClose() { 485 | return 486 | } 487 | 488 | // Stop the evictor before the pool is closed since evict() calls 489 | // assertOpen() 490 | pool.startEvictor(-1) 491 | 492 | pool.setClose(true) 493 | // This clear removes any idle objects 494 | pool.Clear(ctx) 495 | 496 | // Release any goroutines that were waiting for an object 497 | pool.idleObjects.InterruptTakeWaiters() 498 | } 499 | 500 | // StartEvictor start background evictor goroutine, pool.Config.TimeBetweenEvictionRuns must a positive number. 501 | // if ObjectPool.Config.TimeBetweenEvictionRuns change, should call pool method to let it to take effect. 502 | func (pool *ObjectPool) StartEvictor() { 503 | pool.startEvictor(pool.Config.TimeBetweenEvictionRuns) 504 | } 505 | 506 | func (pool *ObjectPool) startEvictor(delay time.Duration) { 507 | pool.evictionLock.Lock() 508 | defer pool.evictionLock.Unlock() 509 | if pool.evictor != nil { 510 | pool.evictor.Stop() 511 | close(pool.evictorStopChan) 512 | //Ensure old evictor goroutine quit, only one evictor goroutine at same time, then set evictor to nil. 513 | pool.evictorStopWG.Wait() 514 | pool.evictor = nil 515 | pool.evictionIterator = nil 516 | } 517 | if delay > 0 { 518 | pool.evictor = time.NewTicker(delay) 519 | pool.evictorStopChan = make(chan struct{}) 520 | pool.evictorStopWG = sync.WaitGroup{} 521 | pool.evictorStopWG.Add(1) 522 | go func() { 523 | for { 524 | select { 525 | case <-pool.evictor.C: 526 | pool.evict(pool.Config.EvictionContext) 527 | pool.ensureMinIdle(pool.Config.EvictionContext) 528 | case <-pool.evictorStopChan: 529 | pool.evictorStopWG.Done() 530 | return 531 | } 532 | } 533 | }() 534 | } 535 | } 536 | 537 | func (pool *ObjectPool) getEvictionPolicy() EvictionPolicy { 538 | evictionPolicy := GetEvictionPolicy(pool.Config.EvictionPolicyName) 539 | if evictionPolicy == nil { 540 | evictionPolicy = GetEvictionPolicy(DefaultEvictionPolicyName) 541 | } 542 | return evictionPolicy 543 | } 544 | 545 | func (pool *ObjectPool) getNumTests() int { 546 | numTestsPerEvictionRun := pool.Config.NumTestsPerEvictionRun 547 | if numTestsPerEvictionRun >= 0 { 548 | if numTestsPerEvictionRun < pool.idleObjects.Size() { 549 | return numTestsPerEvictionRun 550 | } 551 | return pool.idleObjects.Size() 552 | } 553 | return int((math.Ceil(float64(pool.idleObjects.Size()) / math.Abs(float64(numTestsPerEvictionRun))))) 554 | } 555 | 556 | // idleIterator return pool idleObjects iterator 557 | func (pool *ObjectPool) idleIterator() collections.Iterator { 558 | if pool.Config.LIFO { 559 | return pool.idleObjects.DescendingIterator() 560 | } 561 | return pool.idleObjects.Iterator() 562 | } 563 | 564 | func (pool *ObjectPool) getMinIdle() int { 565 | maxIdleSave := pool.Config.MaxIdle 566 | if pool.Config.MinIdle > maxIdleSave { 567 | return maxIdleSave 568 | } 569 | return pool.Config.MinIdle 570 | } 571 | 572 | func (pool *ObjectPool) evict(ctx context.Context) { 573 | defer func() { 574 | ac := pool.AbandonedConfig 575 | if ac != nil && ac.RemoveAbandonedOnMaintenance { 576 | pool.removeAbandoned(ctx, ac) 577 | } 578 | }() 579 | 580 | if pool.idleObjects.Size() == 0 { 581 | return 582 | } 583 | var underTest *PooledObject 584 | evictionPolicy := pool.getEvictionPolicy() 585 | 586 | var idleEvictTime = time.Duration(math.MaxInt64) 587 | if pool.Config.MinEvictableIdleTime > 0 { 588 | idleEvictTime = pool.Config.MinEvictableIdleTime 589 | } 590 | 591 | var idleSoftEvictTime = time.Duration(math.MaxInt64) 592 | if pool.Config.SoftMinEvictableIdleTime > 0 { 593 | idleSoftEvictTime = pool.Config.SoftMinEvictableIdleTime 594 | } 595 | evictionConfig := EvictionConfig{ 596 | IdleEvictTime: idleEvictTime, 597 | IdleSoftEvictTime: idleSoftEvictTime, 598 | MinIdle: pool.Config.MinIdle, 599 | Context: pool.Config.EvictionContext} 600 | 601 | testWhileIdle := pool.Config.TestWhileIdle 602 | for i, m := 0, pool.getNumTests(); i < m; i++ { 603 | if pool.evictionIterator == nil || !pool.evictionIterator.HasNext() { 604 | pool.evictionIterator = pool.idleIterator() 605 | } 606 | if !pool.evictionIterator.HasNext() { 607 | // Pool exhausted, nothing to do here 608 | return 609 | } 610 | 611 | underTest = pool.evictionIterator.Next().(*PooledObject) 612 | if underTest == nil { 613 | // Object was borrowed in another goroutine 614 | // Don't count pool as an eviction test so reduce i; 615 | i-- 616 | pool.evictionIterator = nil 617 | continue 618 | } 619 | 620 | if !underTest.StartEvictionTest() { 621 | // Object was borrowed in another goroutine 622 | // Don't count pool as an eviction test so reduce i; 623 | i-- 624 | continue 625 | } 626 | 627 | // User provided eviction policy could throw all sorts of 628 | // crazy exceptions. Protect against such an exception 629 | // killing the eviction goroutine. 630 | 631 | evict := evictionPolicy.Evict(&evictionConfig, underTest, pool.idleObjects.Size()) 632 | 633 | if evict { 634 | pool.destroy(ctx, underTest) 635 | pool.destroyedByEvictorCount.IncrementAndGet() 636 | } else { 637 | active := false 638 | if testWhileIdle { 639 | err := pool.factory.ActivateObject(ctx, underTest) 640 | if err == nil { 641 | active = true 642 | } else { 643 | pool.destroy(ctx, underTest) 644 | pool.destroyedByEvictorCount.IncrementAndGet() 645 | } 646 | if active { 647 | if !pool.factory.ValidateObject(ctx, underTest) { 648 | pool.destroy(ctx, underTest) 649 | pool.destroyedByEvictorCount.IncrementAndGet() 650 | } else { 651 | err := pool.factory.PassivateObject(ctx, underTest) 652 | if err != nil { 653 | pool.destroy(ctx, underTest) 654 | pool.destroyedByEvictorCount.IncrementAndGet() 655 | } 656 | } 657 | } 658 | } 659 | if !underTest.EndEvictionTest(pool.idleObjects) { 660 | // TODO - May need to add code here once additional 661 | // states are used 662 | } 663 | } 664 | } 665 | } 666 | 667 | func (pool *ObjectPool) getClose() bool { 668 | pool.closeLock.Lock() 669 | defer pool.closeLock.Unlock() 670 | // in java commons pool, closed is volatile, golang has not volatile, so use mutex to avoid data race 671 | return pool.closed 672 | } 673 | 674 | func (pool *ObjectPool) setClose(closed bool) { 675 | pool.closeLock.Lock() 676 | defer pool.closeLock.Unlock() 677 | pool.closed = closed 678 | } 679 | 680 | func (pool *ObjectPool) ensureMinIdle(ctx context.Context) { 681 | pool.ensureIdle(ctx, pool.getMinIdle(), true) 682 | } 683 | 684 | // PreparePool Tries to ensure that {@link #getMinIdle()} idle instances are available 685 | // in the pool. 686 | func (pool *ObjectPool) PreparePool(ctx context.Context) { 687 | if pool.getMinIdle() < 1 { 688 | return 689 | } 690 | pool.ensureMinIdle(ctx) 691 | } 692 | 693 | // Prefill is util func for pre fill pool object 694 | func Prefill(ctx context.Context, pool *ObjectPool, count int) { 695 | for i := 0; i < count; i++ { 696 | pool.AddObject(ctx) 697 | } 698 | } 699 | -------------------------------------------------------------------------------- /pool_abandoned_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/jolestar/go-commons-pool/v2/concurrent" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/suite" 13 | ) 14 | 15 | type AbandonedTestObject struct { 16 | lock sync.Mutex 17 | active bool 18 | destroyed bool 19 | _hash int 20 | _abandoned bool 21 | } 22 | 23 | func (o *AbandonedTestObject) setActive(active bool) { 24 | o.lock.Lock() 25 | o.active = active 26 | o.lock.Unlock() 27 | } 28 | 29 | func NewAbandonedTestObject() *AbandonedTestObject { 30 | object := AbandonedTestObject{} 31 | return &object 32 | } 33 | 34 | func (o *AbandonedTestObject) GetLastUsed() time.Time { 35 | if o._abandoned { 36 | // Abandoned object sweep will occur no matter what the value of removeAbandonedTimeout, 37 | // because suit indicates that suit object was last used decades ago 38 | return time.Time{}.Add(1) 39 | } 40 | // Abandoned object sweep won't clean up suit object 41 | return time.Time{} 42 | } 43 | 44 | func (o *AbandonedTestObject) destroy() { 45 | o.lock.Lock() 46 | o.destroyed = true 47 | o.lock.Unlock() 48 | } 49 | 50 | func (o *AbandonedTestObject) hashCode() int { 51 | return o._hash 52 | } 53 | 54 | func TestAbandonedTestObject(t *testing.T) { 55 | t.Parallel() 56 | 57 | obj := NewAbandonedTestObject() 58 | trackedUse := TrackedUse(obj) 59 | assert.Zero(t, trackedUse.GetLastUsed()) 60 | } 61 | 62 | type SimpleAbandonedFactory struct { 63 | destroyLatency time.Duration 64 | validateLatency time.Duration 65 | counter concurrent.AtomicInteger 66 | } 67 | 68 | func NewSimpleAbandonedFactory() *SimpleAbandonedFactory { 69 | return &SimpleAbandonedFactory{} 70 | } 71 | 72 | func (f *SimpleAbandonedFactory) MakeObject(context.Context) (*PooledObject, error) { 73 | if debugTest { 74 | fmt.Println("factory MakeObject") 75 | } 76 | object := NewAbandonedTestObject() 77 | object._hash = int(f.counter.IncrementAndGet()) 78 | return NewPooledObject(object), nil 79 | } 80 | 81 | func (f *SimpleAbandonedFactory) DestroyObject(ctx context.Context, object *PooledObject) error { 82 | if debugTest { 83 | fmt.Println("factory DestroyObject") 84 | } 85 | object.Object.(*AbandonedTestObject).setActive(false) 86 | // while destroying instances, yield control to other threads 87 | // helps simulate threading errors 88 | //Thread.yield(); 89 | if f.destroyLatency != 0 { 90 | time.Sleep(f.destroyLatency) 91 | } 92 | object.Object.(*AbandonedTestObject).destroy() 93 | return nil 94 | } 95 | 96 | func (f *SimpleAbandonedFactory) ValidateObject(ctx context.Context, object *PooledObject) bool { 97 | if debugTest { 98 | fmt.Println("factory ValidateObject") 99 | } 100 | time.Sleep(f.validateLatency) 101 | return true 102 | } 103 | 104 | func (f *SimpleAbandonedFactory) ActivateObject(ctx context.Context, object *PooledObject) error { 105 | if debugTest { 106 | fmt.Println("factory ActivateObject") 107 | defer fmt.Println("factory ActivateObject end") 108 | } 109 | object.Object.(*AbandonedTestObject).setActive(true) 110 | return nil 111 | } 112 | 113 | func (f *SimpleAbandonedFactory) PassivateObject(ctx context.Context, object *PooledObject) error { 114 | if debugTest { 115 | fmt.Println("factory PassivateObject") 116 | } 117 | object.Object.(*AbandonedTestObject).setActive(false) 118 | return nil 119 | } 120 | 121 | type PoolAbandonedTestSuite struct { 122 | suite.Suite 123 | pool *ObjectPool 124 | factory *SimpleAbandonedFactory 125 | } 126 | 127 | func (suit *PoolAbandonedTestSuite) NoErrorWithResult(object interface{}, err error) interface{} { 128 | suit.NotNil(object) 129 | suit.Nil(err) 130 | return object 131 | } 132 | 133 | func (suit *PoolAbandonedTestSuite) ErrorWithResult(object interface{}, err error) error { 134 | suit.Nil(object) 135 | suit.NotNil(err) 136 | return err 137 | } 138 | 139 | func TestPoolAbandonedTestSuite(t *testing.T) { 140 | t.Parallel() 141 | 142 | suite.Run(t, new(PoolAbandonedTestSuite)) 143 | } 144 | 145 | func (suit *PoolAbandonedTestSuite) SetupTest() { 146 | abandonedConfig := NewDefaultAbandonedConfig() 147 | 148 | // -- Uncomment the following line to enable logging -- 149 | // abandonedConfig.setLogAbandoned(true); 150 | 151 | abandonedConfig.RemoveAbandonedOnBorrow = true 152 | abandonedConfig.RemoveAbandonedTimeout = 1 * time.Second 153 | factory := NewSimpleAbandonedFactory() 154 | config := NewDefaultPoolConfig() 155 | suit.factory = factory 156 | suit.pool = NewObjectPoolWithAbandonedConfig(context.Background(), factory, config, abandonedConfig) 157 | } 158 | 159 | func (suit *PoolAbandonedTestSuite) TearDownTest() { 160 | ctx := context.Background() 161 | suit.pool.Clear(ctx) 162 | suit.pool.Close(ctx) 163 | suit.pool = nil 164 | suit.factory = nil 165 | } 166 | 167 | func concurrentBorrower(ctx context.Context, pool *ObjectPool, objects chan *AbandonedTestObject, wait *sync.WaitGroup) { 168 | go func() { 169 | o, _ := pool.BorrowObject(ctx) 170 | if o != nil { 171 | objects <- o.(*AbandonedTestObject) 172 | } 173 | wait.Done() 174 | }() 175 | } 176 | 177 | func concurrentReturner(pool *ObjectPool, object *AbandonedTestObject, wait *sync.WaitGroup) { 178 | go func() { 179 | ctx := context.Background() 180 | wait.Wait() 181 | time.Sleep(20 * time.Millisecond) 182 | pool.ReturnObject(ctx, object) 183 | }() 184 | } 185 | 186 | /** 187 | * Tests fix for Bug 28579, a bug in AbandonedObjectPool that causes numActive to go negative 188 | * in GenericObjectPool 189 | */ 190 | func (suit *PoolAbandonedTestSuite) TestConcurrentInvalidation() { 191 | ctx := context.Background() 192 | poolSize := 30 193 | suit.pool.Config.MaxTotal = poolSize 194 | suit.pool.Config.MaxIdle = poolSize 195 | suit.pool.Config.BlockWhenExhausted = false 196 | 197 | // Exhaust the connection pool 198 | vec := make([]*AbandonedTestObject, poolSize) 199 | for i := 0; i < poolSize; i++ { 200 | vec[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*AbandonedTestObject) 201 | } 202 | 203 | // Abandon all borrowed objects 204 | for i := 0; i < len(vec); i++ { 205 | vec[i]._abandoned = true 206 | } 207 | 208 | // Try launching a bunch of borrows concurrently. Abandoned sweep will be triggered for each. 209 | concurrentBorrows := 5 210 | 211 | objects := make(chan *AbandonedTestObject, poolSize) 212 | wait := sync.WaitGroup{} 213 | wait.Add(concurrentBorrows) 214 | for i := 0; i < concurrentBorrows; i++ { 215 | concurrentBorrower(ctx, suit.pool, objects, &wait) 216 | } 217 | 218 | // Wait for all the goroutine to finish 219 | wait.Wait() 220 | 221 | for i := 0; i < len(objects); i++ { 222 | vec = append(vec, <-objects) 223 | } 224 | close(objects) 225 | 226 | // Return all objects that have not been destroyed 227 | for i := 0; i < len(vec); i++ { 228 | pto := vec[i] 229 | if pto.active { 230 | suit.NoError(suit.pool.ReturnObject(ctx, pto)) 231 | } 232 | } 233 | 234 | // Now, the number of active instances should be 0 235 | suit.True(suit.pool.GetNumActive() == 0, "numActive should have been 0, was %v", suit.pool.GetNumActive()) 236 | } 237 | 238 | /** 239 | * Verify that an object that gets flagged as abandoned and is subsequently returned 240 | * is destroyed instead of being returned to the pool (and possibly later destroyed 241 | * inappropriately). 242 | */ 243 | func (suit *PoolAbandonedTestSuite) TestAbandonedReturn() { 244 | ctx := context.Background() 245 | suit.pool.AbandonedConfig.RemoveAbandonedOnBorrow = true 246 | suit.pool.AbandonedConfig.RemoveAbandonedOnMaintenance = false 247 | suit.pool.AbandonedConfig.RemoveAbandonedTimeout = 1 * time.Second 248 | suit.factory.destroyLatency = 200 * time.Millisecond 249 | 250 | n := 10 251 | suit.pool.Config.MaxTotal = n 252 | suit.pool.Config.BlockWhenExhausted = false 253 | var obj *AbandonedTestObject 254 | for i := 0; i < n-2; i++ { 255 | obj = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*AbandonedTestObject) 256 | } 257 | if obj == nil { 258 | suit.Fail("Unable to borrow object from pool") 259 | } 260 | wait := new(sync.WaitGroup) 261 | wait.Add(1) 262 | deadMansHash := obj.hashCode() 263 | if debugTest { 264 | fmt.Println("deadMansHash:", deadMansHash) 265 | } 266 | concurrentReturner(suit.pool, obj, wait) 267 | time.Sleep(2 * time.Second) // abandon checked out instances 268 | // Now start a race - returner waits until borrowObject has kicked 269 | // off removeAbandoned and then returns an instance that borrowObject 270 | // will deem abandoned. Make sure it is not returned to the borrower. 271 | wait.Done() // short delay, then return instance 272 | obj2 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*AbandonedTestObject) 273 | suit.True(obj2.hashCode() != deadMansHash) 274 | suit.Equal(0, suit.pool.GetNumIdle()) 275 | suit.Equal(1, suit.pool.GetNumActive()) 276 | } 277 | 278 | /** 279 | * Verify that an object that gets flagged as abandoned and is subsequently 280 | * invalidated is only destroyed (and pool counter decremented) once. 281 | */ 282 | func (suit *PoolAbandonedTestSuite) TestAbandonedInvalidate() { 283 | ctx := context.Background() 284 | suit.pool.AbandonedConfig.RemoveAbandonedOnBorrow = false 285 | suit.pool.AbandonedConfig.RemoveAbandonedOnMaintenance = true 286 | suit.pool.AbandonedConfig.RemoveAbandonedTimeout = 1 * time.Second 287 | // destroys take 200 ms 288 | suit.factory.destroyLatency = 200 * time.Millisecond 289 | n := 10 290 | suit.pool.Config.MaxTotal = n 291 | suit.pool.Config.BlockWhenExhausted = false 292 | suit.pool.Config.TimeBetweenEvictionRuns = 500 * time.Millisecond 293 | suit.pool.StartEvictor() 294 | 295 | var obj *AbandonedTestObject 296 | for i := 0; i < 5; i++ { 297 | obj = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*AbandonedTestObject) 298 | } 299 | 300 | time.Sleep(1 * time.Second) // abandon checked out instances and let evictor start 301 | suit.pool.InvalidateObject(ctx, obj) // Should not trigger another destroy / decrement 302 | time.Sleep(2 * time.Second) // give evictor time to finish destroys 303 | suit.Equal(0, suit.pool.GetNumActive()) 304 | suit.Equal(5, suit.pool.GetDestroyedCount()) 305 | } 306 | 307 | /** 308 | * Verify that an object that the evictor identifies as abandoned while it 309 | * is in process of being returned to the pool is not destroyed. 310 | */ 311 | func (suit *PoolAbandonedTestSuite) TestRemoveAbandonedWhileReturning() { 312 | ctx := context.Background() 313 | 314 | suit.pool.AbandonedConfig.RemoveAbandonedOnBorrow = false 315 | suit.pool.AbandonedConfig.RemoveAbandonedOnMaintenance = true 316 | suit.pool.AbandonedConfig.RemoveAbandonedTimeout = 1 * time.Second 317 | 318 | suit.factory.validateLatency = 1 * time.Second 319 | n := 10 320 | 321 | suit.pool.Config.MaxTotal = n 322 | suit.pool.Config.BlockWhenExhausted = false 323 | suit.pool.Config.TimeBetweenEvictionRuns = 500 * time.Millisecond 324 | suit.pool.Config.TestOnReturn = true 325 | suit.pool.StartEvictor() 326 | 327 | // Borrow an object, wait long enough for it to be abandoned 328 | // then arrange for evictor to run while it is being returned 329 | // validation takes a second, evictor runs every 500 ms 330 | obj := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 331 | time.Sleep(50 * time.Millisecond) // abandon obj 332 | suit.pool.ReturnObject(ctx, obj) // evictor will run during validation 333 | obj2 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 334 | suit.Equal(obj, obj2) // should get original back 335 | suit.True(!obj2.(*AbandonedTestObject).destroyed) // and not destroyed 336 | } 337 | 338 | /** 339 | * Test case for https://issues.apache.org/jira/browse/DBCP-260. 340 | * Borrow and abandon all the available objects then attempt to borrow one 341 | * further object which should block until the abandoned objects are 342 | * removed. We don't want the test to block indefinitely when it fails so 343 | * use maxWait be check we don't actually have to wait that long. 344 | * 345 | */ 346 | func (suit *PoolAbandonedTestSuite) TestWhenExhaustedBlock() { 347 | ctx := context.Background() 348 | 349 | suit.pool.AbandonedConfig.RemoveAbandonedOnBorrow = false 350 | suit.pool.AbandonedConfig.RemoveAbandonedOnMaintenance = true 351 | suit.pool.AbandonedConfig.RemoveAbandonedTimeout = 1 * time.Second 352 | suit.pool.Config.MaxTotal = 1 353 | suit.pool.Config.TimeBetweenEvictionRuns = 500 * time.Millisecond 354 | suit.pool.StartEvictor() 355 | 356 | suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 357 | 358 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 359 | defer cancel() 360 | 361 | start := time.Now() 362 | o2 := suit.NoErrorWithResult(suit.pool.borrowObject(ctx)) 363 | elapsed := time.Since(start) 364 | 365 | suit.pool.ReturnObject(ctx, o2) 366 | 367 | suit.True(elapsed < 5*time.Second) 368 | } 369 | 370 | func (suit *PoolAbandonedTestSuite) TestAbandonedConfigReturnObjectError() { 371 | ctx := context.Background() 372 | 373 | obj := NewAbandonedTestObject() 374 | err := suit.pool.ReturnObject(ctx, obj) 375 | suit.Nil(err) 376 | } 377 | -------------------------------------------------------------------------------- /pool_perf_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "testing" 8 | "time" 9 | 10 | "github.com/jolestar/go-commons-pool/v2/concurrent" 11 | ) 12 | 13 | type BenchObject struct { 14 | Num int32 15 | } 16 | 17 | func BenchmarkPoolBorrowReturn(b *testing.B) { 18 | ctx := context.Background() 19 | pool := NewObjectPoolWithDefaultConfig(ctx, NewPooledObjectFactorySimple(func(context.Context) (interface{}, error) { 20 | return &BenchObject{Num: rand.Int31()}, nil 21 | })) 22 | defer pool.Close(ctx) 23 | for i := 0; i < b.N; i++ { 24 | o, err := pool.BorrowObject(ctx) 25 | if err != nil { 26 | b.Fail() 27 | } 28 | err = pool.ReturnObject(ctx, o) 29 | if err != nil { 30 | b.Fail() 31 | } 32 | } 33 | } 34 | 35 | func BenchmarkPoolBorrowReturnParallel(b *testing.B) { 36 | ctx := context.Background() 37 | pool := NewObjectPoolWithDefaultConfig(ctx, NewPooledObjectFactorySimple(func(context.Context) (interface{}, error) { 38 | return &BenchObject{}, nil 39 | })) 40 | pool.Config.MaxTotal = 100 41 | defer pool.Close(ctx) 42 | b.RunParallel(func(pb *testing.PB) { 43 | for pb.Next() { 44 | o, err := pool.BorrowObject(ctx) 45 | //fmt.Println("borrow:",reflect.ValueOf(o).Pointer()) 46 | if err != nil { 47 | fmt.Println(err) 48 | b.Fail() 49 | } 50 | err = pool.ReturnObject(ctx, o) 51 | //fmt.Println("return:",reflect.ValueOf(o).Pointer()) 52 | if err != nil { 53 | fmt.Println(err) 54 | b.Fail() 55 | } 56 | } 57 | }) 58 | } 59 | 60 | type SleepingObjectFactory struct { 61 | counter concurrent.AtomicInteger 62 | } 63 | 64 | func NewSleepingObjectFactory() *SleepingObjectFactory { 65 | return &SleepingObjectFactory{counter: concurrent.AtomicInteger(0)} 66 | } 67 | 68 | func (f *SleepingObjectFactory) MakeObject(context.Context) (*PooledObject, error) { 69 | if debugTest { 70 | fmt.Println("factory MakeObject", f.counter.Get()) 71 | } 72 | time.Sleep(500 * time.Millisecond) 73 | return NewPooledObject(getNthObject(int(f.counter.Get()))), nil 74 | } 75 | 76 | func (f *SleepingObjectFactory) DestroyObject(ctx context.Context, object *PooledObject) error { 77 | if debugTest { 78 | fmt.Println("factory DestroyObject", object) 79 | } 80 | time.Sleep(250 * time.Millisecond) 81 | return nil 82 | } 83 | 84 | func (f *SleepingObjectFactory) ValidateObject(ctx context.Context, object *PooledObject) bool { 85 | if debugTest { 86 | fmt.Println("factory ValidateObject", object) 87 | } 88 | time.Sleep(30 * time.Millisecond) 89 | return true 90 | } 91 | 92 | func (f *SleepingObjectFactory) ActivateObject(ctx context.Context, object *PooledObject) error { 93 | if debugTest { 94 | fmt.Println("factory ActivateObject", object) 95 | defer fmt.Println("factory ActivateObject end") 96 | } 97 | time.Sleep(10 * time.Millisecond) 98 | return nil 99 | } 100 | 101 | func (f *SleepingObjectFactory) PassivateObject(ctx context.Context, object *PooledObject) error { 102 | if debugTest { 103 | fmt.Println("factory PassivateObject", object) 104 | } 105 | time.Sleep(10 * time.Millisecond) 106 | return nil 107 | } 108 | 109 | type TaskStats struct { 110 | waiting int 111 | complete int 112 | totalBorrowTime time.Duration 113 | totalReturnTime time.Duration 114 | nrSamples int 115 | } 116 | 117 | func runOnce(ctx context.Context, pool *ObjectPool, taskStats *TaskStats) (time.Duration, time.Duration) { 118 | taskStats.waiting++ 119 | if debugTest { 120 | fmt.Println(" waiting: ", taskStats.waiting, " complete: ", taskStats.complete) 121 | } 122 | begin := time.Now() 123 | o, _ := pool.BorrowObject(ctx) 124 | borrowTime := time.Since(begin) 125 | taskStats.waiting-- 126 | 127 | if debugTest { 128 | fmt.Println( 129 | " waiting: ", taskStats.waiting, 130 | " complete: ", taskStats.complete) 131 | } 132 | 133 | begin = time.Now() 134 | pool.ReturnObject(ctx, o) 135 | returnTime := time.Since(begin) 136 | taskStats.complete++ 137 | 138 | return borrowTime, returnTime 139 | } 140 | 141 | func prefTask(ctx context.Context, pool *ObjectPool, nrIterations int) chan TaskStats { 142 | ch := make(chan TaskStats, 1) 143 | go func() { 144 | taskStats := TaskStats{} 145 | runOnce(ctx, pool, &taskStats) // warmup 146 | for i := 0; i < nrIterations; i++ { 147 | borrowTime, returnTime := runOnce(ctx, pool, &taskStats) 148 | taskStats.totalBorrowTime += borrowTime 149 | taskStats.totalReturnTime += returnTime 150 | taskStats.nrSamples++ 151 | if debugTest { 152 | fmt.Println("result ", taskStats.nrSamples, "borrow time: ", borrowTime, "\t"+ 153 | "return time: ", returnTime, "\t", "waiting: ", 154 | taskStats.waiting, "\t", "complete: ", 155 | taskStats.complete) 156 | } 157 | } 158 | ch <- taskStats 159 | }() 160 | return ch 161 | } 162 | 163 | func perfRun(ctx context.Context, iterations int, nrThreads int, maxTotal int, maxIdle int) { 164 | factory := NewSleepingObjectFactory() 165 | 166 | pool := NewObjectPoolWithDefaultConfig(ctx, factory) 167 | pool.Config.MaxTotal = maxTotal 168 | pool.Config.MaxIdle = maxIdle 169 | pool.Config.TestOnBorrow = true 170 | chs := make([]chan TaskStats, nrThreads) 171 | for i := 0; i < nrThreads; i++ { 172 | chs[i] = prefTask(ctx, pool, iterations) 173 | } 174 | 175 | aggregate := TaskStats{} 176 | for i := 0; i < nrThreads; i++ { 177 | taskStats := <-chs[i] 178 | close(chs[i]) 179 | aggregate.complete += taskStats.complete 180 | aggregate.nrSamples += taskStats.nrSamples 181 | aggregate.totalBorrowTime += taskStats.totalBorrowTime 182 | aggregate.totalReturnTime += taskStats.totalReturnTime 183 | aggregate.waiting += taskStats.waiting 184 | } 185 | 186 | fmt.Println("-----------------------------------------") 187 | fmt.Println("nrIterations: ", iterations) 188 | fmt.Println("nrThreads: ", nrThreads) 189 | fmt.Println("maxTotal: ", maxTotal) 190 | fmt.Println("maxIdle: ", maxIdle) 191 | fmt.Println("nrSamples: ", aggregate.nrSamples) 192 | fmt.Println("totalBorrowTime: ", aggregate.totalBorrowTime) 193 | fmt.Println("totalReturnTime: ", aggregate.totalReturnTime) 194 | fmt.Println("avg BorrowTime: ", 195 | aggregate.totalBorrowTime/time.Duration(aggregate.nrSamples)) 196 | fmt.Println("avg ReturnTime: ", 197 | aggregate.totalReturnTime/time.Duration(aggregate.nrSamples)) 198 | } 199 | 200 | func perfMain(ctx context.Context) { 201 | fmt.Println("Increase threads") 202 | perfRun(ctx, 1, 50, 5, 5) 203 | perfRun(ctx, 1, 100, 5, 5) 204 | perfRun(ctx, 1, 200, 5, 5) 205 | perfRun(ctx, 1, 400, 5, 5) 206 | 207 | fmt.Println("Increase threads & poolsize") 208 | perfRun(ctx, 1, 50, 5, 5) 209 | perfRun(ctx, 1, 100, 10, 10) 210 | perfRun(ctx, 1, 200, 20, 20) 211 | perfRun(ctx, 1, 400, 40, 40) 212 | 213 | fmt.Println("Increase maxIdle") 214 | perfRun(ctx, 1, 400, 40, 5) 215 | perfRun(ctx, 1, 400, 40, 40) 216 | } 217 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "math" 9 | "math/rand" 10 | "os" 11 | "reflect" 12 | "sync" 13 | "testing" 14 | "time" 15 | 16 | "github.com/fortytw2/leaktest" 17 | "github.com/jolestar/go-commons-pool/v2/collections" 18 | "github.com/jolestar/go-commons-pool/v2/concurrent" 19 | "github.com/stretchr/testify/assert" 20 | "github.com/stretchr/testify/suite" 21 | ) 22 | 23 | type TestObject struct { 24 | Num int 25 | } 26 | 27 | var ( 28 | debugTest = false 29 | ) 30 | 31 | func init() { 32 | rand.Seed(time.Now().UnixNano()) 33 | } 34 | 35 | type SimpleFactory struct { 36 | makeCounter int 37 | activationCounter int 38 | validateCounter int 39 | activeCount int 40 | evenValid bool 41 | oddValid bool 42 | exceptionOnPassivate bool 43 | exceptionOnActivate bool 44 | exceptionOnDestroy bool 45 | exceptionOnMake bool 46 | enableValidation bool 47 | destroyLatency time.Duration 48 | makeLatency time.Duration 49 | validateLatency time.Duration 50 | maxTotal int 51 | lock sync.Mutex 52 | } 53 | 54 | func NewSimpleFactory() *SimpleFactory { 55 | return &SimpleFactory{maxTotal: math.MaxInt32, evenValid: true, oddValid: true, enableValidation: true} 56 | } 57 | 58 | func (f *SimpleFactory) setValid(valid bool) { 59 | f.lock.Lock() 60 | f.evenValid = valid 61 | f.oddValid = valid 62 | f.lock.Unlock() 63 | } 64 | 65 | func (f *SimpleFactory) setValidateLatency(validate time.Duration) { 66 | f.lock.Lock() 67 | f.validateLatency = validate 68 | f.lock.Unlock() 69 | } 70 | 71 | func (f *SimpleFactory) MakeObject(context.Context) (*PooledObject, error) { 72 | if debugTest { 73 | fmt.Println("factory MakeObject") 74 | } 75 | if f.exceptionOnMake { 76 | return nil, errors.New("make object error") 77 | } 78 | var waitLatency time.Duration 79 | f.lock.Lock() 80 | f.activeCount = f.activeCount + 1 81 | if f.activeCount > f.maxTotal { 82 | return nil, fmt.Errorf("Too many active instances: %v", f.activeCount) 83 | } 84 | waitLatency = f.makeLatency 85 | f.lock.Unlock() 86 | if waitLatency > 0 { 87 | time.Sleep(waitLatency) 88 | } 89 | var counter int 90 | f.lock.Lock() 91 | counter = f.makeCounter 92 | f.makeCounter = f.makeCounter + 1 93 | f.lock.Unlock() 94 | return NewPooledObject(&TestObject{Num: counter}), nil 95 | } 96 | 97 | func (f *SimpleFactory) DestroyObject(ctx context.Context, object *PooledObject) error { 98 | if debugTest { 99 | fmt.Println("factory DestroyObject") 100 | } 101 | var waitLatency time.Duration 102 | var hurl bool 103 | f.lock.Lock() 104 | waitLatency = f.destroyLatency 105 | hurl = f.exceptionOnDestroy 106 | f.lock.Unlock() 107 | if waitLatency > 0 { 108 | time.Sleep(waitLatency) 109 | } 110 | f.lock.Lock() 111 | f.activeCount = f.activeCount - 1 112 | f.lock.Unlock() 113 | if hurl { 114 | return errors.New("destroy error") 115 | } 116 | return nil 117 | } 118 | 119 | func (f *SimpleFactory) ValidateObject(ctx context.Context, object *PooledObject) bool { 120 | if debugTest { 121 | fmt.Println("factory ValidateObject") 122 | } 123 | var validate bool 124 | var evenTest bool 125 | var oddTest bool 126 | var waitLatency time.Duration 127 | var counter int 128 | f.lock.Lock() 129 | validate = f.enableValidation 130 | evenTest = f.evenValid 131 | oddTest = f.oddValid 132 | counter = f.validateCounter 133 | f.validateCounter = f.validateCounter + 1 134 | waitLatency = f.validateLatency 135 | f.lock.Unlock() 136 | if waitLatency > 0 { 137 | time.Sleep(waitLatency) 138 | } 139 | if validate { 140 | if counter%2 == 0 { 141 | return evenTest 142 | } 143 | return oddTest 144 | } 145 | return true 146 | } 147 | 148 | func (f *SimpleFactory) ActivateObject(ctx context.Context, object *PooledObject) error { 149 | if debugTest { 150 | fmt.Println("factory ActivateObject") 151 | defer fmt.Println("factory ActivateObject end") 152 | } 153 | var hurl bool 154 | var evenTest bool 155 | var oddTest bool 156 | var counter int 157 | f.lock.Lock() 158 | hurl = f.exceptionOnActivate 159 | evenTest = f.evenValid 160 | oddTest = f.oddValid 161 | counter = f.activationCounter 162 | f.activationCounter = f.activationCounter + 1 163 | f.lock.Unlock() 164 | if hurl { 165 | var test bool 166 | if counter%2 == 0 { 167 | test = evenTest 168 | } else { 169 | test = oddTest 170 | } 171 | if !test { 172 | return errors.New("activate error") 173 | } 174 | } 175 | return nil 176 | } 177 | 178 | func (f *SimpleFactory) PassivateObject(ctx context.Context, object *PooledObject) error { 179 | if debugTest { 180 | fmt.Println("factory PassivateObject") 181 | } 182 | var hurl bool 183 | f.lock.Lock() 184 | hurl = f.exceptionOnPassivate 185 | f.lock.Unlock() 186 | if hurl { 187 | return errors.New("passivate error") 188 | } 189 | return nil 190 | } 191 | 192 | type PoolTestSuite struct { 193 | suite.Suite 194 | pool *ObjectPool 195 | factory *SimpleFactory 196 | } 197 | 198 | func (suit *PoolTestSuite) assertEquals(expect interface{}, actual interface{}) { 199 | suit.Equal(expect, actual) 200 | } 201 | 202 | func (suit *PoolTestSuite) assertNotNil(object interface{}) { 203 | suit.NotNil(object) 204 | } 205 | 206 | func (suit *PoolTestSuite) assertNil(object interface{}) { 207 | suit.Nil(object) 208 | } 209 | 210 | func (suit *PoolTestSuite) NoErrorWithResult(object interface{}, err error) interface{} { 211 | suit.NotNil(object) 212 | suit.Nil(err) 213 | return object 214 | } 215 | 216 | func (suit *PoolTestSuite) ErrorWithResult(object interface{}, err error) error { 217 | suit.Nil(object) 218 | suit.NotNil(err) 219 | return err 220 | } 221 | 222 | func TestPoolTestSuite(t *testing.T) { 223 | t.Parallel() 224 | 225 | suite.Run(t, new(PoolTestSuite)) 226 | } 227 | 228 | func (suit *PoolTestSuite) SetupTest() { 229 | suit.makeEmptyPool(context.Background(), DefaultMaxTotal) 230 | } 231 | 232 | func (suit *PoolTestSuite) TearDownTest() { 233 | ctx := context.Background() 234 | suit.pool.Clear(ctx) 235 | suit.pool.Close(ctx) 236 | suit.pool = nil 237 | suit.factory = nil 238 | } 239 | 240 | func (suit *PoolTestSuite) makeEmptyPool(ctx context.Context, maxTotal int) { 241 | suit.factory = NewSimpleFactory() 242 | suit.pool = NewObjectPoolWithDefaultConfig(ctx, suit.factory) 243 | suit.pool.Config.MaxTotal = maxTotal 244 | } 245 | 246 | func getNthObject(num int) *TestObject { 247 | return &TestObject{Num: num} 248 | } 249 | 250 | func (suit *PoolTestSuite) TestBaseBorrow() { 251 | ctx := context.Background() 252 | suit.pool.Config.MaxTotal = 3 253 | o0, err := suit.pool.BorrowObject(ctx) 254 | 255 | suit.Nil(err) 256 | suit.NotNil(o0) 257 | 258 | suit.Equal(getNthObject(0), o0) 259 | o1, _ := suit.pool.BorrowObject(ctx) 260 | suit.Equal(getNthObject(1), o1) 261 | o2, _ := suit.pool.BorrowObject(ctx) 262 | suit.Equal(getNthObject(2), o2) 263 | } 264 | 265 | func (suit *PoolTestSuite) TestBaseAddObject() { 266 | ctx := context.Background() 267 | suit.pool.Config.MaxTotal = 3 268 | suit.assertEquals(0, suit.pool.GetNumIdle()) 269 | suit.assertEquals(0, suit.pool.GetNumActive()) 270 | if debugTest { 271 | fmt.Println("test AddObject") 272 | } 273 | suit.pool.AddObject(ctx) 274 | 275 | suit.assertEquals(1, suit.pool.GetNumIdle()) 276 | suit.assertEquals(0, suit.pool.GetNumActive()) 277 | if debugTest { 278 | fmt.Println("test BorrowObject") 279 | } 280 | obj, err := suit.pool.BorrowObject(ctx) 281 | if err != nil { 282 | suit.Fail(err.Error()) 283 | } 284 | 285 | suit.assertEquals(getNthObject(0), obj) 286 | suit.assertEquals(0, suit.pool.GetNumIdle()) 287 | suit.assertEquals(1, suit.pool.GetNumActive()) 288 | err = suit.pool.ReturnObject(ctx, obj) 289 | if err != nil { 290 | suit.Fail(err.Error()) 291 | } 292 | suit.assertEquals(1, suit.pool.GetNumIdle()) 293 | suit.assertEquals(0, suit.pool.GetNumActive()) 294 | } 295 | 296 | func (suit *PoolTestSuite) isLIFO() bool { 297 | return true 298 | } 299 | 300 | func (suit *PoolTestSuite) isFIFO() bool { 301 | return false 302 | } 303 | 304 | func (suit *PoolTestSuite) TestBaseBorrowReturn() { 305 | ctx := context.Background() 306 | 307 | suit.pool.Config.MaxTotal = 3 308 | 309 | obj0 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 310 | suit.assertEquals(getNthObject(0), obj0) 311 | obj1 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 312 | suit.assertEquals(getNthObject(1), obj1) 313 | obj2 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 314 | suit.assertEquals(getNthObject(2), obj2) 315 | 316 | suit.NoError(suit.pool.ReturnObject(ctx, obj2)) 317 | 318 | obj2 = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 319 | suit.assertEquals(getNthObject(2), obj2) 320 | suit.pool.ReturnObject(ctx, obj1) 321 | obj1 = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 322 | 323 | suit.assertEquals(getNthObject(1), obj1) 324 | suit.pool.ReturnObject(ctx, obj0) 325 | suit.pool.ReturnObject(ctx, obj2) 326 | obj2 = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 327 | if suit.isLIFO() { 328 | suit.assertEquals(getNthObject(2), obj2) 329 | } 330 | if suit.isFIFO() { 331 | suit.assertEquals(getNthObject(0), obj2) 332 | } 333 | 334 | obj0 = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 335 | if suit.isLIFO() { 336 | suit.assertEquals(getNthObject(0), obj0) 337 | } 338 | if suit.isFIFO() { 339 | suit.assertEquals(getNthObject(2), obj0) 340 | } 341 | } 342 | 343 | func (suit *PoolTestSuite) TestBorrowReturnAsync() { 344 | suit.pool.Config.MaxTotal = 1 345 | 346 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 347 | defer cancel() 348 | 349 | obj0 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 350 | suit.assertEquals(getNthObject(0), obj0) 351 | 352 | //start new goroutine to borrow will block 353 | ch := make(chan interface{}, 1) 354 | go func() { 355 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 356 | defer cancel() 357 | obj, _ := suit.pool.BorrowObject(ctx) 358 | ch <- obj 359 | }() 360 | time.Sleep(100 * time.Millisecond) 361 | 362 | //return obj0 363 | go func() { 364 | suit.pool.ReturnObject(context.Background(), obj0) 365 | }() 366 | time.Sleep(100 * time.Millisecond) 367 | obj1 := <-ch 368 | suit.NotNil(obj1) 369 | suit.Equal(obj0, obj1) 370 | } 371 | 372 | func (suit *PoolTestSuite) TestBaseNumActiveNumIdle() { 373 | ctx := context.Background() 374 | 375 | suit.pool.Config.MaxTotal = 3 376 | 377 | suit.assertEquals(0, suit.pool.GetNumActive()) 378 | suit.assertEquals(0, suit.pool.GetNumIdle()) 379 | obj0 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 380 | suit.assertEquals(1, suit.pool.GetNumActive()) 381 | suit.assertEquals(0, suit.pool.GetNumIdle()) 382 | obj1 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 383 | suit.assertEquals(2, suit.pool.GetNumActive()) 384 | suit.assertEquals(0, suit.pool.GetNumIdle()) 385 | suit.pool.ReturnObject(ctx, obj1) 386 | suit.assertEquals(1, suit.pool.GetNumActive()) 387 | suit.assertEquals(1, suit.pool.GetNumIdle()) 388 | suit.NoError(suit.pool.ReturnObject(ctx, obj0)) 389 | suit.assertEquals(0, suit.pool.GetNumActive()) 390 | suit.assertEquals(2, suit.pool.GetNumIdle()) 391 | } 392 | 393 | func (suit *PoolTestSuite) TestBaseClear() { 394 | ctx := context.Background() 395 | 396 | suit.pool.Config.MaxTotal = 3 397 | 398 | suit.assertEquals(0, suit.pool.GetNumActive()) 399 | suit.assertEquals(0, suit.pool.GetNumIdle()) 400 | obj0 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 401 | obj1 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 402 | suit.assertEquals(2, suit.pool.GetNumActive()) 403 | suit.assertEquals(0, suit.pool.GetNumIdle()) 404 | suit.pool.ReturnObject(ctx, obj1) 405 | suit.pool.ReturnObject(ctx, obj0) 406 | suit.assertEquals(0, suit.pool.GetNumActive()) 407 | suit.assertEquals(2, suit.pool.GetNumIdle()) 408 | suit.pool.Clear(ctx) 409 | suit.assertEquals(0, suit.pool.GetNumActive()) 410 | suit.assertEquals(0, suit.pool.GetNumIdle()) 411 | obj2 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 412 | suit.assertEquals(getNthObject(2), obj2) 413 | } 414 | 415 | func (suit *PoolTestSuite) TestBaseInvalidateObject() { 416 | ctx := context.Background() 417 | 418 | suit.pool.Config.MaxTotal = 3 419 | 420 | suit.assertEquals(0, suit.pool.GetNumActive()) 421 | suit.assertEquals(0, suit.pool.GetNumIdle()) 422 | obj0 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 423 | obj1 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 424 | suit.assertEquals(2, suit.pool.GetNumActive()) 425 | suit.assertEquals(0, suit.pool.GetNumIdle()) 426 | err := suit.pool.InvalidateObject(ctx, obj0) 427 | suit.NoError(err) 428 | suit.assertEquals(1, suit.pool.GetNumActive()) 429 | suit.assertEquals(0, suit.pool.GetNumIdle()) 430 | err = suit.pool.InvalidateObject(ctx, obj1) 431 | suit.NoError(err) 432 | suit.assertEquals(0, suit.pool.GetNumActive()) 433 | suit.assertEquals(0, suit.pool.GetNumIdle()) 434 | } 435 | 436 | func (suit *PoolTestSuite) TestBaseClosePool() { 437 | ctx := context.Background() 438 | 439 | suit.pool.Config.MaxTotal = 3 440 | 441 | obj, err := suit.pool.BorrowObject(ctx) 442 | suit.NoError(err) 443 | suit.pool.ReturnObject(ctx, obj) 444 | 445 | suit.pool.Close(ctx) 446 | obj, err = suit.pool.BorrowObject(ctx) 447 | suit.NotNil(err) 448 | suit.Nil(obj) 449 | } 450 | 451 | func (suit *PoolTestSuite) TestWhenExhaustedFail() { 452 | ctx := context.Background() 453 | 454 | suit.pool.Config.MaxTotal = 1 455 | 456 | suit.pool.Config.BlockWhenExhausted = false 457 | obj1 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 458 | 459 | err2 := suit.ErrorWithResult(suit.pool.BorrowObject(ctx)) 460 | _, ok := err2.(*NoSuchElementErr) 461 | suit.True(ok, "expect NoSuchElementErr but get", reflect.TypeOf(err2)) 462 | 463 | suit.pool.ReturnObject(ctx, obj1) 464 | suit.assertEquals(1, suit.pool.GetNumIdle()) 465 | } 466 | 467 | func (suit *PoolTestSuite) TestWhenExhaustedBlock() { 468 | suit.pool.Config.MaxTotal = 1 469 | suit.pool.Config.BlockWhenExhausted = true 470 | 471 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) 472 | defer cancel() 473 | 474 | obj1 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 475 | 476 | ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) 477 | defer cancel() 478 | 479 | err2 := suit.ErrorWithResult(suit.pool.BorrowObject(ctx)) 480 | _, ok := err2.(*NoSuchElementErr) 481 | suit.True(ok, "expect NoSuchElementErr but get", reflect.TypeOf(err2)) 482 | 483 | suit.pool.ReturnObject(ctx, obj1) 484 | } 485 | 486 | func borrowAndWait(ctx context.Context, pool *ObjectPool, pause time.Duration) chan time.Duration { 487 | ch := make(chan time.Duration, 1) 488 | go func() { 489 | preborrow := time.Now() 490 | obj, _ := pool.BorrowObject(ctx) 491 | //objectId = obj; 492 | postborrow := time.Now() 493 | ch <- postborrow.Sub(preborrow) 494 | time.Sleep(pause) 495 | if obj != nil { 496 | pool.ReturnObject(ctx, obj) 497 | } 498 | //postreturn = time.Now(); 499 | }() 500 | return ch 501 | } 502 | 503 | func (suit *PoolTestSuite) TestWhenExhaustedBlockInterrupt() { 504 | ctx := context.Background() 505 | 506 | suit.pool.Config.MaxTotal = 1 507 | 508 | suit.pool.Config.BlockWhenExhausted = true 509 | 510 | obj1, _ := suit.pool.BorrowObject(ctx) 511 | 512 | // Make sure on object was obtained 513 | suit.assertNotNil(obj1) 514 | 515 | // Create a separate goroutine to try and borrow another object 516 | //WaitingTestGoroutine wtt = new WaitingTestGoroutine(pool, 200000); 517 | ch := borrowAndWait(ctx, suit.pool, 200000*time.Millisecond) 518 | 519 | // Give wtt time to start 520 | time.Sleep(200 * time.Millisecond) 521 | 522 | suit.pool.idleObjects.InterruptTakeWaiters() 523 | 524 | borrowTime := <-ch 525 | close(ch) 526 | if debugTest { 527 | fmt.Println("TestWhenExhaustedBlockInterrupt borrowTime:", borrowTime) 528 | } 529 | suit.True(borrowTime >= 200) 530 | 531 | // Check goroutine was interrupted 532 | //assertTrue(wtt._thrown instanceof InterruptedException); 533 | 534 | // Return object to the pool 535 | suit.pool.ReturnObject(ctx, obj1) 536 | 537 | // Bug POOL-162 - check there is now an object in the pool 538 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) 539 | defer cancel() 540 | 541 | obj2 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 542 | suit.pool.ReturnObject(ctx, obj2) 543 | } 544 | 545 | func (suit *PoolTestSuite) TestEvictWhileEmpty() { 546 | ctx := context.Background() 547 | 548 | suit.pool.evict(ctx) 549 | suit.pool.evict(ctx) 550 | } 551 | 552 | type TestGoroutineArg struct { 553 | /** pool to borrow from */ 554 | pool *ObjectPool 555 | 556 | /** number of borrow attempts */ 557 | iter int 558 | 559 | /** delay before each borrow attempt */ 560 | startDelay time.Duration 561 | 562 | borrowTimeout time.Duration 563 | 564 | /** time to hold each borrowed object before returning it */ 565 | holdTime time.Duration 566 | 567 | /** whether or not start and hold time are randomly generated */ 568 | randomDelay bool 569 | 570 | /** object expected to be borrowed (fail otherwise) */ 571 | expectedObject interface{} 572 | } 573 | 574 | type TestGoroutineResult struct { 575 | complete bool 576 | failed bool 577 | error error 578 | preborrow time.Time 579 | postborrow time.Time 580 | postreturn time.Time 581 | ended time.Time 582 | objectID interface{} 583 | } 584 | 585 | func NewTesGoroutineArgSimple(pool *ObjectPool, iter int, delay time.Duration, randomDelay bool) *TestGoroutineArg { 586 | return NewTestGoroutineArg(pool, iter, delay, time.Duration(0), delay, randomDelay, nil) 587 | } 588 | 589 | func NewTestGoroutineArg(pool *ObjectPool, iter int, startDelay, borrowTimeout, 590 | holdTime time.Duration, randomDelay bool, obj interface{}) *TestGoroutineArg { 591 | return &TestGoroutineArg{ 592 | pool: pool, 593 | iter: iter, 594 | startDelay: startDelay, 595 | borrowTimeout: borrowTimeout, 596 | holdTime: holdTime, 597 | randomDelay: randomDelay, 598 | expectedObject: obj, 599 | } 600 | } 601 | 602 | func goroutineRun(ctx context.Context, arg *TestGoroutineArg) chan TestGoroutineResult { 603 | resultChan := make(chan TestGoroutineResult, 1) 604 | result := TestGoroutineResult{} 605 | go func() { 606 | for i := 0; i < arg.iter; i++ { 607 | var startDelay time.Duration 608 | if arg.randomDelay { 609 | startDelay = time.Duration(rand.Int63n(int64(arg.startDelay))) 610 | } else { 611 | startDelay = arg.startDelay 612 | } 613 | var holdTime time.Duration 614 | if arg.randomDelay { 615 | holdTime = time.Duration(rand.Int63n(int64(arg.holdTime))) 616 | } else { 617 | holdTime = arg.holdTime 618 | } 619 | time.Sleep(startDelay) 620 | startBorrow := time.Now() 621 | borrowCtx := ctx 622 | if arg.borrowTimeout > 0 { 623 | var borrowCancel func() 624 | borrowCtx, borrowCancel = context.WithTimeout(ctx, arg.borrowTimeout) 625 | defer borrowCancel() 626 | } 627 | obj, err := arg.pool.BorrowObject(borrowCtx) 628 | endBorrow := time.Now() 629 | if err != nil { 630 | if debugTest { 631 | fmt.Println("borrow error, time:", endBorrow.Sub(startBorrow)) 632 | } 633 | result.error = err 634 | result.failed = true 635 | result.complete = true 636 | break 637 | } 638 | if arg.expectedObject != nil && !(arg.expectedObject == obj) { 639 | result.error = fmt.Errorf("Expected: %v found: %v", arg.expectedObject, obj) 640 | result.failed = true 641 | result.complete = true 642 | break 643 | } 644 | time.Sleep(holdTime) 645 | //startReturn := time.Now() 646 | err = arg.pool.ReturnObject(ctx, obj) 647 | //endReturn := time.Now() 648 | //fmt.Println("returnTime:", endReturn.Sub(startReturn)) 649 | if err != nil { 650 | result.error = err 651 | result.failed = true 652 | result.complete = true 653 | break 654 | } 655 | } 656 | result.complete = true 657 | resultChan <- result 658 | }() 659 | return resultChan 660 | } 661 | 662 | func (suit *PoolTestSuite) TestEvictAddObjects() { 663 | ctx := context.Background() 664 | 665 | suit.factory.makeLatency = 300 * time.Millisecond 666 | suit.factory.maxTotal = 2 667 | suit.pool.Config.MaxTotal = 2 668 | suit.pool.Config.MinIdle = 1 669 | suit.pool.BorrowObject(ctx) // numActive = 1, numIdle = 0 670 | // Create a test goroutine that will run once and try a borrow after 671 | // 150ms fixed delay 672 | borrower := NewTesGoroutineArgSimple(suit.pool, 1, 150*time.Millisecond, false) 673 | //// Set evictor to run in 100 ms - will create idle instance 674 | suit.pool.Config.TimeBetweenEvictionRuns = 100 * time.Millisecond 675 | ch := goroutineRun(ctx, borrower) 676 | result := <-ch 677 | close(ch) 678 | if debugTest { 679 | fmt.Printf("TestEvictAddObjects %v error:%v", borrower, result.error) 680 | } 681 | suit.True(!result.failed) 682 | } 683 | 684 | func (suit *PoolTestSuite) TestEvictLIFO() { 685 | suit.checkEvict(context.Background(), true) 686 | } 687 | 688 | func (suit *PoolTestSuite) TestEvictFIFO() { 689 | suit.checkEvict(context.Background(), false) 690 | } 691 | 692 | func (suit *PoolTestSuite) checkEvict(ctx context.Context, lifo bool) { 693 | var idle int 694 | // yea suit is hairy but it tests all the code paths in GOP.evict() 695 | suit.pool.Config.SoftMinEvictableIdleTime = 10 * time.Millisecond 696 | suit.pool.Config.MinIdle = 2 697 | suit.pool.Config.TestWhileIdle = true 698 | suit.pool.Config.LIFO = lifo 699 | Prefill(ctx, suit.pool, 5) 700 | suit.pool.evict(ctx) 701 | idle = suit.pool.GetNumIdle() 702 | if debugTest { 703 | fmt.Printf("checkEvict lifo:%v idel:%v \n", lifo, idle) 704 | } 705 | suit.factory.evenValid = false 706 | suit.factory.oddValid = false 707 | suit.factory.exceptionOnActivate = true 708 | suit.pool.evict(ctx) 709 | idle = suit.pool.GetNumIdle() 710 | if debugTest { 711 | fmt.Printf("checkEvict lifo:%v idel:%v \n", lifo, idle) 712 | } 713 | Prefill(ctx, suit.pool, 5) 714 | suit.factory.exceptionOnActivate = false 715 | suit.factory.exceptionOnPassivate = true 716 | suit.pool.evict(ctx) 717 | idle = suit.pool.GetNumIdle() 718 | if debugTest { 719 | fmt.Printf("checkEvict lifo:%v idel:%v \n", lifo, idle) 720 | } 721 | suit.factory.exceptionOnPassivate = false 722 | suit.factory.evenValid = true 723 | suit.factory.oddValid = true 724 | time.Sleep(time.Duration(125) * time.Millisecond) 725 | suit.pool.evict(ctx) 726 | idle = suit.pool.GetNumIdle() 727 | if debugTest { 728 | fmt.Printf("checkEvict lifo:%v idel:%v \n", lifo, idle) 729 | } 730 | suit.assertEquals(2, suit.pool.GetNumIdle()) 731 | } 732 | 733 | func (suit *PoolTestSuite) TestEvictionOrder() { 734 | ctx := context.Background() 735 | suit.checkEvictionOrder(ctx, false) 736 | suit.TearDownTest() 737 | suit.SetupTest() 738 | suit.checkEvictionOrder(ctx, true) 739 | } 740 | 741 | func (suit *PoolTestSuite) checkEvictionOrder(ctx context.Context, lifo bool) { 742 | suit.checkEvictionOrderPart1(ctx, lifo) 743 | suit.TearDownTest() 744 | suit.SetupTest() 745 | suit.checkEvictionOrderPart2(ctx, lifo) 746 | } 747 | 748 | func (suit *PoolTestSuite) checkEvictionOrderPart1(ctx context.Context, lifo bool) { 749 | suit.pool.Config.NumTestsPerEvictionRun = 2 750 | suit.pool.Config.MinEvictableIdleTime = 100 * time.Millisecond 751 | suit.pool.Config.LIFO = lifo 752 | for i := 0; i < 5; i++ { 753 | suit.pool.AddObject(ctx) 754 | time.Sleep(time.Duration(100) * time.Millisecond) 755 | } 756 | // Order, oldest to youngest, is "0", "1", ...,"4" 757 | suit.pool.evict(ctx) // Should evict "0" and "1" 758 | obj, _ := suit.pool.BorrowObject(ctx) 759 | suit.True(getNthObject(0) != obj, "oldest not evicted") 760 | suit.True(getNthObject(1) != obj, "second oldest not evicted") 761 | // 2 should be next out for FIFO, 4 for LIFO 762 | var expect *TestObject 763 | if lifo { 764 | expect = getNthObject(4) 765 | } else { 766 | expect = getNthObject(2) 767 | } 768 | suit.Equal(expect, obj, "Wrong instance returned") 769 | } 770 | 771 | func (suit *PoolTestSuite) checkEvictionOrderPart2(ctx context.Context, lifo bool) { 772 | // Two eviction runs in sequence 773 | suit.pool.Config.NumTestsPerEvictionRun = 2 774 | suit.pool.Config.MinEvictableIdleTime = 100 * time.Millisecond 775 | suit.pool.Config.LIFO = lifo 776 | for i := 0; i < 5; i++ { 777 | suit.pool.AddObject(ctx) 778 | time.Sleep(time.Duration(100) * time.Millisecond) 779 | } 780 | suit.pool.evict(ctx) // Should evict "0" and "1" 781 | suit.pool.evict(ctx) // Should evict "2" and "3" 782 | obj, _ := suit.pool.BorrowObject(ctx) 783 | suit.Equal(getNthObject(4), obj, "Wrong instance remaining in pool") 784 | } 785 | 786 | func (suit *PoolTestSuite) TestEvictorVisiting() { 787 | suit.checkEvictorVisiting(true) 788 | suit.checkEvictorVisiting(false) 789 | } 790 | 791 | func (suit *PoolTestSuite) checkEvictorVisiting(lifo bool) { 792 | //TODO 793 | } 794 | 795 | func (suit *PoolTestSuite) TestExceptionOnPassivateDuringReturn() { 796 | ctx := context.Background() 797 | obj, _ := suit.pool.BorrowObject(ctx) 798 | suit.factory.exceptionOnPassivate = true 799 | suit.pool.ReturnObject(ctx, obj) 800 | suit.assertEquals(0, suit.pool.GetNumIdle()) 801 | } 802 | 803 | func (suit *PoolTestSuite) TestExceptionOnPassivateDuringReturnWithBorrowWaiting() { 804 | ctx := context.Background() 805 | 806 | suit.pool.Config.MaxTotal = 1 807 | suit.pool.Config.BlockWhenExhausted = true 808 | suit.factory.exceptionOnPassivate = true 809 | 810 | obj, _ := suit.pool.BorrowObject(ctx) 811 | 812 | goBorrow := func(ch chan<- interface{}, pool *ObjectPool) { 813 | obj, _ := suit.pool.BorrowObject(ctx) 814 | ch <- obj 815 | } 816 | 817 | ch1 := make(chan interface{}) 818 | go goBorrow(ch1, suit.pool) 819 | 820 | ch2 := make(chan interface{}) 821 | go goBorrow(ch2, suit.pool) 822 | 823 | select { 824 | case <-ch1: 825 | suit.FailNow("Borrowing additional objects should have blocked") 826 | 827 | case <-ch2: 828 | suit.FailNow("Borrowing additional objects should have blocked") 829 | 830 | case <-time.After(100 * time.Millisecond): 831 | // Just wait a moment to make sure neither of those channels, above, read... 832 | } 833 | 834 | suit.pool.ReturnObject(ctx, obj) 835 | 836 | select { 837 | case obj1 := <-ch1: 838 | suit.T().Log("Returning item borrowed (ch1)") 839 | suit.pool.ReturnObject(ctx, obj1) 840 | 841 | case obj2 := <-ch2: 842 | suit.T().Log("Returning item borrowed (ch2)") 843 | suit.pool.ReturnObject(ctx, obj2) 844 | 845 | case <-time.After(100 * time.Millisecond): 846 | // Just wait a moment to make sure neither of those channels, above, read... 847 | 848 | suit.FailNow("Failed to borrow additional objects") 849 | } 850 | 851 | // Once again, for the other channel 852 | 853 | select { 854 | case obj1 := <-ch1: 855 | suit.T().Log("Returning item borrowed (ch1)") 856 | suit.pool.ReturnObject(ctx, obj1) 857 | 858 | case obj2 := <-ch2: 859 | suit.T().Log("Returning item borrowed (ch2)") 860 | suit.pool.ReturnObject(ctx, obj2) 861 | 862 | case <-time.After(100 * time.Millisecond): 863 | // Just wait a moment to make sure neither of those channels, above, read... 864 | 865 | suit.FailNow("Failed to borrow additional objects") 866 | } 867 | } 868 | 869 | func (suit *PoolTestSuite) TestExceptionOnDestroyDuringBorrow() { 870 | ctx := context.Background() 871 | suit.factory.exceptionOnDestroy = true 872 | suit.pool.Config.TestOnBorrow = true 873 | suit.pool.BorrowObject(ctx) 874 | suit.factory.setValid(false) // Make validation fail on next borrow attempt 875 | _, err := suit.pool.BorrowObject(ctx) 876 | suit.NotNil(err) 877 | suit.assertEquals(1, suit.pool.GetNumActive()) 878 | suit.assertEquals(0, suit.pool.GetNumIdle()) 879 | } 880 | 881 | func (suit *PoolTestSuite) TestExceptionOnDestroyDuringReturn() { 882 | ctx := context.Background() 883 | suit.factory.exceptionOnDestroy = true 884 | suit.pool.Config.TestOnReturn = true 885 | obj1, _ := suit.pool.BorrowObject(ctx) 886 | suit.pool.BorrowObject(ctx) 887 | suit.factory.setValid(false) // Make validation fail 888 | suit.pool.ReturnObject(context.Background(), obj1) 889 | suit.assertEquals(1, suit.pool.GetNumActive()) 890 | suit.assertEquals(0, suit.pool.GetNumIdle()) 891 | } 892 | 893 | func (suit *PoolTestSuite) TestExceptionOnActivateDuringBorrow() { 894 | ctx := context.Background() 895 | obj1, _ := suit.pool.BorrowObject(ctx) 896 | obj2, _ := suit.pool.BorrowObject(ctx) 897 | suit.pool.ReturnObject(ctx, obj1) 898 | suit.pool.ReturnObject(ctx, obj2) 899 | suit.factory.exceptionOnActivate = true 900 | suit.factory.evenValid = false 901 | // Activation will now throw every other time 902 | // First attempt throws, but loop continues and second succeeds 903 | obj, _ := suit.pool.BorrowObject(ctx) 904 | suit.assertEquals(1, suit.pool.GetNumActive()) 905 | suit.assertEquals(0, suit.pool.GetNumIdle()) 906 | 907 | suit.pool.ReturnObject(ctx, obj) 908 | suit.factory.setValid(false) 909 | // Validation will now fail on activation when borrowObject returns 910 | // an idle instance, and then when attempting to create a new instance 911 | _, err := suit.pool.BorrowObject(ctx) 912 | _, ok := err.(*NoSuchElementErr) 913 | suit.True(ok, "expect NoSuchElementErr but get", reflect.TypeOf(err)) 914 | 915 | suit.assertEquals(0, suit.pool.GetNumActive()) 916 | suit.assertEquals(0, suit.pool.GetNumIdle()) 917 | } 918 | 919 | func (suit *PoolTestSuite) TestNegativeMaxTotal() { 920 | ctx := context.Background() 921 | suit.pool.Config.MaxTotal = -1 922 | suit.pool.Config.BlockWhenExhausted = false 923 | obj, _ := suit.pool.BorrowObject(ctx) 924 | suit.assertEquals(getNthObject(0), obj) 925 | suit.pool.ReturnObject(context.Background(), obj) 926 | } 927 | 928 | func (suit *PoolTestSuite) TestMaxIdle() { 929 | ctx := context.Background() 930 | suit.pool.Config.MaxTotal = 100 931 | suit.pool.Config.MaxIdle = 8 932 | active := make([]*TestObject, 100) 933 | for i := 0; i < 100; i++ { 934 | obj, err := suit.pool.BorrowObject(ctx) 935 | suit.NoError(err) 936 | testObj := obj.(*TestObject) 937 | suit.NotNil(testObj) 938 | active[i] = testObj 939 | } 940 | suit.assertEquals(100, suit.pool.GetNumActive()) 941 | suit.assertEquals(0, suit.pool.GetNumIdle()) 942 | for i := 0; i < 100; i++ { 943 | obj := active[i] 944 | if debugTest { 945 | fmt.Printf("TestMaxIdle ReturnObject %v \n", obj) 946 | } 947 | err := suit.pool.ReturnObject(ctx, obj) 948 | suit.NoError(err) 949 | suit.assertEquals(99-i, suit.pool.GetNumActive()) 950 | idle := suit.pool.Config.MaxIdle 951 | if i < idle { 952 | idle = i + 1 953 | } 954 | suit.assertEquals(idle, suit.pool.GetNumIdle()) 955 | } 956 | } 957 | 958 | func (suit *PoolTestSuite) TestMaxIdleZero() { 959 | ctx := context.Background() 960 | suit.pool.Config.MaxTotal = 100 961 | suit.pool.Config.MaxIdle = 0 962 | active := make([]*TestObject, 100) 963 | for i := 0; i < 100; i++ { 964 | obj, err := suit.pool.BorrowObject(ctx) 965 | suit.NoError(err) 966 | testObj := obj.(*TestObject) 967 | suit.NotNil(testObj) 968 | active[i] = testObj 969 | } 970 | suit.assertEquals(100, suit.pool.GetNumActive()) 971 | suit.assertEquals(0, suit.pool.GetNumIdle()) 972 | for i := 0; i < 100; i++ { 973 | suit.pool.ReturnObject(ctx, active[i]) 974 | suit.assertEquals(99-i, suit.pool.GetNumActive()) 975 | suit.assertEquals(0, suit.pool.GetNumIdle()) 976 | } 977 | } 978 | 979 | func (suit *PoolTestSuite) TestMaxTotal() { 980 | ctx := context.Background() 981 | suit.pool.Config.MaxTotal = 3 982 | suit.pool.Config.BlockWhenExhausted = false 983 | 984 | suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 985 | suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 986 | suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 987 | _, err := suit.pool.BorrowObject(ctx) 988 | suit.Error(err) 989 | } 990 | 991 | func (suit *PoolTestSuite) TestTimeoutNoLeak() { 992 | suit.pool.Config.MaxTotal = 2 993 | suit.pool.Config.BlockWhenExhausted = true 994 | 995 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) 996 | defer cancel() 997 | 998 | obj, err := suit.pool.BorrowObject(ctx) 999 | suit.NoError(err) 1000 | 1001 | obj2 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1002 | err3 := suit.ErrorWithResult(suit.pool.BorrowObject(ctx)) 1003 | suit.IsType(&NoSuchElementErr{}, err3, err3) 1004 | 1005 | ctx, cancel2 := context.WithTimeout(context.Background(), 10*time.Millisecond) 1006 | defer cancel2() 1007 | 1008 | suit.NoError(suit.pool.ReturnObject(ctx, obj2)) 1009 | suit.NoError(suit.pool.ReturnObject(ctx, obj)) 1010 | 1011 | suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1012 | suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1013 | } 1014 | 1015 | func (suit *PoolTestSuite) TestMaxTotalZero() { 1016 | ctx := context.Background() 1017 | suit.pool.Config.MaxTotal = 0 1018 | suit.pool.Config.BlockWhenExhausted = false 1019 | err := suit.ErrorWithResult(suit.pool.BorrowObject(ctx)) 1020 | suit.Error(err) 1021 | //fail("Expected NoSuchElementException"); 1022 | } 1023 | 1024 | func (suit *PoolTestSuite) TestMaxTotalUnderLoad() { 1025 | // Config 1026 | numGoroutines := 199 // And main goroutine makes a round 200. 1027 | numIter := 20 1028 | delay := 25 * time.Millisecond 1029 | maxTotal := 10 1030 | 1031 | suit.factory.maxTotal = maxTotal 1032 | suit.pool.Config.MaxTotal = maxTotal 1033 | suit.pool.Config.BlockWhenExhausted = true 1034 | suit.pool.Config.TimeBetweenEvictionRuns = time.Duration(-1) 1035 | 1036 | // Start goroutines to borrow objects 1037 | goroutineArgs := make([]*TestGoroutineArg, numGoroutines) 1038 | resultChans := make([]chan TestGoroutineResult, numGoroutines) 1039 | ctx := context.Background() 1040 | for i := 0; i < numGoroutines; i++ { 1041 | // Factor of 2 on iterations so main goroutine does work whilst other 1042 | // goroutines are running. Factor of 2 on delay so average delay for 1043 | // other goroutines == actual delay for main goroutine 1044 | goroutineArgs[i] = NewTesGoroutineArgSimple(suit.pool, numIter*2, delay*2, true) 1045 | resultChans[i] = goroutineRun(ctx, goroutineArgs[i]) 1046 | } 1047 | // Give the goroutines a chance to start doing some work 1048 | time.Sleep(5 * time.Second) 1049 | 1050 | for i := 0; i < numIter; i++ { 1051 | var obj interface{} 1052 | time.Sleep(delay) 1053 | 1054 | obj, err := suit.pool.BorrowObject(ctx) 1055 | suit.NoError(err) 1056 | // Under load, observed _numActive > _maxTotal 1057 | if suit.pool.GetNumActive() > suit.pool.Config.MaxTotal { 1058 | suit.Fail("Too many active objects") 1059 | } 1060 | time.Sleep(delay) 1061 | if obj != nil { 1062 | suit.NoError(suit.pool.ReturnObject(ctx, obj)) 1063 | } 1064 | } 1065 | 1066 | for i := 0; i < numGoroutines; i++ { 1067 | result := <-resultChans[i] 1068 | close(resultChans[i]) 1069 | if result.failed { 1070 | suit.Fail(fmt.Sprintf("Goroutine %v failed: %v", i, result.error.Error())) 1071 | } 1072 | } 1073 | } 1074 | 1075 | func (suit *PoolTestSuite) TestStartAndStopEvictor() { 1076 | defer leaktest.Check(suit.T())() 1077 | // set up pool without evictor 1078 | suit.pool.Config.MaxIdle = 6 1079 | suit.pool.Config.MaxTotal = 6 1080 | suit.pool.Config.NumTestsPerEvictionRun = 6 1081 | suit.pool.Config.MinEvictableIdleTime = 100 * time.Millisecond 1082 | 1083 | ctx := context.Background() 1084 | for j := 0; j < 2; j++ { 1085 | // populate the pool 1086 | { 1087 | active := make([]*TestObject, 6) 1088 | for i := 0; i < 6; i++ { 1089 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1090 | } 1091 | for i := 0; i < 6; i++ { 1092 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1093 | } 1094 | } 1095 | 1096 | // note that it stays populated 1097 | suit.Equal(6, suit.pool.GetNumIdle(), "Should have 6 idle") 1098 | 1099 | // start the evictor 1100 | suit.pool.Config.TimeBetweenEvictionRuns = 50 * time.Millisecond 1101 | 1102 | //re config evictor 1103 | suit.pool.StartEvictor() 1104 | 1105 | // wait a second (well, .2 seconds) 1106 | time.Sleep(time.Duration(200) * time.Millisecond) 1107 | 1108 | // assert that the evictor has cleared out the pool 1109 | suit.Equal(0, suit.pool.GetNumIdle(), "Should have 0 idle") 1110 | 1111 | // stop the evictor 1112 | suit.pool.startEvictor(0) 1113 | } 1114 | } 1115 | 1116 | func (suit *PoolTestSuite) TestStartAndStopEvictorConcurrent() { 1117 | defer leaktest.Check(suit.T())() 1118 | // set up pool without evictor 1119 | suit.pool.Config.MaxIdle = 6 1120 | suit.pool.Config.MaxTotal = 100 1121 | suit.pool.Config.NumTestsPerEvictionRun = 6 1122 | suit.pool.Config.MinEvictableIdleTime = 100 * time.Millisecond 1123 | 1124 | ctx := context.Background() 1125 | testWG := sync.WaitGroup{} 1126 | testWG.Add(101) 1127 | for i := 0; i < 100; i++ { 1128 | go func(idx int) { 1129 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) 1130 | suit.pool.startEvictor(time.Duration(10+idx) * time.Millisecond) 1131 | testWG.Done() 1132 | }(i) 1133 | } 1134 | go func() { 1135 | for j := 0; j < 10; j++ { 1136 | // populate the pool 1137 | active := make([]*TestObject, 6) 1138 | for i := 0; i < 6; i++ { 1139 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1140 | } 1141 | for i := 0; i < 6; i++ { 1142 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1143 | } 1144 | // wait a second (well, .1 seconds) 1145 | time.Sleep(time.Duration(10) * time.Millisecond) 1146 | } 1147 | testWG.Done() 1148 | }() 1149 | testWG.Wait() 1150 | // wait a second (well, .2 seconds) 1151 | time.Sleep(time.Duration(200) * time.Millisecond) 1152 | 1153 | // assert that the evictor has cleared out the pool 1154 | suit.Equal(0, suit.pool.GetNumIdle(), "Should have 0 idle") 1155 | suit.pool.startEvictor(0) 1156 | } 1157 | 1158 | func (suit *PoolTestSuite) TestEvictionWithNegativeNumTests() { 1159 | // when numTestsPerEvictionRun is negative, it represents a fraction of the idle objects to test 1160 | suit.pool.Config.MaxIdle = 6 1161 | suit.pool.Config.MaxTotal = 6 1162 | suit.pool.Config.NumTestsPerEvictionRun = -2 1163 | suit.pool.Config.MinEvictableIdleTime = 50 * time.Millisecond 1164 | 1165 | suit.pool.Config.TimeBetweenEvictionRuns = 100 * time.Millisecond 1166 | ctx := context.Background() 1167 | suit.pool.StartEvictor() 1168 | 1169 | active := make([]*TestObject, 6) 1170 | for i := 0; i < 6; i++ { 1171 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1172 | } 1173 | for i := 0; i < 6; i++ { 1174 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1175 | } 1176 | 1177 | time.Sleep(time.Duration(100) * time.Millisecond) 1178 | suit.True(suit.pool.GetNumIdle() <= 6, "Should at most 6 idle, found %v", suit.pool.GetNumIdle()) 1179 | time.Sleep(time.Duration(100) * time.Millisecond) 1180 | suit.True(suit.pool.GetNumIdle() <= 3, "Should at most 3 idle, found %v", suit.pool.GetNumIdle()) 1181 | time.Sleep(time.Duration(100) * time.Millisecond) 1182 | suit.True(suit.pool.GetNumIdle() <= 2, "Should be at most 2 idle, found %v", suit.pool.GetNumIdle()) 1183 | time.Sleep(time.Duration(100) * time.Millisecond) 1184 | suit.Equal(0, suit.pool.GetNumIdle(), "Should be zero idle, found %v", suit.pool.GetNumIdle()) 1185 | } 1186 | 1187 | func (suit *PoolTestSuite) TestEviction() { 1188 | suit.pool.Config.MaxIdle = 500 1189 | suit.pool.Config.MaxTotal = 500 1190 | suit.pool.Config.NumTestsPerEvictionRun = 100 1191 | suit.pool.Config.MinEvictableIdleTime = 250 * time.Millisecond 1192 | suit.pool.Config.TimeBetweenEvictionRuns = 500 * time.Millisecond 1193 | ctx := context.Background() 1194 | suit.pool.StartEvictor() 1195 | 1196 | suit.pool.Config.TestWhileIdle = true 1197 | active := make([]*TestObject, 500) 1198 | 1199 | for i := 0; i < 500; i++ { 1200 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1201 | } 1202 | for i := 0; i < 500; i++ { 1203 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1204 | } 1205 | 1206 | time.Sleep(time.Duration(1000) * time.Millisecond) 1207 | suit.True(suit.pool.GetNumIdle() < 500, "Should be less than 500 idle, found %v", suit.pool.GetNumIdle()) 1208 | time.Sleep(time.Duration(600) * time.Millisecond) 1209 | suit.True(suit.pool.GetNumIdle() < 400, "Should be less than 400 idle, found %v", suit.pool.GetNumIdle()) 1210 | time.Sleep(time.Duration(600) * time.Millisecond) 1211 | suit.True(suit.pool.GetNumIdle() < 300, "Should be less than 300 idle, found %v", suit.pool.GetNumIdle()) 1212 | time.Sleep(time.Duration(600) * time.Millisecond) 1213 | suit.True(suit.pool.GetNumIdle() < 200, "Should be less than 200 idle, found %v", suit.pool.GetNumIdle()) 1214 | time.Sleep(time.Duration(600) * time.Millisecond) 1215 | suit.True(suit.pool.GetNumIdle() < 100, "Should be less than 100 idle, found %v", suit.pool.GetNumIdle()) 1216 | time.Sleep(time.Duration(600) * time.Millisecond) 1217 | suit.Equal(0, suit.pool.GetNumIdle(), "Should be zero idle, found %v", suit.pool.GetNumIdle()) 1218 | 1219 | for i := 0; i < 500; i++ { 1220 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1221 | } 1222 | for i := 0; i < 500; i++ { 1223 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1224 | } 1225 | 1226 | time.Sleep(time.Duration(1000) * time.Millisecond) 1227 | suit.True(suit.pool.GetNumIdle() < 500, "Should be less than 500 idle, found %v", suit.pool.GetNumIdle()) 1228 | time.Sleep(time.Duration(600) * time.Millisecond) 1229 | suit.True(suit.pool.GetNumIdle() < 400, "Should be less than 400 idle, found %v", suit.pool.GetNumIdle()) 1230 | time.Sleep(time.Duration(600) * time.Millisecond) 1231 | suit.True(suit.pool.GetNumIdle() < 300, "Should be less than 300 idle, found %v", suit.pool.GetNumIdle()) 1232 | time.Sleep(time.Duration(600) * time.Millisecond) 1233 | suit.True(suit.pool.GetNumIdle() < 200, "Should be less than 200 idle, found %v", suit.pool.GetNumIdle()) 1234 | time.Sleep(time.Duration(600) * time.Millisecond) 1235 | suit.True(suit.pool.GetNumIdle() < 100, "Should be less than 100 idle, found %v", suit.pool.GetNumIdle()) 1236 | time.Sleep(time.Duration(600) * time.Millisecond) 1237 | suit.Equal(0, suit.pool.GetNumIdle(), "Should be zero idle, found %v", suit.pool.GetNumIdle()) 1238 | } 1239 | 1240 | type TestEvictionPolicy struct { 1241 | callCount concurrent.AtomicInteger 1242 | } 1243 | 1244 | func (p *TestEvictionPolicy) Evict(config *EvictionConfig, underTest *PooledObject, idleCount int) bool { 1245 | if p.callCount.IncrementAndGet() > 1500 { 1246 | return true 1247 | } 1248 | return false 1249 | } 1250 | 1251 | var TestEvictionPolicyName = "github.com/jolestar/go-commons-pool/TestEvictionPolicy" 1252 | 1253 | func (suit *PoolTestSuite) TestEvictionPolicy() { 1254 | suit.pool.Config.MaxIdle = 500 1255 | suit.pool.Config.MaxTotal = 500 1256 | suit.pool.Config.NumTestsPerEvictionRun = 500 1257 | suit.pool.Config.MinEvictableIdleTime = 250 * time.Millisecond 1258 | suit.pool.Config.TimeBetweenEvictionRuns = 500 * time.Millisecond 1259 | suit.pool.StartEvictor() 1260 | suit.pool.Config.TestWhileIdle = true 1261 | evictionPolicy := new(TestEvictionPolicy) 1262 | 1263 | RegistryEvictionPolicy(TestEvictionPolicyName, evictionPolicy) 1264 | 1265 | _, ok := suit.pool.getEvictionPolicy().(*DefaultEvictionPolicy) 1266 | suit.True(ok, "EvictionPolicy is not default policy") 1267 | 1268 | suit.pool.Config.EvictionPolicyName = TestEvictionPolicyName 1269 | suit.Equal(evictionPolicy, suit.pool.getEvictionPolicy()) 1270 | 1271 | ctx := context.Background() 1272 | active := make([]*TestObject, 500) 1273 | for i := 0; i < 500; i++ { 1274 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1275 | } 1276 | for i := 0; i < 500; i++ { 1277 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1278 | } 1279 | 1280 | // Eviction policy ignores first 1500 attempts to evict and then always 1281 | // evicts. After 1s, there should have been two runs of 500 tests so no 1282 | // evictions 1283 | time.Sleep(time.Duration(1000) * time.Millisecond) 1284 | suit.Equal(500, suit.pool.GetNumIdle(), "Should be 500 idle") 1285 | // A further 1s wasn't enough so allow 2s for the evictor to clear out 1286 | // all of the idle objects. 1287 | time.Sleep(time.Duration(2000) * time.Millisecond) 1288 | suit.Equal(0, suit.pool.GetNumIdle(), "Should be 0 idle") 1289 | } 1290 | 1291 | func (suit *PoolTestSuite) TestEvictionSoftMinIdle() { 1292 | suit.pool.Config.MaxIdle = 5 1293 | suit.pool.Config.MaxTotal = 5 1294 | suit.pool.Config.NumTestsPerEvictionRun = 5 1295 | suit.pool.Config.MinEvictableIdleTime = 3 * time.Second 1296 | suit.pool.Config.SoftMinEvictableIdleTime = 1 * time.Second 1297 | suit.pool.Config.MinIdle = 2 1298 | 1299 | ctx := context.Background() 1300 | active := make([]*TestObject, 5) 1301 | for i := 0; i < 5; i++ { 1302 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1303 | } 1304 | 1305 | for i := 0; i < 5; i++ { 1306 | suit.pool.ReturnObject(ctx, active[i]) 1307 | } 1308 | 1309 | // Soft evict all but minIdle(2) 1310 | time.Sleep(time.Duration(1500) * time.Millisecond) 1311 | suit.pool.evict(ctx) 1312 | suit.Equal(2, suit.pool.GetNumIdle(), "Idle count different than expected.") 1313 | 1314 | // Hard evict the rest. 1315 | time.Sleep(time.Duration(1600) * time.Millisecond) 1316 | suit.pool.evict(ctx) 1317 | suit.Equal(0, suit.pool.GetNumIdle(), "Idle count different than expected.") 1318 | } 1319 | 1320 | func (suit *PoolTestSuite) TestEvictionNegativeIdleTime() { 1321 | suit.pool.Config.MaxIdle = 5 1322 | suit.pool.Config.MaxTotal = 5 1323 | suit.pool.Config.NumTestsPerEvictionRun = 5 1324 | // zero no negative mean's no evict. 1325 | suit.pool.Config.MinEvictableIdleTime = -1 * time.Second 1326 | suit.pool.Config.SoftMinEvictableIdleTime = -1 * time.Second 1327 | suit.pool.Config.MinIdle = 2 1328 | 1329 | ctx := context.Background() 1330 | active := make([]*TestObject, 5) 1331 | for i := 0; i < 5; i++ { 1332 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1333 | } 1334 | 1335 | for i := 0; i < 5; i++ { 1336 | suit.pool.ReturnObject(ctx, active[i]) 1337 | } 1338 | 1339 | // Soft evict 1340 | time.Sleep(time.Duration(1500) * time.Millisecond) 1341 | suit.pool.evict(ctx) 1342 | suit.Equal(5, suit.pool.GetNumIdle(), "Idle count different than expected.") 1343 | 1344 | // Hard evict 1345 | time.Sleep(time.Duration(1600) * time.Millisecond) 1346 | suit.pool.evict(ctx) 1347 | suit.Equal(5, suit.pool.GetNumIdle(), "Idle count different than expected.") 1348 | } 1349 | 1350 | func (suit *PoolTestSuite) TestEvictionInvalid() { 1351 | ctx := context.Background() 1352 | suit.pool = NewObjectPoolWithDefaultConfig(ctx, NewPooledObjectFactory( 1353 | func(context.Context) (interface{}, error) { 1354 | return &TestObject{}, nil 1355 | }, nil, func(ctx context.Context, object *PooledObject) bool { 1356 | if debugTest { 1357 | fmt.Printf("TestEvictionInvalid valid object %v \n", object) 1358 | } 1359 | time.Sleep(time.Duration(1000) * time.Millisecond) 1360 | return false 1361 | }, nil, nil)) 1362 | 1363 | suit.pool.Config.MaxIdle = 1 1364 | suit.pool.Config.MaxTotal = 1 1365 | suit.pool.Config.TestOnBorrow = false 1366 | suit.pool.Config.TestOnReturn = false 1367 | suit.pool.Config.TestWhileIdle = true 1368 | suit.pool.Config.MinEvictableIdleTime = 100 * time.Second 1369 | suit.pool.Config.NumTestsPerEvictionRun = 1 1370 | 1371 | p := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1372 | suit.NoError(suit.pool.ReturnObject(ctx, p)) 1373 | 1374 | // Run eviction in a separate goroutine 1375 | go func() { 1376 | if debugTest { 1377 | fmt.Println("TestEvictionInvalid evict goroutine.") 1378 | } 1379 | suit.pool.evict(suit.pool.Config.EvictionContext) 1380 | }() 1381 | 1382 | // Sleep to make sure evictor has started 1383 | time.Sleep(300 * time.Millisecond) 1384 | 1385 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) 1386 | defer cancel() 1387 | 1388 | err := suit.ErrorWithResult(suit.pool.borrowObject(ctx)) 1389 | suit.IsType(err, &NoSuchElementErr{}) 1390 | 1391 | // Make sure evictor has finished 1392 | time.Sleep(1000 * time.Millisecond) 1393 | // Should have an empty pool 1394 | suit.Equal(0, suit.pool.GetNumIdle(), "Idle count different than expected.") 1395 | suit.Equal(0, suit.pool.GetNumActive(), "Total count different than expected.") 1396 | } 1397 | 1398 | func (suit *PoolTestSuite) TestConcurrentInvalidate() { 1399 | ctx := context.Background() 1400 | // Get allObjects and idleObjects loaded with some instances 1401 | nObjects := 1000 1402 | suit.pool.Config.MaxTotal = nObjects 1403 | suit.pool.Config.MaxIdle = nObjects 1404 | active := make([]*TestObject, nObjects) 1405 | for i := 0; i < nObjects; i++ { 1406 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1407 | } 1408 | for i := 0; i < nObjects; i++ { 1409 | if i%2 == 0 { 1410 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1411 | } 1412 | } 1413 | nGoroutines := 20 1414 | nIterations := 60 1415 | // Randomly generated list of distinct invalidation targets 1416 | targets := make(map[int]bool) 1417 | for j := 0; j < nIterations; j++ { 1418 | // Get a random invalidation target 1419 | targ := rand.Intn(nObjects) 1420 | for targets[targ] { 1421 | targ = rand.Intn(nObjects) 1422 | } 1423 | targets[targ] = true 1424 | // Launch nGoroutines goroutines all trying to invalidate the target 1425 | results := make(chan bool, nGoroutines) 1426 | for i := 0; i < nGoroutines; i++ { 1427 | go func(pool *ObjectPool, obj *TestObject) { 1428 | err := pool.InvalidateObject(ctx, obj) 1429 | _, ok := err.(*IllegalStateErr) 1430 | if err != nil && !ok { 1431 | results <- false 1432 | if debugTest { 1433 | fmt.Printf("TestConcurrentInvalidate InvalidateObject error:%v, obj: %v \n", err, obj) 1434 | } 1435 | } else { 1436 | results <- true 1437 | } 1438 | }(suit.pool, active[targ]) 1439 | } 1440 | for i := 0; i < nGoroutines; i++ { 1441 | done := <-results 1442 | suit.True(done) 1443 | } 1444 | } 1445 | suit.Equal(nIterations, suit.pool.GetDestroyedCount()) 1446 | } 1447 | 1448 | func (suit *PoolTestSuite) TestMinIdle() { 1449 | ctx := context.Background() 1450 | suit.pool.Config.MaxIdle = 500 1451 | suit.pool.Config.MinIdle = 5 1452 | suit.pool.Config.MaxTotal = 10 1453 | suit.pool.Config.NumTestsPerEvictionRun = 0 1454 | suit.pool.Config.MinEvictableIdleTime = 50 * time.Millisecond 1455 | suit.pool.Config.TimeBetweenEvictionRuns = 100 * time.Millisecond 1456 | suit.pool.Config.TestWhileIdle = true 1457 | suit.pool.StartEvictor() 1458 | 1459 | time.Sleep(150 * time.Millisecond) 1460 | suit.Equal(5, suit.pool.GetNumIdle(), "Should be 5 idle, found %v", suit.pool.GetNumIdle()) 1461 | 1462 | active := make([]*TestObject, 5) 1463 | active[0] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1464 | time.Sleep(150 * time.Millisecond) 1465 | suit.Equal(5, suit.pool.GetNumIdle(), "Should be 5 idle, found %v", suit.pool.GetNumIdle()) 1466 | 1467 | for i := 1; i < 5; i++ { 1468 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1469 | } 1470 | 1471 | time.Sleep(150 * time.Millisecond) 1472 | suit.Equal(5, suit.pool.GetNumIdle(), "Should be 5 idle, found %v", suit.pool.GetNumIdle()) 1473 | 1474 | for i := 0; i < 5; i++ { 1475 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1476 | } 1477 | time.Sleep(150 * time.Millisecond) 1478 | suit.Equal(10, suit.pool.GetNumIdle(), "Should be 10 idle, found %v", suit.pool.GetNumIdle()) 1479 | } 1480 | 1481 | func (suit *PoolTestSuite) TestMinIdleMaxTotal() { 1482 | suit.pool.Config.MaxIdle = 500 1483 | suit.pool.Config.MinIdle = 5 1484 | suit.pool.Config.MaxTotal = 10 1485 | suit.pool.Config.NumTestsPerEvictionRun = 0 1486 | suit.pool.Config.MinEvictableIdleTime = 50 * time.Millisecond 1487 | suit.pool.Config.TimeBetweenEvictionRuns = 100 * time.Millisecond 1488 | suit.pool.Config.TestWhileIdle = true 1489 | ctx := context.Background() 1490 | suit.pool.StartEvictor() 1491 | 1492 | time.Sleep(150 * time.Millisecond) 1493 | suit.Equal(5, suit.pool.GetNumIdle(), "Should be 5 idle, found %v", suit.pool.GetNumIdle()) 1494 | 1495 | active := make([]*TestObject, 10) 1496 | time.Sleep(150 * time.Millisecond) 1497 | suit.Equal(5, suit.pool.GetNumIdle(), "Should be 5 idle, found %v", suit.pool.GetNumIdle()) 1498 | 1499 | for i := 0; i < 5; i++ { 1500 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1501 | } 1502 | time.Sleep(150 * time.Millisecond) 1503 | suit.Equal(5, suit.pool.GetNumIdle(), "Should be 5 idle, found %v", suit.pool.GetNumIdle()) 1504 | 1505 | for i := 0; i < 5; i++ { 1506 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1507 | } 1508 | time.Sleep(150 * time.Millisecond) 1509 | suit.Equal(10, suit.pool.GetNumIdle(), "Should be 10 idle, found %v", suit.pool.GetNumIdle()) 1510 | 1511 | for i := 0; i < 10; i++ { 1512 | active[i] = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)).(*TestObject) 1513 | } 1514 | time.Sleep(150 * time.Millisecond) 1515 | suit.Equal(0, suit.pool.GetNumIdle(), "Should be 0 idle, found %v", suit.pool.GetNumIdle()) 1516 | 1517 | for i := 0; i < 10; i++ { 1518 | suit.NoError(suit.pool.ReturnObject(ctx, active[i])) 1519 | } 1520 | time.Sleep(150 * time.Millisecond) 1521 | suit.Equal(10, suit.pool.GetNumIdle(), "Should be 10 idle, found %v", suit.pool.GetNumIdle()) 1522 | } 1523 | 1524 | func runTestGoroutines(ctx context.Context, t *testing.T, numGoroutines int, iterations int, delay, borrowTimeout time.Duration, testPool *ObjectPool) { 1525 | arg := NewTestGoroutineArg(testPool, iterations, delay, borrowTimeout, delay, true, nil) 1526 | resultChans := make([]chan TestGoroutineResult, numGoroutines) 1527 | for i := 0; i < numGoroutines; i++ { 1528 | resultChans[i] = goroutineRun(ctx, arg) 1529 | } 1530 | results := make([]TestGoroutineResult, numGoroutines) 1531 | var failedGoroutines []int 1532 | for i := 0; i < numGoroutines; i++ { 1533 | result := <-resultChans[i] 1534 | results[i] = result 1535 | close(resultChans[i]) 1536 | if result.failed { 1537 | failedGoroutines = append(failedGoroutines, i) 1538 | } 1539 | } 1540 | if len(failedGoroutines) > 0 { 1541 | for _, t := range failedGoroutines { 1542 | if debugTest { 1543 | fmt.Printf("Goroutine %v failed %v \n", t, results[t].error) 1544 | } 1545 | } 1546 | assert.Fail(t, fmt.Sprintf("Goroutine %v failed", failedGoroutines)) 1547 | } 1548 | } 1549 | 1550 | func (suit *PoolTestSuite) TestGoroutineed1() { 1551 | suit.pool.Config.MaxTotal = 15 1552 | suit.pool.Config.MaxIdle = 15 1553 | ctx := context.Background() 1554 | runTestGoroutines(ctx, suit.T(), 20, 100, 50*time.Millisecond, 1*time.Second, suit.pool) 1555 | } 1556 | 1557 | func (suit *PoolTestSuite) TestMaxTotalInvariant() { 1558 | maxTotal := 15 1559 | suit.factory.evenValid = false // Every other validation fails 1560 | suit.factory.destroyLatency = 100 * time.Millisecond // Destroy takes 100 ms 1561 | suit.factory.maxTotal = maxTotal // (makes - destroys) bound 1562 | suit.factory.enableValidation = true 1563 | suit.pool.Config.MaxTotal = maxTotal 1564 | suit.pool.Config.MaxIdle = -1 1565 | suit.pool.Config.TestOnReturn = true 1566 | ctx := context.Background() 1567 | runTestGoroutines(ctx, suit.T(), 5, 10, 50*time.Millisecond, 1*time.Second, suit.pool) 1568 | } 1569 | 1570 | func concurrentBorrowAndEvictGoroutine(ctx context.Context, borrow bool, pool *ObjectPool) chan interface{} { 1571 | ch := make(chan interface{}, 1) 1572 | go func(borrow bool, pool *ObjectPool) { 1573 | if borrow { 1574 | obj, _ := pool.BorrowObject(ctx) 1575 | ch <- obj 1576 | } else { 1577 | pool.evict(ctx) 1578 | ch <- 1 1579 | } 1580 | }(borrow, pool) 1581 | return ch 1582 | } 1583 | 1584 | func (suit *PoolTestSuite) TestConcurrentBorrowAndEvict() { 1585 | ctx := context.Background() 1586 | suit.pool.Config.MaxTotal = 1 1587 | suit.NoError(suit.pool.AddObject(ctx)) 1588 | 1589 | for i := 0; i < 5000; i++ { 1590 | timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Second) 1591 | defer cancel() 1592 | 1593 | one := concurrentBorrowAndEvictGoroutine(timeoutCtx, true, suit.pool) 1594 | two := concurrentBorrowAndEvictGoroutine(timeoutCtx, false, suit.pool) 1595 | 1596 | obj := <-one 1597 | close(one) 1598 | <-two 1599 | close(two) 1600 | suit.NotNil(obj) 1601 | suit.NoError(suit.pool.ReturnObject(timeoutCtx, obj)) 1602 | 1603 | //Uncomment suit for a progress indication 1604 | // if i%10 == 0 { 1605 | // fmt.Println(i) 1606 | // } 1607 | } 1608 | } 1609 | 1610 | //Verifies that concurrent goroutines never "share" instances 1611 | func (suit *PoolTestSuite) TestNoInstanceOverlap() { 1612 | maxTotal := 5 1613 | numGoroutines := 100 1614 | delay := 1 * time.Millisecond 1615 | iterations := 1000 1616 | suit.pool.Config.MaxTotal = maxTotal 1617 | suit.pool.Config.MaxIdle = maxTotal 1618 | suit.pool.Config.TestOnBorrow = true 1619 | suit.pool.Config.BlockWhenExhausted = true 1620 | ctx := context.Background() 1621 | runTestGoroutines(ctx, suit.T(), numGoroutines, iterations, delay, time.Duration(0), suit.pool) 1622 | suit.Equal(0, suit.pool.GetDestroyedByBorrowValidationCount()) 1623 | } 1624 | 1625 | func (suit *PoolTestSuite) TestWhenExhaustedBlockClosePool() { 1626 | suit.pool.Config.MaxTotal = 1 1627 | suit.pool.Config.BlockWhenExhausted = true 1628 | 1629 | ctx := context.Background() 1630 | obj1 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1631 | 1632 | // Make sure an object was obtained 1633 | suit.NotNil(obj1) 1634 | 1635 | // Create a separate goroutine to try and borrow another object 1636 | ch := waitTestGoroutine(ctx, suit.pool, 200*time.Millisecond) 1637 | // Give wtt time to start 1638 | time.Sleep(200 * time.Millisecond) 1639 | 1640 | // close the pool (Bug POOL-189) 1641 | suit.pool.Close(ctx) 1642 | 1643 | // Give interrupt time to take effect 1644 | time.Sleep(200 * time.Millisecond) 1645 | 1646 | // Check goroutine was interrupted 1647 | result := <-ch 1648 | close(ch) 1649 | suit.IsType(&collections.InterruptedErr{}, result.error, result.error.Error()) 1650 | } 1651 | 1652 | func waitTestGoroutine(ctx context.Context, pool *ObjectPool, pause time.Duration) chan TestGoroutineResult { 1653 | ch := make(chan TestGoroutineResult, 1) 1654 | go func() { 1655 | result := TestGoroutineResult{} 1656 | result.preborrow = time.Now() 1657 | obj, err := pool.BorrowObject(ctx) 1658 | result.objectID = obj 1659 | result.postborrow = time.Now() 1660 | if err == nil { 1661 | time.Sleep(pause) 1662 | pool.ReturnObject(ctx, obj) 1663 | } 1664 | result.complete = true 1665 | result.error = err 1666 | result.failed = err != nil 1667 | result.postreturn = time.Now() 1668 | result.ended = time.Now() 1669 | ch <- result 1670 | }() 1671 | return ch 1672 | } 1673 | 1674 | func (suit *PoolTestSuite) TestFIFO() { 1675 | ctx := context.Background() 1676 | suit.pool.Config.LIFO = false 1677 | suit.NoError(suit.pool.AddObject(ctx)) // "0" 1678 | suit.NoError(suit.pool.AddObject(ctx)) // "1" 1679 | suit.NoError(suit.pool.AddObject(ctx)) // "2" 1680 | suit.Equal(getNthObject(0), suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "Oldest") 1681 | suit.Equal(getNthObject(1), suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "Middle") 1682 | suit.Equal(getNthObject(2), suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "Youngest") 1683 | o := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1684 | suit.Equal(getNthObject(3), o, "new-3") 1685 | suit.NoError(suit.pool.ReturnObject(ctx, o)) 1686 | suit.Equal(o, suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "returned-3") 1687 | suit.Equal(getNthObject(4), suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "new-4") 1688 | } 1689 | 1690 | func (suit *PoolTestSuite) TestLIFO() { 1691 | ctx := context.Background() 1692 | suit.pool.Config.LIFO = true 1693 | suit.NoError(suit.pool.AddObject(ctx)) // "0" 1694 | suit.NoError(suit.pool.AddObject(ctx)) // "1" 1695 | suit.NoError(suit.pool.AddObject(ctx)) // "2" 1696 | suit.Equal(getNthObject(2), suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "Youngest") 1697 | suit.Equal(getNthObject(1), suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "Middle") 1698 | suit.Equal(getNthObject(0), suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "Oldest") 1699 | o := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1700 | suit.Equal(getNthObject(3), o, "new-3") 1701 | suit.NoError(suit.pool.ReturnObject(ctx, o)) 1702 | suit.Equal(o, suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "returned-3") 1703 | suit.Equal(getNthObject(4), suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)), "new-4") 1704 | } 1705 | 1706 | func (suit *PoolTestSuite) TestAddObject() { 1707 | ctx := context.Background() 1708 | suit.Equal(0, suit.pool.GetNumIdle(), "should be zero idle") 1709 | suit.NoError(suit.pool.AddObject(ctx)) 1710 | suit.Equal(1, suit.pool.GetNumIdle(), "should be one idle") 1711 | suit.Equal(0, suit.pool.GetNumActive(), "should be zero active") 1712 | obj := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1713 | suit.Equal(0, suit.pool.GetNumIdle(), "should be zero idle") 1714 | suit.Equal(1, suit.pool.GetNumActive(), "should be one active") 1715 | suit.NoError(suit.pool.ReturnObject(ctx, obj)) 1716 | suit.Equal(1, suit.pool.GetNumIdle(), "should be one idle") 1717 | suit.Equal(0, suit.pool.GetNumActive(), "should be zero active") 1718 | } 1719 | 1720 | //TODO 1721 | //func (suit *PoolTestSuite) TestBorrowObjectFairness() {} 1722 | 1723 | /** 1724 | * On first borrow, first object fails validation, second object is OK. 1725 | * Subsequent borrows are OK. This was POOL-152. 1726 | */ 1727 | func (suit *PoolTestSuite) TestBrokenFactoryShouldNotBlockPool() { 1728 | maxTotal := 1 1729 | 1730 | suit.factory.maxTotal = maxTotal 1731 | suit.pool.Config.MaxTotal = maxTotal 1732 | suit.pool.Config.BlockWhenExhausted = true 1733 | suit.pool.Config.TestOnBorrow = true 1734 | 1735 | ctx := context.Background() 1736 | 1737 | // First borrow object will need to create a new object which will fail 1738 | // validation. 1739 | suit.factory.setValid(false) 1740 | obj, ex := suit.pool.BorrowObject(ctx) 1741 | // Failure expected 1742 | _, ok := ex.(*NoSuchElementErr) 1743 | suit.True(ok, "expect NoSuchElementErr, but get: %v", reflect.TypeOf(ex)) 1744 | suit.Nil(obj) 1745 | 1746 | // Configure factory to create valid objects so subsequent borrows work 1747 | suit.factory.setValid(true) 1748 | 1749 | // Subsequent borrows should be OK 1750 | obj = suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1751 | suit.NoError(suit.pool.ReturnObject(ctx, obj)) 1752 | } 1753 | 1754 | /* 1755 | * Test multi-goroutineed pool access. 1756 | * Multiple goroutines, but maxTotal only allows half the goroutines to succeed. 1757 | * 1758 | * This test was prompted by Continuum build failures in the Commons DBCP test case: 1759 | * TestPerUserPoolDataSource.testMultipleGoroutines2() 1760 | * Let's see if the suit fails on Continuum too! 1761 | */ 1762 | func (suit *PoolTestSuite) TestMaxWaitMultiGoroutineed() { 1763 | maxWait := 500 * time.Millisecond // wait for connection 1764 | holdTime := 2 * maxWait // how long to hold connection 1765 | goroutines := 10 // number of goroutines to grab the object initially 1766 | suit.pool.Config.BlockWhenExhausted = true 1767 | suit.pool.Config.MaxTotal = goroutines 1768 | // Create enough goroutines so half the goroutines will have to wait 1769 | resultChans := make([]chan TestGoroutineResult, goroutines*2) 1770 | origin := time.Now().Add(-1 * time.Second) 1771 | for i := 0; i < len(resultChans); i++ { 1772 | ctx, cancel := context.WithTimeout(context.Background(), maxWait) 1773 | defer cancel() 1774 | resultChans[i] = waitTestGoroutine(ctx, suit.pool, holdTime) 1775 | } 1776 | failed := 0 1777 | results := make([]TestGoroutineResult, len(resultChans)) 1778 | for i := 0; i < len(resultChans); i++ { 1779 | ch := resultChans[i] 1780 | result := <-ch 1781 | close(ch) 1782 | results[i] = result 1783 | if result.error != nil { 1784 | failed++ 1785 | } 1786 | } 1787 | if debugTest || len(resultChans)/2 != failed { 1788 | fmt.Println( 1789 | "MaxWait: ", maxWait, 1790 | " HoldTime: ", holdTime, 1791 | " MaxTotal: ", goroutines, 1792 | " Goroutines: ", len(resultChans), 1793 | " Failed: ", failed) 1794 | for _, result := range results { 1795 | fmt.Println( 1796 | "Preborrow: ", (result.preborrow.Sub(origin)), 1797 | " Postborrow: ", (result.postborrow.Sub(origin)), 1798 | " BorrowTime: ", (result.postborrow.Sub(result.preborrow)), 1799 | " PostReturn: ", (result.postreturn.Sub(origin)), 1800 | " Ended: ", (result.ended.Sub(origin)), 1801 | " ObjId: ", result.objectID) 1802 | } 1803 | } 1804 | suit.Equal(len(resultChans)/2, failed, "Expected half the goroutines to fail") 1805 | } 1806 | 1807 | /** 1808 | * Test the following scenario: 1809 | * Goroutine 1 borrows an instance 1810 | * Goroutine 2 starts to borrow another instance before goroutine 1 returns its instance 1811 | * Goroutine 1 returns its instance while goroutine 2 is validating its newly created instance 1812 | * The test verifies that the instance created by Goroutine 2 is not leaked. 1813 | */ 1814 | func (suit *PoolTestSuite) TestMakeConcurrentWithReturn() { 1815 | suit.pool.Config.TestOnBorrow = true 1816 | suit.factory.setValid(true) 1817 | ctx := context.Background() 1818 | // Borrow and return an instance, with a short wait 1819 | ch := waitTestGoroutine(ctx, suit.pool, 200*time.Millisecond) 1820 | time.Sleep(50 * time.Millisecond) // wait for validation to succeed 1821 | // Slow down validation and borrow an instance 1822 | suit.factory.setValidateLatency(400 * time.Millisecond) 1823 | instance := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1824 | // Now make sure that we have not leaked an instance 1825 | suit.Equal(suit.factory.makeCounter, suit.pool.GetNumIdle()+1) 1826 | suit.NoError(suit.pool.ReturnObject(ctx, instance)) 1827 | suit.Equal(suit.factory.makeCounter, suit.pool.GetNumIdle()) 1828 | <-ch 1829 | close(ch) 1830 | } 1831 | 1832 | /** 1833 | * Verify that goroutines waiting on a depleted pool get served when a checked out object is 1834 | * invalidated. 1835 | * 1836 | * JIRA: POOL-240 1837 | */ 1838 | func (suit *PoolTestSuite) TestInvalidateFreesCapacity() { 1839 | suit.pool.Config.MaxTotal = 2 1840 | suit.pool.Config.BlockWhenExhausted = true 1841 | 1842 | // Borrow an instance and hold if for 5 seconds 1843 | ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) 1844 | defer cancel() 1845 | ch1 := waitTestGoroutine(ctx, suit.pool, 5*time.Second) 1846 | 1847 | // Borrow another instance 1848 | ctx, cancel = context.WithTimeout(context.Background(), 500*time.Millisecond) 1849 | defer cancel() 1850 | obj := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1851 | 1852 | // Launch another goroutine - will block, but fail in 500 ms 1853 | ctx, cancel = context.WithTimeout(context.Background(), 500*time.Millisecond) 1854 | defer cancel() 1855 | ch2 := waitTestGoroutine(ctx, suit.pool, 100*time.Millisecond) 1856 | 1857 | // Invalidate the object borrowed by suit goroutine - should allow goroutine2 to create 1858 | time.Sleep(20 * time.Millisecond) 1859 | suit.NoError(suit.pool.InvalidateObject(context.Background(), obj)) 1860 | time.Sleep(600 * time.Millisecond) // Wait for goroutine2 to timeout 1861 | result2 := <-ch2 1862 | close(ch2) 1863 | if result2.error != nil { 1864 | suit.Fail(result2.error.Error()) 1865 | } 1866 | <-ch1 1867 | close(ch1) 1868 | } 1869 | 1870 | /** 1871 | * Verify that goroutines waiting on a depleted pool get served when a returning object fails 1872 | * validation. 1873 | * 1874 | * JIRA: POOL-240 1875 | * 1876 | */ 1877 | func (suit *PoolTestSuite) TestValidationFailureOnReturnFreesCapacity() { 1878 | suit.factory.setValid(false) // Validate will always fail 1879 | suit.factory.enableValidation = true 1880 | suit.pool.Config.MaxTotal = 2 1881 | suit.pool.Config.TestOnReturn = true 1882 | suit.pool.Config.TestOnBorrow = false 1883 | 1884 | // Borrow an instance and hold if for 5 seconds 1885 | ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond) 1886 | defer cancel() 1887 | ch1 := waitTestGoroutine(ctx, suit.pool, 5*time.Second) 1888 | 1889 | // Borrow another instance and return it after 500 ms (validation will fail) 1890 | ctx, cancel = context.WithTimeout(context.Background(), 1500*time.Millisecond) 1891 | defer cancel() 1892 | ch2 := waitTestGoroutine(ctx, suit.pool, 500*time.Millisecond) 1893 | time.Sleep(50 * time.Millisecond) 1894 | 1895 | // Try to borrow an object 1896 | ctx, cancel = context.WithTimeout(context.Background(), 1500*time.Millisecond) 1897 | defer cancel() 1898 | obj := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1899 | suit.NoError(suit.pool.ReturnObject(ctx, obj)) 1900 | 1901 | <-ch1 1902 | close(ch1) 1903 | <-ch2 1904 | close(ch2) 1905 | } 1906 | 1907 | //TODO 1908 | //func (suit *PoolTestSuite) TestSwallowedExceptionListener() { 1909 | //} 1910 | 1911 | // POOL-248 1912 | func (suit *PoolTestSuite) TestMultipleReturnOfSameObject() { 1913 | ctx := context.Background() 1914 | 1915 | suit.Equal(0, suit.pool.GetNumActive()) 1916 | suit.Equal(0, suit.pool.GetNumIdle()) 1917 | 1918 | obj := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1919 | 1920 | suit.Equal(1, suit.pool.GetNumActive()) 1921 | suit.Equal(0, suit.pool.GetNumIdle()) 1922 | 1923 | suit.NoError(suit.pool.ReturnObject(ctx, obj)) 1924 | 1925 | suit.Equal(0, suit.pool.GetNumActive()) 1926 | suit.Equal(1, suit.pool.GetNumIdle()) 1927 | 1928 | err := suit.pool.ReturnObject(ctx, obj) 1929 | _, ok := err.(*IllegalStateErr) 1930 | suit.True(ok, "expect IllegalStatusErr, but get %v", reflect.TypeOf(err)) 1931 | suit.Equal(0, suit.pool.GetNumActive()) 1932 | suit.Equal(1, suit.pool.GetNumIdle()) 1933 | } 1934 | 1935 | // TODO POOL-259 1936 | //func (suit *PoolTestSuite) TestClientWaitStats() { 1937 | //} 1938 | 1939 | // POOL-276 1940 | func (suit *PoolTestSuite) TestValidationOnCreateOnly() { 1941 | suit.pool.Config.MaxTotal = 1 1942 | suit.pool.Config.TestOnCreate = true 1943 | suit.pool.Config.TestOnBorrow = false 1944 | suit.pool.Config.TestOnReturn = false 1945 | suit.pool.Config.TestWhileIdle = false 1946 | 1947 | ctx := context.Background() 1948 | 1949 | o1 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1950 | suit.Equal(getNthObject(0), o1) 1951 | go func() { 1952 | time.Sleep(3 * time.Second) 1953 | suit.pool.ReturnObject(ctx, o1) 1954 | }() 1955 | 1956 | o2 := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 1957 | suit.Equal(getNthObject(0), o2) 1958 | 1959 | suit.Equal(1, suit.factory.validateCounter) 1960 | } 1961 | 1962 | /** 1963 | * Verifies that when a factory's makeObject produces instances that are not 1964 | * discernible by == , the pool can handle them. 1965 | * 1966 | * JIRA: POOL-283 1967 | */ 1968 | func (suit *PoolTestSuite) TestEqualsIndiscernible() { 1969 | ctx := context.Background() 1970 | pool := NewObjectPoolWithDefaultConfig(ctx, NewPooledObjectFactorySimple(func(context.Context) (interface{}, error) { 1971 | return make(map[string]string), nil 1972 | })) 1973 | m1 := suit.NoErrorWithResult(pool.BorrowObject(ctx)) 1974 | m2 := suit.NoErrorWithResult(pool.BorrowObject(ctx)) 1975 | suit.NoError(pool.ReturnObject(ctx, m1)) 1976 | suit.NoError(pool.ReturnObject(ctx, m2)) 1977 | pool.Close(ctx) 1978 | } 1979 | 1980 | /** 1981 | * Verifies that when a borrowed object is mutated in a way that does not 1982 | * preserve equality and hashcode, the pool can recognized it on return. 1983 | * 1984 | * JIRA: POOL-284 1985 | */ 1986 | func (suit *PoolTestSuite) TestMutable() { 1987 | ctx := context.Background() 1988 | pool := NewObjectPoolWithDefaultConfig(ctx, NewPooledObjectFactorySimple(func(context.Context) (interface{}, error) { 1989 | return make(map[string]string), nil 1990 | })) 1991 | m1 := suit.NoErrorWithResult(pool.BorrowObject(ctx)).(map[string]string) 1992 | m2 := suit.NoErrorWithResult(pool.BorrowObject(ctx)).(map[string]string) 1993 | m1["k1"] = "v1" 1994 | m2["k2"] = "v2" 1995 | suit.NoError(pool.ReturnObject(ctx, m1)) 1996 | suit.NoError(pool.ReturnObject(ctx, m2)) 1997 | suit.Equal(2, pool.GetNumIdle()) 1998 | pool.Close(ctx) 1999 | } 2000 | 2001 | /** 2002 | * Verifies that returning an object twice (without borrow in between) causes ISE 2003 | * but does not re-validate or re-passivate the instance. 2004 | * 2005 | * JIRA: POOL-285 2006 | */ 2007 | //TODO 2008 | //func (suit *PoolTestSuite) TestMultipleReturn() { 2009 | //} 2010 | 2011 | func (suit *PoolTestSuite) TestAddError() { 2012 | suit.pool.factory = nil 2013 | err := suit.pool.AddObject(context.Background()) 2014 | suit.NotNil(err) 2015 | suit.NotNil(err.Error()) 2016 | } 2017 | 2018 | func (suit *PoolTestSuite) TestClosePoolError() { 2019 | ctx := context.Background() 2020 | suit.pool.Close(ctx) 2021 | err := suit.pool.AddObject(ctx) 2022 | suit.NotNil(err) 2023 | } 2024 | 2025 | func (suit *PoolTestSuite) TestMakeObjectError() { 2026 | suit.factory.exceptionOnMake = true 2027 | err := suit.pool.AddObject(context.Background()) 2028 | suit.NotNil(err) 2029 | } 2030 | 2031 | func (suit *PoolTestSuite) TestAddObjectPassivateError() { 2032 | suit.factory.exceptionOnPassivate = true 2033 | suit.Equal(0, suit.pool.GetNumActive()) 2034 | suit.Equal(0, suit.pool.GetNumIdle()) 2035 | err := suit.pool.AddObject(context.Background()) 2036 | suit.EqualError(err, "passivate error") 2037 | suit.Equal(0, suit.pool.GetNumActive()) 2038 | suit.Equal(0, suit.pool.GetNumIdle()) 2039 | } 2040 | 2041 | func (suit *PoolTestSuite) TestReturnObjectError() { 2042 | obj := new(TestObject) 2043 | err := suit.pool.ReturnObject(context.Background(), obj) 2044 | suit.NotNil(err) 2045 | } 2046 | 2047 | func (suit *PoolTestSuite) TestPreparePool() { 2048 | ctx := context.Background() 2049 | suit.pool.Config.MinIdle = 1 2050 | suit.pool.Config.MaxTotal = 1 2051 | suit.pool.PreparePool(ctx) 2052 | suit.Equal(1, suit.pool.GetNumIdle()) 2053 | obj := suit.NoErrorWithResult(suit.pool.BorrowObject(ctx)) 2054 | suit.pool.PreparePool(ctx) 2055 | suit.Equal(0, suit.pool.GetNumIdle()) 2056 | suit.pool.Config.MinIdle = 0 2057 | suit.NoError(suit.pool.ReturnObject(ctx, obj)) 2058 | suit.pool.PreparePool(ctx) 2059 | suit.Equal(1, suit.pool.GetNumIdle()) 2060 | } 2061 | 2062 | func (suit *PoolTestSuite) TestConcurrentCloseAndEvict() { 2063 | ctx := context.Background() 2064 | suit.pool.Config.MinIdle = 1 2065 | suit.pool.Config.SoftMinEvictableIdleTime = time.Millisecond * 100 2066 | suit.pool.Config.TimeBetweenEvictionRuns = time.Millisecond * 500 2067 | suit.factory.destroyLatency = time.Millisecond * 1000 // Destroy takes 1000 ms 2068 | suit.pool.PreparePool(ctx) 2069 | suit.pool.StartEvictor() 2070 | suit.Equal(1, suit.pool.GetNumIdle()) 2071 | ticker := time.NewTicker(time.Millisecond * 1000) 2072 | testTimeoutTicker := time.NewTicker(time.Millisecond * 5000) // if time exceeds test fails 2073 | defer ticker.Stop() 2074 | defer testTimeoutTicker.Stop() 2075 | go func() { 2076 | select { 2077 | case <-testTimeoutTicker.C: 2078 | // Time-out 2079 | suit.FailNow("Time is exceeds on pool close") 2080 | } 2081 | }() 2082 | select { 2083 | case <-ticker.C: 2084 | suit.pool.Close(ctx) 2085 | } 2086 | } 2087 | 2088 | func (suit *PoolTestSuite) TestValueFactory() { 2089 | suit.pool.factory = NewPooledObjectFactorySimple(func(context.Context) (interface{}, error) { 2090 | return "string value", nil 2091 | }) 2092 | 2093 | suit.Panics(func() { 2094 | suit.pool.BorrowObject(context.Background()) 2095 | }) 2096 | } 2097 | 2098 | // https://github.com/jolestar/go-commons-pool/issues/44 2099 | func (suit *PoolTestSuite) TestDeadLock() { 2100 | ctx := context.Background() 2101 | suit.pool.Config.MinIdle = 1 2102 | suit.pool.Config.MaxIdle = 1 2103 | suit.pool.Config.MaxTotal = 1 2104 | count := 1000000 2105 | testWG := sync.WaitGroup{} 2106 | testWG.Add(count) 2107 | 2108 | for i := 0; i < count; i++ { 2109 | obj, err := suit.pool.BorrowObject(ctx) 2110 | if err != nil { 2111 | panic(err) 2112 | } 2113 | go func(obj interface{}) { 2114 | err = suit.pool.ReturnObject(ctx, obj) 2115 | if err != nil { 2116 | panic(err) 2117 | } 2118 | testWG.Done() 2119 | }(obj) 2120 | } 2121 | testWG.Wait() 2122 | } 2123 | 2124 | var perf bool 2125 | 2126 | func init() { 2127 | flag.BoolVar(&perf, "perf", false, "perf") 2128 | flag.BoolVar(&debugTest, "debug_test", false, "debug_test") 2129 | } 2130 | 2131 | func TestMain(m *testing.M) { 2132 | flag.Parse() 2133 | exit := 0 2134 | if perf { 2135 | perfMain(context.Background()) 2136 | } else { 2137 | exit = m.Run() 2138 | } 2139 | os.Exit(exit) 2140 | } 2141 | --------------------------------------------------------------------------------