Skip to content
This repository was archived by the owner on Dec 8, 2021. It is now read-only.

Commit 62bb823

Browse files
authored
feat: provide a mockable Clock class (#1452)
* feat: provide a mockable `Clock` class Like #1449 but different; this should make it easier to add operations in the future (like sleep/wait), at the cost of having to pass it around as a `shared_ptr` even for the `RealClock`. (In #1449 we only needed the `shared_ptr` in the fake case.) fixes #1428 * address review comments * address review comments * address review comments * fix include guards after #1456 * API update, again
1 parent b3720d5 commit 62bb823

File tree

12 files changed

+251
-23
lines changed

12 files changed

+251
-23
lines changed
1.75 KB
Binary file not shown.

google/cloud/spanner/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ add_library(
131131
internal/api_client_header.h
132132
internal/build_info.h
133133
internal/channel.h
134+
internal/clock.h
134135
internal/compiler_info.cc
135136
internal/compiler_info.h
136137
internal/connection_impl.cc
@@ -278,6 +279,7 @@ function (spanner_client_define_tests)
278279
testing/compiler_supports_regexp.h
279280
testing/database_environment.cc
280281
testing/database_environment.h
282+
testing/fake_clock.h
281283
testing/matchers.h
282284
testing/mock_database_admin_stub.h
283285
testing/mock_instance_admin_stub.h
@@ -328,6 +330,7 @@ function (spanner_client_define_tests)
328330
instance_test.cc
329331
internal/api_client_header_test.cc
330332
internal/build_info_test.cc
333+
internal/clock_test.cc
331334
internal/compiler_info_test.cc
332335
internal/connection_impl_test.cc
333336
internal/database_admin_logging_test.cc
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2020 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+
// http://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_SPANNER_INTERNAL_CLOCK_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_INTERNAL_CLOCK_H
17+
18+
#include "google/cloud/spanner/version.h"
19+
#include <chrono>
20+
21+
namespace google {
22+
namespace cloud {
23+
namespace spanner {
24+
inline namespace SPANNER_CLIENT_NS {
25+
namespace internal {
26+
27+
/**
28+
* A simple `Clock` class that can be overridden for testing.
29+
*
30+
* All implementations of this class are required to be thread-safe.
31+
*
32+
* The template type `TrivialClock` must meet the C++ named requirements for
33+
* `TrivialClock` (for example, clocks from `std::chrono`).
34+
*/
35+
template <typename TrivialClock>
36+
class Clock {
37+
public:
38+
using time_point = typename TrivialClock::time_point;
39+
using duration = typename TrivialClock::duration;
40+
41+
virtual ~Clock() = default;
42+
virtual time_point Now() const { return TrivialClock::now(); }
43+
};
44+
45+
/**
46+
* `SteadyClock` is a monotonic clock where time points cannot decrease as
47+
* physical time moves forward. It is not related to wall clock time.
48+
*/
49+
using SteadyClock =
50+
::google::cloud::spanner::internal::Clock<std::chrono::steady_clock>;
51+
52+
/**
53+
* `SystemClock` represents the system-wide real time wall clock.
54+
* It may not be monotonic.
55+
*/
56+
using SystemClock =
57+
::google::cloud::spanner::internal::Clock<std::chrono::system_clock>;
58+
59+
} // namespace internal
60+
} // namespace SPANNER_CLIENT_NS
61+
} // namespace spanner
62+
} // namespace cloud
63+
} // namespace google
64+
65+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_INTERNAL_CLOCK_H
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2020 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+
// http://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/spanner/internal/clock.h"
16+
#include "google/cloud/spanner/testing/fake_clock.h"
17+
#include <gmock/gmock.h>
18+
#include <memory>
19+
20+
namespace google {
21+
namespace cloud {
22+
namespace spanner {
23+
inline namespace SPANNER_CLIENT_NS {
24+
namespace internal {
25+
namespace {
26+
27+
using ::google::cloud::spanner_testing::FakeClock;
28+
29+
TEST(Clock, SteadyClock) {
30+
auto clock = std::make_shared<SteadyClock>();
31+
auto now = clock->Now();
32+
auto now2 = clock->Now();
33+
// `SteadyClock::Now()` can never decrease as physical time moves forward.
34+
EXPECT_LE(now, now2);
35+
}
36+
37+
TEST(Clock, SystemClock) {
38+
auto clock = std::make_shared<SystemClock>();
39+
// There is no guarantee that `SystemClock::Now()` can never decrease, so
40+
// we can't test that like we do for `SteadyClock`, so for now just make
41+
// sure `Now()` is callable.
42+
(void)clock->Now();
43+
}
44+
45+
TEST(Clock, FakeClock) {
46+
SteadyClock real_clock;
47+
FakeClock<SteadyClock> clock;
48+
SteadyClock::time_point time(real_clock.Now());
49+
clock.SetTime(time);
50+
EXPECT_EQ(clock.Now(), time);
51+
52+
time += std::chrono::minutes(3);
53+
clock.SetTime(time);
54+
EXPECT_EQ(clock.Now(), time);
55+
56+
SteadyClock::duration duration = std::chrono::hours(89);
57+
time += duration;
58+
clock.AdvanceTime(duration);
59+
EXPECT_EQ(clock.Now(), time);
60+
time += duration;
61+
clock.AdvanceTime(duration);
62+
EXPECT_EQ(clock.Now(), time);
63+
}
64+
65+
} // namespace
66+
} // namespace internal
67+
} // namespace SPANNER_CLIENT_NS
68+
} // namespace spanner
69+
} // namespace cloud
70+
} // namespace google

google/cloud/spanner/internal/session.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_INTERNAL_SESSION_H
1717

1818
#include "google/cloud/spanner/internal/channel.h"
19+
#include "google/cloud/spanner/internal/clock.h"
1920
#include "google/cloud/spanner/version.h"
2021
#include <atomic>
2122
#include <chrono>
@@ -36,11 +37,14 @@ namespace internal {
3637
*/
3738
class Session {
3839
public:
39-
Session(std::string session_name, std::shared_ptr<Channel> channel)
40+
using Clock = ::google::cloud::spanner::internal::SteadyClock;
41+
Session(std::string session_name, std::shared_ptr<Channel> channel,
42+
std::shared_ptr<Clock> clock = std::make_shared<Clock>())
4043
: session_name_(std::move(session_name)),
4144
channel_(std::move(channel)),
4245
is_bad_(false),
43-
last_use_time_(Clock::now()) {}
46+
clock_(std::move(clock)),
47+
last_use_time_(clock_->Now()) {}
4448

4549
// Not copyable or moveable.
4650
Session(Session const&) = delete;
@@ -55,19 +59,19 @@ class Session {
5559
bool is_bad() const { return is_bad_.load(std::memory_order_relaxed); }
5660

5761
private:
58-
// Give `SessionPool` access to the private types/methods below.
62+
// Give `SessionPool` access to the private methods below.
5963
friend class SessionPool;
60-
using Clock = std::chrono::steady_clock;
6164
std::shared_ptr<Channel> const& channel() const { return channel_; }
6265

6366
// The caller is responsible for ensuring these methods are used in a
6467
// thread-safe manner (i.e. using external locking).
6568
Clock::time_point last_use_time() const { return last_use_time_; }
66-
void update_last_use_time() { last_use_time_ = Clock::now(); }
69+
void update_last_use_time() { last_use_time_ = clock_->Now(); }
6770

6871
std::string const session_name_;
6972
std::shared_ptr<Channel> const channel_;
7073
std::atomic<bool> is_bad_;
74+
std::shared_ptr<Clock> clock_;
7175
Clock::time_point last_use_time_;
7276
};
7377

google/cloud/spanner/internal/session_pool.cc

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ std::shared_ptr<SessionPool> MakeSessionPool(
4040
Database db, std::vector<std::shared_ptr<SpannerStub>> stubs,
4141
SessionPoolOptions options, google::cloud::CompletionQueue cq,
4242
std::unique_ptr<RetryPolicy> retry_policy,
43-
std::unique_ptr<BackoffPolicy> backoff_policy) {
43+
std::unique_ptr<BackoffPolicy> backoff_policy,
44+
std::shared_ptr<Session::Clock> clock) {
4445
auto pool = std::make_shared<SessionPool>(
4546
std::move(db), std::move(stubs), std::move(options), std::move(cq),
46-
std::move(retry_policy), std::move(backoff_policy));
47+
std::move(retry_policy), std::move(backoff_policy), std::move(clock));
4748
pool->Initialize();
4849
return pool;
4950
}
@@ -53,13 +54,15 @@ SessionPool::SessionPool(Database db,
5354
SessionPoolOptions options,
5455
google::cloud::CompletionQueue cq,
5556
std::unique_ptr<RetryPolicy> retry_policy,
56-
std::unique_ptr<BackoffPolicy> backoff_policy)
57+
std::unique_ptr<BackoffPolicy> backoff_policy,
58+
std::shared_ptr<Session::Clock> clock)
5759
: db_(std::move(db)),
5860
options_(std::move(
5961
options.EnforceConstraints(static_cast<int>(stubs.size())))),
6062
cq_(std::move(cq)),
6163
retry_policy_prototype_(std::move(retry_policy)),
6264
backoff_policy_prototype_(std::move(backoff_policy)),
65+
clock_(std::move(clock)),
6366
max_pool_size_(options_.max_sessions_per_channel() *
6467
static_cast<int>(stubs.size())),
6568
random_generator_(std::random_device()()) {
@@ -135,15 +138,14 @@ void SessionPool::MaintainPoolSize() {
135138
void SessionPool::RefreshExpiringSessions() {
136139
std::vector<std::pair<std::shared_ptr<SpannerStub>, std::string>>
137140
sessions_to_refresh;
138-
Session::Clock::time_point now = Session::Clock::now();
139-
Session::Clock::time_point refresh_limit =
140-
now - options_.keep_alive_interval();
141+
auto now = clock_->Now();
142+
auto refresh_limit = now - options_.keep_alive_interval();
141143
{
142144
std::unique_lock<std::mutex> lk(mu_);
143145
if (last_use_time_lower_bound_ <= refresh_limit) {
144146
last_use_time_lower_bound_ = now;
145147
for (auto const& session : sessions_) {
146-
Session::Clock::time_point last_use_time = session->last_use_time();
148+
auto last_use_time = session->last_use_time();
147149
if (last_use_time <= refresh_limit) {
148150
sessions_to_refresh.emplace_back(session->channel()->stub,
149151
session->session_name());
@@ -474,7 +476,7 @@ Status SessionPool::HandleBatchCreateSessionsDone(
474476
sessions_.reserve(sessions_.size() + sessions_created);
475477
for (auto& session : *response->mutable_session()) {
476478
sessions_.push_back(google::cloud::internal::make_unique<Session>(
477-
std::move(*session.mutable_name()), channel));
479+
std::move(*session.mutable_name()), channel, clock_));
478480
}
479481
// Shuffle the pool so we distribute returned sessions across channels.
480482
std::shuffle(sessions_.begin(), sessions_.end(), random_generator_);

google/cloud/spanner/internal/session_pool.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ class SessionPool : public std::enable_shared_from_this<SessionPool> {
6969
SessionPool(Database db, std::vector<std::shared_ptr<SpannerStub>> stubs,
7070
SessionPoolOptions options, google::cloud::CompletionQueue cq,
7171
std::unique_ptr<RetryPolicy> retry_policy,
72-
std::unique_ptr<BackoffPolicy> backoff_policy);
72+
std::unique_ptr<BackoffPolicy> backoff_policy,
73+
std::shared_ptr<Session::Clock> clock);
7374

7475
~SessionPool();
7576

@@ -165,6 +166,7 @@ class SessionPool : public std::enable_shared_from_this<SessionPool> {
165166
google::cloud::CompletionQueue cq_;
166167
std::unique_ptr<RetryPolicy const> retry_policy_prototype_;
167168
std::unique_ptr<BackoffPolicy const> backoff_policy_prototype_;
169+
std::shared_ptr<Session::Clock> clock_;
168170
int const max_pool_size_;
169171
std::mt19937 random_generator_;
170172

@@ -177,7 +179,7 @@ class SessionPool : public std::enable_shared_from_this<SessionPool> {
177179

178180
// Lower bound on all `sessions_[i]->last_use_time()` values.
179181
Session::Clock::time_point last_use_time_lower_bound_ =
180-
Session::Clock::now(); // GUARDED_BY(mu_)
182+
clock_->Now(); // GUARDED_BY(mu_)
181183

182184
future<void> current_timer_;
183185

@@ -200,7 +202,8 @@ std::shared_ptr<SessionPool> MakeSessionPool(
200202
Database db, std::vector<std::shared_ptr<SpannerStub>> stubs,
201203
SessionPoolOptions options, google::cloud::CompletionQueue cq,
202204
std::unique_ptr<RetryPolicy> retry_policy,
203-
std::unique_ptr<BackoffPolicy> backoff_policy);
205+
std::unique_ptr<BackoffPolicy> backoff_policy,
206+
std::shared_ptr<Session::Clock> clock = std::make_shared<Session::Clock>());
204207

205208
} // namespace internal
206209
} // namespace SPANNER_CLIENT_NS

google/cloud/spanner/internal/session_pool_test.cc

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
// limitations under the License.
1414

1515
#include "google/cloud/spanner/internal/session_pool.h"
16+
#include "google/cloud/spanner/internal/clock.h"
1617
#include "google/cloud/spanner/internal/session.h"
18+
#include "google/cloud/spanner/testing/fake_clock.h"
1719
#include "google/cloud/spanner/testing/mock_spanner_stub.h"
1820
#include "google/cloud/internal/background_threads_impl.h"
1921
#include "google/cloud/internal/make_unique.h"
@@ -35,6 +37,7 @@ inline namespace SPANNER_CLIENT_NS {
3537
namespace internal {
3638
namespace {
3739

40+
using ::google::cloud::spanner_testing::FakeSteadyClock;
3841
using ::google::cloud::testing_util::MockAsyncResponseReader;
3942
using ::google::cloud::testing_util::MockCompletionQueue;
4043
using ::testing::_;
@@ -71,13 +74,15 @@ spanner_proto::BatchCreateSessionsResponse MakeSessionsResponse(
7174

7275
std::shared_ptr<SessionPool> MakeSessionPool(
7376
Database db, std::vector<std::shared_ptr<SpannerStub>> stubs,
74-
SessionPoolOptions options, CompletionQueue cq) {
77+
SessionPoolOptions options, CompletionQueue cq,
78+
std::shared_ptr<SteadyClock> clock = std::make_shared<SteadyClock>()) {
7579
return MakeSessionPool(
7680
std::move(db), std::move(stubs), std::move(options), std::move(cq),
7781
google::cloud::internal::make_unique<LimitedTimeRetryPolicy>(
7882
std::chrono::minutes(10)),
7983
google::cloud::internal::make_unique<ExponentialBackoffPolicy>(
80-
std::chrono::milliseconds(100), std::chrono::minutes(1), 2.0));
84+
std::chrono::milliseconds(100), std::chrono::minutes(1), 2.0),
85+
std::move(clock));
8186
}
8287

8388
TEST(SessionPool, Allocate) {
@@ -388,9 +393,6 @@ TEST(SessionPool, GetStubForStublessSession) {
388393
EXPECT_EQ(pool->GetStub(*session), mock);
389394
}
390395

391-
// TODO(#1428): This test has a real-time component (see sleep_for() below)
392-
// as SessionPool does not currently provide a mechanism to inject a clock
393-
// source to control whether sessions require refreshing.
394396
TEST(SessionPool, SessionRefresh) {
395397
auto mock = std::make_shared<StrictMock<spanner_testing::MockSpannerStub>>();
396398
EXPECT_CALL(*mock, BatchCreateSessions(_, _))
@@ -421,7 +423,9 @@ TEST(SessionPool, SessionRefresh) {
421423
SessionPoolOptions options;
422424
options.set_keep_alive_interval(std::chrono::seconds(1));
423425
auto impl = std::make_shared<MockCompletionQueue>();
424-
auto pool = MakeSessionPool(db, {mock}, options, CompletionQueue(impl));
426+
auto clock = std::make_shared<FakeSteadyClock>();
427+
auto pool =
428+
MakeSessionPool(db, {mock}, options, CompletionQueue(impl), clock);
425429

426430
// Allocate and release two session, "s1" and "s2". This will satisfy the
427431
// BatchCreateSessions() expectations.
@@ -435,7 +439,7 @@ TEST(SessionPool, SessionRefresh) {
435439
EXPECT_EQ("s2", (*s2)->session_name());
436440
}
437441
// Wait for "s2" to need refreshing before releasing "s1".
438-
std::this_thread::sleep_for(options.keep_alive_interval() * 2);
442+
clock->AdvanceTime(options.keep_alive_interval() * 2);
439443
}
440444

441445
// Simulate completion of pending operations, which will result in

google/cloud/spanner/spanner_client.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ spanner_client_hdrs = [
3838
"internal/api_client_header.h",
3939
"internal/build_info.h",
4040
"internal/channel.h",
41+
"internal/clock.h",
4142
"internal/compiler_info.h",
4243
"internal/connection_impl.h",
4344
"internal/database_admin_logging.h",

google/cloud/spanner/spanner_client_testing.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ spanner_client_testing_hdrs = [
2020
"testing/cleanup_stale_instances.h",
2121
"testing/compiler_supports_regexp.h",
2222
"testing/database_environment.h",
23+
"testing/fake_clock.h",
2324
"testing/matchers.h",
2425
"testing/mock_database_admin_stub.h",
2526
"testing/mock_instance_admin_stub.h",

0 commit comments

Comments
 (0)