class RGWDataSyncSingleEntryCR : public RGWCoroutine {
RGWDataSyncCtx *sc;
RGWDataSyncEnv *sync_env;
- rgw::bucket_sync::Handle state; // cached bucket-shard state
+ rgw::bucket_sync::ShardHandle state; // cached bucket-shard state
rgw_data_sync_obligation obligation; // input obligation
std::optional<rgw_data_sync_obligation> complete; // obligation to complete
uint32_t obligation_counter = 0;
ceph::real_time progress;
int sync_status = 0;
public:
- RGWDataSyncSingleEntryCR(RGWDataSyncCtx *_sc, rgw::bucket_sync::Handle state,
+ RGWDataSyncSingleEntryCR(RGWDataSyncCtx *_sc, rgw::bucket_sync::ShardHandle state,
rgw_data_sync_obligation _obligation,
RGWDataSyncShardMarkerTrack *_marker_tracker,
const rgw_raw_obj& error_repo,
const std::string marker,
ceph::real_time timestamp,
boost::intrusive_ptr<const RGWContinuousLeaseCR> lease_cr,
- boost::intrusive_ptr<rgw::bucket_sync::Cache> bucket_shard_cache,
+ boost::intrusive_ptr<rgw::bucket_sync::ShardCache> bucket_shard_cache,
RGWDataSyncShardMarkerTrack* marker_tracker,
rgw_raw_obj error_repo,
RGWSyncTraceNodeRef& tn,
rgw_raw_obj error_repo;
ceph::real_time timestamp;
boost::intrusive_ptr<const RGWContinuousLeaseCR> lease_cr;
- boost::intrusive_ptr<rgw::bucket_sync::Cache> bucket_shard_cache;
+ boost::intrusive_ptr<rgw::bucket_sync::ShardCache> bucket_shard_cache;
RGWDataSyncShardMarkerTrack* marker_tracker;
RGWSyncTraceNodeRef tn;
rgw_bucket_index_marker_info remote_info;
RGWDataFullSyncSingleEntryCR(RGWDataSyncCtx *_sc, const rgw_pool& _pool, const rgw_bucket_shard& _source_bs,
const std::string& _key, const rgw_data_sync_status& _sync_status, const rgw_raw_obj& _error_repo,
ceph::real_time _timestamp, boost::intrusive_ptr<const RGWContinuousLeaseCR> _lease_cr,
- boost::intrusive_ptr<rgw::bucket_sync::Cache> _bucket_shard_cache,
+ boost::intrusive_ptr<rgw::bucket_sync::ShardCache> _bucket_shard_cache,
RGWDataSyncShardMarkerTrack* _marker_tracker,
RGWSyncTraceNodeRef& _tn)
: RGWCoroutine(_sc->cct), sc(_sc), sync_env(_sc->env), pool(_pool), source_bs(_source_bs), key(_key),
boost::intrusive_ptr<const RGWContinuousLeaseCR> lease_cr;
const rgw_data_sync_status& sync_status;
RGWObjVersionTracker& objv;
- boost::intrusive_ptr<rgw::bucket_sync::Cache> bucket_shard_cache;
+ boost::intrusive_ptr<rgw::bucket_sync::ShardCache> bucket_shard_cache;
std::optional<RGWDataSyncShardMarkerTrack> marker_tracker;
RGWRadosGetOmapValsCR::ResultPtr omapvals;
boost::intrusive_ptr<const RGWContinuousLeaseCR> lease_cr,
const rgw_data_sync_status& sync_status,
RGWObjVersionTracker& objv,
- const boost::intrusive_ptr<rgw::bucket_sync::Cache>& bucket_shard_cache)
+ const boost::intrusive_ptr<rgw::bucket_sync::ShardCache>& bucket_shard_cache)
: RGWCoroutine(_sc->cct), sc(_sc), pool(pool), shard_id(shard_id),
sync_marker(sync_marker), tn(tn), status_oid(status_oid),
error_repo(error_repo), lease_cr(std::move(lease_cr)),
const string& status_oid, const rgw_raw_obj& error_repo,
boost::intrusive_ptr<const RGWContinuousLeaseCR> lease_cr,
const rgw_data_sync_status& sync_status, RGWObjVersionTracker& objv,
- const boost::intrusive_ptr<rgw::bucket_sync::Cache>& bucket_shard_cache)
+ const boost::intrusive_ptr<rgw::bucket_sync::ShardCache>& bucket_shard_cache)
: RGWDataBaseSyncShardCR(sc, pool, shard_id, sync_marker, tn,
status_oid, error_repo, std::move(lease_cr),
sync_status, objv, bucket_shard_cache) {}
const string& status_oid, const rgw_raw_obj& error_repo,
boost::intrusive_ptr<const RGWContinuousLeaseCR> lease_cr,
const rgw_data_sync_status& sync_status, RGWObjVersionTracker& objv,
- const boost::intrusive_ptr<rgw::bucket_sync::Cache>& bucket_shard_cache,
+ const boost::intrusive_ptr<rgw::bucket_sync::ShardCache>& bucket_shard_cache,
ceph::mutex& inc_lock,
bc::flat_set<rgw_data_notify_entry>& modified_shards)
: RGWDataBaseSyncShardCR(sc, pool, shard_id, sync_marker, tn,
// target number of entries to cache before recycling idle ones
static constexpr size_t target_cache_size = 256;
- boost::intrusive_ptr<rgw::bucket_sync::Cache> bucket_shard_cache {
- rgw::bucket_sync::Cache::create(target_cache_size) };
+ boost::intrusive_ptr<rgw::bucket_sync::ShardCache> bucket_shard_cache {
+ rgw::bucket_sync::ShardCache::create(target_cache_size) };
boost::intrusive_ptr<RGWContinuousLeaseCR> lease_cr;
boost::intrusive_ptr<RGWCoroutinesStack> lease_stack;
#include <boost/smart_ptr/intrusive_ref_counter.hpp>
#include "common/intrusive_lru.h"
#include "rgw_data_sync.h"
+#include "common/ceph_time.h"
namespace rgw::bucket_sync {
-// per bucket-shard state cached by DataSyncShardCR
-struct State {
+// per bucket-shard (dimensioned by generation) state cached by DataSyncShardCR
+struct ShardState {
+ using key_type = std::pair<rgw_bucket_shard, std::optional<uint64_t>>;
// the source bucket shard to sync
- std::pair<rgw_bucket_shard, std::optional<uint64_t>> key;
+ key_type key;
// current sync obligation being processed by DataSyncSingleEntry
std::optional<rgw_data_sync_obligation> obligation;
// incremented with each new obligation
// highest timestamp applied by all sources
ceph::real_time progress_timestamp;
- State(const std::pair<rgw_bucket_shard, std::optional<uint64_t>>& key ) noexcept
- : key(key) {}
- State(const rgw_bucket_shard& shard, std::optional<uint64_t> gen) noexcept
+ ShardState(const key_type& key) noexcept
+ : key(key) {}
+ ShardState(const rgw_bucket_shard& shard, std::optional<uint64_t> gen) noexcept
: key(shard, gen) {}
};
+// per bucket (dimensioned by generation) state cached by DataSyncShardCR
+struct GenState {
+ using key_type = std::pair<std::string, std::optional<uint64_t>>;
+ // the source bucket/generation to sync
+ key_type key;
+ // Last future generation recovery timestamp
+ ceph::coarse_mono_time last_future_generation_recovery = ceph::coarse_mono_clock::zero();
+
+ GenState(const key_type& key) noexcept
+ : key(key) {}
+ GenState(std::string bucket, std::optional<uint64_t> gen) noexcept
+ : key(std::move(bucket), gen) {}
+};
+
+template<typename State>
struct Entry;
+template<typename State>
struct EntryToKey;
+template<typename State>
class Handle;
+template<typename State>
using lru_config = ceph::common::intrusive_lru_config<
- std::pair<rgw_bucket_shard, std::optional<uint64_t>>, Entry, EntryToKey>;
+ typename State::key_type, Entry<State>, EntryToKey<State>>;
// a recyclable cache entry
-struct Entry : State, ceph::common::intrusive_lru_base<lru_config> {
+template<typename State>
+struct Entry : State, ceph::common::intrusive_lru_base<lru_config<State>> {
using State::State;
};
+template<typename State>
struct EntryToKey {
- using type = std::pair<rgw_bucket_shard, std::optional<uint64_t>>;
- const type& operator()(const Entry& e) { return e.key; }
+ using type = typename State::key_type;
+ const type& operator()(const Entry<State>& e) { return e.key; }
};
// use a non-atomic reference count since these aren't shared across threads
using thread_unsafe_ref_counter = boost::intrusive_ref_counter<
T, boost::thread_unsafe_counter>;
-// a state cache for entries within a single datalog shard
-class Cache : public thread_unsafe_ref_counter<Cache> {
- ceph::common::intrusive_lru<lru_config> cache;
+// A state cache for entries within a single datalog shard
+template<typename State>
+class Cache : public thread_unsafe_ref_counter<Cache<State>> {
+ ceph::common::intrusive_lru<lru_config<State>> cache;
protected:
// protected ctor to enforce the use of factory function create()
explicit Cache(size_t target_size) {
// find or create a cache entry for the given key, and return a Handle that
// keeps it lru-pinned until destruction
- Handle get(const rgw_bucket_shard& shard, std::optional<uint64_t> gen);
+ Handle<State> get(const auto& ...args);
};
// a State handle that keeps the Cache referenced
+template<typename State>
class Handle {
- boost::intrusive_ptr<Cache> cache;
- boost::intrusive_ptr<Entry> entry;
+ boost::intrusive_ptr<Cache<State>> cache;
+ boost::intrusive_ptr<Entry<State>> entry;
public:
Handle() noexcept = default;
~Handle() = default;
- Handle(boost::intrusive_ptr<Cache> cache,
- boost::intrusive_ptr<Entry> entry) noexcept
+ Handle(boost::intrusive_ptr<Cache<State>> cache,
+ boost::intrusive_ptr<Entry<State>> entry) noexcept
: cache(std::move(cache)), entry(std::move(entry)) {}
Handle(Handle&&) = default;
Handle(const Handle&) = default;
State* operator->() const noexcept { return entry.get(); }
};
-inline Handle Cache::get(const rgw_bucket_shard& shard, std::optional<uint64_t> gen)
+template<typename State>
+inline Handle<State> Cache<State>::get(const auto& ...args)
{
- auto result = cache.get_or_create({ shard, gen });
+ static_assert(
+ std::is_constructible_v<typename State::key_type, decltype(args)...>,
+ "The arguments to Cache<State>::get must be arguments to construct "
+ "State");
+ auto result = cache.get_or_create({args...});
return {this, std::move(result.first)};
}
+using ShardHandle = Handle<ShardState>;
+using ShardCache = Cache<ShardState>;
+
+using GenHandle = Handle<GenState>;
+using GenCache = Cache<GenState>;
+
} // namespace rgw::bucket_sync
* Foundation. See file COPYING.
*/
-#include "rgw_bucket_sync_cache.h"
+#include <common/ceph_time.h>
#include <gtest/gtest.h>
+#include "rgw_bucket_sync_cache.h"
+
using namespace rgw::bucket_sync;
-// helper function to construct rgw_bucket_shard
-static rgw_bucket_shard make_key(const std::string& tenant,
- const std::string& bucket, int shard)
+using Shard = ShardState;
+using Gen = GenState;
+
+namespace {
+/// Create a key suitable for a given cache
+///
+/// \tparam State One of `Shard` or `Gen`.
+///
+/// \param[in] tenant Owning tenant
+/// \param[in] bucket Bucket name
+/// \param[in] shard Bucket shard, ignored if `State` is `Gen`.
+///
+/// \return A key for the given bucket
+template <typename State>
+auto make_key(const std::string& tenant, const std::string& bucket, int shard) =
+ delete;
+
+template <>
+auto
+make_key<Shard>(const std::string& tenant, const std::string& bucket, int shard)
{
auto key = rgw_bucket_key{tenant, bucket};
return rgw_bucket_shard{std::move(key), shard};
}
-TEST(BucketSyncCache, ReturnCachedPinned)
+template <>
+auto
+make_key<Gen>(
+ const std::string& tenant,
+ const std::string& bucket,
+ // Dummy parameter for overload
+ int)
+{
+ auto key = rgw_bucket_key{tenant, bucket};
+ rgw_bucket b{std::move(key)};
+ return b.get_key();
+}
+
+/// Stick an integer into a state
+///
+/// \tparam State One of `Shard` or `Gen`.
+///
+/// \param[inout] state State to modify
+/// \param[in] value Value to store
+template <typename State>
+void mutate(State&, unsigned int value) = delete;
+
+template <>
+void
+mutate<Shard>(ShardState& state, unsigned int value)
+{
+ state.counter = value;
+}
+
+template <>
+void
+mutate<Gen>(GenState& state, unsigned int value)
+{
+ state.last_future_generation_recovery = ceph::coarse_mono_time{
+ ceph::timespan{value}};
+}
+
+/// Retrieve an integer from a state
+///
+/// \note Intended only for values stored with `mutate`, anything else
+/// will be truncated to the capacity of an `unsigned int`.
+///
+/// \tparam State One of `Shard` or `Gen`.
+///
+/// \param[in] state State to modify
+///
+/// \return The integer previously stored in the state
+template <typename State>
+unsigned int extract(const State&) = delete;
+
+template <>
+unsigned int
+extract<Shard>(const Shard& state)
+{
+ return static_cast<unsigned int>(state.counter);
+}
+
+template <>
+unsigned int
+extract<Gen>(const Gen& state)
{
- auto cache = Cache::create(0);
- const auto key = make_key("", "1", 0);
+ return static_cast<unsigned int>(
+ state.last_future_generation_recovery.time_since_epoch().count());
+}
+} // namespace
+
+template <typename State>
+void
+ReturnCachedPinned()
+{
+ auto cache = Cache<State>::create(0);
+ const auto key = make_key<State>("", "1", 0);
auto h1 = cache->get(key, std::nullopt); // pin
- h1->counter = 1;
+ mutate(*h1, 1);
auto h2 = cache->get(key, std::nullopt);
- EXPECT_EQ(1, h2->counter);
+ EXPECT_EQ(1, extract(*h2));
}
-TEST(BucketSyncCache, ReturnNewUnpinned)
+TEST(BucketShardSyncCache, ReturnCachedPinned) { ReturnCachedPinned<Shard>(); }
+
+TEST(BucketGenSyncCache, ReturnCachedPinned) { ReturnCachedPinned<Gen>(); }
+
+template <typename State>
+void
+ReturnNewUnpinned()
{
- auto cache = Cache::create(0);
- const auto key = make_key("", "1", 0);
- cache->get(key, std::nullopt)->counter = 1; // pin+unpin
- EXPECT_EQ(0, cache->get(key, std::nullopt)->counter);
+ auto cache = Cache<State>::create(0);
+ const auto key = make_key<State>("", "1", 0);
+ mutate(*cache->get(key, std::nullopt), 1); // pin+unpin
+ EXPECT_EQ(0, extract(*cache->get(key, std::nullopt)));
}
-TEST(BucketSyncCache, DistinctTenant)
+TEST(BucketShardSyncCache, ReturnNewUnpinned) { ReturnNewUnpinned<Shard>(); }
+
+TEST(BucketGenSyncCache, ReturnNewUnpinned) { ReturnNewUnpinned<Gen>(); }
+
+template <typename State>
+void
+DistinctTenant()
{
- auto cache = Cache::create(2);
- const auto key1 = make_key("a", "bucket", 0);
- const auto key2 = make_key("b", "bucket", 0);
- cache->get(key1, std::nullopt)->counter = 1;
- EXPECT_EQ(0, cache->get(key2, std::nullopt)->counter);
+ auto cache = Cache<State>::create(2);
+ const auto key1 = make_key<State>("a", "bucket", 0);
+ const auto key2 = make_key<State>("b", "bucket", 0);
+ mutate(*cache->get(key1, std::nullopt), 1);
+ EXPECT_EQ(0, extract(*cache->get(key2, std::nullopt)));
}
-TEST(BucketSyncCache, DistinctShards)
+TEST(BucketShardSyncCache, DistinctTenant) { DistinctTenant<Shard>(); }
+
+TEST(BucketGenSyncCache, DistinctTenant) { DistinctTenant<Gen>(); }
+
+TEST(BucketShardSyncCache, DistinctShards)
{
- auto cache = Cache::create(2);
- const auto key1 = make_key("", "bucket", 0);
- const auto key2 = make_key("", "bucket", 1);
+ auto cache = ShardCache::create(2);
+ const auto key1 = make_key<Shard>("", "bucket", 0);
+ const auto key2 = make_key<Shard>("", "bucket", 1);
cache->get(key1, std::nullopt)->counter = 1;
EXPECT_EQ(0, cache->get(key2, std::nullopt)->counter);
}
-TEST(BucketSyncCache, DistinctGen)
+template <typename State>
+void
+DistinctGen()
{
- auto cache = Cache::create(2);
- const auto key = make_key("", "bucket", 0);
+ auto cache = Cache<State>::create(2);
+ const auto key = make_key<State>("", "bucket", 0);
std::optional<uint64_t> gen1; // empty
std::optional<uint64_t> gen2 = 5;
- cache->get(key, gen1)->counter = 1;
- EXPECT_EQ(0, cache->get(key, gen2)->counter);
+ mutate(*cache->get(key, gen1), 1);
+ EXPECT_EQ(0, extract(*cache->get(key, gen2)));
}
-TEST(BucketSyncCache, DontEvictPinned)
+TEST(BucketShardSyncCache, DistinctGen) { DistinctGen<Shard>(); }
+
+TEST(BucketGenSyncCache, DistinctGen) { DistinctGen<Gen>(); }
+
+template <typename State>
+void
+DontEvictPinned()
{
- auto cache = Cache::create(0);
+ auto cache = Cache<State>::create(0);
- const auto key1 = make_key("", "1", 0);
- const auto key2 = make_key("", "2", 0);
+ const auto key1 = make_key<State>("", "1", 0);
+ const auto key2 = make_key<State>("", "2", 0);
auto h1 = cache->get(key1, std::nullopt);
EXPECT_EQ(key1, h1->key.first);
EXPECT_EQ(key1, h1->key.first); // h1 unchanged
}
-TEST(BucketSyncCache, HandleLifetime)
+TEST(BucketShardSyncCache, DontEvictPinned) { DontEvictPinned<Shard>(); }
+
+TEST(BucketGenSyncCache, DontEvictPinned) { DontEvictPinned<Gen>(); }
+
+template <typename State>
+void
+HandleLifetime()
{
- const auto key = make_key("", "1", 0);
+ const auto key = make_key<State>("", "1", 0);
- Handle h; // test that handles keep the cache referenced
+ Handle<State> h; // test that handles keep the cache referenced
{
- auto cache = Cache::create(0);
+ auto cache = Cache<State>::create(0);
h = cache->get(key, std::nullopt);
}
EXPECT_EQ(key, h->key.first);
}
-TEST(BucketSyncCache, TargetSize)
+TEST(BucketShardSyncCache, HandleLifetime) { HandleLifetime<Shard>(); }
+
+TEST(BucketGenSyncCache, HandleLifetime) { HandleLifetime<Gen>(); }
+
+template <typename State>
+void
+TargetSize()
{
- auto cache = Cache::create(2);
+ auto cache = Cache<State>::create(2);
- const auto key1 = make_key("", "1", 0);
- const auto key2 = make_key("", "2", 0);
- const auto key3 = make_key("", "3", 0);
+ const auto key1 = make_key<State>("", "1", 0);
+ const auto key2 = make_key<State>("", "2", 0);
+ const auto key3 = make_key<State>("", "3", 0);
// fill cache up to target_size=2
- cache->get(key1, std::nullopt)->counter = 1;
- cache->get(key2, std::nullopt)->counter = 2;
+ mutate(*cache->get(key1, std::nullopt), 1);
+ mutate(*cache->get(key2, std::nullopt), 2);
// test that each unpinned entry is still cached
- EXPECT_EQ(1, cache->get(key1, std::nullopt)->counter);
- EXPECT_EQ(2, cache->get(key2, std::nullopt)->counter);
+ EXPECT_EQ(1, extract(*cache->get(key1, std::nullopt)));
+ EXPECT_EQ(2, extract(*cache->get(key2, std::nullopt)));
// overflow the cache and recycle key1
- cache->get(key3, std::nullopt)->counter = 3;
+ mutate(*cache->get(key3, std::nullopt), 3);
// test that the oldest entry was recycled
- EXPECT_EQ(0, cache->get(key1, std::nullopt)->counter);
+ EXPECT_EQ(0, extract(*cache->get(key1, std::nullopt)));
}
-TEST(BucketSyncCache, HandleMoveAssignEmpty)
+TEST(BucketShardSyncCache, TargetSize) { TargetSize<Shard>(); }
+
+TEST(BucketGenSyncCache, TargetSize) { TargetSize<Gen>(); }
+
+template <typename State>
+void
+HandleMoveAssignEmpty()
{
- auto cache = Cache::create(0);
+ auto cache = Cache<State>::create(0);
- const auto key1 = make_key("", "1", 0);
- const auto key2 = make_key("", "2", 0);
+ const auto key1 = make_key<State>("", "1", 0);
+ const auto key2 = make_key<State>("", "2", 0);
- Handle j1;
+ Handle<State> j1;
{
auto h1 = cache->get(key1, std::nullopt);
j1 = std::move(h1); // assign over empty handle
EXPECT_EQ(key1, j1->key.first); // j1 stays pinned
}
-TEST(BucketSyncCache, HandleMoveAssignExisting)
+TEST(BucketShardSyncCache, HandleMoveAssignEmpty)
{
- const auto key1 = make_key("", "1", 0);
- const auto key2 = make_key("", "2", 0);
+ HandleMoveAssignEmpty<Shard>();
+}
+
+TEST(BucketGenSyncCache, HandleMoveAssignEmpty)
+{
+ HandleMoveAssignEmpty<Gen>();
+}
- Handle h1;
+template <typename State>
+void
+HandleMoveAssignExisting()
+{
+ const auto key1 = make_key<State>("", "1", 0);
+ const auto key2 = make_key<State>("", "2", 0);
+
+ Handle<State> h1;
{
- auto cache1 = Cache::create(0);
+ auto cache1 = Cache<State>::create(0);
h1 = cache1->get(key1, std::nullopt);
- } // j1 has the last ref to cache1
+ } // h1 has the last ref to cache1
{
- auto cache2 = Cache::create(0);
+ auto cache2 = Cache<State>::create(0);
auto h2 = cache2->get(key2, std::nullopt);
h1 = std::move(h2); // assign over existing handle
}
EXPECT_EQ(key2, h1->key.first);
}
-TEST(BucketSyncCache, HandleCopyAssignEmpty)
+TEST(BucketShardSyncCache, HandleMoveAssignExisting)
+{
+ HandleMoveAssignExisting<Shard>();
+}
+
+TEST(BucketGenSyncCache, HandleMoveAssignExisting)
+{
+ HandleMoveAssignExisting<Gen>();
+}
+
+template <typename State>
+void
+HandleCopyAssignEmpty()
{
- auto cache = Cache::create(0);
+ auto cache = Cache<State>::create(0);
- const auto key1 = make_key("", "1", 0);
- const auto key2 = make_key("", "2", 0);
+ const auto key1 = make_key<State>("", "1", 0);
+ const auto key2 = make_key<State>("", "2", 0);
- Handle j1;
+ Handle<State> j1;
{
auto h1 = cache->get(key1, std::nullopt);
j1 = h1; // assign over empty handle
EXPECT_EQ(key1, j1->key.first); // j1 stays pinned
}
-TEST(BucketSyncCache, HandleCopyAssignExisting)
+TEST(BucketShardSyncCache, HandleCopyAssignEmpty)
+{
+ HandleCopyAssignEmpty<Shard>();
+}
+
+TEST(BucketGenSyncCache, HandleCopyAssignEmpty)
+{
+ HandleCopyAssignEmpty<Gen>();
+}
+
+template <typename State>
+void
+HandleCopyAssignExisting()
{
- const auto key1 = make_key("", "1", 0);
- const auto key2 = make_key("", "2", 0);
+ const auto key1 = make_key<State>("", "1", 0);
+ const auto key2 = make_key<State>("", "2", 0);
- Handle h1;
+ Handle<State> h1;
{
- auto cache1 = Cache::create(0);
+ auto cache1 = Cache<State>::create(0);
h1 = cache1->get(key1, std::nullopt);
- } // j1 has the last ref to cache1
+ } // h1 has the last ref to cache1
{
- auto cache2 = Cache::create(0);
+ auto cache2 = Cache<State>::create(0);
auto h2 = cache2->get(key2, std::nullopt);
h1 = h2; // assign over existing handle
EXPECT_EQ(&*h1, &*h2);
}
EXPECT_EQ(key2, h1->key.first);
}
+
+TEST(BucketShardSyncCache, HandleCopyAssignExisting)
+{
+ HandleCopyAssignExisting<Shard>();
+}
+
+TEST(BucketGenSyncCache, HandleCopyAssignExisting)
+{
+ HandleCopyAssignExisting<Gen>();
+}