-
Notifications
You must be signed in to change notification settings - Fork 608
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
fix(StargateQueries): use a sync pool when unmarshalling responses of protobuf objects #7346
Changes from 1 commit
7fa9f7a
9039ff5
20fefad
378492b
2192082
801eae8
4cebe95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,14 +33,15 @@ import ( | |
epochtypes "github.com/osmosis-labs/osmosis/x/epochs/types" | ||
) | ||
|
||
// stargateWhitelist keeps whitelist and its deterministic | ||
// stargateResponsePools keeps whitelist and its deterministic | ||
// response binding for stargate queries. | ||
// CONTRACT: since results of queries go into blocks, queries being added here should always be | ||
// deterministic or can cause non-determinism in the state machine. | ||
// | ||
// The query can be multi-thread, so we have to use | ||
// thread safe sync.Map. | ||
var stargateWhitelist sync.Map | ||
// The query is multi-threaded so we're using a sync.Pool | ||
// to manage the allocation and de-allocation of newly created | ||
// pb objects. | ||
var stargateResponsePools map[string]*sync.Pool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we use a sync.Pool of multiple proto responses type so we do not allocate every time, this should provide relief to the GC in moments of high traffic. |
||
|
||
// Note: When adding a migration here, we should also add it to the Async ICQ params in the upgrade. | ||
// In the future we may want to find a better way to keep these in sync | ||
|
@@ -184,34 +185,48 @@ func init() { | |
setWhitelistedQuery("/osmosis.concentratedliquidity.v1beta1.Query/CFMMPoolIdLinkFromConcentratedPoolId", &concentratedliquidityquery.CFMMPoolIdLinkFromConcentratedPoolIdResponse{}) | ||
} | ||
|
||
// GetWhitelistedQuery returns the whitelisted query at the provided path. | ||
// IsWhitelistedQuery returns if the query is not whitelisted. | ||
func IsWhitelistedQuery(queryPath string) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exposed this method in place of getWhitelistedQuery to avoid unexported usage of a function that can leak memory if not used properly |
||
_, isWhitelisted := stargateResponsePools[queryPath] | ||
if !isWhitelisted { | ||
return wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", queryPath)} | ||
} | ||
return nil | ||
} | ||
|
||
// getWhitelistedQuery returns the whitelisted query at the provided path. | ||
// If the query does not exist, or it was setup wrong by the chain, this returns an error. | ||
func GetWhitelistedQuery(queryPath string) (codec.ProtoMarshaler, error) { | ||
protoResponseAny, isWhitelisted := stargateWhitelist.Load(queryPath) | ||
// CONTRACT: must call returnStargateResponseToPool in order to avoid pointless allocs. | ||
func getWhitelistedQuery(queryPath string) (codec.ProtoMarshaler, error) { | ||
protoResponseAny, isWhitelisted := stargateResponsePools[queryPath] | ||
if !isWhitelisted { | ||
return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", queryPath)} | ||
} | ||
protoResponseType, ok := protoResponseAny.(codec.ProtoMarshaler) | ||
if !ok { | ||
return nil, wasmvmtypes.Unknown{} | ||
return protoResponseAny.Get().(codec.ProtoMarshaler), nil | ||
} | ||
|
||
type protoTypeG[T any] interface { | ||
*T | ||
codec.ProtoMarshaler | ||
} | ||
|
||
func setWhitelistedQuery[T any, PT protoTypeG[T]](queryPath string, _ PT) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this creates a sync.Pool for the given protobuf object, we use generics so we can properly instantiate an object that queryPath expects as response. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you comment in the code with this context please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comment here 801eae8 |
||
stargateResponsePools[queryPath] = &sync.Pool{ | ||
New: func() any { | ||
return PT(new(T)) | ||
}, | ||
} | ||
return protoResponseType, nil | ||
} | ||
|
||
func setWhitelistedQuery(queryPath string, protoType codec.ProtoMarshaler) { | ||
stargateWhitelist.Store(queryPath, protoType) | ||
func returnStargateResponseToPool(queryPath string, pb codec.ProtoMarshaler) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this returns the protobuf object to its appropriate pool (based on the queryPath) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Transferring this into a comment would also be helpful IMO 🙏 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comment here 2192082 |
||
stargateResponsePools[queryPath].Put(pb) | ||
} | ||
|
||
func GetStargateWhitelistedPaths() (keys []string) { | ||
// Iterate over the map and collect the keys | ||
stargateWhitelist.Range(func(key, value interface{}) bool { | ||
keyStr, ok := key.(string) | ||
if !ok { | ||
panic("key is not a string") | ||
} | ||
keys = append(keys, keyStr) | ||
return true | ||
}) | ||
|
||
keys = make([]string, 0, len(stargateResponsePools)) | ||
for k := range stargateResponsePools { | ||
keys = append(keys, k) | ||
} | ||
return keys | ||
} |
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.
we need to return the sync.Pool object, so it does not leak