Skip to content

Commit 9046dd5

Browse files
authored
Bootstrap shutdown (aws#112)
Allow user to know when `ClientBootstrap` is done shutting down its behind-the-scenes resources. User may register a `std::function<void()>` that fires upon shutdown complete. I considered offering a `std::future` to users, but realized you can only do blocking wait()/get() operations on a c++ future, you can't register a callback to fire (a feature available in Python and Java's future primitives). So if a user wants to use `std::future` they can hook that up themselves with the callback (I did this in a test). Also added an `EnableBlockingShutdown()` function. I was looking around at how async-shutdown was handled in other `aws-crt-cpp` classes and saw this feature on the `HttpClientConnectionManager`. It's simplifies things when the IO classes are created on the main thread's stack (all of our tests and samples). I chose to make these functions you call on the constructed class, rather than constructor arguments, or fields in an "Options" struct for 2 reasons: 1) This class wasn't using an Options struct before, so it felt weird to add one now. 2) If these were overloaded constructor args, I feared a user would enable the "blocking shutdown" by copy/pasting a constructor with `..., true, ...` in it and later have no idea why they occasionally dead-locked.
1 parent 506422e commit 9046dd5

File tree

9 files changed

+106
-9
lines changed

9 files changed

+106
-9
lines changed

include/aws/crt/io/Bootstrap.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,25 @@
2222
#include <aws/io/channel_bootstrap.h>
2323
#include <aws/io/host_resolver.h>
2424

