Skip to content

Commit c30053a

Browse files
committed
feat(storage): introduce core feature adoption tracking bitmask and options (#2657)
1 parent a82ff3b commit c30053a

6 files changed

Lines changed: 166 additions & 1 deletion

File tree

google/cloud/storage/google_cloud_cpp_storage.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ google_cloud_cpp_storage_hdrs = [
6161
"internal/curl/request_builder.h",
6262
"internal/default_object_acl_requests.h",
6363
"internal/empty_response.h",
64+
"internal/feature_tracker.h",
6465
"internal/generate_message_boundary.h",
6566
"internal/generic_object_request.h",
6667
"internal/generic_request.h",
@@ -173,6 +174,7 @@ google_cloud_cpp_storage_srcs = [
173174
"internal/crc32c.cc",
174175
"internal/default_object_acl_requests.cc",
175176
"internal/empty_response.cc",
177+
"internal/feature_tracker.cc",
176178
"internal/generate_message_boundary.cc",
177179
"internal/generic_stub_adapter.cc",
178180
"internal/generic_stub_factory.cc",

google/cloud/storage/google_cloud_cpp_storage.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ add_library(
9595
internal/default_object_acl_requests.h
9696
internal/empty_response.cc
9797
internal/empty_response.h
98+
internal/feature_tracker.cc
99+
internal/feature_tracker.h
98100
internal/generate_message_boundary.cc
99101
internal/generate_message_boundary.h
100102
internal/generic_object_request.h
@@ -437,6 +439,7 @@ if (BUILD_TESTING)
437439
internal/const_buffer_test.cc
438440
internal/crc32c_test.cc
439441
internal/default_object_acl_requests_test.cc
442+
internal/feature_tracker_test.cc
440443
internal/generate_message_boundary_test.cc
441444
internal/generic_request_test.cc
442445
internal/hash_function_impl_test.cc
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/storage/internal/feature_tracker.h"
16+
#include "google/cloud/storage/internal/base64.h"
17+
#include "google/cloud/storage/options.h"
18+
#include "google/cloud/options.h"
19+
#include <memory>
20+
#include <vector>
21+
22+
namespace google {
23+
namespace cloud {
24+
namespace storage {
25+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
26+
namespace internal {
27+
28+
std::string EncodeFeatureTrackerBitmask(std::uint32_t mask) {
29+
if (mask == 0) return {};
30+
31+
std::vector<std::uint8_t> bytes;
32+
for (int i = 0; i < 4; ++i) {
33+
auto b = static_cast<std::uint8_t>((mask >> (24 - i * 8)) & 0xFF);
34+
if (!bytes.empty() || b != 0) {
35+
bytes.push_back(b);
36+
}
37+
}
38+
return Base64Encode(bytes);
39+
}
40+
41+
Options SetupFeatureTracker(Options opts) {
42+
if (opts.has<EnableFeatureReportsOption>() &&
43+
!opts.get<EnableFeatureReportsOption>()) {
44+
return std::move(opts);
45+
}
46+
if (opts.has<FeatureTrackerOption>()) return std::move(opts);
47+
48+
std::uint32_t mask = 0;
49+
// Add checks for configuration-driven options here as they are introduced.
50+
opts.set<FeatureTrackerOption>(std::make_shared<FeatureTracker>(mask));
51+
return std::move(opts);
52+
}
53+
54+
} // namespace internal
55+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
56+
} // namespace storage
57+
} // namespace cloud
58+
} // namespace google
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_FEATURE_TRACKER_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_FEATURE_TRACKER_H
17+
18+
#include "google/cloud/storage/version.h"
19+
#include "google/cloud/options.h"
20+
#include <atomic>
21+
#include <cstdint>
22+
#include <memory>
23+
#include <string>
24+
25+
namespace google {
26+
namespace cloud {
27+
namespace storage {
28+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
29+
namespace internal {
30+
31+
inline constexpr auto kFeatureTrackerHeaderName = "x-goog-storage-cpp-features";
32+
33+
// Tracked features represented as bit positions in a bitmask.
34+
enum class TrackedFeature : std::uint32_t {
35+
// Operation-Driven Optimizations
36+
kMultiStreamInMRD = 0,
37+
kPCU = 1,
38+
39+
// Configuration-Driven Options
40+
kGrpcDirectPathEnforced = 2,
41+
kJsonReads = 3,
42+
};
43+
44+
// Converts a feature bitmask to a Base64-encoded string, stripping leading
45+
// zero bytes to maintain compact representation.
46+
std::string EncodeFeatureTrackerBitmask(std::uint32_t mask);
47+
48+
// A thread-safe state object that can be shared between operation connections
49+
// (like ObjectDescriptorImpl) and RPC factories (like OpenStreamFactory)
50+
// to track which features are adopted during an operation.
51+
class FeatureTracker {
52+
public:
53+
FeatureTracker() = default;
54+
explicit FeatureTracker(std::uint32_t initial) : mask_(initial) {}
55+
56+
void RegisterFeature(TrackedFeature feature) {
57+
mask_.fetch_or(1U << static_cast<std::uint32_t>(feature),
58+
std::memory_order_relaxed);
59+
}
60+
61+
std::uint32_t GetMask() const {
62+
return mask_.load(std::memory_order_relaxed);
63+
}
64+
65+
std::string HeaderValue() const {
66+
return EncodeFeatureTrackerBitmask(GetMask());
67+
}
68+
69+
private:
70+
std::atomic<std::uint32_t> mask_{0};
71+
};
72+
73+
struct FeatureTrackerOption {
74+
using Type = std::shared_ptr<FeatureTracker>;
75+
};
76+
77+
// Evaluates client configuration options, creates a shared FeatureTracker
78+
// initialized with configuration-driven feature flags (if any), and stores it
79+
// into the Options list under FeatureTrackerOption.
80+
Options SetupFeatureTracker(Options opts);
81+
82+
} // namespace internal
83+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
84+
} // namespace storage
85+
} // namespace cloud
86+
} // namespace google
87+
88+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_FEATURE_TRACKER_H

google/cloud/storage/options.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,27 @@ struct IdempotencyPolicyOption {
317317
using Type = std::shared_ptr<IdempotencyPolicy>;
318318
};
319319

320+
/**
321+
* Enable adoption reporting of client-side optimizations and configuration
322+
* choices.
323+
*
324+
* When this option is enabled (the default), the GCS client sends a bitmask in
325+
* the `x-goog-storage-cpp-features` header.
326+
*
327+
* @ingroup storage-options
328+
*/
329+
struct EnableFeatureReportsOption {
330+
using Type = bool;
331+
};
332+
320333
/// The complete list of options accepted by `storage::Client`.
321334
using ClientOptionList = ::google::cloud::OptionList<
322335
RestEndpointOption, IamEndpointOption, ProjectIdOption, ProjectIdOption,
323336
ConnectionPoolSizeOption, DownloadBufferSizeOption, UploadBufferSizeOption,
324337
EnableCurlSslLockingOption, EnableCurlSigpipeHandlerOption,
325338
MaximumCurlSocketRecvSizeOption, MaximumCurlSocketSendSizeOption,
326339
TransferStallTimeoutOption, RetryPolicyOption, BackoffPolicyOption,
327-
IdempotencyPolicyOption, CARootsFilePathOption,
340+
IdempotencyPolicyOption, CARootsFilePathOption, EnableFeatureReportsOption,
328341
storage_experimental::HttpVersionOption>;
329342

330343
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END

google/cloud/storage/storage_client_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ storage_client_unit_tests = [
6060
"internal/const_buffer_test.cc",
6161
"internal/crc32c_test.cc",
6262
"internal/default_object_acl_requests_test.cc",
63+
"internal/feature_tracker_test.cc",
6364
"internal/generate_message_boundary_test.cc",
6465
"internal/generic_request_test.cc",
6566
"internal/hash_function_impl_test.cc",

0 commit comments

Comments
 (0)