Skip to content

Commit d12b9c4

Browse files
authored
impl(GCS+gRPC): add AsyncReadObjectRange (#9305)
Implement a function to asynchronously read a portion of the object contents. The callers must set the size of this portion, which gives them control over how much memory this call uses. Use this new function from one of the integration tests.
1 parent b39b353 commit d12b9c4

File tree

5 files changed

+106
-8
lines changed

5 files changed

+106
-8
lines changed

google/cloud/storage/async_client.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,64 @@ class AsyncClient {
106106
public:
107107
~AsyncClient() = default;
108108

109+
/**
110+
* Reads the contents of an object.
111+
*
112+
* When satisfied, the returned future has the contents of the given object
113+
* between @p offset and @p offset + @p limit (exclusive).
114+
*
115+
* Be aware that this will accumulate all the bytes in memory, you need to
116+
* consider whether @p limit is too large for your deployment environment.
117+
*
118+
* @param bucket_name the name of the bucket that contains the object.
119+
* @param object_name the name of the object to be read.
120+
* @param offset where to begin reading from the object, results in an error
121+
* if the offset is larger than the object
122+
* @param limit how much data to read starting at @p offset
123+
* @param options a list of optional query parameters and/or request headers.
124+
* Valid types for this operation include `DisableCrc32cChecksum`,
125+
* `DisableMD5Hash`, `EncryptionKey`, `Generation`, `IfGenerationMatch`,
126+
* `IfGenerationNotMatch`, `IfMetagenerationMatch`,
127+
* `IfMetagenerationNotMatch`, `UserProject`, and `AcceptEncoding`.
128+
*
129+
* @par Idempotency
130+
* This is a read-only operation and is always idempotent.
131+
*/
132+
template <typename... RequestOptions>
133+
future<AsyncReadObjectRangeResponse> ReadObject(
134+
std::string const& bucket_name, std::string const& object_name,
135+
std::int64_t offset, std::int64_t limit, RequestOptions&&... options) {
136+
struct HasReadRange
137+
: public absl::disjunction<
138+
std::is_same<storage::ReadRange, RequestOptions>...> {};
139+
struct HasReadFromOffset
140+
: public absl::disjunction<
141+
std::is_same<storage::ReadFromOffset, RequestOptions>...> {};
142+
struct HasReadLast
143+
: public absl::disjunction<
144+
std::is_same<storage::ReadLast, RequestOptions>...> {};
145+
146+
static_assert(!HasReadRange::value,
147+
"Cannot use `ReadRange()` as a request option in "
148+
"`AsyncClient::ReadObject()`, use the `offset` and `limit` "
149+
"parameters instead.");
150+
static_assert(!HasReadFromOffset::value,
151+
"Cannot use `ReadFromOffset()` as a request option in "
152+
"`AsyncClient::ReadObject()`, use the `offset` and `limit` "
153+
"parameters instead.");
154+
static_assert(!HasReadLast::value,
155+
"Cannot use `ReadLast()` as a request option in "
156+
"`AsyncClient::ReadObject()`, use the `offset` and `limit` "
157+
"parameters instead.");
158+
159+
google::cloud::internal::OptionsSpan const span(
160+
SpanOptions(std::forward<Options>(options)...));
161+
storage::internal::ReadObjectRangeRequest request(bucket_name, object_name);
162+
request.set_multiple_options(std::forward<Options>(options)...,
163+
storage::ReadRange(offset, offset + limit));
164+
return connection_->AsyncReadObjectRange(request);
165+
}
166+
109167
/**
110168
* Deletes an object.
111169
*

google/cloud/storage/internal/async_connection.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_ASYNC_CONNECTION_H
1616
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_ASYNC_CONNECTION_H
1717

18+
#include "google/cloud/storage/async_object_responses.h"
1819
#include "google/cloud/storage/internal/object_requests.h"
1920
#include "google/cloud/completion_queue.h"
2021
#include "google/cloud/future.h"
@@ -46,6 +47,10 @@ class AsyncConnection {
4647

4748
virtual Options options() const = 0;
4849

50+
virtual future<storage_experimental::AsyncReadObjectRangeResponse>
51+
AsyncReadObjectRange(
52+
storage::internal::ReadObjectRangeRequest const& request) = 0;
53+
4954
virtual future<Status> AsyncDeleteObject(
5055
storage::internal::DeleteObjectRequest const& request) = 0;
5156
};

google/cloud/storage/internal/async_connection_impl.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
// limitations under the License.
1414

1515
#include "google/cloud/storage/internal/async_connection_impl.h"
16+
#include "google/cloud/storage/internal/async_accumulate_read_object.h"
1617
#include "google/cloud/storage/internal/grpc_client.h"
1718
#include "google/cloud/storage/internal/grpc_configure_client_context.h"
1819
#include "google/cloud/storage/internal/grpc_object_request_parser.h"
1920
#include "google/cloud/storage/internal/storage_stub_factory.h"
21+
#include "google/cloud/storage/options.h"
2022
#include "google/cloud/internal/async_retry_loop.h"
2123

2224
namespace google {
@@ -31,6 +33,30 @@ AsyncConnectionImpl::AsyncConnectionImpl(CompletionQueue cq,
3133
stub_(std::move(stub)),
3234
options_(std::move(options)) {}
3335

36+
future<storage_experimental::AsyncReadObjectRangeResponse>
37+
AsyncConnectionImpl::AsyncReadObjectRange(
38+
storage::internal::ReadObjectRangeRequest const& request) {
39+
auto proto = storage::internal::GrpcObjectRequestParser::ToProto(request);
40+
if (!proto) {
41+
auto response = storage_experimental::AsyncReadObjectRangeResponse{};
42+
response.status = std::move(proto).status();
43+
return make_ready_future(std::move(response));
44+
}
45+
46+
auto context_factory = [request]() {
47+
auto context = absl::make_unique<grpc::ClientContext>();
48+
ApplyQueryParameters(*context, request);
49+
return context;
50+
};
51+
auto const& current = internal::CurrentOptions();
52+
return storage_internal::AsyncAccumulateReadObjectFull(
53+
cq_, stub_, std::move(context_factory), *std::move(proto), current)
54+
.then([current](
55+
future<storage_internal::AsyncAccumulateReadObjectResult> f) {
56+
return ToResponse(f.get(), current);
57+
});
58+
}
59+
3460
future<Status> AsyncConnectionImpl::AsyncDeleteObject(
3561
storage::internal::DeleteObjectRequest const& request) {
3662
auto proto = storage::internal::GrpcObjectRequestParser::ToProto(request);

google/cloud/storage/internal/async_connection_impl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class AsyncConnectionImpl : public AsyncConnection {
3939

4040
Options options() const override { return options_; }
4141

42+
future<storage_experimental::AsyncReadObjectRangeResponse>
43+
AsyncReadObjectRange(
44+
storage::internal::ReadObjectRangeRequest const& request) override;
45+
4246
future<Status> AsyncDeleteObject(
4347
storage::internal::DeleteObjectRequest const& request) override;
4448

google/cloud/storage/tests/async_client_integration_test.cc

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,16 @@ class AsyncClientIntegrationTest
3636
: public google::cloud::storage::testing::StorageIntegrationTest {
3737
protected:
3838
void SetUp() override {
39-
// TODO(#5673) - enable against production.
4039
if (!UsingEmulator()) GTEST_SKIP();
41-
project_id_ = GetEnv("GOOGLE_CLOUD_PROJECT").value_or("");
42-
ASSERT_THAT(project_id_, Not(IsEmpty()))
43-
<< "GOOGLE_CLOUD_PROJECT is not set";
44-
4540
bucket_name_ =
4641
GetEnv("GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME").value_or("");
4742
ASSERT_THAT(bucket_name_, Not(IsEmpty()))
4843
<< "GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME is not set";
4944
}
5045

51-
std::string project_id() const { return project_id_; }
52-
std::string bucket_name() const { return bucket_name_; }
46+
std::string const& bucket_name() const { return bucket_name_; }
5347

5448
private:
55-
std::string project_id_;
5649
std::string bucket_name_;
5750
};
5851

@@ -68,6 +61,18 @@ TEST_F(AsyncClientIntegrationTest, ObjectCRUD) {
6861
ScheduleForDelete(*insert);
6962

7063
auto async = MakeAsyncClient();
64+
auto pending0 =
65+
async.ReadObject(bucket_name(), object_name, 0, LoremIpsum().size());
66+
auto pending1 =
67+
async.ReadObject(bucket_name(), object_name, 0, LoremIpsum().size());
68+
69+
for (auto* p : {&pending1, &pending0}) {
70+
auto response = p->get();
71+
EXPECT_STATUS_OK(response.status);
72+
auto const full = std::accumulate(response.contents.begin(),
73+
response.contents.end(), std::string{});
74+
EXPECT_EQ(full, LoremIpsum());
75+
}
7176
auto status = async
7277
.DeleteObject(bucket_name(), object_name,
7378
gcs::Generation(insert->generation()))

0 commit comments

Comments
 (0)