| 178 | } |
| 179 | |
| 180 | void QueryMetricLog::finishQuery(const String & query_id, TimePoint finish_time, QueryStatusInfoPtr query_info) |
| 181 | { |
| 182 | /// Test-only failpoint placed before `queries_mutex` so a concurrent periodic |
| 183 | /// `collectMetric` can overtake `finishQuery` and advance `info.last_collect_time` |
| 184 | /// past `finish_time`. The gate on `query_info` and the dedicated query-id prefix |
| 185 | /// ensures unrelated traffic is unaffected even when the failpoint is enabled. |
| 186 | /// The `query_info` gate filters `nullptr` finish paths. If a non-null phantom |
| 187 | /// finish-call still reaches this failpoint, `it == queries.end()` after resume |
| 188 | /// makes this function return immediately. |
| 189 | if (query_info && FailPointInjection::hasAnyFailPointBeenRegistered() |
| 190 | && query_id.starts_with(query_metric_log_final_row_failpoint_query_id_prefix)) |
| 191 | FailPointInjection::pauseFailPoint(FailPoints::query_metric_log_pause_before_finish); |
| 192 | |
| 193 | UniqueLock global_lock(queries_mutex); |
| 194 | auto it = queries.find(query_id); |
| 195 | |
| 196 | /// finishQuery may be called from logExceptionBeforeStart when the query has not even started |
| 197 | /// yet, so its corresponding startQuery is never called. |
| 198 | if (it == queries.end()) |
| 199 | return; |
| 200 | |
| 201 | auto & query_status = it->second; |
| 202 | |
| 203 | /// Get a refcounted reference to the mutex to ensure it's not destroyed until the query is |
| 204 | /// removed from queries. Otherwise, query_lock would attempt to unlock a non-existing mutex. |
| 205 | auto mutex = query_status.mutex; |
| 206 | UniqueLock query_lock(query_status.getMutex()); |
| 207 | |
| 208 | /// finishQuery may be called twice for the same query_id if a new query with the same query_id |
| 209 | /// is attempted to run in case replace_running_query=0 (the default). Make sure we only execute |
| 210 | /// the finishQuery once. |
| 211 | auto thread_id = CurrentThread::get().thread_id; |
| 212 | if (thread_id != query_status.thread_id || query_status.finished) |
| 213 | { |
| 214 | LOG_TEST(logger, "Query {} finished from a different thread_id than the one it started it: " |
| 215 | "original was {}, this one is {}. Ignoring this finishQuery", |
| 216 | query_id, query_status.thread_id, thread_id); |
| 217 | return; |
| 218 | } |
| 219 | |
| 220 | setQueryFinished(query_status); |
| 221 | global_lock.unlock(); |
| 222 | |
| 223 | if (query_info) |
| 224 | { |
| 225 | auto elem = query_status.createLogMetricElement(query_id, *query_info, finish_time, /* is_final = */ true); |
| 226 | if (elem) |
| 227 | add(std::move(elem.value())); |
| 228 | } |
| 229 | |
| 230 | /// The task has an `exec_mutex` locked while being executed. This same mutex is locked when |
| 231 | /// deactivating the task, which happens automatically on its destructor. Thus, we cannot |
| 232 | /// deactivate/destroy the task while it's running. Now, the task locks `queries_mutex` to |
| 233 | /// prevent concurrent edition of the `queries`. In short, the mutex order is: `exec_mutex` -> |
| 234 | /// `queries_mutex` -> `query_status.mutex`. So, to prevent a deadlock we need to make sure that |
| 235 | /// we always lock them in that order. |
| 236 | { |
| 237 | /// Take ownership of the task so that we can destroy it in this scope after unlocking `queries_mutex`. |
no test coverage detected