Skip to content

Commit 6a3db69

Browse files
Logan Gorefacebook-github-bot
authored andcommitted
Use singleton per-region for S3Client (#384)
Summary: Pull Request resolved: #384 # What * Use a singleton for each region when constructing our S3Client instead of a _single_ singleton * This is kind of a follow-up to D36727729 (47182c6) # Why * Follow-up to S281873 - S3Client singleton breaks multi-region support in PCF IO NOTE: PCF should be owning this code in the long-term since it's out of PSI's scope, but since we owned the initial SEV, I took ownership of this follow-up. Differential Revision: D39174441 fbshipit-source-id: a44d96b31a140ed2a220d516dfbda74504bed7f7
1 parent 6541152 commit 6a3db69

3 files changed

Lines changed: 40 additions & 5 deletions

File tree

fbpcf/io/cloud_util/CloudFileUtil.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ std::unique_ptr<IFileUploader> getCloudFileUploader(
7676
return std::make_unique<S3FileUploader>(
7777
fbpcf::cloudio::S3Client::getInstance(
7878
fbpcf::aws::S3ClientOption{.region = ref.region})
79-
.getS3Client(),
79+
->getS3Client(),
8080
filePath);
8181
} else {
8282
throw fbpcf::PcfException("Not supported yet.");

fbpcf/io/cloud_util/S3Client.cpp

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,47 @@
77

88
#include "fbpcf/io/cloud_util/S3Client.h"
99

10+
#include <string>
11+
1012
#include <aws/core/Aws.h>
1113
#include <aws/s3/S3Client.h>
1214

15+
#include <folly/container/F14Map.h>
16+
#include <folly/Synchronized.h>
17+
1318
namespace fbpcf::cloudio {
14-
S3Client& S3Client::getInstance(const fbpcf::aws::S3ClientOption& option) {
15-
static S3Client s3Client(option);
16-
return s3Client;
19+
std::shared_ptr<S3Client> S3Client::getInstance(const fbpcf::aws::S3ClientOption& option) {
20+
/* Due to previous problems, we create a Singleton instance of the S3Client,
21+
* but there's a catch: we need a distinct S3Client for each region, or we
22+
* run into other issues. For that reason, we store this map from string to
23+
* S3Client with the assumption that the keys are region names. Since region
24+
* is optional, we also allow for a default empty string region.
25+
* ***************************** NOT THREAD SAFE ****************************
26+
* NOTE: Significant refactoring is required to make this thread safe
27+
* Downstream usage wants a mutable reference, but a folly::Synchronized
28+
* RWLock will return a const ref to a reader, meaning it's hard to refactor.
29+
* Simply trying to use folly::Synchronized around the map isn't sufficient,
30+
* because we'll leak a reference to an object in the map which is unsafe.
31+
* ***************************** NOT THREAD SAFE ****************************
32+
*/
33+
static folly::Synchronized<folly::F14FastMap<std::string, std::shared_ptr<S3Client>>> m;
34+
35+
std::string defaultStr{};
36+
auto region = option.region.value_or(defaultStr);
37+
38+
m.withWLock([&](auto& clientMap) {
39+
if (clientMap.find(region) == clientMap.end()) {
40+
std::shared_ptr<S3Client> ptr{new S3Client{option}};
41+
clientMap.at(region) = ptr;
42+
}
43+
});
44+
45+
/* You may see this and think, "Hey, the NOT THREAD SAFE warning above is
46+
* outdated, it looks like we fixed it!", but you're wrong. This still does
47+
* not fully solve the problem. Because the downstream consumer takes a
48+
* mutable reference, there's no guarantee that this is thread safe. It's
49+
* better than nothing, but you still shouldn't fully trust this code.
50+
*/
51+
return m.wlock()->at(region);
1752
}
1853
} // namespace fbpcf::cloudio

fbpcf/io/cloud_util/S3Client.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class S3Client {
2222
}
2323

2424
public:
25-
static S3Client& getInstance(const fbpcf::aws::S3ClientOption& option);
25+
static std::shared_ptr<S3Client> getInstance(const fbpcf::aws::S3ClientOption& option);
2626

2727
std::shared_ptr<Aws::S3::S3Client> getS3Client() {
2828
return awsS3Client_;

0 commit comments

Comments
 (0)