-
Notifications
You must be signed in to change notification settings - Fork 1.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
copyable smithy client changes #3236
Conversation
src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h
Outdated
Show resolved
Hide resolved
src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h
Outdated
Show resolved
Hide resolved
src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h
Outdated
Show resolved
Hide resolved
src/aws-cpp-sdk-core/include/aws/core/client/AWSClientAsyncCRTP.h
Outdated
Show resolved
Hide resolved
@@ -27,6 +27,31 @@ namespace Aws | |||
explicit Cache(size_t initialSize = 1000) : m_maxSize(initialSize) | |||
{ | |||
} | |||
|
|||
//this can't be default as compiler wont implicitly create copy constructor for const qualified members | |||
Cache(const Cache& other):m_entries{other.m_entries},m_maxSize{other.m_maxSize}{ |
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 are NOT making anything with a cache copy constructible, if the client is not copy constructible from before the smithy client migration, it does not need to be copy constructible afterwards.
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.
ok, will remove this
...in/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/smithy/SmithyClientHeader.vm
Show resolved
Hide resolved
tests/aws-cpp-sdk-dynamodb-integration-tests/TableOperationTest.cpp
Outdated
Show resolved
Hide resolved
...in/resources/com/amazonaws/util/awsclientgenerator/velocity/cpp/smithy/SmithyClientHeader.vm
Show resolved
Hide resolved
src/aws-cpp-sdk-core/include/aws/core/client/AWSClientAsyncCRTP.h
Outdated
Show resolved
Hide resolved
src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h
Outdated
Show resolved
Hide resolved
@@ -131,11 +167,11 @@ namespace client | |||
} | |||
|
|||
protected: | |||
ServiceClientConfigurationT& m_clientConfiguration; | |||
ServiceClientConfigurationT m_clientConfiguration; |
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 dont think we want to be removing the refence. i think this might root cause to the conversation we were having about deep vs shallow copy ctor. I think this might be more aptly summarized as "lets not share references between constructs in ClientConfiguration via the default copy constructor".
i.e.
#include <aws/core/Aws.h>
using namespace Aws;
auto main() -> int {
SDKOptions options;
options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug;
InitAPI(options);
{
Client::ClientConfiguration configuration{};
// new_configuration shares all the same shared ptrs as configuration!
Client::ClientConfiguration new_configuration{configuration};
}
ShutdownAPI(options);
return 0;
}
For example in client configuration we have a retry strategy that is a shared pointer
std::shared_ptr<RetryStrategy> retryStrategy = nullptr;
which if we call the default copy constructor on will create a shared reference to the same retry strategy that is owned by the original client.
What we want to do is that the new client owns its own retry strategy. i.e. the retry strategy is initialized from the factory that we provide
/**
* Retry Strategy factory method. Default is DefaultRetryStrategy (i.e. exponential backoff).
*/
std::function<std::shared_ptr<RetryStrategy>()> retryStrategyCreateFn;
This isnt limited to specifically the retry strategy but the shared pointers in client configuration that are shared on copy.
So I think the high level ask here is "Lets create a deep copy function on ClientConfiguration that returns a client configuration with the factories called instead of shared ownership over the pointers, that is explicitly called when copying the new smithy clients".
Note: ServiceClientConfigurationT isnt constrainted here, and thats likely a miss, but for all intents and purposes it always a child of ClientConfiguration
and we can statically assert on that if we want.
I know thats a mouthful of words, this is touching a lot of legacy stuff and dealing with a lot of things we have got to conveniently ignore for a while, but we're really close to having a better and cleaner design with this change for this moving forward!
return *this; | ||
} | ||
AwsSmithyClientT::operator=(std::move(other)); | ||
Aws::Client::ClientWithAsyncTemplateMethods<DynamoDBClient>::operator=(std::move(other)); |
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.
dont think you can call std::move on a r value twice without entering UB land.
return *this; | ||
} | ||
|
||
DynamoDBClient& operator=(DynamoDBClient&& other) |
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.
do we even need a custom move ctor/assignment operator? thinking that default
would be enough because the target would be the new owner of the all of the resources, that are already intiialized.
thinking default for move should be enough
{ | ||
m_serviceName = ServiceNameT; | ||
} | ||
|
||
AwsSmithyClientT(const AwsSmithyClientT& other): | ||
AwsSmithyClientBase(Aws::MakeShared<ServiceClientConfigurationT>(ServiceNameT, other.m_clientConfig), other.m_serviceName, Aws::Http::CreateHttpClient(*other.m_clientConfig), Aws::MakeShared<MarshallerT>(other.m_serviceName.c_str())), |
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.
for the sake of consistency MakeShared
should be called with the same log tag, i.e.
Aws::MakeShared<ServiceClientConfigurationT>(ServiceNameT, other.m_clientConfig)
vs
Aws::MakeShared<MarshallerT>(other.m_serviceName.c_str())
other.m_serviceName
should be the same ServiceNameT
but since this is the class that is calling MakeShared
lets make it consistent to ServiceNameT
for both
@@ -65,6 +67,33 @@ namespace client | |||
/* Non-template base client class that contains main Aws Client Request pipeline logic */ | |||
class SMITHY_API AwsSmithyClientBase | |||
{ | |||
private: |
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.
lets move private definitions after public per the google style guide
A class definition should usually start with a public: section, followed by protected:, then private:. Omit sections that would be empty.
void deepCopyClientConfiguration(const AwsSmithyClientBase& target) | ||
{ | ||
//first create copy from target | ||
m_clientConfig = Aws::MakeShared<Aws::Client::ClientConfiguration>(target.m_serviceName.c_str(), *target.m_clientConfig); |
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 dont think this is going to work the way we want it to because of slicing here we declare a pointer of ClientConfiguration
not ServiceClientConfigurationT
. in AwsSmithyClientT
we call it as such
m_clientConfiguration{*static_cast<ServiceClientConfigurationT*>(AwsSmithyClientBase::m_clientConfig.get())},
since this is NOT a ServiceClientConfigurationT
we enter UB territory.
trying to think of a creative solution around this let me talk to you offline about this
} | ||
|
||
void init() { | ||
if (!m_clientConfig->retryStrategy) |
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.
imo, we should remove these checks and do an unconditional invocation of the functors because when we create the shared ptr from another, it does a shallow copy of the functors too and then it won't reinitialize
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 dont think we can do that. consider the code
#include <aws/core/Aws.h>
#include <aws/core/client/AdaptiveRetryStrategy.h>
#include <aws/dynamodb/DynamoDBClient.h>
using namespace Aws;
using namespace Aws::DynamoDB;
namespace {
const char* LOG_TAG = "TAG";
}
auto main() -> int {
SDKOptions options;
options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug;
InitAPI(options);
{
Client::ClientConfiguration configuration{};
configuration.retryStrategy = Aws::MakeShared<Client::AdaptiveRetryStrategy>(LOG_TAG);
DynamoDBClient client{};
}
ShutdownAPI(options);
return 0;
}
if we remove the check we would unconditionally write over the users specifed retry strategy with the default factory.
So we need to check for the existence of a user specified entity on the configuration before invoking the factory
as for specifically having different logic for copy constructor vs regular constructor, it makes sense to have the same logic so we dont have to update in two places
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.
In the above example
DynamoDBClient client
This calls the constructor which is supposed to do the initialization of shared ptr members because in the very beginning sp is default nullptr.
Now say we copy the client which will invoke the deepcopy method and first it will shallow copy the shared ptr members and then wont reinitialize it using the factory functions. This means 2 clients will have same copy of shared_ptr to retry strategy for example. But in reality each client should have its own separate retry strategy.
if (!m_clientConfig->retryStrategy)
will otherwise always be false on 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.
ooooo good catch let me push a update
src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClientBase.h
Outdated
Show resolved
Hide resolved
…ard compatibility
4f9a138
to
34ff59f
Compare
lgtm |
7abb2bf
to
dd9db61
Compare
Issue #, if available:
Description of changes:
Check all that applies:
Check which platforms you have built SDK on to verify the correctness of this PR.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.