pax_global_header00006660000000000000000000000064137264453570014532gustar00rootroot0000000000000052 comment=9355c1b759d063c9a2a5928a45370b2c3cee5f8c pool-0.0.2/000077500000000000000000000000001372644535700125025ustar00rootroot00000000000000pool-0.0.2/LICENSE000066400000000000000000000261351372644535700135160ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.pool-0.0.2/README.md000066400000000000000000000006261372644535700137650ustar00rootroot00000000000000pool is a copy of a few packages from https://github.com/vitessio/vitess. Vitess has some useful Go packages, however they are not versioned with Go modules, which causes issues (e.g. https://github.com/ThalesIgnite/crypto11/issues/56). They are also buried inside a large project, which forms a heavyweight dependency. This package exposes the resource pool implementation and some of the atomic types. pool-0.0.2/atomic.go000066400000000000000000000114131372644535700143050ustar00rootroot00000000000000/* Copyright 2017 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package pool import ( "sync" "sync/atomic" "time" ) // AtomicInt32 is a wrapper with a simpler interface around atomic.(Add|Store|Load|CompareAndSwap)Int32 functions. type AtomicInt32 struct { int32 } // NewAtomicInt32 initializes a new AtomicInt32 with a given value. func NewAtomicInt32(n int32) AtomicInt32 { return AtomicInt32{n} } // Add atomically adds n to the value. func (i *AtomicInt32) Add(n int32) int32 { return atomic.AddInt32(&i.int32, n) } // Set atomically sets n as new value. func (i *AtomicInt32) Set(n int32) { atomic.StoreInt32(&i.int32, n) } // Get atomically returns the current value. func (i *AtomicInt32) Get() int32 { return atomic.LoadInt32(&i.int32) } // CompareAndSwap atomatically swaps the old with the new value. func (i *AtomicInt32) CompareAndSwap(oldval, newval int32) (swapped bool) { return atomic.CompareAndSwapInt32(&i.int32, oldval, newval) } // AtomicInt64 is a wrapper with a simpler interface around atomic.(Add|Store|Load|CompareAndSwap)Int64 functions. type AtomicInt64 struct { int64 } // NewAtomicInt64 initializes a new AtomicInt64 with a given value. func NewAtomicInt64(n int64) AtomicInt64 { return AtomicInt64{n} } // Add atomically adds n to the value. func (i *AtomicInt64) Add(n int64) int64 { return atomic.AddInt64(&i.int64, n) } // Set atomically sets n as new value. func (i *AtomicInt64) Set(n int64) { atomic.StoreInt64(&i.int64, n) } // Get atomically returns the current value. func (i *AtomicInt64) Get() int64 { return atomic.LoadInt64(&i.int64) } // CompareAndSwap atomatically swaps the old with the new value. func (i *AtomicInt64) CompareAndSwap(oldval, newval int64) (swapped bool) { return atomic.CompareAndSwapInt64(&i.int64, oldval, newval) } // AtomicDuration is a wrapper with a simpler interface around atomic.(Add|Store|Load|CompareAndSwap)Int64 functions. type AtomicDuration struct { int64 } // NewAtomicDuration initializes a new AtomicDuration with a given value. func NewAtomicDuration(duration time.Duration) AtomicDuration { return AtomicDuration{int64(duration)} } // Add atomically adds duration to the value. func (d *AtomicDuration) Add(duration time.Duration) time.Duration { return time.Duration(atomic.AddInt64(&d.int64, int64(duration))) } // Set atomically sets duration as new value. func (d *AtomicDuration) Set(duration time.Duration) { atomic.StoreInt64(&d.int64, int64(duration)) } // Get atomically returns the current value. func (d *AtomicDuration) Get() time.Duration { return time.Duration(atomic.LoadInt64(&d.int64)) } // CompareAndSwap atomatically swaps the old with the new value. func (d *AtomicDuration) CompareAndSwap(oldval, newval time.Duration) (swapped bool) { return atomic.CompareAndSwapInt64(&d.int64, int64(oldval), int64(newval)) } // AtomicBool gives an atomic boolean variable. type AtomicBool struct { int32 } // NewAtomicBool initializes a new AtomicBool with a given value. func NewAtomicBool(n bool) AtomicBool { if n { return AtomicBool{1} } return AtomicBool{0} } // Set atomically sets n as new value. func (i *AtomicBool) Set(n bool) { if n { atomic.StoreInt32(&i.int32, 1) } else { atomic.StoreInt32(&i.int32, 0) } } // Get atomically returns the current value. func (i *AtomicBool) Get() bool { return atomic.LoadInt32(&i.int32) != 0 } // CompareAndSwap atomatically swaps the old with the new value. func (i *AtomicBool) CompareAndSwap(o, n bool) bool { var old, new int32 if o { old = 1 } if n { new = 1 } return atomic.CompareAndSwapInt32(&i.int32, old, new) } // AtomicString gives you atomic-style APIs for string, but // it's only a convenience wrapper that uses a mutex. So, it's // not as efficient as the rest of the atomic types. type AtomicString struct { mu sync.Mutex str string } // Set atomically sets str as new value. func (s *AtomicString) Set(str string) { s.mu.Lock() s.str = str s.mu.Unlock() } // Get atomically returns the current value. func (s *AtomicString) Get() string { s.mu.Lock() str := s.str s.mu.Unlock() return str } // CompareAndSwap atomatically swaps the old with the new value. func (s *AtomicString) CompareAndSwap(oldval, newval string) (swqpped bool) { s.mu.Lock() defer s.mu.Unlock() if s.str == oldval { s.str = newval return true } return false } pool-0.0.2/atomic_test.go000066400000000000000000000034001372644535700153410ustar00rootroot00000000000000/* Copyright 2017 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package pool import ( "testing" ) func TestAtomicString(t *testing.T) { var s AtomicString if s.Get() != "" { t.Errorf("want empty, got %s", s.Get()) } s.Set("a") if s.Get() != "a" { t.Errorf("want a, got %s", s.Get()) } if s.CompareAndSwap("b", "c") { t.Errorf("want false, got true") } if s.Get() != "a" { t.Errorf("want a, got %s", s.Get()) } if !s.CompareAndSwap("a", "c") { t.Errorf("want true, got false") } if s.Get() != "c" { t.Errorf("want c, got %s", s.Get()) } } func TestAtomicBool(t *testing.T) { b := NewAtomicBool(true) if !b.Get() { t.Error("b.Get: false, want true") } b.Set(false) if b.Get() { t.Error("b.Get: true, want false") } b.Set(true) if !b.Get() { t.Error("b.Get: false, want true") } if b.CompareAndSwap(false, true) { t.Error("b.CompareAndSwap false, true should fail") } if !b.CompareAndSwap(true, false) { t.Error("b.CompareAndSwap true, false should succeed") } if !b.CompareAndSwap(false, false) { t.Error("b.CompareAndSwap false, false should NOP") } if !b.CompareAndSwap(false, true) { t.Error("b.CompareAndSwap false, true should succeed") } if !b.CompareAndSwap(true, true) { t.Error("b.CompareAndSwap true, true should NOP") } } pool-0.0.2/go.mod000066400000000000000000000000621372644535700136060ustar00rootroot00000000000000module github.com/thales-e-security/pool go 1.12 pool-0.0.2/resource_pool.go000066400000000000000000000236731372644535700157240ustar00rootroot00000000000000/* Copyright 2017 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package pools provides functionality to manage and reuse resources // like connections. // // Modified by Duncan Jones to reduce the number of external dependencies. package pool import ( "context" "errors" "fmt" "sync" "time" ) var ( // ErrClosed is returned if ResourcePool is used when it's closed. ErrClosed = errors.New("resource pool is closed") // ErrTimeout is returned if a resource get times out. ErrTimeout = errors.New("resource pool timed out") prefillTimeout = 30 * time.Second ) // Factory is a function that can be used to create a resource. type Factory func() (Resource, error) // Resource defines the interface that every resource must provide. // Thread synchronization between Close() and IsClosed() // is the responsibility of the caller. type Resource interface { Close() } // ResourcePool allows you to use a pool of resources. type ResourcePool struct { // stats. Atomic fields must remain at the top in order to prevent panics on certain architectures. available AtomicInt64 active AtomicInt64 inUse AtomicInt64 waitCount AtomicInt64 waitTime AtomicDuration idleClosed AtomicInt64 capacity AtomicInt64 idleTimeout AtomicDuration resources chan resourceWrapper factory Factory idleTimer *Timer } type resourceWrapper struct { resource Resource timeUsed time.Time } // NewResourcePool creates a new ResourcePool pool. // capacity is the number of possible resources in the pool: // there can be up to 'capacity' of these at a given time. // maxCap specifies the extent to which the pool can be resized // in the future through the SetCapacity function. // You cannot resize the pool beyond maxCap. // If a resource is unused beyond idleTimeout, it's replaced // with a new one. // An idleTimeout of 0 means that there is no timeout. // A non-zero value of prefillParallelism causes the pool to be pre-filled. // The value specifies how many resources can be opened in parallel. func NewResourcePool(factory Factory, capacity, maxCap int, idleTimeout time.Duration, prefillParallelism int) *ResourcePool { if capacity <= 0 || maxCap <= 0 || capacity > maxCap { panic(errors.New("invalid/out of range capacity")) } rp := &ResourcePool{ resources: make(chan resourceWrapper, maxCap), factory: factory, available: NewAtomicInt64(int64(capacity)), capacity: NewAtomicInt64(int64(capacity)), idleTimeout: NewAtomicDuration(idleTimeout), } for i := 0; i < capacity; i++ { rp.resources <- resourceWrapper{} } ctx, cancel := context.WithTimeout(context.TODO(), prefillTimeout) defer cancel() if prefillParallelism != 0 { sem := NewSemaphore(prefillParallelism, 0 /* timeout */) var wg sync.WaitGroup for i := 0; i < capacity; i++ { wg.Add(1) go func() { defer wg.Done() _ = sem.Acquire() defer sem.Release() // If context has expired, give up. select { case <-ctx.Done(): return default: } r, err := rp.Get(ctx) if err != nil { return } rp.Put(r) }() } wg.Wait() } if idleTimeout != 0 { rp.idleTimer = NewTimer(idleTimeout / 10) rp.idleTimer.Start(rp.closeIdleResources) } return rp } // Close empties the pool calling Close on all its resources. // You can call Close while there are outstanding resources. // It waits for all resources to be returned (Put). // After a Close, Get is not allowed. func (rp *ResourcePool) Close() { if rp.idleTimer != nil { rp.idleTimer.Stop() } _ = rp.SetCapacity(0) } // IsClosed returns true if the resource pool is closed. func (rp *ResourcePool) IsClosed() (closed bool) { return rp.capacity.Get() == 0 } // closeIdleResources scans the pool for idle resources func (rp *ResourcePool) closeIdleResources() { available := int(rp.Available()) idleTimeout := rp.IdleTimeout() for i := 0; i < available; i++ { var wrapper resourceWrapper select { case wrapper = <-rp.resources: default: // stop early if we don't get anything new from the pool return } func() { defer func() { rp.resources <- wrapper }() if wrapper.resource != nil && idleTimeout > 0 && time.Until(wrapper.timeUsed.Add(idleTimeout)) < 0 { wrapper.resource.Close() rp.idleClosed.Add(1) rp.reopenResource(&wrapper) } }() } } // Get will return the next available resource. If capacity // has not been reached, it will create a new one using the factory. Otherwise, // it will wait till the next resource becomes available or a timeout. // A timeout of 0 is an indefinite wait. func (rp *ResourcePool) Get(ctx context.Context) (resource Resource, err error) { return rp.get(ctx) } func (rp *ResourcePool) get(ctx context.Context) (resource Resource, err error) { // If ctx has already expired, avoid racing with rp's resource channel. select { case <-ctx.Done(): return nil, ErrTimeout default: } // Fetch var wrapper resourceWrapper var ok bool select { case wrapper, ok = <-rp.resources: default: startTime := time.Now() select { case wrapper, ok = <-rp.resources: case <-ctx.Done(): return nil, ErrTimeout } rp.recordWait(startTime) } if !ok { return nil, ErrClosed } // Unwrap if wrapper.resource == nil { wrapper.resource, err = rp.factory() if err != nil { rp.resources <- resourceWrapper{} return nil, err } rp.active.Add(1) } rp.available.Add(-1) rp.inUse.Add(1) return wrapper.resource, err } // Put will return a resource to the pool. For every successful Get, // a corresponding Put is required. If you no longer need a resource, // you will need to call Put(nil) instead of returning the closed resource. // This will cause a new resource to be created in its place. func (rp *ResourcePool) Put(resource Resource) { var wrapper resourceWrapper if resource != nil { wrapper = resourceWrapper{ resource: resource, timeUsed: time.Now(), } } else { rp.reopenResource(&wrapper) } select { case rp.resources <- wrapper: default: panic(errors.New("attempt to Put into a full ResourcePool")) } rp.inUse.Add(-1) rp.available.Add(1) } func (rp *ResourcePool) reopenResource(wrapper *resourceWrapper) { if r, err := rp.factory(); err == nil { wrapper.resource = r wrapper.timeUsed = time.Now() } else { wrapper.resource = nil rp.active.Add(-1) } } // SetCapacity changes the capacity of the pool. // You can use it to shrink or expand, but not beyond // the max capacity. If the change requires the pool // to be shrunk, SetCapacity waits till the necessary // number of resources are returned to the pool. // A SetCapacity of 0 is equivalent to closing the ResourcePool. func (rp *ResourcePool) SetCapacity(capacity int) error { if capacity < 0 || capacity > cap(rp.resources) { return fmt.Errorf("capacity %d is out of range", capacity) } // Atomically swap new capacity with old, but only // if old capacity is non-zero. var oldcap int for { oldcap = int(rp.capacity.Get()) if oldcap == 0 { return ErrClosed } if oldcap == capacity { return nil } if rp.capacity.CompareAndSwap(int64(oldcap), int64(capacity)) { break } } if capacity < oldcap { for i := 0; i < oldcap-capacity; i++ { wrapper := <-rp.resources if wrapper.resource != nil { wrapper.resource.Close() rp.active.Add(-1) } rp.available.Add(-1) } } else { for i := 0; i < capacity-oldcap; i++ { rp.resources <- resourceWrapper{} rp.available.Add(1) } } if capacity == 0 { close(rp.resources) } return nil } func (rp *ResourcePool) recordWait(start time.Time) { rp.waitCount.Add(1) rp.waitTime.Add(time.Since(start)) } // SetIdleTimeout sets the idle timeout. It can only be used if there was an // idle timeout set when the pool was created. func (rp *ResourcePool) SetIdleTimeout(idleTimeout time.Duration) { if rp.idleTimer == nil { panic("SetIdleTimeout called when timer not initialized") } rp.idleTimeout.Set(idleTimeout) rp.idleTimer.SetInterval(idleTimeout / 10) } // StatsJSON returns the stats in JSON format. func (rp *ResourcePool) StatsJSON() string { return fmt.Sprintf(`{"Capacity": %v, "Available": %v, "Active": %v, "InUse": %v, "MaxCapacity": %v, "WaitCount": %v, "WaitTime": %v, "IdleTimeout": %v, "IdleClosed": %v}`, rp.Capacity(), rp.Available(), rp.Active(), rp.InUse(), rp.MaxCap(), rp.WaitCount(), rp.WaitTime().Nanoseconds(), rp.IdleTimeout().Nanoseconds(), rp.IdleClosed(), ) } // Capacity returns the capacity. func (rp *ResourcePool) Capacity() int64 { return rp.capacity.Get() } // Available returns the number of currently unused and available resources. func (rp *ResourcePool) Available() int64 { return rp.available.Get() } // Active returns the number of active (i.e. non-nil) resources either in the // pool or claimed for use func (rp *ResourcePool) Active() int64 { return rp.active.Get() } // InUse returns the number of claimed resources from the pool func (rp *ResourcePool) InUse() int64 { return rp.inUse.Get() } // MaxCap returns the max capacity. func (rp *ResourcePool) MaxCap() int64 { return int64(cap(rp.resources)) } // WaitCount returns the total number of waits. func (rp *ResourcePool) WaitCount() int64 { return rp.waitCount.Get() } // WaitTime returns the total wait time. func (rp *ResourcePool) WaitTime() time.Duration { return rp.waitTime.Get() } // IdleTimeout returns the idle timeout. func (rp *ResourcePool) IdleTimeout() time.Duration { return rp.idleTimeout.Get() } // IdleClosed returns the count of resources closed due to idle timeout. func (rp *ResourcePool) IdleClosed() int64 { return rp.idleClosed.Get() } pool-0.0.2/resource_pool_test.go000066400000000000000000000360061372644535700167550ustar00rootroot00000000000000/* Copyright 2017 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Modified by Duncan Jones to remove external dependencies. */ package pool import ( "context" "errors" "testing" "time" ) var lastID, count AtomicInt64 type TestResource struct { num int64 closed bool } func (tr *TestResource) Close() { if !tr.closed { count.Add(-1) tr.closed = true } } func PoolFactory() (Resource, error) { count.Add(1) return &TestResource{lastID.Add(1), false}, nil } func FailFactory() (Resource, error) { return nil, errors.New("Failed") } func SlowFailFactory() (Resource, error) { time.Sleep(10 * time.Millisecond) return nil, errors.New("Failed") } func TestOpen(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 6, 6, time.Second, 0) p.SetCapacity(5) var resources [10]Resource // Test Get for i := 0; i < 5; i++ { r, err := p.Get(ctx) resources[i] = r if err != nil { t.Errorf("Unexpected error %v", err) } if p.Available() != int64(5-i-1) { t.Errorf("expecting %d, received %d", 5-i-1, p.Available()) } if p.WaitCount() != 0 { t.Errorf("expecting 0, received %d", p.WaitCount()) } if p.WaitTime() != 0 { t.Errorf("expecting 0, received %d", p.WaitTime()) } if lastID.Get() != int64(i+1) { t.Errorf("Expecting %d, received %d", i+1, lastID.Get()) } if count.Get() != int64(i+1) { t.Errorf("Expecting %d, received %d", i+1, count.Get()) } } // Test that Get waits ch := make(chan bool) go func() { for i := 0; i < 5; i++ { r, err := p.Get(ctx) if err != nil { t.Errorf("Get failed: %v", err) } resources[i] = r } for i := 0; i < 5; i++ { p.Put(resources[i]) } ch <- true }() for i := 0; i < 5; i++ { // Sleep to ensure the goroutine waits time.Sleep(10 * time.Millisecond) p.Put(resources[i]) } <-ch if p.WaitCount() != 5 { t.Errorf("Expecting 5, received %d", p.WaitCount()) } if p.WaitTime() == 0 { t.Errorf("Expecting non-zero") } if lastID.Get() != 5 { t.Errorf("Expecting 5, received %d", lastID.Get()) } // Test Close resource r, err := p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } r.Close() // A nil Put should cause the resource to be reopened. p.Put(nil) if count.Get() != 5 { t.Errorf("Expecting 5, received %d", count.Get()) } for i := 0; i < 5; i++ { r, err := p.Get(ctx) if err != nil { t.Errorf("Get failed: %v", err) } resources[i] = r } for i := 0; i < 5; i++ { p.Put(resources[i]) } if count.Get() != 5 { t.Errorf("Expecting 5, received %d", count.Get()) } if lastID.Get() != 6 { t.Errorf("Expecting 6, received %d", lastID.Get()) } // SetCapacity p.SetCapacity(3) if count.Get() != 3 { t.Errorf("Expecting 3, received %d", count.Get()) } if lastID.Get() != 6 { t.Errorf("Expecting 6, received %d", lastID.Get()) } if p.Capacity() != 3 { t.Errorf("Expecting 3, received %d", p.Capacity()) } if p.Available() != 3 { t.Errorf("Expecting 3, received %d", p.Available()) } p.SetCapacity(6) if p.Capacity() != 6 { t.Errorf("Expecting 6, received %d", p.Capacity()) } if p.Available() != 6 { t.Errorf("Expecting 6, received %d", p.Available()) } for i := 0; i < 6; i++ { r, err := p.Get(ctx) if err != nil { t.Errorf("Get failed: %v", err) } resources[i] = r } for i := 0; i < 6; i++ { p.Put(resources[i]) } if count.Get() != 6 { t.Errorf("Expecting 5, received %d", count.Get()) } if lastID.Get() != 9 { t.Errorf("Expecting 9, received %d", lastID.Get()) } // Close p.Close() if p.Capacity() != 0 { t.Errorf("Expecting 0, received %d", p.Capacity()) } if p.Available() != 0 { t.Errorf("Expecting 0, received %d", p.Available()) } if count.Get() != 0 { t.Errorf("Expecting 0, received %d", count.Get()) } } func TestPrefill(t *testing.T) { lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 5, 5, time.Second, 1) defer p.Close() if p.Active() != 5 { t.Errorf("p.Active(): %d, want 5", p.Active()) } p = NewResourcePool(FailFactory, 5, 5, time.Second, 1) defer p.Close() if p.Active() != 0 { t.Errorf("p.Active(): %d, want 0", p.Active()) } } func TestPrefillTimeout(t *testing.T) { lastID.Set(0) count.Set(0) saveTimeout := prefillTimeout prefillTimeout = 1 * time.Millisecond defer func() { prefillTimeout = saveTimeout }() start := time.Now() p := NewResourcePool(SlowFailFactory, 5, 5, time.Second, 1) defer p.Close() if elapsed := time.Since(start); elapsed > 20*time.Millisecond { t.Errorf("elapsed: %v, should be around 10ms", elapsed) } if p.Active() != 0 { t.Errorf("p.Active(): %d, want 0", p.Active()) } } func TestShrinking(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0) var resources [10]Resource // Leave one empty slot in the pool for i := 0; i < 4; i++ { r, err := p.Get(ctx) if err != nil { t.Errorf("Get failed: %v", err) } resources[i] = r } done := make(chan bool) go func() { p.SetCapacity(3) done <- true }() expected := `{"Capacity": 3, "Available": 0, "Active": 4, "InUse": 4, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` for i := 0; i < 10; i++ { time.Sleep(10 * time.Millisecond) stats := p.StatsJSON() if stats != expected { if i == 9 { t.Errorf(`expecting '%s', received '%s'`, expected, stats) } } } // There are already 2 resources available in the pool. // So, returning one should be enough for SetCapacity to complete. p.Put(resources[3]) <-done // Return the rest of the resources for i := 0; i < 3; i++ { p.Put(resources[i]) } stats := p.StatsJSON() expected = `{"Capacity": 3, "Available": 3, "Active": 3, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` if stats != expected { t.Errorf(`expecting '%s', received '%s'`, expected, stats) } if count.Get() != 3 { t.Errorf("Expecting 3, received %d", count.Get()) } // Ensure no deadlock if SetCapacity is called after we start // waiting for a resource var err error for i := 0; i < 3; i++ { resources[i], err = p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } } // This will wait because pool is empty go func() { r, err := p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } p.Put(r) done <- true }() // This will also wait go func() { p.SetCapacity(2) done <- true }() time.Sleep(10 * time.Millisecond) // This should not hang for i := 0; i < 3; i++ { p.Put(resources[i]) } <-done <-done if p.Capacity() != 2 { t.Errorf("Expecting 2, received %d", p.Capacity()) } if p.Available() != 2 { t.Errorf("Expecting 2, received %d", p.Available()) } if p.WaitCount() != 1 { t.Errorf("Expecting 1, received %d", p.WaitCount()) } if count.Get() != 2 { t.Errorf("Expecting 2, received %d", count.Get()) } // Test race condition of SetCapacity with itself p.SetCapacity(3) for i := 0; i < 3; i++ { resources[i], err = p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } } // This will wait because pool is empty go func() { r, err := p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } p.Put(r) done <- true }() time.Sleep(10 * time.Millisecond) // This will wait till we Put go p.SetCapacity(2) time.Sleep(10 * time.Millisecond) go p.SetCapacity(4) time.Sleep(10 * time.Millisecond) // This should not hang for i := 0; i < 3; i++ { p.Put(resources[i]) } <-done err = p.SetCapacity(-1) if err == nil { t.Errorf("Expecting error") } err = p.SetCapacity(255555) if err == nil { t.Errorf("Expecting error") } if p.Capacity() != 4 { t.Errorf("Expecting 4, received %d", p.Capacity()) } if p.Available() != 4 { t.Errorf("Expecting 4, received %d", p.Available()) } } func TestClosing(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0) var resources [10]Resource for i := 0; i < 5; i++ { r, err := p.Get(ctx) if err != nil { t.Errorf("Get failed: %v", err) } resources[i] = r } ch := make(chan bool) go func() { p.Close() ch <- true }() // Wait for goroutine to call Close time.Sleep(10 * time.Millisecond) stats := p.StatsJSON() expected := `{"Capacity": 0, "Available": 0, "Active": 5, "InUse": 5, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` if stats != expected { t.Errorf(`expecting '%s', received '%s'`, expected, stats) } // Put is allowed when closing for i := 0; i < 5; i++ { p.Put(resources[i]) } // Wait for Close to return <-ch // SetCapacity must be ignored after Close err := p.SetCapacity(1) if err == nil { t.Errorf("expecting error") } stats = p.StatsJSON() expected = `{"Capacity": 0, "Available": 0, "Active": 0, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` if stats != expected { t.Errorf(`expecting '%s', received '%s'`, expected, stats) } if lastID.Get() != 5 { t.Errorf("Expecting 5, received %d", count.Get()) } if count.Get() != 0 { t.Errorf("Expecting 0, received %d", count.Get()) } } func TestIdleTimeout(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 1, 1, 10*time.Millisecond, 0) defer p.Close() r, err := p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } if count.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if p.IdleClosed() != 0 { t.Errorf("Expecting 0, received %d", p.IdleClosed()) } p.Put(r) if lastID.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if count.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if p.IdleClosed() != 0 { t.Errorf("Expecting 0, received %d", p.IdleClosed()) } time.Sleep(15 * time.Millisecond) if count.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if p.IdleClosed() != 1 { t.Errorf("Expecting 1, received %d", p.IdleClosed()) } r, err = p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } if lastID.Get() != 2 { t.Errorf("Expecting 2, received %d", count.Get()) } if count.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if p.IdleClosed() != 1 { t.Errorf("Expecting 1, received %d", p.IdleClosed()) } // sleep to let the idle closer run while all resources are in use // then make sure things are still as we expect time.Sleep(15 * time.Millisecond) if lastID.Get() != 2 { t.Errorf("Expecting 2, received %d", count.Get()) } if count.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if p.IdleClosed() != 1 { t.Errorf("Expecting 1, received %d", p.IdleClosed()) } p.Put(r) r, err = p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } if lastID.Get() != 2 { t.Errorf("Expecting 2, received %d", count.Get()) } if count.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if p.IdleClosed() != 1 { t.Errorf("Expecting 1, received %d", p.IdleClosed()) } // the idle close thread wakes up every 1/100 of the idle time, so ensure // the timeout change applies to newly added resources p.SetIdleTimeout(1000 * time.Millisecond) p.Put(r) time.Sleep(15 * time.Millisecond) if lastID.Get() != 2 { t.Errorf("Expecting 2, received %d", count.Get()) } if count.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if p.IdleClosed() != 1 { t.Errorf("Expecting 1, received %d", p.IdleClosed()) } // Get and Put to refresh timeUsed r, err = p.Get(ctx) if err != nil { t.Errorf("Unexpected error %v", err) } p.Put(r) p.SetIdleTimeout(10 * time.Millisecond) time.Sleep(15 * time.Millisecond) if lastID.Get() != 3 { t.Errorf("Expecting 3, received %d", lastID.Get()) } if count.Get() != 1 { t.Errorf("Expecting 1, received %d", count.Get()) } if p.IdleClosed() != 2 { t.Errorf("Expecting 2, received %d", p.IdleClosed()) } } func TestIdleTimeoutCreateFail(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 1, 1, 10*time.Millisecond, 0) defer p.Close() r, err := p.Get(ctx) if err != nil { t.Fatal(err) } // Change the factory before putting back // to prevent race with the idle closer, who will // try to use it. p.factory = FailFactory p.Put(r) time.Sleep(15 * time.Millisecond) if p.Active() != 0 { t.Errorf("p.Active(): %d, want 0", p.Active()) } } func TestCreateFail(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(FailFactory, 5, 5, time.Second, 0) defer p.Close() if _, err := p.Get(ctx); err.Error() != "Failed" { t.Errorf("Expecting Failed, received %v", err) } stats := p.StatsJSON() expected := `{"Capacity": 5, "Available": 5, "Active": 0, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` if stats != expected { t.Errorf(`expecting '%s', received '%s'`, expected, stats) } } func TestCreateFailOnPut(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0) defer p.Close() _, err := p.Get(ctx) if err != nil { t.Fatal(err) } p.factory = FailFactory p.Put(nil) if p.Active() != 0 { t.Errorf("p.Active(): %d, want 0", p.Active()) } } func TestSlowCreateFail(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(SlowFailFactory, 2, 2, time.Second, 0) defer p.Close() ch := make(chan bool) // The third Get should not wait indefinitely for i := 0; i < 3; i++ { go func() { p.Get(ctx) ch <- true }() } for i := 0; i < 3; i++ { <-ch } if p.Available() != 2 { t.Errorf("Expecting 2, received %d", p.Available()) } } func TestTimeout(t *testing.T) { ctx := context.Background() lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 1, 1, time.Second, 0) defer p.Close() r, err := p.Get(ctx) if err != nil { t.Fatal(err) } newctx, cancel := context.WithTimeout(ctx, 1*time.Millisecond) _, err = p.Get(newctx) cancel() want := "resource pool timed out" if err == nil || err.Error() != want { t.Errorf("got %v, want %s", err, want) } p.Put(r) } func TestExpired(t *testing.T) { lastID.Set(0) count.Set(0) p := NewResourcePool(PoolFactory, 1, 1, time.Second, 0) defer p.Close() ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-1*time.Second)) r, err := p.Get(ctx) if err == nil { p.Put(r) } cancel() want := "resource pool timed out" if err == nil || err.Error() != want { t.Errorf("got %v, want %s", err, want) } } pool-0.0.2/semaphore.go000066400000000000000000000040601372644535700150140ustar00rootroot00000000000000/* Copyright 2017 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package pool // What's in a name? Channels have all you need to emulate a counting // semaphore with a boatload of extra functionality. However, in some // cases, you just want a familiar API. import ( "time" ) // Semaphore is a counting semaphore with the option to // specify a timeout. type Semaphore struct { slots chan struct{} timeout time.Duration } // NewSemaphore creates a Semaphore. The count parameter must be a positive // number. A timeout of zero means that there is no timeout. func NewSemaphore(count int, timeout time.Duration) *Semaphore { sem := &Semaphore{ slots: make(chan struct{}, count), timeout: timeout, } for i := 0; i < count; i++ { sem.slots <- struct{}{} } return sem } // Acquire returns true on successful acquisition, and // false on a timeout. func (sem *Semaphore) Acquire() bool { if sem.timeout == 0 { <-sem.slots return true } tm := time.NewTimer(sem.timeout) defer tm.Stop() select { case <-sem.slots: return true case <-tm.C: return false } } // TryAcquire acquires a semaphore if it's immediately available. // It returns false otherwise. func (sem *Semaphore) TryAcquire() bool { select { case <-sem.slots: return true default: return false } } // Release releases the acquired semaphore. You must // not release more than the number of semaphores you've // acquired. func (sem *Semaphore) Release() { sem.slots <- struct{}{} } // Size returns the current number of available slots. func (sem *Semaphore) Size() int { return len(sem.slots) } pool-0.0.2/semaphore_flaky_test.go000066400000000000000000000026711372644535700172470ustar00rootroot00000000000000/* Copyright 2017 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package pool import ( "testing" "time" ) func TestSemaNoTimeout(t *testing.T) { s := NewSemaphore(1, 0) s.Acquire() released := false go func() { time.Sleep(10 * time.Millisecond) released = true s.Release() }() s.Acquire() if !released { t.Errorf("release: false, want true") } } func TestSemaTimeout(t *testing.T) { s := NewSemaphore(1, 5*time.Millisecond) s.Acquire() go func() { time.Sleep(10 * time.Millisecond) s.Release() }() if s.Acquire() { t.Errorf("Acquire: true, want false") } time.Sleep(10 * time.Millisecond) if !s.Acquire() { t.Errorf("Acquire: false, want true") } } func TestSemaTryAcquire(t *testing.T) { s := NewSemaphore(1, 0) if !s.TryAcquire() { t.Errorf("TryAcquire: false, want true") } if s.TryAcquire() { t.Errorf("TryAcquire: true, want false") } s.Release() if !s.TryAcquire() { t.Errorf("TryAcquire: false, want true") } } pool-0.0.2/timer.go000066400000000000000000000067141372644535700141610ustar00rootroot00000000000000/* Copyright 2017 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Modified by Duncan Jones to remove the external dependency. */ // Package timer provides various enhanced timer functions. package pool import ( "sync" "time" ) // Out-of-band messages type typeAction int const ( timerStop typeAction = iota timerReset timerTrigger ) /* Timer provides timer functionality that can be controlled by the user. You start the timer by providing it a callback function, which it will call at the specified interval. var t = timer.NewTimer(1e9) t.Start(KeepHouse) func KeepHouse() { // do house keeping work } You can stop the timer by calling t.Stop, which is guaranteed to wait if KeepHouse is being executed. You can create an untimely trigger by calling t.Trigger. You can also schedule an untimely trigger by calling t.TriggerAfter. The timer interval can be changed on the fly by calling t.SetInterval. A zero value interval will cause the timer to wait indefinitely, and it will react only to an explicit Trigger or Stop. */ type Timer struct { interval AtomicDuration // state management mu sync.Mutex running bool // msg is used for out-of-band messages msg chan typeAction } // NewTimer creates a new Timer object func NewTimer(interval time.Duration) *Timer { tm := &Timer{ msg: make(chan typeAction), } tm.interval.Set(interval) return tm } // Start starts the timer. func (tm *Timer) Start(keephouse func()) { tm.mu.Lock() defer tm.mu.Unlock() if tm.running { return } tm.running = true go tm.run(keephouse) } func (tm *Timer) run(keephouse func()) { var timer *time.Timer for { var ch <-chan time.Time interval := tm.interval.Get() if interval > 0 { timer = time.NewTimer(interval) ch = timer.C } select { case action := <-tm.msg: if timer != nil { timer.Stop() timer = nil } switch action { case timerStop: return case timerReset: continue } case <-ch: } keephouse() } } // SetInterval changes the wait interval. // It will cause the timer to restart the wait. func (tm *Timer) SetInterval(ns time.Duration) { tm.interval.Set(ns) tm.mu.Lock() defer tm.mu.Unlock() if tm.running { tm.msg <- timerReset } } // Trigger will cause the timer to immediately execute the keephouse function. // It will then cause the timer to restart the wait. func (tm *Timer) Trigger() { tm.mu.Lock() defer tm.mu.Unlock() if tm.running { tm.msg <- timerTrigger } } // TriggerAfter waits for the specified duration and triggers the next event. func (tm *Timer) TriggerAfter(duration time.Duration) { go func() { time.Sleep(duration) tm.Trigger() }() } // Stop will stop the timer. It guarantees that the timer will not execute // any more calls to keephouse once it has returned. func (tm *Timer) Stop() { tm.mu.Lock() defer tm.mu.Unlock() if tm.running { tm.msg <- timerStop tm.running = false } } // Interval returns the current interval. func (tm *Timer) Interval() time.Duration { return tm.interval.Get() } pool-0.0.2/timer_flaky_test.go000066400000000000000000000036721372644535700164060ustar00rootroot00000000000000/* Copyright 2017 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package pool import ( "sync/atomic" "testing" "time" ) const ( half = time.Duration(500e5) quarter = time.Duration(250e5) tenth = time.Duration(100e5) ) var numcalls int32 func f() { atomic.AddInt32(&numcalls, 1) } func TestWait(t *testing.T) { atomic.StoreInt32(&numcalls, 0) timer := NewTimer(quarter) timer.Start(f) defer timer.Stop() time.Sleep(tenth) if atomic.LoadInt32(&numcalls) != 0 { t.Errorf("want 0, received %v", numcalls) } time.Sleep(quarter) if atomic.LoadInt32(&numcalls) != 1 { t.Errorf("want 1, received %v", numcalls) } time.Sleep(quarter) if atomic.LoadInt32(&numcalls) != 2 { t.Errorf("want 1, received %v", numcalls) } } func TestReset(t *testing.T) { atomic.StoreInt32(&numcalls, 0) timer := NewTimer(half) timer.Start(f) defer timer.Stop() timer.SetInterval(quarter) time.Sleep(tenth) if atomic.LoadInt32(&numcalls) != 0 { t.Errorf("want 0, received %v", numcalls) } time.Sleep(quarter) if atomic.LoadInt32(&numcalls) != 1 { t.Errorf("want 1, received %v", numcalls) } } func TestIndefinite(t *testing.T) { atomic.StoreInt32(&numcalls, 0) timer := NewTimer(0) timer.Start(f) defer timer.Stop() timer.TriggerAfter(quarter) time.Sleep(tenth) if atomic.LoadInt32(&numcalls) != 0 { t.Errorf("want 0, received %v", numcalls) } time.Sleep(quarter) if atomic.LoadInt32(&numcalls) != 1 { t.Errorf("want 1, received %v", numcalls) } }