-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
[WIP] SDK overhaul #725
[WIP] SDK overhaul #725
Changes from all commits
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 |
---|---|---|
|
@@ -6,8 +6,6 @@ import ( | |
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"reflect" | ||
"strings" | ||
"time" | ||
|
||
|
@@ -77,28 +75,21 @@ func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers, | |
p = "/" | ||
} | ||
|
||
httpReq, _ := http.NewRequest(method, "", nil) | ||
|
||
var err error | ||
httpReq.URL, err = url.Parse(clientInfo.Endpoint + p) | ||
httpReq, err := http.NewRequest(method, clientInfo.Endpoint+p, nil) | ||
if err != nil { | ||
httpReq.URL = &url.URL{} | ||
err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err) | ||
return &Request{Error: err} | ||
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. I'm not sure if this can exit early here. It is possible for user's custom request handlers to depend on the I don't see any problem with getting ride of the 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. My take on this is the following:
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. Both cases are an error, but if the SDK were to start panicking it would break user's applications where before only an error would of been returned. The SDK's min supported version is 1.5, and plans to continue to support this version in the future. Go 1.4 is not officially supported by the SDK, but many users still use this version of Go, and we'd like to continue unofficial support for this version as long as possible. The SDK supporting these additional Go versions does not impact the overall performance of the SDK. This does introduce some limitations on what new stdlib features the SDK can use, but this has not introduced any issues as of yet. |
||
} | ||
|
||
r := &Request{ | ||
Config: cfg, | ||
ClientInfo: clientInfo, | ||
Handlers: handlers.Copy(), | ||
|
||
Handlers: handlers, | ||
Retryer: retryer, | ||
Time: time.Now(), | ||
ExpireTime: 0, | ||
Operation: operation, | ||
HTTPRequest: httpReq, | ||
Body: nil, | ||
Params: params, | ||
Error: err, | ||
Data: data, | ||
} | ||
r.SetBufferBody([]byte{}) | ||
|
@@ -115,14 +106,14 @@ func (r *Request) WillRetry() bool { | |
// and the parameters are valid. False is returned if no parameters are | ||
// provided or invalid. | ||
func (r *Request) ParamsFilled() bool { | ||
return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid() | ||
return r.Params != nil | ||
} | ||
|
||
// DataFilled returns true if the request's data for response deserialization | ||
// target has been set and is a valid. False is returned if data is not | ||
// set, or is invalid. | ||
func (r *Request) DataFilled() bool { | ||
return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid() | ||
return r.Data != nil | ||
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. The check for nil of Are you seeing these two calls impacting the performance of the SDK? In the pprof's we've been running this hasn't registered. The portion of the overhead which is impacted by reflection will be focused more on the (un)marshaling of the API operation's request/response data. As visible in #722. |
||
} | ||
|
||
// SetBufferBody will set the request's body bytes that will be sent to | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,20 +61,56 @@ func (w *Waiter) Wait() error { | |
break | ||
} | ||
result = true | ||
for _, val := range vals { | ||
if !awsutil.DeepEqual(val, a.Expected) { | ||
result = false | ||
break | ||
if exp, ok := a.Expected.(string); ok { | ||
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. The change to be type specific is a fine change instead of reflection, but an I don't think off hand there are any that actually use complex types, so a primitive type matching might be used without reflection. Though at any time a service's waiter could use a complex type, so the SDK would need to use the DeepEquals as a fallback. In either case any functionality like this should be in its own independent function so its not duplicated through the code, and can be tested. 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. Under which condition do you see this happening? I've searched the SDK and all I could find was that the waiter waits for either a string or an int. I'm rather tempted to split this into two fields actually, ExpectString and ExpectInt. 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.
If the fields were split out they would be There is nothing wrong with updating this section to use non-reflection for for the known cases int, string, bool, and falling back to the |
||
for _, val := range vals { | ||
if got, ok := val.(*string); ok { | ||
if exp != *got { | ||
result = false | ||
break | ||
} | ||
} else { | ||
result = false | ||
break | ||
} | ||
} | ||
} else if exp, ok := a.Expected.(int); ok { | ||
for _, val := range vals { | ||
if got, ok := val.(*int); ok { | ||
if exp != *got { | ||
result = false | ||
break | ||
} | ||
} else { | ||
result = false | ||
break | ||
} | ||
} | ||
} else { | ||
panic("Unexpected type for expected value") | ||
} | ||
case "pathAny": | ||
// Only a single match needs to equal for the result to match | ||
vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument) | ||
for _, val := range vals { | ||
if awsutil.DeepEqual(val, a.Expected) { | ||
result = true | ||
break | ||
if exp, ok := a.Expected.(string); ok { | ||
for _, val := range vals { | ||
if got, ok := val.(*string); ok { | ||
if exp == *got { | ||
result = true | ||
break | ||
} | ||
} | ||
} | ||
} else if exp, ok := a.Expected.(int); ok { | ||
for _, val := range vals { | ||
if got, ok := val.(*int); ok { | ||
if exp == *got { | ||
result = true | ||
break | ||
} | ||
} | ||
} | ||
} else { | ||
panic("Unexpected type for expected value") | ||
} | ||
case "status": | ||
s := a.Expected.(int) | ||
|
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.
These copy's are actually needed.
HandlerList
is a struct, but it contains a slice ofHandlers
. This slice needs to be independently mutable at each stage of a request's pipeline without impacting any upstream functionality.Specifically this change will cause any modifications made to a request handler's list to be propagated upstream to the service's request handler list. This means that If a customization needs to modify a request handler to perform some functionality for a specific API operation, the mutation of the handler list will be propagated upstream to the service, and now all API operation made will now also include the request handler customization. A common pattern is to share
session.Session
between multiple service clients, and this would polute all other service client instances with the customized request handlers.This also introduces race conditions in SDK. Which is the panic shown in the failed Travis test. Since the backing arrays of handlers would be shared between multiple instances of service clients, and API operations being made concurrently, any mutation to the handler list would introduce race conditions.
This Go blog post provides a good rundown on how Arrays vs Slices could break if down stream changes make unintended mutations to a upstream owned slice.
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.
How frequently this Copy is being called? Once per request?
Edit: Yes, once per request. It takes around 3000ns on my laptop to make a copy.
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.
Correct, the only time Copy will be called is when a service client is created from a
session.Session
, and then each time an API operation is called. Outside of that it should be very rare of a HandlerList to be copied.An alternative approach might be to implement lazy copy of the handlers, but this would require mutexes to wrap the HandlerList functions, since concurrent mutations/operation need to be supported. As a guess adding the critical section check will have more overhead over the life time of the request than the copy of slices of function pointers.
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.
I think it really should be implemented somehow differently. Let say if I initialize a client for dynamo db, everything should be initialized only and only once. I can't see there is anything needs to be copied in that already prepared context.
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.
Users are always free to add or remove request handlers from the various HandlerLists based on their use cases. This can be performed both on the service client instance, and the individual request such as with,
dynamodb.CreateTableRequest
. All of the xxxRequest methods return arequest.Request
that can be modified based on the user's preferences. This is commonly done for debugging, or metrics reporting.In addition there are customizations the SDK implements for some of the service client's and their API operations, and these are applied to the individual operation requests as they are called.
The HandlerList could be created once the first time each API operation is called by the service client, but mutex's would need to be added around the HandlerLists. This is due to API operations are allowed to be called concurrently. With that said, the handler lists still must be a unique copy per API operation request call instance, because users could choose to augment an operation's request Handlers uniformly. An upfront, or first time use copy would prevent this functionality.
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.
Like @vburenin said, this happens a lot.
I believe this is a good case where the so called convenience of user trying to add a metric or a logging as you say should be taken away and have the user add the code for monitoring manually.
In @vburenin's case, this alone takes 3ms to happen for every single time. It's a third of the "single digit millisecond" "feature" of DynamoDB.
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.
Just to note 3000ns is only 3us or 0.003ms not 3ms. Since requests can take multiple ms to cross the wire 0.003ms once per request is not significant.
Having user modifiable handlers is important, and some users depend on this functionality today. Removing this would be a breaking change to the SDK.
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.
Good point, my math is wrong in the morning.