25+
#include <future>
26+
2527
namespace Aws
2628
{
2729
namespace Crt
2830
{
2931
namespace Io
3032
{
33+
using OnClientBootstrapShutdownComplete = std::function<void()>;
34+
35+
/**
36+
* A ClientBootstrap handles creation and setup of socket connections
37+
* to specific endpoints.
38+
*
39+
* Note that ClientBootstrap may not clean up all its behind-the-scenes
40+
* resources immediately upon destruction. If you need to know when
41+
* behind-the-scenes shutdown is complete, use SetShutdownCompleteCallback()
42+
* or EnableBlockingShutdown() (only safe on main thread).
43+
*/
3144
class AWS_CRT_CPP_API ClientBootstrap final
3245
{
3346
public:
@@ -44,11 +57,36 @@ namespace Aws
4457
operator bool() const noexcept;
4558
int LastError() const noexcept;
4659

60+
/**
61+
* Set function to invoke when ClientBootstrap's behind-the-scenes
62+
* resources finish shutting down. This function may be invoked
63+
* on any thread. Shutdown begins when the ClientBootstrap's
64+
* destructor runs.
65+
*/
66+
void SetShutdownCompleteCallback(OnClientBootstrapShutdownComplete callback);
67+
68+
/**
69+
* Force the ClientBootstrap's destructor to block until all
70+
* behind-the-scenes resources finish shutting down.
71+
*
72+
* This isn't necessary during the normal flow of an application,
73+
* but it is useful for scenarios, such as tests, that need deterministic
74+
* shutdown ordering. Be aware, if you use this anywhere other
75+
* than the main thread, YOU WILL MOST LIKELY CAUSE A DEADLOCK.
76+
*
77+
* Use SetShutdownCompleteCallback() for a thread-safe way to
78+
* know when shutdown is complete.
79+
*/
80+
void EnableBlockingShutdown() noexcept;
81+
4782
aws_client_bootstrap *GetUnderlyingHandle() const noexcept;
4883

4984
private:
5085
aws_client_bootstrap *m_bootstrap;
5186
int m_lastError;
87+
std::unique_ptr<struct ClientBootstrapCallbackData> m_callbackData;
88+
std::future<void> m_shutdownFuture;
89+
bool m_enableBlockingShutdown;
5290
};
5391
} // namespace Io
5492
} // namespace Crt

source/io/Bootstrap.cpp

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,74 @@ namespace Aws
2020
{
2121
namespace Io
2222
{
23+
24+
// Holds the bootstrap's shutdown promise.
25+
// Lives until the bootstrap's shutdown-complete callback fires.
26+
struct ClientBootstrapCallbackData
27+
{
28+
std::promise<void> ShutdownPromise;
29+
OnClientBootstrapShutdownComplete ShutdownCallback;
30+
31+
static void OnShutdownComplete(void *userData)
32+
{
33+
auto callbackData = static_cast<ClientBootstrapCallbackData *>(userData);
34+
35+
callbackData->ShutdownPromise.set_value();
36+
callbackData->ShutdownCallback();
37+
38+
delete callbackData;
39+
}
40+
};
41+
2342
ClientBootstrap::ClientBootstrap(
2443
EventLoopGroup &elGroup,
2544
HostResolver &resolver,
2645
Allocator *allocator) noexcept
27-
: m_lastError(AWS_ERROR_SUCCESS)
46+
: m_bootstrap(nullptr), m_lastError(AWS_ERROR_SUCCESS),
47+
m_callbackData(new ClientBootstrapCallbackData()), m_enableBlockingShutdown(false)
2848
{
49+
m_shutdownFuture = m_callbackData->ShutdownPromise.get_future();
50+
2951
aws_client_bootstrap_options options;
3052
options.event_loop_group = elGroup.GetUnderlyingHandle();
3153
options.host_resolution_config = resolver.GetConfig();
3254
options.host_resolver = resolver.GetUnderlyingHandle();
33-
options.on_shutdown_complete = NULL;
34-
options.user_data = NULL;
55+
options.on_shutdown_complete = ClientBootstrapCallbackData::OnShutdownComplete;
56+
options.user_data = m_callbackData.get();
3557
m_bootstrap = aws_client_bootstrap_new(allocator, &options);
58+
if (!m_bootstrap)
59+
{
60+
m_lastError = aws_last_error();
61+
}
3662
}
3763

3864
ClientBootstrap::~ClientBootstrap()
3965
{
4066
if (m_bootstrap)
4167
{
68+
// Release m_callbackData, it destroys itself when shutdown completes.
69+
m_callbackData.release();
70+
4271
aws_client_bootstrap_release(m_bootstrap);
43-
m_bootstrap = nullptr;
44-
m_lastError = AWS_ERROR_UNKNOWN;
72+
if (m_enableBlockingShutdown)
73+
{
74+
// If your program is stuck here, stop using EnableBlockingShutdown()
75+
m_shutdownFuture.wait();
76+
}
4577
}
4678
}
4779

4880
ClientBootstrap::operator bool() const noexcept { return m_lastError == AWS_ERROR_SUCCESS; }
4981

5082
int ClientBootstrap::LastError() const noexcept { return m_lastError; }
5183

84+
void ClientBootstrap::SetShutdownCompleteCallback(OnClientBootstrapShutdownComplete callback)
85+
{
86+
m_callbackData->ShutdownCallback = std::move(callback);
87+
}
88+
89+
void ClientBootstrap::EnableBlockingShutdown() noexcept { m_enableBlockingShutdown = true; }
90+
5291
aws_client_bootstrap *ClientBootstrap::GetUnderlyingHandle() const noexcept
5392
{
5493
if (*this)

tests/ChannelBootstrapTest.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <aws/crt/Api.h>
1616
#include <aws/testing/aws_test_harness.h>
1717

18+
#include <future>
1819
#include <utility>
1920

2021
static int s_TestClientBootstrapResourceSafety(struct aws_allocator *allocator, void *)
@@ -29,9 +30,17 @@ static int s_TestClientBootstrapResourceSafety(struct aws_allocator *allocator,
2930
ASSERT_TRUE(defaultHostResolver);
3031
ASSERT_NOT_NULL(defaultHostResolver.GetUnderlyingHandle());
3132

32-
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
33-
ASSERT_TRUE(clientBootstrap);
34-
ASSERT_NOT_NULL(clientBootstrap.GetUnderlyingHandle());
33+
std::promise<void> bootstrapShutdownPromise;
34+
std::future<void> bootstrapShutdownFuture = bootstrapShutdownPromise.get_future();
35+
{
36+
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
37+
ASSERT_TRUE(clientBootstrap);
38+
ASSERT_NOT_NULL(clientBootstrap.GetUnderlyingHandle());
39+
clientBootstrap.EnableBlockingShutdown();
40+
clientBootstrap.SetShutdownCompleteCallback([&]() { bootstrapShutdownPromise.set_value(); });
41+
}
42+
43+
ASSERT_TRUE(std::future_status::ready == bootstrapShutdownFuture.wait_for(std::chrono::seconds(10)));
3544

3645
return AWS_ERROR_SUCCESS;
3746
}

tests/CredentialsTest.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ static int s_TestProviderImdsGet(struct aws_allocator *allocator, void *ctx)
149149

150150
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
151151
ASSERT_TRUE(clientBootstrap);
152+
clientBootstrap.EnableBlockingShutdown();
152153

153154
CredentialsProviderImdsConfig config;
154155
config.Bootstrap = &clientBootstrap;
@@ -178,6 +179,7 @@ static int s_TestProviderDefaultChainGet(struct aws_allocator *allocator, void *
178179

179180
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
180181
ASSERT_TRUE(clientBootstrap);
182+
clientBootstrap.EnableBlockingShutdown();
181183

182184
CredentialsProviderChainDefaultConfig config;
183185
config.Bootstrap = &clientBootstrap;

tests/HttpClientConnectionManagerTest.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ static int s_TestHttpClientConnectionManagerResourceSafety(struct aws_allocator
5656

5757
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
5858
ASSERT_TRUE(clientBootstrap);
59+
clientBootstrap.EnableBlockingShutdown();
5960

6061
std::condition_variable semaphore;
6162
std::mutex semaphoreLock;
@@ -158,8 +159,9 @@ static int s_TestHttpClientConnectionWithPendingAcquisitions(struct aws_allocato
158159
Aws::Crt::Io::DefaultHostResolver defaultHostResolver(eventLoopGroup, 8, 30, allocator);
159160
ASSERT_TRUE(defaultHostResolver);
160161

161-
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
162+
Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
162163
ASSERT_TRUE(clientBootstrap);
164+
clientBootstrap.EnableBlockingShutdown();
163165

164166
std::condition_variable semaphore;
165167
std::mutex semaphoreLock;
@@ -270,6 +272,7 @@ static int s_TestHttpClientConnectionWithPendingAcquisitionsAndClosedConnections
270272

271273
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
272274
ASSERT_TRUE(clientBootstrap);
275+
clientBootstrap.EnableBlockingShutdown();
273276

274277
std::condition_variable semaphore;
275278
std::mutex semaphoreLock;

tests/HttpClientTest.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ static int s_TestHttpDownloadNoBackPressure(struct aws_allocator *allocator, voi
9999

100100
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
101101
ASSERT_TRUE(clientBootstrap);
102+
clientBootstrap.EnableBlockingShutdown();
102103

103104
std::shared_ptr<Http::HttpClientConnection> connection(nullptr);
104105
bool errorOccured = true;
@@ -232,6 +233,7 @@ static int s_TestHttpStreamUnActivated(struct aws_allocator *allocator, void *ct
232233

233234
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
234235
ASSERT_TRUE(clientBootstrap);
236+
clientBootstrap.EnableBlockingShutdown();
235237

236238
std::shared_ptr<Http::HttpClientConnection> connection(nullptr);
237239
bool errorOccured = true;

tests/IotServiceTest.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ static int s_TestIotPublishSubscribe(Aws::Crt::Allocator *allocator, void *ctx)
6767

6868
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
6969
ASSERT_TRUE(allocator);
70+
clientBootstrap.EnableBlockingShutdown();
7071

7172
Aws::Crt::Mqtt::MqttClient mqttClient(clientBootstrap, allocator);
7273
ASSERT_TRUE(mqttClient);

tests/MqttClientTest.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ static int s_TestMqttClientResourceSafety(Aws::Crt::Allocator *allocator, void *
3737

3838
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
3939
ASSERT_TRUE(allocator);
40+
clientBootstrap.EnableBlockingShutdown();
4041

4142
Aws::Crt::Mqtt::MqttClient mqttClient(clientBootstrap, allocator);
4243
ASSERT_TRUE(mqttClient);

tests/Sigv4SigningTest.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ static int s_Sigv4SigningTestCreateDestroy(struct aws_allocator *allocator, void
136136

137137
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
138138
ASSERT_TRUE(clientBootstrap);
139+
clientBootstrap.EnableBlockingShutdown();
139140

140141
CredentialsProviderChainDefaultConfig config;
141142
config.Bootstrap = &clientBootstrap;
@@ -164,6 +165,7 @@ static int s_Sigv4SigningTestSimple(struct aws_allocator *allocator, void *ctx)
164165

165166
Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
166167
ASSERT_TRUE(clientBootstrap);
168+
clientBootstrap.EnableBlockingShutdown();
167169

168170
CredentialsProviderChainDefaultConfig config;
169171
config.Bootstrap = &clientBootstrap;

0 commit comments

Comments
 (0)