Skip to content

Conversation

@ermyar
Copy link
Collaborator

@ermyar ermyar commented Nov 24, 2025

Added Response's interface method Buffer.
Connection now contains sync.Pool that was used to reduce allocations.

Fixes #493

I didn't forget about (remove if it is not applicable):

@ermyar ermyar requested a review from oleg-jukovec November 24, 2025 11:20
@ermyar ermyar force-pushed the ermyar/gh-493-reduce-reader-allocations branch from 1f3999a to e37fa1b Compare November 25, 2025 15:56
@ermyar ermyar marked this pull request as ready for review November 25, 2025 16:25
@ermyar ermyar force-pushed the ermyar/gh-493-reduce-reader-allocations branch from e37fa1b to bf0bbdc Compare November 25, 2025 16:31
@ermyar
Copy link
Collaborator Author

ermyar commented Nov 25, 2025

Detected flaky TestSQLBindings and TestStressSQL

@ermyar ermyar requested a review from bigbes November 28, 2025 10:08
Copy link
Collaborator

@oleg-jukovec oleg-jukovec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the patch!

See issues of the current implementation, fix them and we will try again.

response.go Outdated
resp.decoded = false
resp.decodedTyped = false
resp.err = nil
return &ptr
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've actually return a pointer into a local value (address of ptr) instead of an original slice that was allocated at a pool.

https://go.dev/play/p/MsjOHvigpnI

response.go Outdated
Comment on lines 16 to 17
// Release free responses data and returns buffer's data.
Release() *[]byte
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The release method must free all data allocated by the response. Otherwise, the semantic of the call is very confusing.

Copy link
Collaborator Author

@ermyar ermyar Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ofc, we could design better

but if we want to keep to each connection their own sync.Pool, so we should provide some relations between response and connection (to do this, i used future and future.conn). Or could we single pool for all connections?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i understand my fault, sorry..

response.go Outdated
Comment on lines 65 to 72
resp.header.RequestId, resp.header.Error = 0, 0
resp.data = nil
ptr := resp.buf.b
resp.buf.b = nil
resp.buf.p = 0
resp.decoded = false
resp.decodedTyped = false
resp.err = nil
Copy link
Collaborator

@oleg-jukovec oleg-jukovec Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is shorter, but still not work as expected.

Suggested change
resp.header.RequestId, resp.header.Error = 0, 0
resp.data = nil
ptr := resp.buf.b
resp.buf.b = nil
resp.buf.p = 0
resp.decoded = false
resp.decodedTyped = false
resp.err = nil
ptr := resp.buf.b
*resp = SelectResponse{}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but we are allocating here a new SelectResponse object, aren't we?

(and this is a baseResponse's method, why do we use SelectResponse?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but we are allocating here a new SelectResponse object, aren't we?

No, nothing will be allocated here.

(and this is a baseResponse's method, why do we use SelectResponse?)

My fault, should be baseResponse{}.

response.go Outdated
Comment on lines 37 to 41
var baseResponsePool *sync.Pool = &sync.Pool{
New: func() interface{} {
return &baseResponse{}
},
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like overengineering. We don't have any allocations of the baseResponse objects. And therefore all this stuff about baseResponse is not needed at all.

Comment on lines 168 to 137
// Release is freeing the Future resources.
// After this, using this Future becomes invalid.
func (fut *Future) Release() {
if fut.pool != nil && fut.resp != nil {
ptr := fut.resp.Release()
fut.pool.Put(ptr)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should look like this (all resources related to a response should be release inside response.Release()):

Suggested change
// Release is freeing the Future resources.
// After this, using this Future becomes invalid.
func (fut *Future) Release() {
if fut.pool != nil && fut.resp != nil {
ptr := fut.resp.Release()
fut.pool.Put(ptr)
}
}
// Release is freeing the Future resources.
// After this, using this Future becomes invalid.
func (fut *Future) Release() {
if fut.resp != nil {
fut.resp.Release()
}
}

future.go Outdated
Comment on lines 97 to 105
func (fut *Future) Get() ([]interface{}, error) {
defer fut.Release()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (fut *Future) Get() ([]interface{}, error) {
defer fut.Release()
func (fut *Future) Get() ([]interface{}, error) {

The code:

fut := conn.Do(req)
data, err := fut.Get()
data, err := fut.Get()

Should work fine by design. A user should call future.Release() after usage of the future directly:

fut := conn.Do(req)
data, err := fut.Get()
data, err := fut.Get()
fut.Release()

future.go Outdated
Comment on lines 134 to 135
defer fut.Release()
fut.wait()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defer fut.Release()
fut.wait()
fut.wait()

connection.go Outdated

go conn.eventer(events)

buf := smallBuf{}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will not work since the same object will be used inside multiple futures:

if err := fut.SetResponse(header, &buf); err != nil {

The simplest solution - a pool of smallBuf objects.

@ermyar ermyar force-pushed the ermyar/gh-493-reduce-reader-allocations branch 2 times, most recently from a15281c to 078cb7d Compare December 1, 2025 22:56
@ermyar ermyar requested a review from oleg-jukovec December 1, 2025 23:26
@ermyar ermyar force-pushed the ermyar/gh-493-reduce-reader-allocations branch from 078cb7d to beda545 Compare December 2, 2025 00:21
Added method Release to Future and Response's interface,
that allows to free used data directly by calling.

Fixes #493
@ermyar ermyar force-pushed the ermyar/gh-493-reduce-reader-allocations branch from beda545 to 50c8953 Compare December 2, 2025 01:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v3: design API to avoid allocations in responses

3 participants