-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace conditional variable with semaphore #24
Closed
Closed
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
4afd988
Add acquire benchmarks
779b683
Add banchmarks with cancelled acquires
5649f8b
Add test for release after acquire
74d1d89
Fix goroutine leak
b36d491
Replace useless locking by atomic variable
e8129c1
Separate duplicate code to functions
0e86296
Use semaphore rather than conditional variable
83d37d1
Use circular queue for idle connections
552d282
Perform logarithmic number of steps in AcquireAllIdle
7a9160c
Go mod tidy
c9b65ee
Code review changes
23547ae
[pool] Replace queue with stack
118316a
[pool] Increase test coverage
55c4bb3
Use generational stack for idle connections
2e7bcd8
Use Go 1.19 atomics
012cecc
Merge branch 'master' into drop-cond
jackc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package puddle | ||
|
||
import ( | ||
"context" | ||
"time" | ||
) | ||
|
||
// valueCancelCtx combines two contexts into one. One context is used for values and the other is used for cancellation. | ||
type valueCancelCtx struct { | ||
valueCtx context.Context | ||
cancelCtx context.Context | ||
} | ||
|
||
func (ctx *valueCancelCtx) Deadline() (time.Time, bool) { return ctx.cancelCtx.Deadline() } | ||
func (ctx *valueCancelCtx) Done() <-chan struct{} { return ctx.cancelCtx.Done() } | ||
func (ctx *valueCancelCtx) Err() error { return ctx.cancelCtx.Err() } | ||
func (ctx *valueCancelCtx) Value(key any) any { return ctx.valueCtx.Value(key) } | ||
|
||
func newValueCancelCtx(valueCtx, cancelContext context.Context) context.Context { | ||
return &valueCancelCtx{ | ||
valueCtx: valueCtx, | ||
cancelCtx: cancelContext, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package puddle | ||
|
||
import "context" | ||
|
||
func (p *Pool[T]) AcquireRaw(ctx context.Context) (*Resource[T], error) { | ||
return p.acquire(ctx) | ||
} | ||
|
||
var AcquireSemAll = acquireSemAll |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,12 @@ | ||
module github.com/jackc/puddle/v2 | ||
|
||
go 1.18 | ||
go 1.19 | ||
|
||
require github.com/stretchr/testify v1.8.0 | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
github.com/stretchr/objx v0.4.0 // indirect | ||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package genstack | ||
|
||
// GenStack implements a generational stack. | ||
// | ||
// GenStack works as common stack except for the fact that all elements in the | ||
// older generation are guaranteed to be popped before any element in the newer | ||
// generation. New elements are always pushed to the current (newest) | ||
// generation. | ||
// | ||
// We could also say that GenStack behaves as a stack in case of a single | ||
// generation, but it behaves as a queue of individual generation stacks. | ||
type GenStack[T any] struct { | ||
// We can represent arbitrary number of generations using 2 stacks. The | ||
// new stack stores all new pushes and the old stack serves all reads. | ||
// Old stack can represent multiple generations. If old == new, then all | ||
// elements pushed in previous (not current) generations have already | ||
// been popped. | ||
|
||
old *stack[T] | ||
new *stack[T] | ||
} | ||
|
||
// NewGenStack creates a new empty GenStack. | ||
func NewGenStack[T any]() *GenStack[T] { | ||
s := &stack[T]{} | ||
return &GenStack[T]{ | ||
old: s, | ||
new: s, | ||
} | ||
} | ||
|
||
func (s *GenStack[T]) Pop() (T, bool) { | ||
// Pushes always append to the new stack, so if the old once becomes | ||
// empty, it will remail empty forever. | ||
if s.old.len() == 0 && s.old != s.new { | ||
s.old = s.new | ||
} | ||
|
||
if s.old.len() == 0 { | ||
var zero T | ||
return zero, false | ||
} | ||
|
||
return s.old.pop(), true | ||
} | ||
|
||
// Push pushes a new element at the top of the stack. | ||
func (s *GenStack[T]) Push(v T) { s.new.push(v) } | ||
|
||
// NextGen starts a new stack generation. | ||
func (s *GenStack[T]) NextGen() { | ||
if s.old == s.new { | ||
s.new = &stack[T]{} | ||
return | ||
} | ||
|
||
// We need to pop from the old stack to the top of the new stack. Let's | ||
// have an example: | ||
// | ||
// Old: <bottom> 4 3 2 1 | ||
// New: <bottom> 8 7 6 5 | ||
// PopOrder: 1 2 3 4 5 6 7 8 | ||
// | ||
// | ||
// To preserve pop order, we have to take all elements from the old | ||
// stack and push them to the top of new stack: | ||
// | ||
// New: 8 7 6 5 4 3 2 1 | ||
// | ||
s.new.push(s.old.takeAll()...) | ||
|
||
// We have the old stack allocated and empty, so why not to reuse it as | ||
// new new stack. | ||
s.old, s.new = s.new, s.old | ||
} | ||
|
||
// Len returns number of elements in the stack. | ||
func (s *GenStack[T]) Len() int { | ||
l := s.old.len() | ||
if s.old != s.new { | ||
l += s.new.len() | ||
} | ||
|
||
return l | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package genstack | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func requirePopEmpty[T any](t testing.TB, s *GenStack[T]) { | ||
v, ok := s.Pop() | ||
require.False(t, ok) | ||
require.Zero(t, v) | ||
} | ||
|
||
func requirePop[T any](t testing.TB, s *GenStack[T], expected T) { | ||
v, ok := s.Pop() | ||
require.True(t, ok) | ||
require.Equal(t, expected, v) | ||
} | ||
|
||
func TestGenStack_Empty(t *testing.T) { | ||
s := NewGenStack[int]() | ||
requirePopEmpty(t, s) | ||
} | ||
|
||
func TestGenStack_SingleGen(t *testing.T) { | ||
r := require.New(t) | ||
s := NewGenStack[int]() | ||
|
||
s.Push(1) | ||
s.Push(2) | ||
r.Equal(2, s.Len()) | ||
|
||
requirePop(t, s, 2) | ||
requirePop(t, s, 1) | ||
requirePopEmpty(t, s) | ||
} | ||
|
||
func TestGenStack_TwoGen(t *testing.T) { | ||
r := require.New(t) | ||
s := NewGenStack[int]() | ||
|
||
s.Push(3) | ||
s.Push(4) | ||
s.Push(5) | ||
r.Equal(3, s.Len()) | ||
s.NextGen() | ||
r.Equal(3, s.Len()) | ||
s.Push(6) | ||
s.Push(7) | ||
r.Equal(5, s.Len()) | ||
|
||
requirePop(t, s, 5) | ||
requirePop(t, s, 4) | ||
requirePop(t, s, 3) | ||
requirePop(t, s, 7) | ||
requirePop(t, s, 6) | ||
requirePopEmpty(t, s) | ||
} | ||
|
||
func TestGenStack_MuptiGen(t *testing.T) { | ||
r := require.New(t) | ||
s := NewGenStack[int]() | ||
|
||
s.Push(10) | ||
s.Push(11) | ||
s.Push(12) | ||
r.Equal(3, s.Len()) | ||
s.NextGen() | ||
r.Equal(3, s.Len()) | ||
s.Push(13) | ||
s.Push(14) | ||
r.Equal(5, s.Len()) | ||
s.NextGen() | ||
r.Equal(5, s.Len()) | ||
s.Push(15) | ||
s.Push(16) | ||
s.Push(17) | ||
r.Equal(8, s.Len()) | ||
|
||
requirePop(t, s, 12) | ||
requirePop(t, s, 11) | ||
requirePop(t, s, 10) | ||
requirePop(t, s, 14) | ||
requirePop(t, s, 13) | ||
requirePop(t, s, 17) | ||
requirePop(t, s, 16) | ||
requirePop(t, s, 15) | ||
requirePopEmpty(t, s) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package genstack | ||
|
||
// stack is a wrapper around an array implementing a stack. | ||
// | ||
// We cannot use slice to represent the stack because append might change the | ||
// pointer value of the slice. That would be an issue in GenStack | ||
// implementation. | ||
type stack[T any] struct { | ||
arr []T | ||
} | ||
|
||
// push pushes a new element at the top of a stack. | ||
func (s *stack[T]) push(vs ...T) { s.arr = append(s.arr, vs...) } | ||
|
||
// pop pops the stack top-most element. | ||
// | ||
// If stack length is zero, this method panics. | ||
func (s *stack[T]) pop() T { | ||
idx := s.len() - 1 | ||
val := s.arr[idx] | ||
|
||
// Avoid memory leak | ||
var zero T | ||
s.arr[idx] = zero | ||
|
||
s.arr = s.arr[:idx] | ||
return val | ||
} | ||
|
||
// takeAll returns all elements in the stack in order as they are stored - i.e. | ||
// the top-most stack element is the last one. | ||
func (s *stack[T]) takeAll() []T { | ||
arr := s.arr | ||
s.arr = nil | ||
return arr | ||
} | ||
|
||
// len returns number of elements in the stack. | ||
func (s *stack[T]) len() int { return len(s.arr) } |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package puddle | ||
|
||
import "unsafe" | ||
|
||
type ints interface { | ||
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | ||
} | ||
|
||
// log2Int returns log2 of an integer. This function panics if val < 0. For val | ||
// == 0, returns 0. | ||
func log2Int[T ints](val T) uint8 { | ||
if val <= 0 { | ||
panic("log2 of non-positive number does not exist") | ||
} | ||
|
||
return log2IntRange(val, 0, uint8(8*unsafe.Sizeof(val))) | ||
} | ||
|
||
func log2IntRange[T ints](val T, begin, end uint8) uint8 { | ||
length := end - begin | ||
if length == 1 { | ||
return begin | ||
} | ||
|
||
delim := begin + length/2 | ||
mask := T(1) << delim | ||
if mask > val { | ||
return log2IntRange(val, begin, delim) | ||
} else { | ||
return log2IntRange(val, delim, end) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package puddle | ||
|
||
import ( | ||
"math" | ||
"math/rand" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestLog2Uint(t *testing.T) { | ||
r := require.New(t) | ||
|
||
r.Equal(uint8(0), log2Int(1)) | ||
r.Equal(uint8(0), log2Int[uint64](1)) | ||
r.Equal(uint8(1), log2Int[uint32](2)) | ||
r.Equal(uint8(7), log2Int[uint8](math.MaxUint8)) | ||
r.Equal(uint8(15), log2Int[uint16](math.MaxUint16)) | ||
r.Equal(uint8(31), log2Int[uint32](math.MaxUint32)) | ||
r.Equal(uint8(63), log2Int[uint64](math.MaxUint64)) | ||
|
||
r.Panics(func() { log2Int[uint64](0) }) | ||
r.Panics(func() { log2Int[int64](-1) }) | ||
} | ||
|
||
func FuzzLog2Uint(f *testing.F) { | ||
const cnt = 1000 | ||
|
||
rand := rand.New(rand.NewSource(time.Now().UnixNano())) | ||
for i := 0; i < cnt; i++ { | ||
val := uint64(rand.Int63()) | ||
// val + 1 not to test val == 0. | ||
f.Add(val + 1) | ||
} | ||
|
||
f.Fuzz(func(t *testing.T, val uint64) { | ||
var mx uint8 | ||
for i := 63; i >= 0; i-- { | ||
mask := uint64(1) << i | ||
if mask&val != 0 { | ||
mx = uint8(i) | ||
break | ||
} | ||
} | ||
|
||
require.Equal(t, mx, log2Int(val)) | ||
}) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Contexts compose, so you shouldn't have to create your own type. (But maybe I'm missing something?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just code relocation.
But to answer your question: There is no way how to combine values of one context and cancellation of another context. Not without 3rd party libraries.