|
14 | 14 |
|
15 | 15 | #include "google/cloud/bigtable/internal/query_plan.h" |
16 | 16 | #include "google/cloud/completion_queue.h" |
| 17 | +#include "google/cloud/internal/time_utils.h" |
17 | 18 | #include <google/bigtable/v2/data.pb.h> |
18 | 19 |
|
19 | 20 | namespace google { |
20 | 21 | namespace cloud { |
21 | 22 | namespace bigtable_internal { |
22 | 23 | GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN |
| 24 | +namespace { |
| 25 | +auto constexpr kRefreshDeadlineOffset = 120; |
| 26 | +} // namespace |
23 | 27 |
|
24 | 28 | std::shared_ptr<QueryPlan> QueryPlan::Create( |
25 | 29 | CompletionQueue cq, google::bigtable::v2::PrepareQueryResponse response, |
26 | | - RefreshFn fn) { |
27 | | - auto plan = std::shared_ptr<QueryPlan>( |
28 | | - new QueryPlan(std::move(cq), std::move(response), std::move(fn))); |
| 30 | + RefreshFn fn, std::shared_ptr<Clock> clock) { |
| 31 | + auto plan = std::shared_ptr<QueryPlan>(new QueryPlan( |
| 32 | + std::move(cq), std::move(clock), std::move(fn), std::move(response))); |
29 | 33 | plan->Initialize(); |
30 | 34 | return plan; |
31 | 35 | } |
32 | 36 |
|
33 | | -bool QueryPlan::IsExpired() { return false; } |
| 37 | +void QueryPlan::Initialize() { |
| 38 | + std::unique_lock<std::mutex> lock(mu_); |
| 39 | + ScheduleRefresh(lock); |
| 40 | +} |
| 41 | + |
| 42 | +// ScheduleRefresh should only be called after updating response_. |
| 43 | +void QueryPlan::ScheduleRefresh(std::unique_lock<std::mutex> const&) { |
| 44 | + if (!response_.ok()) return; |
| 45 | + // We want to start the refresh process before the query plan expires. |
| 46 | + auto refresh_deadline = |
| 47 | + internal::ToChronoTimePoint(response_->valid_until()) - |
| 48 | + std::chrono::seconds(kRefreshDeadlineOffset); |
| 49 | + std::weak_ptr<QueryPlan> plan = shared_from_this(); |
| 50 | + refresh_timer_ = |
| 51 | + cq_.MakeDeadlineTimer(refresh_deadline) |
| 52 | + .then([plan](future<StatusOr<std::chrono::system_clock::time_point>> |
| 53 | + result) { |
| 54 | + if (result.get().ok()) { |
| 55 | + if (auto p = plan.lock()) { |
| 56 | + p->ExpiredRefresh(); |
| 57 | + } |
| 58 | + } |
| 59 | + }); |
| 60 | +} |
| 61 | + |
| 62 | +bool QueryPlan::IsRefreshing(std::unique_lock<std::mutex> const&) const { |
| 63 | + return state_ == RefreshState::kBegin || state_ == RefreshState::kPending; |
| 64 | +} |
| 65 | + |
| 66 | +void QueryPlan::ExpiredRefresh() { |
| 67 | + { |
| 68 | + std::unique_lock<std::mutex> lock(mu_); |
| 69 | + if (!(IsRefreshing(lock))) { |
| 70 | + if (response_.ok()) old_query_plan_id_ = response_->prepared_query(); |
| 71 | + state_ = RefreshState::kBegin; |
| 72 | + } |
| 73 | + } |
| 74 | + RefreshQueryPlan(RefreshMode::kExpired); |
| 75 | +} |
| 76 | + |
| 77 | +void QueryPlan::Invalidate(Status status, |
| 78 | + std::string const& invalid_query_plan_id) { |
| 79 | + { |
| 80 | + std::unique_lock<std::mutex> lock(mu_); |
| 81 | + // We want to avoid a late arrival causing a refresh of an already refreshed |
| 82 | + // query plan, so we track what the previous plan id was. |
| 83 | + if (!IsRefreshing(lock) && old_query_plan_id_ != invalid_query_plan_id) { |
| 84 | + old_query_plan_id_ = invalid_query_plan_id; |
| 85 | + state_ = RefreshState::kBegin; |
| 86 | + } |
| 87 | + } |
| 88 | + RefreshQueryPlan(RefreshMode::kInvalidated, std::move(status)); |
| 89 | +} |
34 | 90 |
|
35 | | -StatusOr<std::string> QueryPlan::prepared_query() const { |
36 | | - std::lock_guard<std::mutex> lock(mu_); |
37 | | - if (IsExpired()) { |
38 | | - return Status(StatusCode::kUnavailable, "Query plan has expired"); |
| 91 | +void QueryPlan::RefreshQueryPlan(RefreshMode mode, Status error) { |
| 92 | + { |
| 93 | + std::unique_lock<std::mutex> lock_1(mu_); |
| 94 | +#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_QUERY_PLAN_REFRESH_ASSERT |
| 95 | + assert(waiting_threads_ >= 0); |
| 96 | +#endif |
| 97 | + ++waiting_threads_; |
| 98 | + cond_.wait(lock_1, [this] { return state_ != RefreshState::kPending; }); |
| 99 | + --waiting_threads_; |
| 100 | +#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_QUERY_PLAN_REFRESH_ASSERT |
| 101 | + assert(waiting_threads_ >= 0); |
| 102 | +#endif |
| 103 | + if (state_ == RefreshState::kDone) return; |
| 104 | + if (mode == RefreshMode::kInvalidated) response_ = std::move(error); |
| 105 | + state_ = RefreshState::kPending; |
| 106 | + } |
| 107 | + auto response = refresh_fn_().get(); |
| 108 | + bool done = false; |
| 109 | + { |
| 110 | + std::unique_lock<std::mutex> lock_2(mu_); |
| 111 | + response_ = std::move(response); |
| 112 | + if (response_.ok()) { |
| 113 | + state_ = RefreshState::kDone; |
| 114 | + done = true; |
| 115 | + // If we have to refresh an invalidated query plan, cancel any existing |
| 116 | + // timer before starting a new one. |
| 117 | + refresh_timer_.cancel(); |
| 118 | + ScheduleRefresh(lock_2); |
| 119 | + } else { |
| 120 | + // If there are no waiting threads that could call the refresh_fn, then |
| 121 | + // we need to accept that the refresh is in a failed state and wait for |
| 122 | + // some new event that would start this refresh process anew. |
| 123 | + // |
| 124 | + // If there are waiting threads, then we want to try again to get a |
| 125 | + // refreshed query plan, but we want to avoid a stampede of refresh RPCs |
| 126 | + // so we only notify one of the waiting threads. |
| 127 | +#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_QUERY_PLAN_REFRESH_ASSERT |
| 128 | + assert(waiting_threads_ >= 0); |
| 129 | +#endif |
| 130 | + if (waiting_threads_ == 0) { |
| 131 | + state_ = RefreshState::kDone; |
| 132 | + done = true; |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + if (done) { |
| 137 | + cond_.notify_all(); |
| 138 | + } else { |
| 139 | + cond_.notify_one(); |
39 | 140 | } |
40 | | - return response_.prepared_query(); |
41 | 141 | } |
42 | 142 |
|
43 | | -StatusOr<google::bigtable::v2::ResultSetMetadata> QueryPlan::metadata() const { |
44 | | - std::lock_guard<std::mutex> lock(mu_); |
45 | | - if (IsExpired()) { |
46 | | - return Status(StatusCode::kUnavailable, "Query plan has expired"); |
| 143 | +StatusOr<QueryPlan::ResponseData> QueryPlan::response_data() { |
| 144 | + std::unique_lock<std::mutex> lock(mu_); |
| 145 | + if (IsRefreshing(lock)) { |
| 146 | + if (response_.ok()) { |
| 147 | + return QueryPlan::ResponseData{response_->prepared_query(), |
| 148 | + response_->metadata()}; |
| 149 | + } |
| 150 | + lock.unlock(); |
| 151 | + RefreshQueryPlan(RefreshMode::kAlreadyRefreshing); |
| 152 | + lock.lock(); |
| 153 | + } |
| 154 | + |
| 155 | + if (state_ == RefreshState::kDone && !response_.ok()) { |
| 156 | + return response_.status(); |
47 | 157 | } |
48 | | - return response_.metadata(); |
| 158 | + |
| 159 | + return QueryPlan::ResponseData{response_->prepared_query(), |
| 160 | + response_->metadata()}; |
| 161 | +} |
| 162 | + |
| 163 | +StatusOr<std::string> QueryPlan::prepared_query() { |
| 164 | + auto data = response_data(); |
| 165 | + if (!data.ok()) return std::move(data.status()); |
| 166 | + return std::move(data->prepared_query); |
| 167 | +} |
| 168 | + |
| 169 | +StatusOr<google::bigtable::v2::ResultSetMetadata> QueryPlan::metadata() { |
| 170 | + auto data = response_data(); |
| 171 | + if (!data.ok()) return std::move(data.status()); |
| 172 | + return std::move(data->metadata); |
49 | 173 | } |
50 | 174 |
|
51 | 175 | GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END |
|
0 commit comments