| 189 | } |
| 190 | |
| 191 | DDLTaskPtr DatabaseReplicatedDDLWorker::initAndCheckTask(const String & entry_name, String & out_reason, const ZooKeeperPtr & zookeeper) |
| 192 | { |
| 193 | { |
| 194 | std::lock_guard lock{mutex}; |
| 195 | if (current_task < entry_name) |
| 196 | { |
| 197 | current_task = entry_name; |
| 198 | wait_current_task_change.notify_all(); |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | UInt32 our_log_ptr = parse<UInt32>(zookeeper->get(fs::path(database->replica_path) / "log_ptr")); |
| 203 | UInt32 entry_num = DatabaseReplicatedTask::getLogEntryNumber(entry_name); |
| 204 | |
| 205 | if (entry_num <= our_log_ptr) |
| 206 | { |
| 207 | out_reason = fmt::format("Task {} already executed according to log pointer {}", entry_name, our_log_ptr); |
| 208 | return {}; |
| 209 | } |
| 210 | |
| 211 | String entry_path = fs::path(queue_dir) / entry_name; |
| 212 | auto task = std::make_unique<DatabaseReplicatedTask>(entry_name, entry_path, database); |
| 213 | |
| 214 | String initiator_name; |
| 215 | zkutil::EventPtr wait_committed_or_failed = std::make_shared<Poco::Event>(); |
| 216 | |
| 217 | String try_node_path = fs::path(entry_path) / "try"; |
| 218 | if (zookeeper->tryGet(try_node_path, initiator_name, nullptr, wait_committed_or_failed)) |
| 219 | { |
| 220 | task->is_initial_query = initiator_name == task->host_id_str; |
| 221 | |
| 222 | /// Query is not committed yet. We cannot just skip it and execute next one, because reordering may break replication. |
| 223 | LOG_TRACE(log, "Waiting for initiator {} to commit or rollback entry {}", initiator_name, entry_path); |
| 224 | constexpr size_t wait_time_ms = 1000; |
| 225 | size_t max_iterations = database->db_settings.wait_entry_commited_timeout_sec; |
| 226 | size_t iteration = 0; |
| 227 | |
| 228 | while (!wait_committed_or_failed->tryWait(wait_time_ms)) |
| 229 | { |
| 230 | if (stop_flag) |
| 231 | { |
| 232 | /// We cannot return task to process and we cannot return nullptr too, |
| 233 | /// because nullptr means "task should not be executed". |
| 234 | /// We can only exit by exception. |
| 235 | throw Exception(ErrorCodes::UNFINISHED, "Replication was stopped"); |
| 236 | } |
| 237 | |
| 238 | if (max_iterations <= ++iteration) |
| 239 | { |
| 240 | /// What can we do if initiator hangs for some reason? Seems like we can remove /try node. |
| 241 | /// Initiator will fail to commit ZooKeeperMetadataTransaction (including ops for replicated table) if /try does not exist. |
| 242 | /// But it's questionable. |
| 243 | |
| 244 | /// We use tryRemove(...) because multiple hosts (including initiator) may try to do it concurrently. |
| 245 | auto code = zookeeper->tryRemove(try_node_path); |
| 246 | if (code != Coordination::Error::ZOK && code != Coordination::Error::ZNONODE) |
| 247 | throw Coordination::Exception(code, try_node_path); |
| 248 |
nothing calls this directly
no test coverage detected