Skip to content

Commit 33e230c

Browse files
authored
impl(spanner): add support for Multiplexed Session maintenance (#15226)
1 parent a2dc298 commit 33e230c

File tree

7 files changed

+373
-513
lines changed

7 files changed

+373
-513
lines changed

google/cloud/spanner/internal/connection_impl_test.cc

Lines changed: 1 addition & 399 deletions
Large diffs are not rendered by default.

google/cloud/spanner/internal/session.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2121

2222
SessionHolder MakeDissociatedSessionHolder(std::string session_name) {
2323
return SessionHolder(
24-
new Session(std::move(session_name), /*channel=*/nullptr),
24+
new Session(std::move(session_name), Session::Mode::kDisassociated),
25+
std::default_delete<Session>());
26+
}
27+
28+
SessionHolder MakeMultiplexedSessionHolder(
29+
std::string session_name, std::shared_ptr<Session::Clock> clock) {
30+
return SessionHolder(
31+
new Session(std::move(session_name), Session::Mode::kMultiplexed,
32+
std::move(clock)),
2533
std::default_delete<Session>());
2634
}
2735

google/cloud/spanner/internal/session.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,23 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
3939
*/
4040
class Session {
4141
public:
42+
enum class Mode { kPooled, kDisassociated, kMultiplexed };
43+
4244
using Clock = ::google::cloud::internal::SteadyClock;
4345
Session(std::string session_name, std::shared_ptr<Channel> channel,
4446
std::shared_ptr<Clock> clock = std::make_shared<Clock>())
4547
: session_name_(std::move(session_name)),
4648
channel_(std::move(channel)),
4749
is_bad_(false),
4850
clock_(std::move(clock)),
49-
last_use_time_(clock_->Now()) {}
51+
last_use_time_(clock_->Now()),
52+
creation_time_(clock_->Now()) {}
53+
54+
explicit Session(std::string session_name, Mode mode,
55+
std::shared_ptr<Clock> clock = std::make_shared<Clock>())
56+
: Session(std::move(session_name), nullptr, std::move(clock)) {
57+
mode_ = mode;
58+
}
5059

5160
// Not copyable or moveable.
5261
Session(Session const&) = delete;
@@ -60,6 +69,9 @@ class Session {
6069
void set_bad() { is_bad_.store(true, std::memory_order_relaxed); }
6170
bool is_bad() const { return is_bad_.load(std::memory_order_relaxed); }
6271

72+
bool is_disassociated() const { return mode_ == Mode::kDisassociated; }
73+
bool is_multiplexed() const { return mode_ == Mode::kMultiplexed; }
74+
6375
private:
6476
// Give `SessionPool` access to the private methods below.
6577
friend class SessionPool;
@@ -71,11 +83,15 @@ class Session {
7183
Clock::time_point last_use_time() const { return last_use_time_; }
7284
void update_last_use_time() { last_use_time_ = clock_->Now(); }
7385

86+
Clock::time_point creation_time() const { return creation_time_; }
87+
7488
std::string const session_name_;
7589
std::shared_ptr<Channel> const channel_;
7690
std::atomic<bool> is_bad_;
7791
std::shared_ptr<Clock> clock_;
7892
Clock::time_point last_use_time_;
93+
Clock::time_point creation_time_;
94+
Mode mode_ = Mode::kPooled;
7995
};
8096

8197
/**
@@ -94,6 +110,14 @@ using SessionHolder = std::shared_ptr<Session>;
94110
*/
95111
SessionHolder MakeDissociatedSessionHolder(std::string session_name);
96112

113+
/**
114+
* Returns a `SessionHolder` for a new `Session` that is not associated with
115+
* any pool; it just deletes the `Session`. This is for use with Multiplexed
116+
* Sessions that do not require a pool.
117+
*/
118+
SessionHolder MakeMultiplexedSessionHolder(
119+
std::string session_name, std::shared_ptr<Session::Clock> clock);
120+
97121
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
98122
} // namespace spanner_internal
99123
} // namespace cloud

google/cloud/spanner/internal/session_pool.cc

Lines changed: 121 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,28 @@ SessionPool::SessionPool(spanner::Database db,
6363
opts_.get<spanner::SessionPoolMaxSessionsPerChannelOption>() *
6464
static_cast<int>(stubs.size())),
6565
random_generator_(std::random_device()()),
66+
multiplexed_session_replacement_interval_(
67+
opts_.has<
68+
spanner_internal::MultiplexedSessionReplacementIntervalOption>()
69+
? opts_.get<spanner_internal::
70+
MultiplexedSessionReplacementIntervalOption>()
71+
: std::chrono::hours(24 * 7)),
72+
multiplexed_session_background_interval_(
73+
opts_.has<spanner_internal::
74+
MultiplexedSessionBackgroundWorkIntervalOption>()
75+
? opts_.get<spanner_internal::
76+
MultiplexedSessionBackgroundWorkIntervalOption>()
77+
: std::chrono::minutes(10)),
6678
channels_(stubs.size()) {
6779
if (stubs.empty()) {
6880
google::cloud::internal::ThrowInvalidArgument(
6981
"SessionPool requires a non-empty set of stubs");
7082
}
7183

84+
multiplexed_session_ = internal::NotFoundError(
85+
"no multiplexed session is currently available; try again later",
86+
GCP_ERROR_INFO());
87+
7288
for (auto i = 0U; i < stubs.size(); ++i) {
7389
channels_[i] = std::make_shared<Channel>(std::move(stubs[i]));
7490
}
@@ -78,13 +94,19 @@ SessionPool::SessionPool(spanner::Database db,
7894

7995
void SessionPool::Initialize() {
8096
internal::OptionsSpan span(opts_);
81-
CreateMultiplexedSession();
82-
auto const min_sessions = opts_.get<spanner::SessionPoolMinSessionsOption>();
83-
if (min_sessions > 0) {
97+
if (opts_.has<spanner_experimental::EnableMultiplexedSessionOption>()) {
8498
std::unique_lock<std::mutex> lk(mu_);
85-
Grow(lk, min_sessions, WaitForSessionAllocation::kWait);
99+
CreateMultiplexedSession(lk);
100+
ScheduleMultiplexedBackgroundWork(multiplexed_session_background_interval_);
101+
} else {
102+
auto const min_sessions =
103+
opts_.get<spanner::SessionPoolMinSessionsOption>();
104+
if (min_sessions > 0) {
105+
std::unique_lock<std::mutex> lk(mu_);
106+
Grow(lk, min_sessions, WaitForSessionAllocation::kWait);
107+
}
108+
ScheduleBackgroundWork(std::chrono::seconds(5));
86109
}
87-
ScheduleBackgroundWork(std::chrono::seconds(5));
88110
}
89111

90112
SessionPool::~SessionPool() {
@@ -98,21 +120,60 @@ SessionPool::~SessionPool() {
98120
// they must not have successfully finished a call to `lock()` on the
99121
// `weak_ptr` to `this` they hold. Any in-progress or subsequent `lock()`
100122
// will now return `nullptr`, in which case no work is done.
101-
current_timer_.cancel();
123+
if (opts_.has<spanner_experimental::EnableMultiplexedSessionOption>()) {
124+
current_multiplexed_timer_.cancel();
125+
} else {
126+
current_timer_.cancel();
127+
}
102128

103129
// Send fire-and-forget `AsyncDeleteSession()` calls for all sessions.
104-
if (HasValidMultiplexedSession(std::unique_lock<std::mutex>(mu_))) {
105-
AsyncDeleteSession(cq_, GetStub(*multiplexed_session_),
106-
multiplexed_session_->session_name())
107-
.then([](auto result) { auto status = result.get(); });
108-
}
130+
// Multiplexed Sessions do not require an explicit Delete call.
109131
for (auto const& session : sessions_) {
110132
if (session->is_bad()) continue;
111133
AsyncDeleteSession(cq_, GetStub(*session), session->session_name())
112134
.then([](auto result) { auto status = result.get(); });
113135
}
114136
}
115137

138+
void SessionPool::ScheduleMultiplexedBackgroundWork(
139+
std::chrono::seconds relative_time) {
140+
std::weak_ptr<SessionPool> pool = shared_from_this();
141+
current_multiplexed_timer_ =
142+
cq_.MakeRelativeTimer(relative_time)
143+
.then([pool](future<StatusOr<std::chrono::system_clock::time_point>>
144+
result) {
145+
if (result.get().ok()) {
146+
if (auto shared_pool = pool.lock()) {
147+
shared_pool->DoMultiplexedBackgroundWork();
148+
}
149+
}
150+
});
151+
}
152+
153+
void SessionPool::ReplaceMultiplexedSession() {
154+
auto now = clock_->Now();
155+
auto refresh_limit = now - multiplexed_session_replacement_interval_;
156+
std::unique_lock<std::mutex> lk(mu_);
157+
if (create_calls_in_progress_ == 0 &&
158+
(*multiplexed_session_)->creation_time() <= refresh_limit) {
159+
++create_calls_in_progress_;
160+
auto stub = GetStub(std::move(lk));
161+
std::weak_ptr<SessionPool> pool = shared_from_this();
162+
CreateMultiplexedSessionAsync(std::move(stub))
163+
.then([pool](future<StatusOr<google::spanner::v1::Session>> response) {
164+
if (auto shared_pool = pool.lock()) {
165+
shared_pool->HandleMultiplexedCreateSessionDone(
166+
std::move(response).get());
167+
}
168+
});
169+
}
170+
}
171+
172+
void SessionPool::DoMultiplexedBackgroundWork() {
173+
ReplaceMultiplexedSession();
174+
ScheduleMultiplexedBackgroundWork(multiplexed_session_background_interval_);
175+
}
176+
116177
void SessionPool::ScheduleBackgroundWork(std::chrono::seconds relative_time) {
117178
std::weak_ptr<SessionPool> pool = shared_from_this();
118179
current_timer_ =
@@ -136,7 +197,6 @@ void SessionPool::DoBackgroundWork() {
136197
// Ensure the pool size conforms to what was specified in the `SessionOptions`,
137198
// creating or deleting sessions as necessary.
138199
void SessionPool::MaintainPoolSize() {
139-
CreateMultiplexedSession();
140200
auto const min_sessions = opts_.get<spanner::SessionPoolMinSessionsOption>();
141201
std::unique_lock<std::mutex> lk(mu_);
142202
if (create_calls_in_progress_ == 0 && total_sessions_ < min_sessions) {
@@ -205,22 +265,30 @@ void SessionPool::Erase(std::string const& session_name) {
205265
}
206266
}
207267

208-
Status SessionPool::CreateMultiplexedSession() {
268+
Status SessionPool::HandleMultiplexedCreateSessionDone(
269+
StatusOr<google::spanner::v1::Session> response) {
209270
std::unique_lock<std::mutex> lk(mu_);
210-
if (!HasValidMultiplexedSession(lk)) {
271+
--create_calls_in_progress_;
272+
if (!response.ok()) {
273+
multiplexed_session_ = response.status();
274+
return response.status();
275+
}
276+
277+
multiplexed_session_ = MakeMultiplexedSessionHolder(response->name(), clock_);
278+
return {};
279+
}
280+
281+
Status SessionPool::CreateMultiplexedSession(std::unique_lock<std::mutex>& lk) {
282+
if (create_calls_in_progress_ == 0) {
283+
create_calls_in_progress_++;
211284
auto stub = GetStub(std::move(lk));
212-
auto name = CreateMultiplexedSession(std::move(stub));
213-
if (!name) return name.status();
214-
auto session = std::make_shared<Session>(*std::move(name),
215-
/*channel=*/nullptr, clock_);
216-
std::unique_lock<std::mutex> lk(mu_);
217-
multiplexed_session_ = std::move(session);
285+
return CreateMultiplexedSessionSync(std::move(stub));
218286
}
219287
return Status{};
220288
}
221289

222-
StatusOr<std::string> SessionPool::CreateMultiplexedSession(
223-
std::shared_ptr<SpannerStub> stub) const {
290+
Status SessionPool::CreateMultiplexedSessionSync(
291+
std::shared_ptr<SpannerStub> stub) {
224292
google::spanner::v1::CreateSessionRequest request;
225293
request.set_database(db_.FullName());
226294
auto* session = request.mutable_session();
@@ -241,13 +309,33 @@ StatusOr<std::string> SessionPool::CreateMultiplexedSession(
241309
return stub->CreateSession(context, options, request);
242310
},
243311
opts_, request, __func__);
244-
if (!response) return std::move(response).status();
245-
return response->name();
312+
return HandleMultiplexedCreateSessionDone(std::move(response));
246313
}
247314

248-
bool SessionPool::HasValidMultiplexedSession(
249-
std::unique_lock<std::mutex> const&) const {
250-
return multiplexed_session_ && !multiplexed_session_->is_bad();
315+
future<StatusOr<google::spanner::v1::Session>>
316+
SessionPool::CreateMultiplexedSessionAsync(std::shared_ptr<SpannerStub> stub) {
317+
google::spanner::v1::CreateSessionRequest request;
318+
request.set_database(db_.FullName());
319+
auto* session = request.mutable_session();
320+
auto const& labels = opts_.get<spanner::SessionPoolLabelsOption>();
321+
if (!labels.empty()) {
322+
session->mutable_labels()->insert(labels.begin(), labels.end());
323+
}
324+
auto const& role = opts_.get<spanner::SessionCreatorRoleOption>();
325+
if (!role.empty()) session->set_creator_role(role);
326+
session->set_multiplexed(true);
327+
328+
return google::cloud::internal::AsyncRetryLoop(
329+
retry_policy_prototype_->clone(), backoff_policy_prototype_->clone(),
330+
google::cloud::Idempotency::kIdempotent, cq_,
331+
[&stub](CompletionQueue cq, std::shared_ptr<grpc::ClientContext> context,
332+
internal::ImmutableOptions options,
333+
google::spanner::v1::CreateSessionRequest const& request) {
334+
RouteToLeader(*context); // always for CreateSession()
335+
return stub->AsyncCreateSession(cq, std::move(context),
336+
std::move(options), request);
337+
},
338+
internal::SaveCurrentOptions(), std::move(request), __func__);
251339
}
252340

253341
/*
@@ -354,10 +442,12 @@ StatusOr<SessionHolder> SessionPool::Allocate(bool dissociate_from_pool) {
354442
}
355443

356444
StatusOr<SessionHolder> SessionPool::Multiplexed() {
357-
std::unique_lock<std::mutex> lk(mu_);
358-
// If we don't have a multiplexed session (yet), use a regular one.
359-
if (!HasValidMultiplexedSession(lk)) return Allocate(std::move(lk), false);
360-
return multiplexed_session_;
445+
if (opts_.has<spanner_experimental::EnableMultiplexedSessionOption>()) {
446+
std::unique_lock<std::mutex> lk(mu_);
447+
return multiplexed_session_;
448+
}
449+
return internal::FailedPreconditionError(
450+
"multiplexed sessions are not enabled", GCP_ERROR_INFO());
361451
}
362452

363453
std::shared_ptr<SpannerStub> SessionPool::GetStub(Session const& session) {

google/cloud/spanner/internal/session_pool.h

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ struct SessionPoolClockOption {
5151
using Type = std::shared_ptr<Session::Clock>;
5252
};
5353

54+
// Frequency of when the existing multiplexed sessions is replaced with a new
55+
// multiplexed sessions.
56+
struct MultiplexedSessionReplacementIntervalOption {
57+
using Type = std::chrono::hours;
58+
};
59+
60+
// Frequency of when background work is performed.
61+
struct MultiplexedSessionBackgroundWorkIntervalOption {
62+
using Type = std::chrono::minutes;
63+
};
64+
5465
class SessionPool;
5566

5667
/**
@@ -167,10 +178,13 @@ class SessionPool : public std::enable_shared_from_this<SessionPool> {
167178
--num_waiting_for_session_;
168179
}
169180

170-
Status CreateMultiplexedSession(); // LOCKS_EXCLUDED(mu_)
171-
StatusOr<std::string> CreateMultiplexedSession(
172-
std::shared_ptr<SpannerStub>) const;
173-
bool HasValidMultiplexedSession(std::unique_lock<std::mutex> const&) const;
181+
Status CreateMultiplexedSession(
182+
std::unique_lock<std::mutex>& lk); // EXCLUSIVE_LOCKS_REQUIRED(mu_)
183+
Status CreateMultiplexedSessionSync(std::shared_ptr<SpannerStub>);
184+
future<StatusOr<google::spanner::v1::Session>> CreateMultiplexedSessionAsync(
185+
std::shared_ptr<SpannerStub>);
186+
Status HandleMultiplexedCreateSessionDone(
187+
StatusOr<google::spanner::v1::Session> response);
174188

175189
Status Grow(std::unique_lock<std::mutex>& lk, int sessions_to_create,
176190
WaitForSessionAllocation wait); // EXCLUSIVE_LOCKS_REQUIRED(mu_)
@@ -208,6 +222,10 @@ class SessionPool : public std::enable_shared_from_this<SessionPool> {
208222
std::shared_ptr<Channel> const& channel,
209223
StatusOr<google::spanner::v1::BatchCreateSessionsResponse> response);
210224

225+
void ScheduleMultiplexedBackgroundWork(std::chrono::seconds relative_time);
226+
void DoMultiplexedBackgroundWork();
227+
void ReplaceMultiplexedSession();
228+
211229
void ScheduleBackgroundWork(std::chrono::seconds relative_time);
212230
void DoBackgroundWork();
213231
void MaintainPoolSize();
@@ -228,10 +246,12 @@ class SessionPool : public std::enable_shared_from_this<SessionPool> {
228246
std::shared_ptr<Session::Clock> clock_;
229247
int const max_pool_size_;
230248
std::mt19937 random_generator_;
249+
std::chrono::hours multiplexed_session_replacement_interval_;
250+
std::chrono::minutes multiplexed_session_background_interval_;
231251

232252
mutable std::mutex mu_;
233253
std::condition_variable cond_;
234-
SessionHolder multiplexed_session_; // GUARDED_BY(mu_)
254+
StatusOr<SessionHolder> multiplexed_session_; // GUARDED_BY(mu_)
235255
std::vector<std::unique_ptr<Session>> sessions_; // GUARDED_BY(mu_)
236256
// total_sessions_ tracks the number of sessions in the pool, a.k.a.
237257
// sessions_.size(), plus the sessions that have been allocated.
@@ -243,6 +263,7 @@ class SessionPool : public std::enable_shared_from_this<SessionPool> {
243263
Session::Clock::time_point last_use_time_lower_bound_ =
244264
clock_->Now(); // GUARDED_BY(mu_)
245265

266+
future<void> current_multiplexed_timer_;
246267
future<void> current_timer_;
247268

248269
// `channels_` is guaranteed to be non-empty and will not be resized after

0 commit comments

Comments
 (0)