From eaa5a2b704e55e22192306e8dbb1badf8a243af3 Mon Sep 17 00:00:00 2001
From: Yongzao <532741407@qq.com>
Date: Wed, 22 Jul 2026 19:17:15 +0800
Subject: [PATCH 1/5] [ConfigNode] Fence region creation during database
deletion
---
.../confignode/i18n/ConfigNodeMessages.java | 12 +
.../confignode/i18n/ManagerMessages.java | 5 +
.../confignode/i18n/ConfigNodeMessages.java | 12 +
.../confignode/i18n/ManagerMessages.java | 5 +
.../consensus/request/ConfigPhysicalPlan.java | 4 +
.../request/ConfigPhysicalPlanType.java | 1 +
.../BatchRemoveRegionCreateTasksPlan.java | 82 +++++
.../confignode/manager/ProcedureManager.java | 93 +++--
.../manager/partition/PartitionManager.java | 337 +++++++++++++++---
.../manager/schema/ClusterSchemaManager.java | 128 ++++---
.../executor/ConfigPlanExecutor.java | 4 +
.../persistence/partition/PartitionInfo.java | 123 +++++--
.../procedure/env/ConfigNodeProcedureEnv.java | 38 +-
.../impl/AbstractDatabaseProcedure.java | 67 ++++
.../region/CreateRegionGroupsProcedure.java | 85 +++--
.../region/RemoveRegionGroupProcedure.java | 18 +-
.../impl/schema/DeleteDatabaseProcedure.java | 67 ++--
.../DatabaseLifecycleLockManager.java | 279 +++++++++++++++
.../request/ConfigPhysicalPlanSerDeTest.java | 23 ++
.../manager/ClusterSchemaManagerTest.java | 60 ++++
.../manager/ProcedureManagerTest.java | 33 ++
.../persistence/PartitionInfoTest.java | 194 +++++++++-
.../impl/CreateRegionGroupsProcedureTest.java | 255 +++++++++++++
.../schema/DeleteDatabaseProcedureTest.java | 90 +++++
.../DatabaseLifecycleLockManagerTest.java | 141 ++++++++
25 files changed, 1930 insertions(+), 226 deletions(-)
create mode 100644 iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/write/region/BatchRemoveRegionCreateTasksPlan.java
create mode 100644 iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/AbstractDatabaseProcedure.java
create mode 100644 iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
create mode 100644 iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java
diff --git a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
index 494201b1e9fec..0b7ff7eb0edb5 100644
--- a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
+++ b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
@@ -665,4 +665,16 @@ private ConfigNodeMessages() {}
public static final String
EXCEPTION_FAILED_TO_CREATE_OR_ALTER_TOPIC_MODE_CONSENSUS_DOES_NOT_SUPPORT_TOPIC_ATTRIBUTES_ARG_3C2D0BDA =
"Failed to create or alter topic, mode=consensus does not support topic attributes %s";
+ public static final String
+ LOG_REJECT_CREATEREGIONGROUPSPLAN_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_616E0CDE =
+ "Reject CreateRegionGroupsPlan because database {} does not exist";
+ public static final String
+ LOG_REJECT_CREATEREGIONGROUPSPLAN_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_C085AC01 =
+ "Reject CreateRegionGroupsPlan because database {} is being deleted";
+ public static final String
+ MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_AF0F2440 =
+ "Create RegionGroups failed because database %s does not exist";
+ public static final String
+ MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_651DB780 =
+ "Create RegionGroups failed because database %s is being deleted";
}
diff --git a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 20316edc39713..45361a98d8053 100644
--- a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -587,6 +587,11 @@ private ManagerMessages() {}
public static final String MESSAGE_SCHEMA_ENGINE_MODE_E37ED98C = "schema_engine_mode";
public static final String MESSAGE_TAG_ATTRIBUTE_TOTAL_SIZE_AF658CFE = "tag_attribute_total_size";
public static final String MESSAGE_DATABASE_LIMIT_THRESHOLD_45C23274 = "database_limit_threshold";
+ public static final String
+ MESSAGE_DATABASE_ARG_STILL_HAS_UNFINISHED_LIFECYCLE_PROCEDURES_67573924 =
+ "Database %s still has unfinished lifecycle procedures";
+ public static final String MESSAGE_SOME_OTHER_TASK_IS_DELETING_DATABASE_ARG_7BDB2C0F =
+ "Some other task is deleting database %s";
public static final String LOG_UNEXPECTED_ERROR_HAPPENED_SETTING_SPACE_QUOTA_DATABASE_ARG_F6ED7586 = "Unexpected error happened while setting space quota on database: %s ";
public static final String LOG_UNEXPECTED_ERROR_HAPPENED_SETTING_THROTTLE_QUOTA_USER_ARG_C111BE81 = "Unexpected error happened while setting throttle quota on user: %s ";
public static final String LOG_SCHEMA_TEMPLATE_NEED_TWO_FILES_1E57542A = "schema_template need two files";
diff --git a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
index 0193e5a04854e..d5228705755b5 100644
--- a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
+++ b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
@@ -710,4 +710,16 @@ private ConfigNodeMessages() {}
public static final String
EXCEPTION_FAILED_TO_CREATE_OR_ALTER_TOPIC_MODE_CONSENSUS_DOES_NOT_SUPPORT_TOPIC_ATTRIBUTES_ARG_3C2D0BDA =
"创建或修改 topic 失败,mode=consensus 不支持 topic 属性 %s";
+ public static final String
+ LOG_REJECT_CREATEREGIONGROUPSPLAN_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_616E0CDE =
+ "拒绝 CreateRegionGroupsPlan,因为数据库 {} 不存在";
+ public static final String
+ LOG_REJECT_CREATEREGIONGROUPSPLAN_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_C085AC01 =
+ "拒绝 CreateRegionGroupsPlan,因为数据库 {} 正在删除";
+ public static final String
+ MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_AF0F2440 =
+ "创建 RegionGroups 失败,因为数据库 %s 不存在";
+ public static final String
+ MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_651DB780 =
+ "创建 RegionGroups 失败,因为数据库 %s 正在删除";
}
diff --git a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 8dc9afd1e8bf0..5ed644c0f6dab 100644
--- a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -579,6 +579,11 @@ private ManagerMessages() {}
public static final String MESSAGE_SCHEMA_ENGINE_MODE_E37ED98C = "schema_engine_mode";
public static final String MESSAGE_TAG_ATTRIBUTE_TOTAL_SIZE_AF658CFE = "tag_attribute_total_size";
public static final String MESSAGE_DATABASE_LIMIT_THRESHOLD_45C23274 = "database_limit_threshold";
+ public static final String
+ MESSAGE_DATABASE_ARG_STILL_HAS_UNFINISHED_LIFECYCLE_PROCEDURES_67573924 =
+ "数据库 %s 仍有未完成的生命周期流程";
+ public static final String MESSAGE_SOME_OTHER_TASK_IS_DELETING_DATABASE_ARG_7BDB2C0F =
+ "其他任务正在删除数据库 %s";
public static final String LOG_UNEXPECTED_ERROR_HAPPENED_SETTING_SPACE_QUOTA_DATABASE_ARG_F6ED7586 = "设置数据库 %s 的空间配额时发生意外错误 ";
public static final String LOG_UNEXPECTED_ERROR_HAPPENED_SETTING_THROTTLE_QUOTA_USER_ARG_C111BE81 = "设置用户 %s 的限流配额时发生意外错误 ";
public static final String LOG_SCHEMA_TEMPLATE_NEED_TWO_FILES_1E57542A = "schema_template 需要两个文件";
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlan.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlan.java
index 0d9ca912571a4..8b6546f662584 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlan.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlan.java
@@ -85,6 +85,7 @@
import org.apache.iotdb.confignode.consensus.request.write.procedure.UpdateProcedurePlan;
import org.apache.iotdb.confignode.consensus.request.write.quota.SetSpaceQuotaPlan;
import org.apache.iotdb.confignode.consensus.request.write.quota.SetThrottleQuotaPlan;
+import org.apache.iotdb.confignode.consensus.request.write.region.BatchRemoveRegionCreateTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.OfferRegionMaintainTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.PollRegionMaintainTaskPlan;
@@ -266,6 +267,9 @@ public static ConfigPhysicalPlan create(final ByteBuffer buffer) throws IOExcept
case PollSpecificRegionMaintainTask:
plan = new PollSpecificRegionMaintainTaskPlan();
break;
+ case BatchRemoveRegionCreateTasks:
+ plan = new BatchRemoveRegionCreateTasksPlan();
+ break;
case CreateSchemaPartition:
plan = new CreateSchemaPartitionPlan();
break;
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanType.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanType.java
index 1be9518141483..e6f9c9847d148 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanType.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanType.java
@@ -72,6 +72,7 @@ public enum ConfigPhysicalPlanType {
AddRegionLocation((short) 311),
RemoveRegionLocation((short) 312),
GetRegionGroupsByTime((short) 313),
+ BatchRemoveRegionCreateTasks((short) 314),
/** Partition. */
GetSchemaPartition((short) 400),
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/write/region/BatchRemoveRegionCreateTasksPlan.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/write/region/BatchRemoveRegionCreateTasksPlan.java
new file mode 100644
index 0000000000000..256b290eb6250
--- /dev/null
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/write/region/BatchRemoveRegionCreateTasksPlan.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.confignode.consensus.request.write.region;
+
+import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan;
+import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType;
+
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * Removes every queued RegionCreateTask that belongs to the specified pre-deleted database.
+ *
+ *
The state machine ignores this plan when the database is missing or active, so replaying a
+ * cancellation cannot affect a later database incarnation that reuses the same name.
+ */
+public class BatchRemoveRegionCreateTasksPlan extends ConfigPhysicalPlan {
+
+ private String database;
+
+ public BatchRemoveRegionCreateTasksPlan() {
+ super(ConfigPhysicalPlanType.BatchRemoveRegionCreateTasks);
+ }
+
+ public BatchRemoveRegionCreateTasksPlan(final String database) {
+ super(ConfigPhysicalPlanType.BatchRemoveRegionCreateTasks);
+ this.database = database;
+ }
+
+ public String getDatabase() {
+ return database;
+ }
+
+ @Override
+ protected void serializeImpl(DataOutputStream stream) throws IOException {
+ stream.writeShort(getType().getPlanType());
+ ReadWriteIOUtils.write(database, stream);
+ }
+
+ @Override
+ protected void deserializeImpl(ByteBuffer buffer) throws IOException {
+ database = ReadWriteIOUtils.readString(buffer);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof BatchRemoveRegionCreateTasksPlan)) {
+ return false;
+ }
+ final BatchRemoveRegionCreateTasksPlan that = (BatchRemoveRegionCreateTasksPlan) o;
+ return Objects.equals(database, that.database);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(database);
+ }
+}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
index 8f5b27b7e2fa4..c76615d1fc6d9 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
@@ -134,6 +134,7 @@
import org.apache.iotdb.confignode.procedure.impl.testonly.CreateManyDatabasesProcedure;
import org.apache.iotdb.confignode.procedure.impl.trigger.CreateTriggerProcedure;
import org.apache.iotdb.confignode.procedure.impl.trigger.DropTriggerProcedure;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
import org.apache.iotdb.confignode.procedure.scheduler.ProcedureScheduler;
import org.apache.iotdb.confignode.procedure.scheduler.SimpleProcedureScheduler;
import org.apache.iotdb.confignode.procedure.store.ConfigProcedureStore;
@@ -183,6 +184,7 @@
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -305,34 +307,40 @@ public TSStatus deleteDatabases(
for (final TDatabaseSchema databaseSchema : deleteSgSchemaList) {
final String database = databaseSchema.getName();
boolean hasOverlappedTask = false;
- synchronized (this) {
- while (executor.isRunning()
- && System.currentTimeMillis() - startCheckTimeForProcedures < PROCEDURE_WAIT_TIME_OUT) {
- final Pair procedureIdDuplicatePair =
- checkDuplicateTableTask(
- database, null, null, null, null, ProcedureType.DELETE_DATABASE_PROCEDURE);
- hasOverlappedTask = procedureIdDuplicatePair.getRight();
-
- if (Boolean.FALSE.equals(procedureIdDuplicatePair.getRight())) {
- DeleteDatabaseProcedure procedure =
- new DeleteDatabaseProcedure(databaseSchema, isGeneratedByPipe);
- this.executor.submitProcedure(procedure);
- procedures.add(procedure);
- break;
+ while (executor.isRunning()
+ && System.currentTimeMillis() - startCheckTimeForProcedures < PROCEDURE_WAIT_TIME_OUT) {
+ try (final DatabaseLock ignored = acquireDatabaseLifecycleLock(database)) {
+ synchronized (this) {
+ final Pair procedureIdDuplicatePair =
+ checkDuplicateTableTask(
+ database, null, null, null, null, ProcedureType.DELETE_DATABASE_PROCEDURE);
+ hasOverlappedTask = procedureIdDuplicatePair.getRight();
+
+ if (Boolean.FALSE.equals(procedureIdDuplicatePair.getRight())) {
+ final DeleteDatabaseProcedure procedure =
+ new DeleteDatabaseProcedure(databaseSchema, isGeneratedByPipe);
+ this.executor.submitProcedure(procedure);
+ procedures.add(procedure);
+ }
}
+ }
+ if (!hasOverlappedTask) {
+ break;
+ }
+ synchronized (this) {
try {
wait(PROCEDURE_WAIT_RETRY_TIMEOUT);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
- if (hasOverlappedTask) {
- return RpcUtils.getStatus(
- TSStatusCode.OVERLAP_WITH_EXISTING_TASK,
- String.format(
- "Some other task is operating table under the database %s, please retry after the procedure finishes.",
- database));
- }
+ }
+ if (hasOverlappedTask) {
+ return RpcUtils.getStatus(
+ TSStatusCode.OVERLAP_WITH_EXISTING_TASK,
+ String.format(
+ "Some other task is operating table under the database %s, please retry after the procedure finishes.",
+ database));
}
}
List results = new ArrayList<>(procedures.size());
@@ -1501,18 +1509,28 @@ private static TDataNodeLocation buildFakeDataNodeLocation(int dataNodeId, Strin
// endregion
+ public CreateRegionGroupsProcedure submitCreateRegionGroups(
+ final TConsensusGroupType consensusGroupType,
+ final CreateRegionGroupsPlan createRegionGroupsPlan) {
+ final CreateRegionGroupsProcedure procedure =
+ new CreateRegionGroupsProcedure(consensusGroupType, createRegionGroupsPlan);
+ // Reentrant for PartitionManager, which holds the same database locks while allocating the
+ // plan. Keeping this guard here also makes a future direct submission visible atomically to
+ // same-name database creation and deletion.
+ try (final DatabaseLock ignored =
+ acquireDatabaseLifecycleLocks(createRegionGroupsPlan.getRegionGroupMap().keySet())) {
+ executor.submitProcedure(procedure);
+ }
+ return procedure;
+ }
+
/**
- * Generate {@link CreateRegionGroupsProcedure} and wait until it finished.
+ * Wait until a {@link CreateRegionGroupsProcedure} finishes.
*
* @return {@link TSStatusCode#SUCCESS_STATUS} if all RegionGroups have been created successfully,
* {@link TSStatusCode#CREATE_REGION_ERROR} otherwise
*/
- public TSStatus createRegionGroups(
- final TConsensusGroupType consensusGroupType,
- final CreateRegionGroupsPlan createRegionGroupsPlan) {
- final CreateRegionGroupsProcedure procedure =
- new CreateRegionGroupsProcedure(consensusGroupType, createRegionGroupsPlan);
- executor.submitProcedure(procedure);
+ public TSStatus waitCreateRegionGroups(final CreateRegionGroupsProcedure procedure) {
final TSStatus status = waitingProcedureFinished(procedure);
if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
return status;
@@ -1522,6 +1540,25 @@ public TSStatus createRegionGroups(
}
}
+ public DatabaseLock acquireDatabaseLifecycleLock(final String database) {
+ return acquireDatabaseLifecycleLocks(Collections.singleton(database));
+ }
+
+ public DatabaseLock acquireDatabaseLifecycleLocks(final Set databases) {
+ return env.getDatabaseLifecycleLockManager().acquireLocks(databases);
+ }
+
+ public boolean hasUnfinishedDatabaseLifecycleProcedure(final String database) {
+ return executor.getProcedures().values().stream()
+ .filter(procedure -> !procedure.isFinished())
+ .anyMatch(
+ procedure ->
+ (procedure instanceof DeleteDatabaseProcedure
+ && database.equals(((DeleteDatabaseProcedure) procedure).getDatabase()))
+ || (procedure instanceof CreateRegionGroupsProcedure
+ && ((CreateRegionGroupsProcedure) procedure).containsDatabase(database)));
+ }
+
/**
* Generate {@link CreateTriggerProcedure} and wait until it finished.
*
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
index 7efdae278db3c..95325f268c983 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
@@ -27,6 +27,7 @@
import org.apache.iotdb.common.rpc.thrift.TSeriesPartitionSlot;
import org.apache.iotdb.common.rpc.thrift.TTimePartitionSlot;
import org.apache.iotdb.commons.cluster.RegionRoleType;
+import org.apache.iotdb.commons.cluster.RegionStatus;
import org.apache.iotdb.commons.concurrent.IoTDBThreadPoolFactory;
import org.apache.iotdb.commons.concurrent.ThreadName;
import org.apache.iotdb.commons.concurrent.threadpool.ScheduledExecutorUtil;
@@ -59,6 +60,7 @@
import org.apache.iotdb.confignode.consensus.request.write.partition.CreateDataPartitionPlan;
import org.apache.iotdb.confignode.consensus.request.write.partition.CreateSchemaPartitionPlan;
import org.apache.iotdb.confignode.consensus.request.write.partition.RemoveRegionLocationPlan;
+import org.apache.iotdb.confignode.consensus.request.write.region.BatchRemoveRegionCreateTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.PollSpecificRegionMaintainTaskPlan;
import org.apache.iotdb.confignode.consensus.response.partition.CountTimeSlotListResp;
@@ -86,6 +88,8 @@
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainTask;
import org.apache.iotdb.confignode.procedure.impl.partition.DataPartitionTableIntegrityCheckProcedure;
+import org.apache.iotdb.confignode.procedure.impl.region.CreateRegionGroupsProcedure;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
import org.apache.iotdb.confignode.rpc.thrift.TCountTimeSlotListReq;
import org.apache.iotdb.confignode.rpc.thrift.TGetRegionGroupsByTimeReq;
import org.apache.iotdb.confignode.rpc.thrift.TGetRegionIdReq;
@@ -123,6 +127,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -154,8 +159,16 @@ public class PartitionManager {
// Try to delete Regions in every 10s
private static final int REGION_MAINTAINER_WORK_INTERVAL = 10;
+ private static final int SCHEMA_REGION_CREATE_BATCH_SIZE_PER_DATA_NODE = 32;
+ private static final int DATA_REGION_CREATE_BATCH_SIZE_PER_DATA_NODE = 64;
+ private static final long REGION_CREATE_BACKOFF_BASE_NANOS =
+ TimeUnit.SECONDS.toNanos(REGION_MAINTAINER_WORK_INTERVAL);
+ private static final long REGION_CREATE_BACKOFF_MAX_NANOS = TimeUnit.MINUTES.toNanos(5);
+
private final ScheduledExecutorService regionMaintainer;
private Future> currentRegionMaintainerFuture;
+ private final Map> regionCreateBackoffMap =
+ new EnumMap<>(TConsensusGroupType.class);
private final AtomicBoolean dataPartitionTableIntegrityCheckProcedureRunning =
new AtomicBoolean(false);
@@ -741,11 +754,22 @@ private TSStatus generateAndAllocateRegionGroups(
final Map allotmentMap, final TConsensusGroupType consensusGroupType)
throws NotEnoughDataNodeException, DatabaseNotExistsException {
if (!allotmentMap.isEmpty()) {
- final CreateRegionGroupsPlan createRegionGroupsPlan =
- getLoadManager().allocateRegionGroups(allotmentMap, consensusGroupType);
- LOGGER.info(ManagerMessages.CREATEREGIONGROUPS_STARTING_TO_CREATE_THE_FOLLOWING_REGIONGROUPS);
- createRegionGroupsPlan.planLog(LOGGER);
- return getProcedureManager().createRegionGroups(consensusGroupType, createRegionGroupsPlan);
+ final CreateRegionGroupsProcedure procedure;
+ // Cover both ID allocation and submission with the same per-database locks used by database
+ // creation and deletion. A delayed plan therefore cannot become invisible between deleting
+ // and recreating a database with the same name.
+ try (final DatabaseLock ignored =
+ getProcedureManager().acquireDatabaseLifecycleLocks(allotmentMap.keySet())) {
+ final CreateRegionGroupsPlan createRegionGroupsPlan =
+ getLoadManager().allocateRegionGroups(allotmentMap, consensusGroupType);
+ LOGGER.info(
+ ManagerMessages.CREATEREGIONGROUPS_STARTING_TO_CREATE_THE_FOLLOWING_REGIONGROUPS);
+ createRegionGroupsPlan.planLog(LOGGER);
+ procedure =
+ getProcedureManager()
+ .submitCreateRegionGroups(consensusGroupType, createRegionGroupsPlan);
+ }
+ return getProcedureManager().waitCreateRegionGroups(procedure);
} else {
return RpcUtils.SUCCESS_STATUS;
}
@@ -1117,14 +1141,29 @@ public SchemaNodeManagementResp getNodePathsPartition(GetNodePathsPartitionPlan
}
}
- public void preDeleteDatabase(
+ public TSStatus preDeleteDatabase(
final String database, final PreDeleteDatabasePlan.PreDeleteType preDeleteType) {
final PreDeleteDatabasePlan preDeleteDatabasePlan =
new PreDeleteDatabasePlan(database, preDeleteType);
try {
- getConsensusManager().write(preDeleteDatabasePlan);
+ return getConsensusManager().write(preDeleteDatabasePlan);
+ } catch (final ConsensusException e) {
+ LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ final TSStatus status = new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode());
+ status.setMessage(e.getMessage());
+ return status;
+ }
+ }
+
+ /** Durably removes all queued RegionCreateTasks of the specified database. */
+ public TSStatus batchRemoveRegionCreateTasks(final String database) {
+ try {
+ return getConsensusManager().write(new BatchRemoveRegionCreateTasksPlan(database));
} catch (final ConsensusException e) {
LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ final TSStatus status = new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode());
+ status.setMessage(e.getMessage());
+ return status;
}
}
@@ -1132,6 +1171,10 @@ public boolean isDatabasePreDeleted(final String database) {
return partitionInfo.isDatabasePreDeleted(database);
}
+ public TSStatus validateCreateRegionGroups(final CreateRegionGroupsPlan plan) {
+ return partitionInfo.validateCreateRegionGroups(plan);
+ }
+
/**
* Get TSeriesPartitionSlot.
*
@@ -1365,11 +1408,60 @@ public void maintainRegionReplicas() {
return;
}
+ final List databases =
+ partitionInfo.getRegionMaintainEntryList().stream()
+ .filter(RegionCreateTask.class::isInstance)
+ .map(RegionCreateTask.class::cast)
+ .map(RegionCreateTask::getStorageGroup)
+ .distinct()
+ .sorted()
+ .collect(Collectors.toList());
+ for (final String database : databases) {
+ try (final DatabaseLock ignored =
+ getProcedureManager().acquireDatabaseLifecycleLock(database)) {
+ // Leadership may have changed while this invocation was waiting for an in-flight database
+ // procedure. The next leader will repair and retry the persisted queue.
+ if (!getConsensusManager().isLeader()) {
+ return;
+ }
+ maintainRegionReplicasUnderLock(database);
+ }
+ }
+ }
+
+ private void maintainRegionReplicasUnderLock(final String database) {
+ final List persistedTasks =
+ partitionInfo.getRegionMaintainEntryList().stream()
+ .filter(RegionCreateTask.class::isInstance)
+ .map(RegionCreateTask.class::cast)
+ .filter(task -> database.equals(task.getStorageGroup()))
+ .collect(Collectors.toList());
+ final Map invalidTaskCountByRegion = new HashMap<>();
+ for (RegionMaintainTask task : persistedTasks) {
+ if (!(task instanceof RegionCreateTask)
+ || !isRegionCreateTaskRegionValid((RegionCreateTask) task)) {
+ invalidTaskCountByRegion.merge(task.getRegionId(), 1, Integer::sum);
+ }
+ }
+ if (!removeInvalidRegionCreateTasks(invalidTaskCountByRegion)) {
+ return;
+ }
+ final Set invalidRegionIds = invalidTaskCountByRegion.keySet();
+
+ // Do not infer that an Unknown cache entry means a missing replica until the new leader has
+ // collected enough heartbeats. Orphan/pre-deleted tasks above are still cleaned immediately.
+ if (!getLoadManager().isLoadReady()) {
+ return;
+ }
+
// Group the queued tasks into one FIFO sub-queue per region. The queue only ever holds
// RegionCreateTasks now (delete tasks are filtered out at the PartitionInfo ingestion points),
// and a region may carry several of them when more than one of its replicas failed to create.
- final Map> tasksByRegion = new HashMap<>();
- for (RegionMaintainTask task : partitionInfo.getRegionMaintainEntryList()) {
+ final Map> tasksByRegion = new LinkedHashMap<>();
+ for (RegionMaintainTask task : persistedTasks) {
+ if (invalidRegionIds.contains(task.getRegionId())) {
+ continue;
+ }
if (!(task instanceof RegionCreateTask)) {
// Unreachable: the queue only holds create tasks now (legacy delete tasks are dropped at
// the
@@ -1383,51 +1475,105 @@ public void maintainRegionReplicas() {
.add((RegionCreateTask) task);
}
- // Drain the sub-queues head-by-head. Each round takes the head of every region, batches those
- // heads by region type into a single create RPC per type, then durably polls the tasks that
- // succeeded. Tasks of the same region are advanced one at a time to preserve their offer order.
- while (!tasksByRegion.isEmpty()) {
- final Map> headsByType =
- new EnumMap<>(TConsensusGroupType.class);
- for (Queue queue : tasksByRegion.values()) {
- final RegionCreateTask head = queue.peek();
- headsByType.computeIfAbsent(head.getRegionId().getType(), k -> new ArrayList<>()).add(head);
+ final Set invalidHeadRegionIds = new HashSet<>();
+ final Map> headsByType =
+ new EnumMap<>(TConsensusGroupType.class);
+ final Map> selectedCountByTypeAndDataNode =
+ new EnumMap<>(TConsensusGroupType.class);
+ for (Queue queue : tasksByRegion.values()) {
+ final RegionCreateTask head = queue.peek();
+ if (!isRegionCreateTaskTargetValid(head)) {
+ invalidHeadRegionIds.add(head.getRegionId());
+ continue;
}
-
- final Set successfulRegions = new HashSet<>();
- int selectedCount = 0;
- for (Map.Entry> entry : headsByType.entrySet()) {
- selectedCount += entry.getValue().size();
- successfulRegions.addAll(submitRegionCreateTasks(entry.getKey(), entry.getValue()));
+ if (isRegionCreateTargetInBackoff(head)) {
+ continue;
}
-
- if (successfulRegions.isEmpty()) {
- break;
+ final TConsensusGroupType type = head.getRegionId().getType();
+ final int dataNodeId = head.getTargetDataNode().getDataNodeId();
+ final Map selectedCountByDataNode =
+ selectedCountByTypeAndDataNode.computeIfAbsent(type, ignored -> new HashMap<>());
+ final int selectedCount = selectedCountByDataNode.getOrDefault(dataNodeId, 0);
+ if (selectedCount >= getRegionCreateBatchSize(type)) {
+ continue;
}
+ selectedCountByDataNode.put(dataNodeId, selectedCount + 1);
+ headsByType.computeIfAbsent(type, ignored -> new ArrayList<>()).add(head);
+ }
- // Advance the in-memory sub-queues so the next round picks the following task of each region.
- for (TConsensusGroupId regionId : successfulRegions) {
- tasksByRegion.computeIfPresent(
- regionId,
- (k, queue) -> {
- queue.poll();
- return queue.isEmpty() ? null : queue;
- });
- }
+ // A target-specific stale task only removes the head of its Region queue. A following task of
+ // the same Region may still point to another replica that is genuinely missing.
+ if (!invalidHeadRegionIds.isEmpty()
+ && !writeRegionCreateTaskPlan(
+ new PollSpecificRegionMaintainTaskPlan(invalidHeadRegionIds))) {
+ return;
+ }
- // Durably remove the head of every successfully created region from the persisted queue.
- try {
- getConsensusManager().write(new PollSpecificRegionMaintainTaskPlan(successfulRegions));
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
- }
+ final Set successfulRegions = new HashSet<>();
+ for (Map.Entry> entry : headsByType.entrySet()) {
+ successfulRegions.addAll(submitRegionCreateTasks(entry.getKey(), entry.getValue()));
+ }
+ if (!successfulRegions.isEmpty()) {
+ writeRegionCreateTaskPlan(new PollSpecificRegionMaintainTaskPlan(successfulRegions));
+ }
+ }
- if (successfulRegions.size() < selectedCount) {
- // Some tasks failed this round; stop and retry on the next schedule so that the tasks of
- // each region keep being executed in the order they were offered.
- break;
+ private boolean writeRegionCreateTaskPlan(ConfigPhysicalPlan plan) {
+ try {
+ return getConsensusManager().write(plan).getCode()
+ == TSStatusCode.SUCCESS_STATUS.getStatusCode();
+ } catch (ConsensusException e) {
+ LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ return false;
+ }
+ }
+
+ private boolean removeInvalidRegionCreateTasks(
+ final Map invalidTaskCountByRegion) {
+ if (invalidTaskCountByRegion.isEmpty()) {
+ return true;
+ }
+ final int maxTaskCount =
+ invalidTaskCountByRegion.values().stream().mapToInt(Integer::intValue).max().orElse(0);
+ for (int taskIndex = 0; taskIndex < maxTaskCount; taskIndex++) {
+ final int currentTaskIndex = taskIndex;
+ final Set regionIds =
+ invalidTaskCountByRegion.entrySet().stream()
+ .filter(entry -> entry.getValue() > currentTaskIndex)
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toSet());
+ if (!writeRegionCreateTaskPlan(new PollSpecificRegionMaintainTaskPlan(regionIds))) {
+ return false;
}
}
+ return true;
+ }
+
+ private boolean isRegionCreateTaskRegionValid(RegionCreateTask task) {
+ return partitionInfo.isDatabaseExisted(task.getStorageGroup())
+ && Objects.equals(
+ task.getStorageGroup(), partitionInfo.getRegionDatabase(task.getRegionId()));
+ }
+
+ private boolean isRegionCreateTaskTargetValid(RegionCreateTask task) {
+ final List currentReplicaSets =
+ partitionInfo.getReplicaSets(
+ task.getStorageGroup(), Collections.singletonList(task.getRegionId()));
+ if (currentReplicaSets.size() != 1
+ || currentReplicaSets.get(0).getDataNodeLocations().stream()
+ .noneMatch(
+ location -> location.getDataNodeId() == task.getTargetDataNode().getDataNodeId())) {
+ return false;
+ }
+ return RegionStatus.Unknown.equals(
+ getLoadManager()
+ .getRegionStatus(task.getRegionId(), task.getTargetDataNode().getDataNodeId()));
+ }
+
+ private int getRegionCreateBatchSize(TConsensusGroupType regionType) {
+ return TConsensusGroupType.SchemaRegion.equals(regionType)
+ ? SCHEMA_REGION_CREATE_BATCH_SIZE_PER_DATA_NODE
+ : DATA_REGION_CREATE_BATCH_SIZE_PER_DATA_NODE;
}
/**
@@ -1451,10 +1597,8 @@ private Set submitRegionCreateTasks(
new TCreateSchemaRegionReq(task.getRegionReplicaSet(), task.getStorageGroup()));
schemaHandler.putNodeLocation(task.getRegionId().getId(), task.getTargetDataNode());
}
- CnToDnInternalServiceAsyncRequestManager.getInstance()
- .sendAsyncRequestWithRetry(schemaHandler);
- collectSuccessfulRegions(
- schemaHandler.getResponseMap(), TConsensusGroupType.SchemaRegion, successfulRegions);
+ CnToDnInternalServiceAsyncRequestManager.getInstance().sendAsyncRequest(schemaHandler);
+ collectSuccessfulRegions(schemaHandler.getResponseMap(), createTasks, successfulRegions);
break;
case DataRegion:
final DataNodeAsyncRequestContext dataHandler =
@@ -1469,10 +1613,8 @@ private Set submitRegionCreateTasks(
new TCreateDataRegionReq(task.getRegionReplicaSet(), task.getStorageGroup()));
dataHandler.putNodeLocation(task.getRegionId().getId(), task.getTargetDataNode());
}
- CnToDnInternalServiceAsyncRequestManager.getInstance()
- .sendAsyncRequestWithRetry(dataHandler);
- collectSuccessfulRegions(
- dataHandler.getResponseMap(), TConsensusGroupType.DataRegion, successfulRegions);
+ CnToDnInternalServiceAsyncRequestManager.getInstance().sendAsyncRequest(dataHandler);
+ collectSuccessfulRegions(dataHandler.getResponseMap(), createTasks, successfulRegions);
break;
default:
break;
@@ -1482,13 +1624,92 @@ private Set submitRegionCreateTasks(
private void collectSuccessfulRegions(
Map responseMap,
- TConsensusGroupType regionType,
+ List createTasks,
Set successfulRegions) {
- for (Map.Entry entry : responseMap.entrySet()) {
- if (entry.getValue().getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- successfulRegions.add(new TConsensusGroupId(regionType, entry.getKey()));
+ final Map> tasksByDataNode = new HashMap<>();
+ for (RegionCreateTask task : createTasks) {
+ tasksByDataNode
+ .computeIfAbsent(task.getTargetDataNode().getDataNodeId(), ignored -> new ArrayList<>())
+ .add(task);
+ final TSStatus status = responseMap.get(task.getRegionId().getId());
+ if (status != null && status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ successfulRegions.add(task.getRegionId());
+ }
+ }
+ for (List dataNodeTasks : tasksByDataNode.values()) {
+ RegionCreateTask failedTask = null;
+ TSStatus failedStatus = null;
+ for (RegionCreateTask task : dataNodeTasks) {
+ final TSStatus status = responseMap.get(task.getRegionId().getId());
+ if (status == null || status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ if (failedTask == null || isDirectMemoryFailure(status)) {
+ failedTask = task;
+ failedStatus = status;
+ }
+ }
}
+ if (failedTask != null) {
+ recordRegionCreateFailure(failedTask, failedStatus);
+ } else {
+ clearRegionCreateBackoff(dataNodeTasks.get(0));
+ }
+ }
+ }
+
+ private boolean isRegionCreateTargetInBackoff(RegionCreateTask task) {
+ return Optional.ofNullable(regionCreateBackoffMap.get(task.getRegionId().getType()))
+ .map(backoffByDataNode -> backoffByDataNode.get(task.getTargetDataNode().getDataNodeId()))
+ .map(backoff -> backoff.nextAttemptNanos > System.nanoTime())
+ .orElse(false);
+ }
+
+ private void clearRegionCreateBackoff(RegionCreateTask task) {
+ Optional.ofNullable(regionCreateBackoffMap.get(task.getRegionId().getType()))
+ .ifPresent(
+ backoffByDataNode ->
+ backoffByDataNode.remove(task.getTargetDataNode().getDataNodeId()));
+ }
+
+ private void recordRegionCreateFailure(RegionCreateTask task, TSStatus status) {
+ final RegionCreateBackoff backoff =
+ regionCreateBackoffMap
+ .computeIfAbsent(task.getRegionId().getType(), ignored -> new HashMap<>())
+ .computeIfAbsent(
+ task.getTargetDataNode().getDataNodeId(), ignored -> new RegionCreateBackoff());
+ backoff.failureCount++;
+ long delayNanos =
+ Math.min(
+ REGION_CREATE_BACKOFF_MAX_NANOS,
+ REGION_CREATE_BACKOFF_BASE_NANOS << Math.min(backoff.failureCount - 1, 5));
+ if (isDirectMemoryFailure(status)) {
+ delayNanos = REGION_CREATE_BACKOFF_MAX_NANOS;
+ } else {
+ final long jitterBound = Math.max(1, delayNanos / 4);
+ delayNanos =
+ Math.min(
+ REGION_CREATE_BACKOFF_MAX_NANOS,
+ delayNanos + ThreadLocalRandom.current().nextLong(jitterBound));
+ }
+ backoff.nextAttemptNanos = System.nanoTime() + delayNanos;
+ }
+
+ private boolean isDirectMemoryFailure(TSStatus status) {
+ if (status == null || status.getMessage() == null) {
+ return false;
}
+ final String normalizedMessage = status.getMessage().toLowerCase(java.util.Locale.ROOT);
+ return normalizedMessage.contains("direct memory")
+ || normalizedMessage.contains("direct buffer")
+ || normalizedMessage.contains("directbuffer")
+ || normalizedMessage.contains("outofmemory")
+ || normalizedMessage.contains("out of memory")
+ || normalizedMessage.contains("oom")
+ || normalizedMessage.contains("内存");
+ }
+
+ private static class RegionCreateBackoff {
+ private int failureCount;
+ private long nextAttemptNanos;
}
public void startRegionCleaner() {
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
index a0284445ea20a..d850c6d4f3482 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
@@ -92,6 +92,7 @@
import org.apache.iotdb.confignode.i18n.ManagerMessages;
import org.apache.iotdb.confignode.i18n.ProcedureMessages;
import org.apache.iotdb.confignode.manager.IManager;
+import org.apache.iotdb.confignode.manager.ProcedureManager;
import org.apache.iotdb.confignode.manager.consensus.ConsensusManager;
import org.apache.iotdb.confignode.manager.node.NodeManager;
import org.apache.iotdb.confignode.manager.partition.PartitionManager;
@@ -99,6 +100,7 @@
import org.apache.iotdb.confignode.manager.partition.RegionGroupExtensionPolicy;
import org.apache.iotdb.confignode.persistence.schema.ClusterSchemaInfo;
import org.apache.iotdb.confignode.persistence.schema.ConfigSchemaStatistics;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
import org.apache.iotdb.confignode.rpc.thrift.TDatabaseInfo;
import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
import org.apache.iotdb.confignode.rpc.thrift.TDescTable4InformationSchemaResp;
@@ -172,66 +174,90 @@ public ClusterSchemaManager(
/** Set Database */
public TSStatus setDatabase(
final DatabaseSchemaPlan databaseSchemaPlan, final boolean isGeneratedByPipe) {
- TSStatus result;
-
final TDatabaseSchema schema = databaseSchemaPlan.getSchema();
- if (getPartitionManager().isDatabasePreDeleted(schema.getName())) {
- return RpcUtils.getStatus(
- TSStatusCode.METADATA_ERROR,
- String.format("Some other task is deleting database %s", schema.getName()));
- }
-
- createDatabaseLock.lock();
- try {
- clusterSchemaInfo.isDatabaseNameValid(
- schema.getName(), schema.isSetIsTableModel() && schema.isIsTableModel());
- if (!schema.getName().equals(SchemaConstant.SYSTEM_DATABASE)
- && !schema.getName().equals(SchemaConstant.AUDIT_DATABASE)
- && !schema.getName().equals(Audit.TABLE_MODEL_AUDIT_DATABASE)) {
- clusterSchemaInfo.checkDatabaseLimit();
+ final ProcedureManager procedureManager = configManager.getProcedureManager();
+ try (final DatabaseLock ignored =
+ procedureManager.acquireDatabaseLifecycleLock(schema.getName())) {
+ if (procedureManager.hasUnfinishedDatabaseLifecycleProcedure(schema.getName())) {
+ return RpcUtils.getStatus(
+ TSStatusCode.METADATA_ERROR,
+ String.format(
+ ManagerMessages
+ .MESSAGE_DATABASE_ARG_STILL_HAS_UNFINISHED_LIFECYCLE_PROCEDURES_67573924,
+ schema.getName()));
}
- // Cache DatabaseSchema
- result =
- getConsensusManager()
- .write(
- isGeneratedByPipe
- ? new PipeEnrichedPlan(databaseSchemaPlan)
- : databaseSchemaPlan);
- // set ttl
- if (schema.isSetTTL()) {
- result = configManager.getTTLManager().setTTL(databaseSchemaPlan, isGeneratedByPipe);
+ if (getPartitionManager().isDatabasePreDeleted(schema.getName())) {
+ return RpcUtils.getStatus(
+ TSStatusCode.METADATA_ERROR,
+ String.format(
+ ManagerMessages.MESSAGE_SOME_OTHER_TASK_IS_DELETING_DATABASE_ARG_7BDB2C0F,
+ schema.getName()));
}
- // Bind Database metrics
- PartitionMetrics.bindDatabaseRelatedMetricsWhenUpdate(
- MetricService.getInstance(),
- configManager,
- schema.getName(),
- schema.getDataReplicationFactor(),
- schema.getSchemaReplicationFactor());
- PartitionMetrics.bindDatabaseTableMetrics(
- MetricService.getInstance(),
- clusterSchemaInfo.getConfigSchemaStatistics(),
- schema.getName());
- // Adjust the maximum RegionGroup number of each Database
- adjustMaxRegionGroupNum();
- } catch (final ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
- result = new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode());
- result.setMessage(e.getMessage());
- } catch (final MetadataException metadataException) {
- // Reject if Database already set
- result = new TSStatus(metadataException.getErrorCode());
- result.setMessage(metadataException.getMessage());
- } finally {
- createDatabaseLock.unlock();
- }
- return result;
+ TSStatus result;
+ createDatabaseLock.lock();
+ try {
+ clusterSchemaInfo.isDatabaseNameValid(
+ schema.getName(), schema.isSetIsTableModel() && schema.isIsTableModel());
+ if (!schema.getName().equals(SchemaConstant.SYSTEM_DATABASE)
+ && !schema.getName().equals(SchemaConstant.AUDIT_DATABASE)
+ && !schema.getName().equals(Audit.TABLE_MODEL_AUDIT_DATABASE)) {
+ clusterSchemaInfo.checkDatabaseLimit();
+ }
+ // Cache DatabaseSchema
+ result =
+ getConsensusManager()
+ .write(
+ isGeneratedByPipe
+ ? new PipeEnrichedPlan(databaseSchemaPlan)
+ : databaseSchemaPlan);
+ // set ttl
+ if (schema.isSetTTL()) {
+ result = configManager.getTTLManager().setTTL(databaseSchemaPlan, isGeneratedByPipe);
+ }
+ // Bind Database metrics
+ PartitionMetrics.bindDatabaseRelatedMetricsWhenUpdate(
+ MetricService.getInstance(),
+ configManager,
+ schema.getName(),
+ schema.getDataReplicationFactor(),
+ schema.getSchemaReplicationFactor());
+ PartitionMetrics.bindDatabaseTableMetrics(
+ MetricService.getInstance(),
+ clusterSchemaInfo.getConfigSchemaStatistics(),
+ schema.getName());
+ // Adjust the maximum RegionGroup number of each Database
+ adjustMaxRegionGroupNum();
+ } catch (final ConsensusException e) {
+ LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+ result = new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode());
+ result.setMessage(e.getMessage());
+ } catch (final MetadataException metadataException) {
+ // Reject if Database already set
+ result = new TSStatus(metadataException.getErrorCode());
+ result.setMessage(metadataException.getMessage());
+ } finally {
+ createDatabaseLock.unlock();
+ }
+
+ return result;
+ }
}
/** Alter Database */
public TSStatus alterDatabase(
final DatabaseSchemaPlan databaseSchemaPlan, final boolean isGeneratedByPipe) {
+ final TDatabaseSchema databaseSchema = databaseSchemaPlan.getSchema();
+ try (final DatabaseLock ignored =
+ configManager
+ .getProcedureManager()
+ .acquireDatabaseLifecycleLock(databaseSchema.getName())) {
+ return alterDatabaseUnderLock(databaseSchemaPlan, isGeneratedByPipe);
+ }
+ }
+
+ private TSStatus alterDatabaseUnderLock(
+ final DatabaseSchemaPlan databaseSchemaPlan, final boolean isGeneratedByPipe) {
TSStatus result;
final TDatabaseSchema databaseSchema = databaseSchemaPlan.getSchema();
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java
index 772f46baa3162..7fe8923855ac5 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java
@@ -111,6 +111,7 @@
import org.apache.iotdb.confignode.consensus.request.write.procedure.UpdateProcedurePlan;
import org.apache.iotdb.confignode.consensus.request.write.quota.SetSpaceQuotaPlan;
import org.apache.iotdb.confignode.consensus.request.write.quota.SetThrottleQuotaPlan;
+import org.apache.iotdb.confignode.consensus.request.write.region.BatchRemoveRegionCreateTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.OfferRegionMaintainTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.PollSpecificRegionMaintainTaskPlan;
@@ -454,6 +455,9 @@ public TSStatus executeNonQueryPlan(ConfigPhysicalPlan physicalPlan)
case PollSpecificRegionMaintainTask:
return partitionInfo.pollSpecificRegionMaintainTask(
(PollSpecificRegionMaintainTaskPlan) physicalPlan);
+ case BatchRemoveRegionCreateTasks:
+ return partitionInfo.batchRemoveRegionCreateTasks(
+ (BatchRemoveRegionCreateTasksPlan) physicalPlan);
case CreateSchemaPartition:
return partitionInfo.createSchemaPartition((CreateSchemaPartitionPlan) physicalPlan);
case CreateDataPartition:
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
index e71b61d7b338c..47685a60fcfe5 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
@@ -49,6 +49,7 @@
import org.apache.iotdb.confignode.consensus.request.write.partition.CreateSchemaPartitionPlan;
import org.apache.iotdb.confignode.consensus.request.write.partition.RemoveRegionLocationPlan;
import org.apache.iotdb.confignode.consensus.request.write.partition.UpdateRegionLocationPlan;
+import org.apache.iotdb.confignode.consensus.request.write.region.BatchRemoveRegionCreateTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.OfferRegionMaintainTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.PollSpecificRegionMaintainTaskPlan;
@@ -63,6 +64,7 @@
import org.apache.iotdb.confignode.consensus.response.partition.SchemaPartitionResp;
import org.apache.iotdb.confignode.exception.DatabaseNotExistsException;
import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
+import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainTask;
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainType;
import org.apache.iotdb.confignode.rpc.thrift.TRegionInfo;
@@ -192,37 +194,71 @@ public TSStatus createDatabase(final DatabaseSchemaPlan plan) {
* @return {@link TSStatusCode#SUCCESS_STATUS}
*/
public TSStatus createRegionGroups(CreateRegionGroupsPlan plan) {
- TSStatus result;
- AtomicInteger maxRegionId = new AtomicInteger(Integer.MIN_VALUE);
+ updateNextRegionGroupId(plan);
+
+ final TSStatus validationStatus = validateCreateRegionGroups(plan);
+ if (validationStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ return validationStatus;
+ }
plan.getRegionGroupMap()
.forEach(
(database, regionReplicaSets) -> {
- if (isDatabasePreDeleted(database)) {
- LOGGER.warn(
- ConfigNodeMessages
- .CREATEREGIONGROUPS_DATABASE_HAS_BEEN_DELETED_CORRESPONDING_REGIONGROUPS,
- database);
- return;
- }
databasePartitionTables.get(database).createRegionGroups(regionReplicaSets);
- regionReplicaSets.forEach(
- regionReplicaSet ->
- maxRegionId.set(
- Math.max(maxRegionId.get(), regionReplicaSet.getRegionId().getId())));
});
+ return new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
+ }
+
+ /** Validates all databases before any RegionGroup in a potentially batched plan is persisted. */
+ public TSStatus validateCreateRegionGroups(final CreateRegionGroupsPlan plan) {
+ for (final String database : plan.getRegionGroupMap().keySet()) {
+ final DatabasePartitionTable databasePartitionTable = databasePartitionTables.get(database);
+ if (databasePartitionTable == null) {
+ LOGGER.warn(
+ ConfigNodeMessages
+ .LOG_REJECT_CREATEREGIONGROUPSPLAN_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_616E0CDE,
+ database);
+ return new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode())
+ .setMessage(
+ String.format(
+ ConfigNodeMessages
+ .MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_AF0F2440,
+ database));
+ }
+ if (!databasePartitionTable.isNotPreDeleted()) {
+ LOGGER.warn(
+ ConfigNodeMessages
+ .LOG_REJECT_CREATEREGIONGROUPSPLAN_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_C085AC01,
+ database);
+ return new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode())
+ .setMessage(
+ String.format(
+ ConfigNodeMessages
+ .MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_651DB780,
+ database));
+ }
+ }
+
+ return new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
+ }
+
+ private void updateNextRegionGroupId(final CreateRegionGroupsPlan plan) {
+ final int maxRegionId =
+ plan.getRegionGroupMap().values().stream()
+ .flatMap(List::stream)
+ .mapToInt(regionReplicaSet -> regionReplicaSet.getRegionId().getId())
+ .max()
+ .orElse(Integer.MIN_VALUE);
+
// To ensure that the nextRegionGroupId is updated correctly when
// the ConfigNode-followers concurrently processes CreateRegionsPlan,
// we need to add a synchronization lock here
synchronized (nextRegionGroupId) {
- if (nextRegionGroupId.get() < maxRegionId.get()) {
- nextRegionGroupId.set(maxRegionId.get());
+ if (nextRegionGroupId.get() < maxRegionId) {
+ nextRegionGroupId.set(maxRegionId);
}
}
-
- result = new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
- return result;
}
/**
@@ -233,6 +269,7 @@ public TSStatus createRegionGroups(CreateRegionGroupsPlan plan) {
public TSStatus offerRegionMaintainTasks(
OfferRegionMaintainTasksPlan offerRegionMaintainTasksPlan) {
synchronized (regionMaintainTaskList) {
+ final Set existingTasks = new HashSet<>(regionMaintainTaskList);
// The RegionMaintainer queue only recreates failed region replicas now; region deletion is
// owned by RemoveRegionGroupProcedure. Drop any legacy DELETE task that an upgraded node may
// replay from an old consensus log, so it cannot get stuck in the queue and block the
@@ -245,7 +282,13 @@ public TSStatus offerRegionMaintainTasks(
task.getRegionId());
continue;
}
- regionMaintainTaskList.add(task);
+ final RegionCreateTask createTask = (RegionCreateTask) task;
+ if (!isRegionCreateTaskOwnedByCurrentPartitionTable(createTask)) {
+ continue;
+ }
+ if (existingTasks.add(task)) {
+ regionMaintainTaskList.add(task);
+ }
}
return RpcUtils.SUCCESS_STATUS;
}
@@ -290,6 +333,37 @@ public TSStatus pollSpecificRegionMaintainTask(PollSpecificRegionMaintainTaskPla
}
}
+ /** Idempotently remove all RegionCreateTasks that belong to the specified database. */
+ public TSStatus batchRemoveRegionCreateTasks(BatchRemoveRegionCreateTasksPlan plan) {
+ synchronized (regionMaintainTaskList) {
+ final DatabasePartitionTable databasePartitionTable =
+ databasePartitionTables.get(plan.getDatabase());
+ if (databasePartitionTable == null || databasePartitionTable.isNotPreDeleted()) {
+ return RpcUtils.SUCCESS_STATUS;
+ }
+ regionMaintainTaskList.removeIf(
+ task ->
+ task instanceof RegionCreateTask
+ && Objects.equals(
+ plan.getDatabase(), ((RegionCreateTask) task).getStorageGroup()));
+ return RpcUtils.SUCCESS_STATUS;
+ }
+ }
+
+ private boolean isRegionCreateTaskOwnedByCurrentPartitionTable(RegionCreateTask task) {
+ if (!isDatabaseExisted(task.getStorageGroup())) {
+ return false;
+ }
+ return getReplicaSets(task.getStorageGroup(), Collections.singletonList(task.getRegionId()))
+ .stream()
+ .anyMatch(
+ replicaSet ->
+ replicaSet.getDataNodeLocations().stream()
+ .anyMatch(
+ location ->
+ location.getDataNodeId() == task.getTargetDataNode().getDataNodeId()));
+ }
+
/**
* Get a deep copy of RegionCleanList for RegionCleaner to maintain cluster RegionReplicas.
*
@@ -1024,10 +1098,13 @@ public boolean processTakeSnapshot(File snapshotDir) throws TException, IOExcept
databasePartitionTableEntry.getValue().serialize(bufferedOutputStream, protocol);
}
- // serialize regionCleanList
- ReadWriteIOUtils.write(regionMaintainTaskList.size(), bufferedOutputStream);
- for (RegionMaintainTask task : regionMaintainTaskList) {
- task.serialize(bufferedOutputStream, protocol);
+ // Serialize the queue under the same monitor used by every consensus mutation so the count
+ // and entries belong to one atomic snapshot.
+ synchronized (regionMaintainTaskList) {
+ ReadWriteIOUtils.write(regionMaintainTaskList.size(), bufferedOutputStream);
+ for (RegionMaintainTask task : regionMaintainTaskList) {
+ task.serialize(bufferedOutputStream, protocol);
+ }
}
// write to file
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
index 76bf575681c87..2124761521c75 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
@@ -53,8 +53,10 @@
import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
import org.apache.iotdb.confignode.persistence.partition.PartitionInfo;
import org.apache.iotdb.confignode.persistence.schema.ClusterSchemaInfo;
+import org.apache.iotdb.confignode.procedure.Procedure;
import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
import org.apache.iotdb.confignode.procedure.impl.schema.SchemaUtils;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager;
import org.apache.iotdb.confignode.procedure.scheduler.LockQueue;
import org.apache.iotdb.confignode.procedure.scheduler.ProcedureScheduler;
import org.apache.iotdb.confignode.rpc.thrift.TAddConsensusGroupReq;
@@ -115,6 +117,8 @@ public class ConfigNodeProcedureEnv {
/** Add or remove node lock. */
private final LockQueue nodeLock = new LockQueue();
+ private final DatabaseLifecycleLockManager databaseLifecycleLockManager;
+
private final ReentrantLock schedulerLock = new ReentrantLock(true);
private final ReentrantLock submitRegionMigrateLock = new ReentrantLock(true);
@@ -132,6 +136,7 @@ public class ConfigNodeProcedureEnv {
public ConfigNodeProcedureEnv(ConfigManager configManager, ProcedureScheduler scheduler) {
this.configManager = configManager;
this.scheduler = scheduler;
+ this.databaseLifecycleLockManager = new DatabaseLifecycleLockManager(scheduler);
this.regionMaintainHandler = new RegionMaintainHandler(configManager);
this.removeDataNodeHandler = new RemoveDataNodeHandler(configManager);
this.removeConfigNodeLock = new ReentrantLock();
@@ -159,9 +164,13 @@ public TSStatus deleteDatabaseConfig(final String name, final boolean isGenerate
* @param preDeleteType execute/rollback
* @param deleteSgName database name
*/
- public void preDeleteDatabase(
+ public TSStatus preDeleteDatabase(
final PreDeleteDatabasePlan.PreDeleteType preDeleteType, final String deleteSgName) {
- getPartitionManager().preDeleteDatabase(deleteSgName, preDeleteType);
+ return getPartitionManager().preDeleteDatabase(deleteSgName, preDeleteType);
+ }
+
+ public TSStatus batchRemoveRegionCreateTasks(final String database) {
+ return getPartitionManager().batchRemoveRegionCreateTasks(database);
}
public boolean invalidateCache(final String databaseName) throws IOException, TException {
@@ -490,6 +499,10 @@ public TSStatus persistRegionGroup(CreateRegionGroupsPlan createRegionGroupsPlan
}
}
+ public TSStatus validateCreateRegionGroups(final CreateRegionGroupsPlan createRegionGroupsPlan) {
+ return getPartitionManager().validateCreateRegionGroups(createRegionGroupsPlan);
+ }
+
/**
* Force activating RegionGroup by setting status to Running, therefore the ConfigNode-leader can
* select leader for it and use it to allocate new Partitions
@@ -1135,6 +1148,27 @@ public LockQueue getNodeLock() {
return nodeLock;
}
+ /**
+ * Atomically tries to lock all databases in lexical order.
+ *
+ * @return the first database whose lock is unavailable, or null when all locks are acquired
+ */
+ public String tryLockDatabases(final Procedure> procedure, final Set databaseNames) {
+ return databaseLifecycleLockManager.tryLock(procedure, databaseNames);
+ }
+
+ public void waitDatabaseLock(final Procedure> procedure, final String databaseName) {
+ databaseLifecycleLockManager.waitProcedure(procedure, databaseName);
+ }
+
+ public void releaseDatabaseLocks(final Procedure> procedure, final Set databaseNames) {
+ databaseLifecycleLockManager.releaseLocks(procedure, databaseNames);
+ }
+
+ public DatabaseLifecycleLockManager getDatabaseLifecycleLockManager() {
+ return databaseLifecycleLockManager;
+ }
+
public ProcedureScheduler getScheduler() {
return scheduler;
}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/AbstractDatabaseProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/AbstractDatabaseProcedure.java
new file mode 100644
index 0000000000000..fc41b9afbc5a3
--- /dev/null
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/AbstractDatabaseProcedure.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.confignode.procedure.impl;
+
+import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
+import org.apache.iotdb.confignode.procedure.state.ProcedureLockState;
+
+import java.util.Set;
+
+/** A procedure that holds exclusive lifecycle locks for its databases until it finishes. */
+public abstract class AbstractDatabaseProcedure
+ extends StateMachineProcedure {
+
+ private transient String waitingDatabase;
+
+ protected AbstractDatabaseProcedure() {
+ super();
+ }
+
+ protected AbstractDatabaseProcedure(final boolean isGeneratedByPipe) {
+ super(isGeneratedByPipe);
+ }
+
+ protected abstract Set getDatabaseNames();
+
+ @Override
+ protected ProcedureLockState acquireLock(final ConfigNodeProcedureEnv env) {
+ waitingDatabase = env.tryLockDatabases(this, getDatabaseNames());
+ return waitingDatabase == null
+ ? ProcedureLockState.LOCK_ACQUIRED
+ : ProcedureLockState.LOCK_EVENT_WAIT;
+ }
+
+ @Override
+ protected void waitForLock(final ConfigNodeProcedureEnv env) {
+ if (waitingDatabase != null) {
+ env.waitDatabaseLock(this, waitingDatabase);
+ }
+ }
+
+ @Override
+ protected void releaseLock(final ConfigNodeProcedureEnv env) {
+ env.releaseDatabaseLocks(this, getDatabaseNames());
+ }
+
+ @Override
+ protected boolean holdLock(final ConfigNodeProcedureEnv env) {
+ return true;
+ }
+}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/CreateRegionGroupsProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/CreateRegionGroupsProcedure.java
index 276cdf432d9e6..8672e8e793d80 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/CreateRegionGroupsProcedure.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/CreateRegionGroupsProcedure.java
@@ -38,7 +38,7 @@
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
-import org.apache.iotdb.confignode.procedure.impl.StateMachineProcedure;
+import org.apache.iotdb.confignode.procedure.impl.AbstractDatabaseProcedure;
import org.apache.iotdb.confignode.procedure.state.CreateRegionGroupsState;
import org.apache.iotdb.confignode.procedure.store.ProcedureType;
import org.apache.iotdb.consensus.exception.ConsensusException;
@@ -61,7 +61,7 @@
import java.util.stream.Collectors;
public class CreateRegionGroupsProcedure
- extends StateMachineProcedure {
+ extends AbstractDatabaseProcedure {
private static final Logger LOGGER = LoggerFactory.getLogger(CreateRegionGroupsProcedure.class);
@@ -100,6 +100,23 @@ public CreateRegionGroupsProcedure(
@Override
protected Flow executeFromState(
final ConfigNodeProcedureEnv env, final CreateRegionGroupsState state) {
+ final TSStatus validationStatus = env.validateCreateRegionGroups(createRegionGroupsPlan);
+ if (validationStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ // Only SHUNT_REGION_REPLICAS can have sent create RPCs without transferring ownership of the
+ // planned RegionGroups to PartitionInfo. Delete every planned replica idempotently; all later
+ // states are already owned and cleaned by DeleteDatabaseProcedure.
+ if (state == CreateRegionGroupsState.SHUNT_REGION_REPLICAS) {
+ addPlannedRegionReplicaCleanup();
+ // The children run before this existing terminal state. Validation is repeated when the
+ // parent resumes there, so the procedure reports the original fence failure only after all
+ // possible pre-persistence replicas have been deleted.
+ setNextState(CreateRegionGroupsState.CREATE_REGION_GROUPS_FINISH);
+ return Flow.HAS_MORE_STATE;
+ }
+ setFailure(new ProcedureException(new IoTDBException(validationStatus)));
+ return Flow.NO_MORE_STATE;
+ }
+
switch (state) {
case CREATE_REGION_GROUPS:
failedRegionReplicaSets = env.doRegionCreation(consensusGroupType, createRegionGroupsPlan);
@@ -109,10 +126,7 @@ protected Flow executeFromState(
persistPlan = new CreateRegionGroupsPlan();
final OfferRegionMaintainTasksPlan offerPlan = new OfferRegionMaintainTasksPlan();
// RegionGroups that failed to reach a serving quorum have their redundant (already-created)
- // replicas removed via an independent root RemoveRegionGroupProcedure. Submitting them as
- // root procedures (instead of children) keeps this procedure from waiting for or being
- // failed by the cleanup: each one retries forever until those replicas are deleted, while
- // this procedure proceeds to activate the region groups that did form a quorum.
+ // replicas removed by child procedures while this procedure retains its database locks.
final List removeRegionGroupProcedures = new ArrayList<>();
// Filter those RegionGroups that created successfully
createRegionGroupsPlan
@@ -191,35 +205,32 @@ protected Flow executeFromState(
final TSStatus persistStatus = env.persistRegionGroup(persistPlan);
if (persistStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- setFailure(new ProcedureException(new IoTDBException(persistStatus)));
- return Flow.NO_MORE_STATE;
+ // Keep the database locks and retry the idempotent Consensus write. Releasing the lock or
+ // compensating here would create a gap in which the DataNodes may contain replicas but
+ // neither PartitionInfo nor a running lifecycle procedure owns them.
+ setNextState(CreateRegionGroupsState.SHUNT_REGION_REPLICAS);
+ return Flow.HAS_MORE_STATE;
}
+ final TSStatus offerStatus;
try {
- env.getConfigManager().getConsensusManager().write(offerPlan);
+ offerStatus = env.getConfigManager().getConsensusManager().write(offerPlan);
} catch (final ConsensusException e) {
LOGGER.warn(
ConfigNodeMessages.FAILED_IN_THE_WRITE_API_EXECUTING_THE_CONSENSUS_LAYER_DUE, e);
+ setNextState(CreateRegionGroupsState.SHUNT_REGION_REPLICAS);
+ return Flow.HAS_MORE_STATE;
+ }
+ if (offerStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ setNextState(CreateRegionGroupsState.SHUNT_REGION_REPLICAS);
+ return Flow.HAS_MORE_STATE;
}
- // Submit the redundant-replica cleanups as independent root procedures. This is
- // intentionally NOT guarded by isStateDeserialized(): the executor persists a procedure at
- // a state BEFORE that state's body has run (it advances the state on the previous cycle,
- // then may stop at the inter-state boundary on a leader switch — see
- // ProcedureExecutor#executeProcedure), so a recovery that lands on SHUNT_REGION_REPLICAS
- // means the submissions have NOT happened yet. Skipping them would leave the
- // already-created
- // replicas of sub-quorum region groups on disk with no cleanup and no partition-table
- // record
- // (the else branch above never persisted them). Re-submitting on recovery is safe instead:
- // the cleanups are recomputed from the serialized failedRegionReplicaSets, each gets a
- // fresh
- // procId and performs an idempotent delete, so a duplicate is harmless whereas a skip
- // leaks.
- removeRegionGroupProcedures.forEach(
- removeRegionGroupProcedure ->
- env.getConfigManager()
- .getProcedureManager()
- .getExecutor()
- .submitProcedure(removeRegionGroupProcedure));
+ // Run redundant-replica cleanups as child procedures. The parent keeps the database locks
+ // until they finish, so database deletion cannot overtake an RPC whose RegionGroup was
+ // never persisted. This is intentionally NOT guarded by isStateDeserialized(): the
+ // executor persists a procedure at a state before that state's body has run, so recovery at
+ // SHUNT_REGION_REPLICAS must add these children. They are recomputed from the serialized
+ // failure map and delete idempotently.
+ removeRegionGroupProcedures.forEach(this::addChildProcedure);
setNextState(CreateRegionGroupsState.REBALANCE_DATA_PARTITION_POLICY);
break;
case REBALANCE_DATA_PARTITION_POLICY:
@@ -323,6 +334,22 @@ protected CreateRegionGroupsState getInitialState() {
return CreateRegionGroupsState.CREATE_REGION_GROUPS;
}
+ @Override
+ protected Set getDatabaseNames() {
+ return createRegionGroupsPlan.getRegionGroupMap().keySet();
+ }
+
+ public boolean containsDatabase(final String database) {
+ return createRegionGroupsPlan.getRegionGroupMap().containsKey(database);
+ }
+
+ private void addPlannedRegionReplicaCleanup() {
+ createRegionGroupsPlan.getRegionGroupMap().values().stream()
+ .flatMap(List::stream)
+ .map(RemoveRegionGroupProcedure::new)
+ .forEach(this::addChildProcedure);
+ }
+
@Override
public void serialize(final DataOutputStream stream) throws IOException {
// Must serialize CREATE_REGION_GROUPS.getTypeCode() firstly
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedure.java
index b2b49a688d204..c9c460d855a51 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedure.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedure.java
@@ -60,16 +60,14 @@
* never finished forming. The DataNode runs the deletion asynchronously and this procedure polls
* for the result, so a slow deletion is never wrongly reported as finished.
*
- * This procedure is submitted as an independent root procedure (not a child) by its callers,
- * which only enqueue the deletion and return immediately. It therefore owns the deletion end to
- * end: on any failure it retries the current replica forever (backing off between attempts) instead
- * of giving up, because there is no parent left to fall back to and the region's peer/data must not
- * be left on disk. Each genuine re-attempt uses a FRESH DataNode-side taskId (the DataNode dedups
- * by taskId and caches a terminal result forever, so reusing one taskId would make every retry a
- * no-op that never re-runs the delete); the in-flight taskId is persisted so a leader change
- * re-polls the same task rather than double-submitting. It carries its own {@link
- * TRegionReplicaSet} copy, so it can finish even after the caller has dropped the partition table,
- * and it survives ConfigNode leader change / restart.
+ *
Database lifecycle procedures run this procedure as a child while retaining the database lock.
+ * On any failure it retries the current replica forever (backing off between attempts) instead of
+ * giving up, because the region's peer/data must not be left on disk. Each genuine re-attempt uses
+ * a FRESH DataNode-side taskId (the DataNode dedups by taskId and caches a terminal result forever,
+ * so reusing one taskId would make every retry a no-op that never re-runs the delete); the
+ * in-flight taskId is persisted so a leader change re-polls the same task rather than
+ * double-submitting. It carries its own {@link TRegionReplicaSet} copy and survives ConfigNode
+ * leader change / restart.
*/
public class RemoveRegionGroupProcedure extends RegionOperationProcedure {
private static final Logger LOGGER = LoggerFactory.getLogger(RemoveRegionGroupProcedure.class);
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
index 2af7bd7ad699c..c9240878c9dae 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
@@ -29,7 +29,7 @@
import org.apache.iotdb.confignode.manager.partition.PartitionMetrics;
import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
-import org.apache.iotdb.confignode.procedure.impl.StateMachineProcedure;
+import org.apache.iotdb.confignode.procedure.impl.AbstractDatabaseProcedure;
import org.apache.iotdb.confignode.procedure.impl.region.RemoveRegionGroupProcedure;
import org.apache.iotdb.confignode.procedure.state.schema.DeleteDatabaseState;
import org.apache.iotdb.confignode.procedure.store.ProcedureType;
@@ -43,11 +43,12 @@
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
-public class DeleteDatabaseProcedure
- extends StateMachineProcedure {
+public class DeleteDatabaseProcedure extends AbstractDatabaseProcedure {
private static final Logger LOG = LoggerFactory.getLogger(DeleteDatabaseProcedure.class);
private static final int RETRY_THRESHOLD = 5;
@@ -83,16 +84,35 @@ protected Flow executeFromState(final ConfigNodeProcedureEnv env, final DeleteDa
LOG.info(
ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_PRE_DELETE_DATABASE_ARG_6A1FEACC,
deleteDatabaseSchema.getName());
- env.preDeleteDatabase(
- PreDeleteDatabasePlan.PreDeleteType.EXECUTE, deleteDatabaseSchema.getName());
- setNextState(DeleteDatabaseState.INVALIDATE_CACHE);
+ final TSStatus preDeleteStatus =
+ env.preDeleteDatabase(
+ PreDeleteDatabasePlan.PreDeleteType.EXECUTE, deleteDatabaseSchema.getName());
+ if (preDeleteStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ setNextState(DeleteDatabaseState.INVALIDATE_CACHE);
+ } else if (getCycles() > RETRY_THRESHOLD) {
+ setFailure(
+ new ProcedureException(
+ ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
+ } else {
+ setNextState(DeleteDatabaseState.PRE_DELETE_DATABASE);
+ }
break;
case INVALIDATE_CACHE:
LOG.info(
ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_INVALIDATE_CACHE_DATABASE_ARG_299FC9BC,
deleteDatabaseSchema.getName());
if (env.invalidateCache(deleteDatabaseSchema.getName())) {
- setNextState(DeleteDatabaseState.DELETE_DATABASE_SCHEMA);
+ final TSStatus removeTasksStatus =
+ env.batchRemoveRegionCreateTasks(deleteDatabaseSchema.getName());
+ if (removeTasksStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ setNextState(DeleteDatabaseState.DELETE_DATABASE_SCHEMA);
+ } else if (getCycles() > RETRY_THRESHOLD) {
+ setFailure(
+ new ProcedureException(
+ ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
+ } else {
+ setNextState(DeleteDatabaseState.INVALIDATE_CACHE);
+ }
} else {
setFailure(
new ProcedureException(
@@ -104,13 +124,10 @@ protected Flow executeFromState(final ConfigNodeProcedureEnv env, final DeleteDa
ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_ARG_A49A47AC,
deleteDatabaseSchema.getName());
- // Enqueue deletion of every region group (both schema and data regions) of this database.
- // Each is submitted as an INDEPENDENT root RemoveRegionGroupProcedure rather than a
- // child:
- // this procedure only submits the deletions and then returns, so it can neither wait for
- // nor be failed/rolled-back by a slow or failing region deletion. Each carries its own
- // copy of the replica set, so the deletion still completes (and survives leader change /
- // restart) even after the next state drops the partition table.
+ // Delete every RegionGroup as a child procedure. This procedure keeps the database lock
+ // while the children run, so neither same-name recreation nor a delayed Region creation
+ // can overtake cleanup. Each child carries its own replica-set copy and survives leader
+ // change or restart.
//
// Submission is intentionally NOT guarded by isStateDeserialized(): the executor persists
// a procedure at a state BEFORE that state's body has run (it advances the state on the
@@ -118,10 +135,8 @@ protected Flow executeFromState(final ConfigNodeProcedureEnv env, final DeleteDa
// ProcedureExecutor#executeProcedure). So a recovery that lands on this state means the
// submission has NOT happened yet; skipping it would drop every region group's cleanup
// while the next state still drops the partition table, orphaning the region peers/data
- // on
- // disk with no record of where they live. Re-submitting on recovery is safe instead:
- // every RemoveRegionGroupProcedure gets a fresh procId and performs an idempotent delete,
- // so a duplicate is harmless whereas a skip leaks data.
+ // on disk with no record of where they live. Adding the children again on recovery is
+ // safe because RegionGroup deletion is idempotent.
final List regionReplicaSets =
env.getAllReplicaSets(deleteDatabaseSchema.getName());
regionReplicaSets.forEach(
@@ -130,10 +145,7 @@ protected Flow executeFromState(final ConfigNodeProcedureEnv env, final DeleteDa
env.getConfigManager()
.getLoadManager()
.removeRegionGroupRelatedCache(regionReplicaSet.getRegionId());
- env.getConfigManager()
- .getProcedureManager()
- .getExecutor()
- .submitProcedure(new RemoveRegionGroupProcedure(regionReplicaSet));
+ addChildProcedure(new RemoveRegionGroupProcedure(regionReplicaSet));
});
setNextState(DeleteDatabaseState.DELETE_DATABASE_CONFIG);
break;
@@ -166,6 +178,8 @@ protected Flow executeFromState(final ConfigNodeProcedureEnv env, final DeleteDa
setFailure(
new ProcedureException(
ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
+ } else {
+ setNextState(DeleteDatabaseState.DELETE_DATABASE_CONFIG);
}
}
} catch (final TException | IOException e) {
@@ -187,6 +201,8 @@ protected Flow executeFromState(final ConfigNodeProcedureEnv env, final DeleteDa
setFailure(
new ProcedureException(
ProcedureMessages.DELETEDATABASEPROCEDURE_STATE_STUCK_AT + state));
+ } else {
+ setNextState(state);
}
}
}
@@ -236,6 +252,13 @@ protected DeleteDatabaseState getInitialState() {
return DeleteDatabaseState.PRE_DELETE_DATABASE;
}
+ @Override
+ protected Set getDatabaseNames() {
+ return deleteDatabaseSchema == null
+ ? Collections.emptySet()
+ : Collections.singleton(deleteDatabaseSchema.getName());
+ }
+
public String getDatabase() {
return deleteDatabaseSchema.getName();
}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
new file mode 100644
index 0000000000000..31f75950eb43d
--- /dev/null
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.confignode.procedure.scheduler;
+
+import org.apache.iotdb.confignode.procedure.Procedure;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * The single source of database lifecycle locks in a ConfigNode.
+ *
+ * A lock is keyed by the exact database name. Both short-lived manager requests and procedures
+ * use the same ownership table, so database creation, Region creation, maintenance retries, and
+ * database deletion cannot bypass one another. Procedure ownership is identified by procedure id
+ * rather than worker thread because a procedure can resume on a different executor thread.
+ */
+public class DatabaseLifecycleLockManager {
+
+ private final ProcedureScheduler scheduler;
+ private final ReentrantLock stateLock = new ReentrantLock(true);
+ private final Condition lockReleased = stateLock.newCondition();
+ private final Map lockStateMap = new HashMap<>();
+
+ public DatabaseLifecycleLockManager(final ProcedureScheduler scheduler) {
+ this.scheduler = scheduler;
+ }
+
+ /** Acquire database locks for a synchronous manager request. */
+ public DatabaseLock acquireLocks(final Set databaseNames) {
+ final List orderedDatabases = orderedDatabases(databaseNames);
+ final Thread owner = Thread.currentThread();
+ stateLock.lock();
+ try {
+ while (!canAcquireRequestLocks(owner, orderedDatabases)) {
+ lockReleased.awaitUninterruptibly();
+ }
+ orderedDatabases.forEach(
+ database ->
+ lockStateMap
+ .computeIfAbsent(database, ignored -> new DatabaseLockState())
+ .acquireRequestLock(owner));
+ return new DatabaseLock(this, orderedDatabases, owner);
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ /**
+ * Atomically tries to lock all databases for a procedure.
+ *
+ * @return the first database whose lock is unavailable, or null when all locks are acquired
+ */
+ public String tryLock(final Procedure> procedure, final Set databaseNames) {
+ stateLock.lock();
+ try {
+ final List acquiredDatabases = new ArrayList<>();
+ for (final String database : orderedDatabases(databaseNames)) {
+ final DatabaseLockState lockState =
+ lockStateMap.computeIfAbsent(database, ignored -> new DatabaseLockState());
+ if (!lockState.canAcquireProcedureLock(procedure)) {
+ acquiredDatabases.forEach(
+ acquiredDatabase -> releaseProcedureLock(procedure, acquiredDatabase));
+ return database;
+ }
+ if (lockState.acquireProcedureLock(procedure)) {
+ acquiredDatabases.add(database);
+ }
+ }
+ return null;
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ public void waitProcedure(final Procedure> procedure, final String databaseName) {
+ stateLock.lock();
+ try {
+ final DatabaseLockState lockState =
+ lockStateMap.computeIfAbsent(databaseName, ignored -> new DatabaseLockState());
+ if (lockState.isUnlocked()) {
+ scheduler.addFront(procedure);
+ removeIfIdle(databaseName, lockState);
+ } else {
+ lockState.waitProcedure(procedure);
+ }
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ public void releaseLocks(final Procedure> procedure, final Set databaseNames) {
+ stateLock.lock();
+ try {
+ orderedDatabases(databaseNames)
+ .forEach(database -> releaseProcedureLock(procedure, database));
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ private boolean canAcquireRequestLocks(final Thread owner, final List orderedDatabases) {
+ for (final String database : orderedDatabases) {
+ final DatabaseLockState lockState = lockStateMap.get(database);
+ if (lockState != null && !lockState.canAcquireRequestLock(owner)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void releaseRequestLocks(final List orderedDatabases, final Thread requestOwner) {
+ stateLock.lock();
+ try {
+ for (final String database : orderedDatabases) {
+ final DatabaseLockState lockState = lockStateMap.get(database);
+ if (lockState != null && lockState.releaseRequestLock(requestOwner)) {
+ wakeWaiters(lockState);
+ removeIfIdle(database, lockState);
+ }
+ }
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ private void releaseProcedureLock(final Procedure> procedure, final String database) {
+ final DatabaseLockState lockState = lockStateMap.get(database);
+ if (lockState != null && lockState.releaseProcedureLock(procedure)) {
+ wakeWaiters(lockState);
+ removeIfIdle(database, lockState);
+ }
+ }
+
+ private void wakeWaiters(final DatabaseLockState lockState) {
+ lockState.wakeWaitingProcedures(scheduler);
+ lockReleased.signalAll();
+ }
+
+ private void removeIfIdle(final String database, final DatabaseLockState lockState) {
+ if (lockState.isIdle()) {
+ lockStateMap.remove(database, lockState);
+ }
+ }
+
+ private static List orderedDatabases(final Set databaseNames) {
+ return new ArrayList<>(new TreeSet<>(databaseNames));
+ }
+
+ public static final class DatabaseLock implements AutoCloseable {
+ private final DatabaseLifecycleLockManager lockManager;
+ private final List databaseNames;
+ private final Thread owner;
+ private boolean closed;
+
+ private DatabaseLock(
+ final DatabaseLifecycleLockManager lockManager,
+ final List databaseNames,
+ final Thread owner) {
+ this.lockManager = lockManager;
+ this.databaseNames = databaseNames;
+ this.owner = owner;
+ }
+
+ @Override
+ public void close() {
+ if (!closed) {
+ closed = true;
+ lockManager.releaseRequestLocks(databaseNames, owner);
+ }
+ }
+ }
+
+ private static final class DatabaseLockState {
+ private final ArrayDeque> waitingProcedures = new ArrayDeque<>();
+ private Procedure> procedureOwner;
+ private Thread requestOwner;
+ private int requestHoldCount;
+
+ private boolean canAcquireRequestLock(final Thread owner) {
+ if (requestOwner == owner) {
+ return true;
+ }
+ return requestOwner == null && procedureOwner == null && waitingProcedures.isEmpty();
+ }
+
+ private void acquireRequestLock(final Thread owner) {
+ requestOwner = owner;
+ requestHoldCount++;
+ }
+
+ /**
+ * @return true when the lock became fully released
+ */
+ private boolean releaseRequestLock(final Thread owner) {
+ if (requestOwner != owner) {
+ return false;
+ }
+ requestHoldCount--;
+ if (requestHoldCount == 0) {
+ requestOwner = null;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean canAcquireProcedureLock(final Procedure> procedure) {
+ return requestOwner == null
+ && (procedureOwner == null || procedureOwner.getProcId() == procedure.getProcId());
+ }
+
+ /**
+ * @return true when this invocation newly acquired the lock
+ */
+ private boolean acquireProcedureLock(final Procedure> procedure) {
+ if (procedureOwner == null) {
+ procedureOwner = procedure;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return true when the lock was released
+ */
+ private boolean releaseProcedureLock(final Procedure> procedure) {
+ if (procedureOwner == null || procedureOwner.getProcId() != procedure.getProcId()) {
+ return false;
+ }
+ procedureOwner = null;
+ return true;
+ }
+
+ private void waitProcedure(final Procedure> procedure) {
+ if (waitingProcedures.stream()
+ .noneMatch(waitingProcedure -> waitingProcedure.getProcId() == procedure.getProcId())) {
+ waitingProcedures.addLast(procedure);
+ }
+ }
+
+ private void wakeWaitingProcedures(final ProcedureScheduler procedureScheduler) {
+ while (!waitingProcedures.isEmpty()) {
+ procedureScheduler.addFront(waitingProcedures.pollFirst());
+ }
+ }
+
+ private boolean isUnlocked() {
+ return procedureOwner == null && requestOwner == null;
+ }
+
+ private boolean isIdle() {
+ return isUnlocked() && waitingProcedures.isEmpty();
+ }
+ }
+}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanSerDeTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanSerDeTest.java
index e844dcf6910e9..4fe56aa3f7d43 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanSerDeTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanSerDeTest.java
@@ -118,6 +118,7 @@
import org.apache.iotdb.confignode.consensus.request.write.procedure.UpdateProcedurePlan;
import org.apache.iotdb.confignode.consensus.request.write.quota.SetSpaceQuotaPlan;
import org.apache.iotdb.confignode.consensus.request.write.quota.SetThrottleQuotaPlan;
+import org.apache.iotdb.confignode.consensus.request.write.region.BatchRemoveRegionCreateTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.OfferRegionMaintainTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.PollRegionMaintainTaskPlan;
@@ -273,6 +274,19 @@ public void CreateDatabasePlanTest() throws IOException {
Assert.assertEquals(req0, req1);
}
+ @Test
+ public void CreateRegionGroupsPlanTest() throws IOException {
+ final CreateRegionGroupsPlan plan = new CreateRegionGroupsPlan();
+ plan.addRegionGroup(
+ "root.sg",
+ new TRegionReplicaSet(
+ new TConsensusGroupId(TConsensusGroupType.DataRegion, 1), Collections.emptyList()));
+
+ final CreateRegionGroupsPlan deserializedPlan =
+ (CreateRegionGroupsPlan) ConfigPhysicalPlan.Factory.create(plan.serializeToByteBuffer());
+ Assert.assertEquals(plan, deserializedPlan);
+ }
+
@Test
public void AlterDatabasePlanTest() throws IOException {
DatabaseSchemaPlan req0 =
@@ -423,6 +437,15 @@ public void PollRegionMaintainTaskPlan() throws IOException {
Assert.assertEquals(plan0, plan1);
}
+ @Test
+ public void BatchRemoveRegionCreateTasksPlanTest() throws IOException {
+ final BatchRemoveRegionCreateTasksPlan plan0 = new BatchRemoveRegionCreateTasksPlan("root.sg");
+ final BatchRemoveRegionCreateTasksPlan plan1 =
+ (BatchRemoveRegionCreateTasksPlan)
+ ConfigPhysicalPlan.Factory.create(plan0.serializeToByteBuffer());
+ Assert.assertEquals(plan0, plan1);
+ }
+
@Test
public void CreateSchemaPartitionPlanTest() throws IOException {
TDataNodeLocation dataNodeLocation = new TDataNodeLocation();
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
index 261f60ed65a1d..db40647f2377b 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
@@ -18,10 +18,29 @@
*/
package org.apache.iotdb.confignode.manager;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType;
+import org.apache.iotdb.confignode.consensus.request.write.database.DatabaseSchemaPlan;
import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
+import org.apache.iotdb.confignode.manager.schema.ClusterSchemaQuotaStatistics;
+import org.apache.iotdb.confignode.persistence.schema.ClusterSchemaInfo;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
+import org.apache.iotdb.confignode.procedure.scheduler.SimpleProcedureScheduler;
+import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.rpc.TSStatusCode;
import org.junit.Assert;
import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
public class ClusterSchemaManagerTest {
@@ -37,4 +56,45 @@ public void testCalcMaxRegionGroupNum() {
// (resourceWeight * resource) / (createdStorageGroupNum * replicationFactor)
Assert.assertEquals(20, ClusterSchemaManager.calcMaxRegionGroupNum(3, 1.0, 120, 2, 3, 5));
}
+
+ @Test
+ public void testSetDatabaseWaitsForLifecycleAdmissionBeforeCheckingProcedures() throws Exception {
+ final String database = "root.sg";
+ final IManager configManager = Mockito.mock(IManager.class);
+ final ProcedureManager procedureManager = Mockito.mock(ProcedureManager.class);
+ final DatabaseLifecycleLockManager lockManager =
+ new DatabaseLifecycleLockManager(new SimpleProcedureScheduler());
+ final CountDownLatch admissionAttempted = new CountDownLatch(1);
+ final AtomicBoolean unfinishedProcedure = new AtomicBoolean(false);
+ Mockito.when(configManager.getProcedureManager()).thenReturn(procedureManager);
+ Mockito.when(procedureManager.acquireDatabaseLifecycleLock(database))
+ .thenAnswer(
+ ignored -> {
+ admissionAttempted.countDown();
+ return lockManager.acquireLocks(Collections.singleton(database));
+ });
+ Mockito.when(procedureManager.hasUnfinishedDatabaseLifecycleProcedure(database))
+ .thenAnswer(ignored -> unfinishedProcedure.get());
+
+ final ClusterSchemaManager schemaManager =
+ new ClusterSchemaManager(
+ configManager,
+ Mockito.mock(ClusterSchemaInfo.class),
+ Mockito.mock(ClusterSchemaQuotaStatistics.class));
+ final DatabaseSchemaPlan plan =
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema(database));
+ final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ final Future statusFuture;
+ try (final DatabaseLock ignored = lockManager.acquireLocks(Collections.singleton(database))) {
+ statusFuture = executor.submit(() -> schemaManager.setDatabase(plan, false));
+ Assert.assertTrue(admissionAttempted.await(10, TimeUnit.SECONDS));
+ unfinishedProcedure.set(true);
+ }
+ final TSStatus status = statusFuture.get(10, TimeUnit.SECONDS);
+ Assert.assertEquals(TSStatusCode.METADATA_ERROR.getStatusCode(), status.getCode());
+ Mockito.verify(procedureManager).hasUnfinishedDatabaseLifecycleProcedure(database);
+ executor.shutdownNow();
+ }
}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ProcedureManagerTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ProcedureManagerTest.java
index 4de883b9c158f..193abc74cfa5e 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ProcedureManagerTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ProcedureManagerTest.java
@@ -23,19 +23,24 @@
import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.commons.cluster.NodeStatus;
import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
import org.apache.iotdb.commons.schema.table.TreeViewSchema;
import org.apache.iotdb.commons.schema.table.TsTable;
+import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
import org.apache.iotdb.confignode.manager.load.LoadManager;
import org.apache.iotdb.confignode.procedure.Procedure;
import org.apache.iotdb.confignode.procedure.ProcedureExecutor;
import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
import org.apache.iotdb.confignode.procedure.env.RemoveDataNodeHandler;
import org.apache.iotdb.confignode.procedure.impl.node.RemoveDataNodesProcedure;
+import org.apache.iotdb.confignode.procedure.impl.region.CreateRegionGroupsProcedure;
import org.apache.iotdb.confignode.procedure.impl.region.RegionMigrateProcedure;
import org.apache.iotdb.confignode.procedure.impl.region.RegionMigrationPlan;
+import org.apache.iotdb.confignode.procedure.impl.schema.DeleteDatabaseProcedure;
+import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -128,6 +133,7 @@ public static void setUp() throws IOException {
when(PROCEDURE_MANAGER.getExecutor()).thenReturn(PROCEDURE_EXECUTOR);
when(PROCEDURE_EXECUTOR.getProcedures()).thenReturn(procedureMap);
+ PROCEDURE_MANAGER.setExecutor(PROCEDURE_EXECUTOR);
when(PROCEDURE_MANAGER.getEnv()).thenReturn(ENV);
when(ENV.getRemoveDataNodeHandler()).thenReturn(REMOVE_DATA_NODE_HANDLER);
}
@@ -233,4 +239,31 @@ public void testInjectTreeViewSourceAttributesPreservesUserTreePattern() {
Assert.assertFalse(
topicAttributes.containsKey(PipeSourceConstant.SOURCE_PATTERN_INCLUSION_KEY));
}
+
+ @Test
+ public void testDetectUnfinishedDatabaseLifecycleProcedures() {
+ final CreateRegionGroupsPlan createPlan = new CreateRegionGroupsPlan();
+ createPlan.addRegionGroup(
+ "root.create",
+ new TRegionReplicaSet(
+ new TConsensusGroupId(TConsensusGroupType.DataRegion, 10), List.of()));
+ final CreateRegionGroupsProcedure createProcedure =
+ new CreateRegionGroupsProcedure(TConsensusGroupType.DataRegion, createPlan);
+ createProcedure.setProcId(100);
+ final DeleteDatabaseProcedure deleteProcedure =
+ new DeleteDatabaseProcedure(new TDatabaseSchema("root.delete"), false);
+ deleteProcedure.setProcId(101);
+
+ procedureMap.clear();
+ try {
+ procedureMap.put(createProcedure.getProcId(), createProcedure);
+ procedureMap.put(deleteProcedure.getProcId(), deleteProcedure);
+
+ Assert.assertTrue(PROCEDURE_MANAGER.hasUnfinishedDatabaseLifecycleProcedure("root.create"));
+ Assert.assertTrue(PROCEDURE_MANAGER.hasUnfinishedDatabaseLifecycleProcedure("root.delete"));
+ Assert.assertFalse(PROCEDURE_MANAGER.hasUnfinishedDatabaseLifecycleProcedure("root.other"));
+ } finally {
+ procedureMap.clear();
+ }
+ }
}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
index 8c32378cfcb1a..8a13c24384be0 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
@@ -24,6 +24,7 @@
import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.common.rpc.thrift.TSeriesPartitionSlot;
import org.apache.iotdb.common.rpc.thrift.TTimePartitionSlot;
import org.apache.iotdb.commons.partition.DataPartitionTable;
@@ -32,11 +33,15 @@
import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType;
import org.apache.iotdb.confignode.consensus.request.read.region.GetRegionInfoListPlan;
import org.apache.iotdb.confignode.consensus.request.write.database.DatabaseSchemaPlan;
+import org.apache.iotdb.confignode.consensus.request.write.database.DeleteDatabasePlan;
+import org.apache.iotdb.confignode.consensus.request.write.database.PreDeleteDatabasePlan;
import org.apache.iotdb.confignode.consensus.request.write.partition.CreateDataPartitionPlan;
import org.apache.iotdb.confignode.consensus.request.write.partition.CreateSchemaPartitionPlan;
+import org.apache.iotdb.confignode.consensus.request.write.region.BatchRemoveRegionCreateTasksPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
import org.apache.iotdb.confignode.consensus.request.write.region.OfferRegionMaintainTasksPlan;
import org.apache.iotdb.confignode.consensus.response.partition.RegionInfoListResp;
+import org.apache.iotdb.confignode.exception.DatabaseNotExistsException;
import org.apache.iotdb.confignode.persistence.partition.PartitionInfo;
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionDeleteTask;
@@ -44,6 +49,7 @@
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainType;
import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
import org.apache.iotdb.confignode.rpc.thrift.TShowRegionReq;
+import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.thrift.TException;
import org.apache.tsfile.external.commons.io.FileUtils;
@@ -52,8 +58,10 @@
import org.junit.Before;
import org.junit.Test;
+import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -148,6 +156,17 @@ public void testSnapshot() throws TException, IOException {
partitionInfo.offerRegionMaintainTasks(generateOfferRegionMaintainTasksPlan());
Assert.assertTrue(partitionInfo.processTakeSnapshot(snapshotDir));
+ try (final DataInputStream inputStream =
+ new DataInputStream(
+ Files.newInputStream(new File(snapshotDir, "partition_info.bin").toPath()))) {
+ // Keep the historical snapshot framing: next RegionGroupId followed by database count.
+ Assert.assertEquals(
+ Math.max(
+ schemaRegionReplicaSet.getRegionId().getId(),
+ dataRegionReplicaSet.getRegionId().getId()),
+ inputStream.readInt());
+ Assert.assertEquals(1, inputStream.readInt());
+ }
PartitionInfo partitionInfo1 = new PartitionInfo();
partitionInfo1.processLoadSnapshot(snapshotDir);
@@ -162,7 +181,16 @@ public void testLegacyRegionDeleteTasksAreFiltered() throws TException, IOExcept
// it cannot block the recreation of that region's other replicas.
// The offer plan mixes two RegionCreateTasks with one legacy RegionDeleteTask.
- partitionInfo.offerRegionMaintainTasks(generateOfferRegionMaintainTasksPlan());
+ final OfferRegionMaintainTasksPlan offerPlan = generateOfferRegionMaintainTasksPlan();
+ final RegionCreateTask createTask =
+ (RegionCreateTask) offerPlan.getRegionMaintainTaskList().get(0);
+ partitionInfo.createDatabase(
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema("root.sg")));
+ final CreateRegionGroupsPlan createRegionGroupsPlan = new CreateRegionGroupsPlan();
+ createRegionGroupsPlan.addRegionGroup("root.sg", createTask.getRegionReplicaSet());
+ partitionInfo.createRegionGroups(createRegionGroupsPlan);
+ partitionInfo.offerRegionMaintainTasks(offerPlan);
// The DELETE task is filtered out at offer time; only the two CREATE tasks remain queued.
List queuedTasks = partitionInfo.getRegionMaintainEntryList();
@@ -179,6 +207,165 @@ public void testLegacyRegionDeleteTasksAreFiltered() throws TException, IOExcept
Assert.assertEquals(2, loaded.getRegionMaintainEntryList().size());
}
+ @Test
+ public void testBatchRemoveAllRegionCreateTasksAndSnapshot() throws TException, IOException {
+ final String database = "root.sg";
+ final String otherDatabase = "root.other";
+ partitionInfo.createDatabase(
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema(database)));
+ partitionInfo.createDatabase(
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema(otherDatabase)));
+
+ final TRegionReplicaSet region0 =
+ generateTRegionReplicaSet(0, new TConsensusGroupId(TConsensusGroupType.DataRegion, 0));
+ final TRegionReplicaSet region1 =
+ generateTRegionReplicaSet(10, new TConsensusGroupId(TConsensusGroupType.DataRegion, 1));
+ final TRegionReplicaSet otherRegion =
+ generateTRegionReplicaSet(20, new TConsensusGroupId(TConsensusGroupType.DataRegion, 2));
+ final CreateRegionGroupsPlan createRegionGroupsPlan = new CreateRegionGroupsPlan();
+ createRegionGroupsPlan.addRegionGroup(database, region0);
+ createRegionGroupsPlan.addRegionGroup(database, region1);
+ createRegionGroupsPlan.addRegionGroup(otherDatabase, otherRegion);
+ partitionInfo.createRegionGroups(createRegionGroupsPlan);
+
+ final OfferRegionMaintainTasksPlan offerPlan = new OfferRegionMaintainTasksPlan();
+ offerPlan.appendRegionMaintainTask(
+ new RegionCreateTask(region0.getDataNodeLocations().get(0), database, region0));
+ offerPlan.appendRegionMaintainTask(
+ new RegionCreateTask(region0.getDataNodeLocations().get(1), database, region0));
+ offerPlan.appendRegionMaintainTask(
+ new RegionCreateTask(region1.getDataNodeLocations().get(0), database, region1));
+ offerPlan.appendRegionMaintainTask(
+ new RegionCreateTask(
+ otherRegion.getDataNodeLocations().get(0), otherDatabase, otherRegion));
+ partitionInfo.offerRegionMaintainTasks(offerPlan);
+ Assert.assertEquals(4, partitionInfo.getRegionMaintainEntryList().size());
+ // Retrying an uncertain Consensus write must not duplicate durable create tasks.
+ partitionInfo.offerRegionMaintainTasks(offerPlan);
+ Assert.assertEquals(4, partitionInfo.getRegionMaintainEntryList().size());
+
+ partitionInfo.preDeleteDatabase(
+ new PreDeleteDatabasePlan(database, PreDeleteDatabasePlan.PreDeleteType.EXECUTE));
+ partitionInfo.batchRemoveRegionCreateTasks(new BatchRemoveRegionCreateTasksPlan(database));
+ Assert.assertEquals(1, partitionInfo.getRegionMaintainEntryList().size());
+ Assert.assertEquals(
+ otherRegion.getRegionId(), partitionInfo.getRegionMaintainEntryList().get(0).getRegionId());
+
+ // Replaying the same consensus plan is idempotent, and a snapshot cannot revive removed tasks.
+ partitionInfo.batchRemoveRegionCreateTasks(new BatchRemoveRegionCreateTasksPlan(database));
+ Assert.assertTrue(partitionInfo.processTakeSnapshot(snapshotDir));
+ final PartitionInfo loaded = new PartitionInfo();
+ loaded.processLoadSnapshot(snapshotDir);
+ Assert.assertEquals(1, loaded.getRegionMaintainEntryList().size());
+ Assert.assertEquals(
+ otherRegion.getRegionId(), loaded.getRegionMaintainEntryList().get(0).getRegionId());
+ }
+
+ @Test
+ public void testCancelledTasksCannotAffectRecreatedDatabase() {
+ final String database = "root.sg";
+ partitionInfo.createDatabase(
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema(database)));
+ final TRegionReplicaSet oldRegion =
+ generateTRegionReplicaSet(0, new TConsensusGroupId(TConsensusGroupType.DataRegion, 0));
+ final CreateRegionGroupsPlan oldCreatePlan = new CreateRegionGroupsPlan();
+ oldCreatePlan.addRegionGroup(database, oldRegion);
+ partitionInfo.createRegionGroups(oldCreatePlan);
+
+ final OfferRegionMaintainTasksPlan oldOfferPlan = new OfferRegionMaintainTasksPlan();
+ oldOfferPlan.appendRegionMaintainTask(
+ new RegionCreateTask(oldRegion.getDataNodeLocations().get(0), database, oldRegion));
+ partitionInfo.offerRegionMaintainTasks(oldOfferPlan);
+ Assert.assertEquals(1, partitionInfo.getRegionMaintainEntryList().size());
+
+ partitionInfo.preDeleteDatabase(
+ new PreDeleteDatabasePlan(database, PreDeleteDatabasePlan.PreDeleteType.EXECUTE));
+ final BatchRemoveRegionCreateTasksPlan oldCancellation =
+ new BatchRemoveRegionCreateTasksPlan(database);
+ partitionInfo.batchRemoveRegionCreateTasks(oldCancellation);
+ Assert.assertTrue(partitionInfo.getRegionMaintainEntryList().isEmpty());
+
+ // A late offer from the old create procedure is rejected after PRE_DELETE.
+ partitionInfo.offerRegionMaintainTasks(oldOfferPlan);
+ Assert.assertTrue(partitionInfo.getRegionMaintainEntryList().isEmpty());
+
+ partitionInfo.deleteDatabase(new DeleteDatabasePlan(database));
+ partitionInfo.createDatabase(
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema(database)));
+ final TRegionReplicaSet newRegion =
+ generateTRegionReplicaSet(10, new TConsensusGroupId(TConsensusGroupType.DataRegion, 1));
+ final CreateRegionGroupsPlan newCreatePlan = new CreateRegionGroupsPlan();
+ newCreatePlan.addRegionGroup(database, newRegion);
+ partitionInfo.createRegionGroups(newCreatePlan);
+ final OfferRegionMaintainTasksPlan newOfferPlan = new OfferRegionMaintainTasksPlan();
+ newOfferPlan.appendRegionMaintainTask(
+ new RegionCreateTask(newRegion.getDataNodeLocations().get(0), database, newRegion));
+ partitionInfo.offerRegionMaintainTasks(newOfferPlan);
+
+ // Replaying the old cancellation is a no-op because the same-name database is not pre-deleted.
+ // A late task offer from the old RegionGroup is rejected by Region ownership validation.
+ partitionInfo.batchRemoveRegionCreateTasks(oldCancellation);
+ partitionInfo.offerRegionMaintainTasks(oldOfferPlan);
+ Assert.assertEquals(1, partitionInfo.getRegionMaintainEntryList().size());
+ Assert.assertEquals(
+ newRegion.getRegionId(), partitionInfo.getRegionMaintainEntryList().get(0).getRegionId());
+ }
+
+ @Test
+ public void testCreateRegionGroupsRejectsPreDeletedAndMissingDatabase()
+ throws DatabaseNotExistsException {
+ final String database = "root.lifecycle";
+ partitionInfo.createDatabase(
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema(database)));
+
+ final CreateRegionGroupsPlan preDeletedPlan = new CreateRegionGroupsPlan();
+ preDeletedPlan.addRegionGroup(
+ database,
+ generateTRegionReplicaSet(0, new TConsensusGroupId(TConsensusGroupType.DataRegion, 1)));
+ partitionInfo.preDeleteDatabase(
+ new PreDeleteDatabasePlan(database, PreDeleteDatabasePlan.PreDeleteType.EXECUTE));
+
+ TSStatus status = partitionInfo.createRegionGroups(preDeletedPlan);
+ Assert.assertEquals(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode(), status.getCode());
+ Assert.assertTrue(
+ partitionInfo.getAllReplicaSets(database, TConsensusGroupType.DataRegion).isEmpty());
+
+ final CreateRegionGroupsPlan missingPlan = new CreateRegionGroupsPlan();
+ missingPlan.addRegionGroup(
+ "root.missing",
+ generateTRegionReplicaSet(10, new TConsensusGroupId(TConsensusGroupType.SchemaRegion, 2)));
+ status = partitionInfo.createRegionGroups(missingPlan);
+ Assert.assertEquals(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode(), status.getCode());
+ }
+
+ @Test
+ public void testBatchedCreateRegionGroupsPlanIsValidatedAtomically()
+ throws DatabaseNotExistsException {
+ final String existingDatabase = "root.existing";
+ partitionInfo.createDatabase(
+ new DatabaseSchemaPlan(
+ ConfigPhysicalPlanType.CreateDatabase, new TDatabaseSchema(existingDatabase)));
+
+ final CreateRegionGroupsPlan batchedPlan = new CreateRegionGroupsPlan();
+ batchedPlan.addRegionGroup(
+ existingDatabase,
+ generateTRegionReplicaSet(0, new TConsensusGroupId(TConsensusGroupType.DataRegion, 40)));
+ batchedPlan.addRegionGroup(
+ "root.missing",
+ generateTRegionReplicaSet(10, new TConsensusGroupId(TConsensusGroupType.DataRegion, 41)));
+
+ final TSStatus status = partitionInfo.createRegionGroups(batchedPlan);
+ Assert.assertEquals(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode(), status.getCode());
+ Assert.assertEquals(
+ 0, partitionInfo.getRegionGroupCount(existingDatabase, TConsensusGroupType.DataRegion));
+ Assert.assertEquals(42, partitionInfo.generateNextRegionGroupId());
+ }
+
@Test
public void testGetRegionType() {
@@ -322,14 +509,15 @@ private OfferRegionMaintainTasksPlan generateOfferRegionMaintainTasksPlan() {
dataNodeLocation.setMPPDataExchangeEndPoint(new TEndPoint("0.0.0.0", 10740));
dataNodeLocation.setDataRegionConsensusEndPoint(new TEndPoint("0.0.0.0", 10760));
dataNodeLocation.setSchemaRegionConsensusEndPoint(new TEndPoint("0.0.0.0", 10750));
+ final TDataNodeLocation secondDataNodeLocation = dataNodeLocation.deepCopy().setDataNodeId(1);
TRegionReplicaSet regionReplicaSet = new TRegionReplicaSet();
regionReplicaSet.setRegionId(new TConsensusGroupId(TConsensusGroupType.DataRegion, 0));
- regionReplicaSet.setDataNodeLocations(Collections.singletonList(dataNodeLocation));
+ regionReplicaSet.setDataNodeLocations(List.of(dataNodeLocation, secondDataNodeLocation));
OfferRegionMaintainTasksPlan offerPlan = new OfferRegionMaintainTasksPlan();
offerPlan.appendRegionMaintainTask(
- new RegionCreateTask(dataNodeLocation, "root.sg", regionReplicaSet));
+ new RegionCreateTask(secondDataNodeLocation, "root.sg", regionReplicaSet));
offerPlan.appendRegionMaintainTask(
new RegionCreateTask(dataNodeLocation, "root.sg", regionReplicaSet));
offerPlan.appendRegionMaintainTask(
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/CreateRegionGroupsProcedureTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/CreateRegionGroupsProcedureTest.java
index 6269953736100..85358fbfe8d5f 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/CreateRegionGroupsProcedureTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/CreateRegionGroupsProcedureTest.java
@@ -24,20 +24,35 @@
import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
+import org.apache.iotdb.confignode.manager.ConfigManager;
+import org.apache.iotdb.confignode.manager.consensus.ConsensusManager;
+import org.apache.iotdb.confignode.procedure.Procedure;
+import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
import org.apache.iotdb.confignode.procedure.impl.region.CreateRegionGroupsProcedure;
+import org.apache.iotdb.confignode.procedure.impl.region.RemoveRegionGroupProcedure;
+import org.apache.iotdb.confignode.procedure.impl.schema.DeleteDatabaseProcedure;
+import org.apache.iotdb.confignode.procedure.scheduler.ProcedureScheduler;
+import org.apache.iotdb.confignode.procedure.state.CreateRegionGroupsState;
+import org.apache.iotdb.confignode.procedure.state.ProcedureLockState;
import org.apache.iotdb.confignode.procedure.store.ProcedureFactory;
import org.apache.iotdb.confignode.procedure.store.ProcedureType;
+import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.tsfile.utils.PublicBAOS;
import org.junit.Assert;
import org.junit.Test;
+import org.mockito.Mockito;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import static org.apache.iotdb.common.rpc.thrift.TConsensusGroupType.DataRegion;
@@ -47,6 +62,72 @@
public class CreateRegionGroupsProcedureTest {
+ private static class TestCreateRegionGroupsProcedure extends CreateRegionGroupsProcedure {
+
+ private final List> childProcedures = new ArrayList<>();
+
+ private TestCreateRegionGroupsProcedure() {
+ super();
+ }
+
+ private TestCreateRegionGroupsProcedure(
+ final TConsensusGroupType consensusGroupType,
+ final CreateRegionGroupsPlan createRegionGroupsPlan,
+ final CreateRegionGroupsPlan persistPlan,
+ final Map failedRegionReplicaSets) {
+ super(consensusGroupType, createRegionGroupsPlan, persistPlan, failedRegionReplicaSets);
+ }
+
+ private void executeShunt(final ConfigNodeProcedureEnv env) {
+ executeFromState(env, CreateRegionGroupsState.SHUNT_REGION_REPLICAS);
+ }
+
+ private void executeCreate(final ConfigNodeProcedureEnv env) {
+ executeFromState(env, CreateRegionGroupsState.CREATE_REGION_GROUPS);
+ }
+
+ private void executePostPersist(final ConfigNodeProcedureEnv env) {
+ executeFromState(env, CreateRegionGroupsState.REBALANCE_DATA_PARTITION_POLICY);
+ }
+
+ private void executeFinish(final ConfigNodeProcedureEnv env) {
+ executeFromState(env, CreateRegionGroupsState.CREATE_REGION_GROUPS_FINISH);
+ }
+
+ private ProcedureLockState acquireDatabaseLock(final ConfigNodeProcedureEnv env) {
+ return acquireLock(env);
+ }
+
+ private void releaseDatabaseLock(final ConfigNodeProcedureEnv env) {
+ releaseLock(env);
+ }
+
+ @Override
+ protected void addChildProcedure(final Procedure childProcedure) {
+ super.addChildProcedure(childProcedure);
+ childProcedures.add(childProcedure);
+ }
+
+ private List> getChildProcedures() {
+ return childProcedures;
+ }
+ }
+
+ private static class TestDeleteDatabaseProcedure extends DeleteDatabaseProcedure {
+
+ private TestDeleteDatabaseProcedure(final TDatabaseSchema databaseSchema) {
+ super(databaseSchema, false);
+ }
+
+ private ProcedureLockState acquireDatabaseLock(final ConfigNodeProcedureEnv env) {
+ return acquireLock(env);
+ }
+
+ private void releaseDatabaseLock(final ConfigNodeProcedureEnv env) {
+ releaseLock(env);
+ }
+ }
+
@Test
public void serializeDeserializeTest() {
TDataNodeLocation dataNodeLocation0 = new TDataNodeLocation();
@@ -111,6 +192,7 @@ public void serializeDeserializeTest() {
ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size());
Assert.assertEquals(ProcedureType.CREATE_REGION_GROUPS.getTypeCode(), buffer.getShort());
procedure1.deserialize(buffer);
+ Assert.assertFalse(buffer.hasRemaining());
assertEquals(procedure0, procedure1);
assertEquals(procedure0.hashCode(), procedure1.hashCode());
@@ -124,4 +206,177 @@ public void serializeDeserializeTest() {
fail();
}
}
+
+ @Test
+ public void testPersistFailureRetriesWithoutReleasingOwnership() {
+ final TDataNodeLocation createdDataNode =
+ new TDataNodeLocation().setDataNodeId(1).setInternalEndPoint(new TEndPoint("0.0.0.1", 1));
+ final TDataNodeLocation failedDataNode =
+ new TDataNodeLocation().setDataNodeId(2).setInternalEndPoint(new TEndPoint("0.0.0.2", 2));
+ final TDataNodeLocation otherFailedDataNode =
+ new TDataNodeLocation().setDataNodeId(3).setInternalEndPoint(new TEndPoint("0.0.0.3", 3));
+ final TConsensusGroupId regionId = new TConsensusGroupId(DataRegion, 10);
+ final TConsensusGroupId otherRegionId = new TConsensusGroupId(DataRegion, 11);
+ final TRegionReplicaSet allocatedReplicaSet =
+ new TRegionReplicaSet(regionId, List.of(createdDataNode, failedDataNode));
+ final TRegionReplicaSet failedReplicaSet =
+ new TRegionReplicaSet(regionId, Collections.singletonList(failedDataNode));
+ final TRegionReplicaSet otherAllocatedReplicaSet =
+ new TRegionReplicaSet(otherRegionId, Collections.singletonList(otherFailedDataNode));
+
+ final CreateRegionGroupsPlan createPlan = new CreateRegionGroupsPlan();
+ createPlan.addRegionGroup("root.sg", allocatedReplicaSet);
+ createPlan.addRegionGroup("root.sg", otherAllocatedReplicaSet);
+ final Map failedReplicaSets = new HashMap<>();
+ failedReplicaSets.put(regionId, failedReplicaSet);
+ failedReplicaSets.put(otherRegionId, otherAllocatedReplicaSet);
+ final TestCreateRegionGroupsProcedure procedure =
+ new TestCreateRegionGroupsProcedure(
+ DataRegion, createPlan, new CreateRegionGroupsPlan(), failedReplicaSets);
+
+ final ConfigNodeProcedureEnv env = Mockito.mock(ConfigNodeProcedureEnv.class);
+ Mockito.when(env.validateCreateRegionGroups(createPlan))
+ .thenReturn(new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()));
+ Mockito.when(env.persistRegionGroup(Mockito.any()))
+ .thenReturn(new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode()));
+
+ procedure.executeShunt(env);
+
+ Assert.assertTrue(procedure.getChildProcedures().isEmpty());
+ Assert.assertFalse(procedure.isFailed());
+ Mockito.verify(env).persistRegionGroup(Mockito.any());
+ }
+
+ @Test
+ public void testRegionCreateTaskPersistenceRetriesIdempotently() throws Exception {
+ final TDataNodeLocation dataNode0 = new TDataNodeLocation().setDataNodeId(1);
+ final TDataNodeLocation dataNode1 = new TDataNodeLocation().setDataNodeId(2);
+ final TDataNodeLocation failedDataNode = new TDataNodeLocation().setDataNodeId(3);
+ final TConsensusGroupId regionId = new TConsensusGroupId(DataRegion, 10);
+ final TRegionReplicaSet allocatedReplicaSet =
+ new TRegionReplicaSet(regionId, List.of(dataNode0, dataNode1, failedDataNode));
+ final TRegionReplicaSet failedReplicaSet =
+ new TRegionReplicaSet(regionId, Collections.singletonList(failedDataNode));
+ final CreateRegionGroupsPlan createPlan = new CreateRegionGroupsPlan();
+ createPlan.addRegionGroup("root.sg", allocatedReplicaSet);
+ final Map failedReplicaSets = new HashMap<>();
+ failedReplicaSets.put(regionId, failedReplicaSet);
+ final TestCreateRegionGroupsProcedure procedure =
+ new TestCreateRegionGroupsProcedure(
+ DataRegion, createPlan, new CreateRegionGroupsPlan(), failedReplicaSets);
+
+ final ConfigNodeProcedureEnv env = Mockito.mock(ConfigNodeProcedureEnv.class);
+ final ConfigManager configManager = Mockito.mock(ConfigManager.class);
+ final ConsensusManager consensusManager = Mockito.mock(ConsensusManager.class);
+ final TSStatus success = new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
+ final TSStatus failure = new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode());
+ Mockito.when(env.validateCreateRegionGroups(createPlan)).thenReturn(success);
+ Mockito.when(env.persistRegionGroup(Mockito.any())).thenReturn(success);
+ Mockito.when(env.getConfigManager()).thenReturn(configManager);
+ Mockito.when(configManager.getConsensusManager()).thenReturn(consensusManager);
+ Mockito.when(consensusManager.write(Mockito.any())).thenReturn(failure, success);
+
+ procedure.executeShunt(env);
+ procedure.executeShunt(env);
+
+ Assert.assertFalse(procedure.isFailed());
+ Mockito.verify(env, Mockito.times(2)).persistRegionGroup(Mockito.any());
+ Mockito.verify(consensusManager, Mockito.times(2)).write(Mockito.any());
+ }
+
+ @Test
+ public void testFencedBeforeCreateRpcDoesNotSubmitCleanup() {
+ final String database = "root.sg";
+ final CreateRegionGroupsPlan createPlan = new CreateRegionGroupsPlan();
+ createPlan.addRegionGroup(
+ database,
+ new TRegionReplicaSet(new TConsensusGroupId(DataRegion, 10), Collections.emptyList()));
+ final TestCreateRegionGroupsProcedure procedure =
+ new TestCreateRegionGroupsProcedure(
+ DataRegion, createPlan, new CreateRegionGroupsPlan(), Collections.emptyMap());
+
+ final ConfigNodeProcedureEnv env = Mockito.mock(ConfigNodeProcedureEnv.class);
+ Mockito.when(env.validateCreateRegionGroups(createPlan))
+ .thenReturn(new TSStatus(TSStatusCode.DATABASE_CONFIG_ERROR.getStatusCode()));
+ procedure.executeCreate(env);
+
+ Mockito.verify(env, Mockito.never())
+ .doRegionCreation(Mockito.any(), Mockito.any(CreateRegionGroupsPlan.class));
+ Mockito.verify(env, Mockito.never()).getConfigManager();
+ Assert.assertTrue(procedure.isFailed());
+ }
+
+ @Test
+ public void testFencedAfterCreateRpcCleansEveryPlannedRegionReplica() {
+ final TRegionReplicaSet replicaSet =
+ new TRegionReplicaSet(
+ new TConsensusGroupId(DataRegion, 10),
+ Collections.singletonList(new TDataNodeLocation().setDataNodeId(1)));
+ final CreateRegionGroupsPlan createPlan = new CreateRegionGroupsPlan();
+ createPlan.addRegionGroup("root.sg", replicaSet);
+ final TestCreateRegionGroupsProcedure procedure =
+ new TestCreateRegionGroupsProcedure(
+ DataRegion, createPlan, new CreateRegionGroupsPlan(), Collections.emptyMap());
+
+ final ConfigNodeProcedureEnv env = Mockito.mock(ConfigNodeProcedureEnv.class);
+ Mockito.when(env.validateCreateRegionGroups(createPlan))
+ .thenReturn(new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode()));
+
+ procedure.executeShunt(env);
+
+ Assert.assertEquals(
+ Collections.singletonList(new RemoveRegionGroupProcedure(replicaSet)),
+ procedure.getChildProcedures());
+ Mockito.verify(env, Mockito.never()).persistRegionGroup(Mockito.any());
+ Assert.assertFalse(procedure.isFailed());
+ procedure.executeFinish(env);
+ Assert.assertTrue(procedure.isFailed());
+ }
+
+ @Test
+ public void testFencedAfterPersistenceDoesNotSubmitCleanup() {
+ final CreateRegionGroupsPlan createPlan = new CreateRegionGroupsPlan();
+ createPlan.addRegionGroup(
+ "root.sg",
+ new TRegionReplicaSet(new TConsensusGroupId(DataRegion, 10), Collections.emptyList()));
+ final TestCreateRegionGroupsProcedure procedure =
+ new TestCreateRegionGroupsProcedure(
+ DataRegion, createPlan, createPlan, Collections.emptyMap());
+
+ final ConfigNodeProcedureEnv env = Mockito.mock(ConfigNodeProcedureEnv.class);
+ Mockito.when(env.validateCreateRegionGroups(createPlan))
+ .thenReturn(new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode()));
+
+ procedure.executePostPersist(env);
+
+ Mockito.verify(env, Mockito.never()).getConfigManager();
+ Assert.assertTrue(procedure.isFailed());
+ }
+
+ @Test
+ public void testCreateAndDeleteDatabaseLifecycleAreMutuallyExclusive() {
+ final String database = "root.sg";
+ final CreateRegionGroupsPlan createPlan = new CreateRegionGroupsPlan();
+ createPlan.addRegionGroup(
+ database,
+ new TRegionReplicaSet(new TConsensusGroupId(DataRegion, 1), Collections.emptyList()));
+ final TestCreateRegionGroupsProcedure createProcedure =
+ new TestCreateRegionGroupsProcedure(
+ DataRegion, createPlan, new CreateRegionGroupsPlan(), Collections.emptyMap());
+ createProcedure.setProcId(1);
+ final TestDeleteDatabaseProcedure deleteProcedure =
+ new TestDeleteDatabaseProcedure(new TDatabaseSchema(database));
+ deleteProcedure.setProcId(2);
+
+ final ConfigNodeProcedureEnv env =
+ new ConfigNodeProcedureEnv(
+ Mockito.mock(ConfigManager.class), Mockito.mock(ProcedureScheduler.class));
+ Assert.assertEquals(ProcedureLockState.LOCK_ACQUIRED, createProcedure.acquireDatabaseLock(env));
+ Assert.assertEquals(
+ ProcedureLockState.LOCK_EVENT_WAIT, deleteProcedure.acquireDatabaseLock(env));
+
+ createProcedure.releaseDatabaseLock(env);
+ Assert.assertEquals(ProcedureLockState.LOCK_ACQUIRED, deleteProcedure.acquireDatabaseLock(env));
+ deleteProcedure.releaseDatabaseLock(env);
+ }
}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedureTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedureTest.java
index b12f49d9bd7da..2050605df61e2 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedureTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedureTest.java
@@ -19,20 +19,65 @@
package org.apache.iotdb.confignode.procedure.impl.schema;
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupId;
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
+import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
+import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.confignode.consensus.request.write.database.PreDeleteDatabasePlan;
+import org.apache.iotdb.confignode.manager.ConfigManager;
+import org.apache.iotdb.confignode.manager.load.LoadManager;
+import org.apache.iotdb.confignode.procedure.Procedure;
+import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
+import org.apache.iotdb.confignode.procedure.impl.region.RemoveRegionGroupProcedure;
+import org.apache.iotdb.confignode.procedure.state.schema.DeleteDatabaseState;
import org.apache.iotdb.confignode.procedure.store.ProcedureFactory;
import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.tsfile.utils.PublicBAOS;
import org.junit.Test;
+import org.mockito.Mockito;
import java.io.DataOutputStream;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
public class DeleteDatabaseProcedureTest {
+ private static class TestDeleteDatabaseProcedure extends DeleteDatabaseProcedure {
+ private final List> childProcedures = new ArrayList<>();
+
+ private TestDeleteDatabaseProcedure(final TDatabaseSchema databaseSchema) {
+ super(databaseSchema, false);
+ }
+
+ @Override
+ protected void addChildProcedure(final Procedure childProcedure) {
+ super.addChildProcedure(childProcedure);
+ childProcedures.add(childProcedure);
+ }
+
+ private void executeRegionGroupDeletion(final ConfigNodeProcedureEnv env)
+ throws InterruptedException {
+ executeFromState(env, DeleteDatabaseState.DELETE_DATABASE_SCHEMA);
+ }
+
+ private void executeOneStep(final ConfigNodeProcedureEnv env) throws InterruptedException {
+ execute(env);
+ }
+
+ private DeleteDatabaseState currentState() {
+ return getCurrentState();
+ }
+ }
+
@Test
public void serializeDeserializeTest() {
@@ -47,10 +92,55 @@ public void serializeDeserializeTest() {
DeleteDatabaseProcedure p2 =
(DeleteDatabaseProcedure) ProcedureFactory.getInstance().create(buffer);
+ assertFalse(buffer.hasRemaining());
assertEquals(p1, p2);
} catch (Exception e) {
fail();
}
}
+
+ @Test
+ public void testRegionGroupsAreDeletedByChildProcedures() throws InterruptedException {
+ final TRegionReplicaSet regionReplicaSet =
+ new TRegionReplicaSet(
+ new TConsensusGroupId(TConsensusGroupType.DataRegion, 1),
+ Collections.singletonList(new TDataNodeLocation().setDataNodeId(1)));
+ final TestDeleteDatabaseProcedure procedure =
+ new TestDeleteDatabaseProcedure(new TDatabaseSchema("root.sg"));
+ final ConfigNodeProcedureEnv env = Mockito.mock(ConfigNodeProcedureEnv.class);
+ final ConfigManager configManager = Mockito.mock(ConfigManager.class);
+ Mockito.when(env.getConfigManager()).thenReturn(configManager);
+ Mockito.when(configManager.getLoadManager()).thenReturn(Mockito.mock(LoadManager.class));
+ Mockito.when(env.getAllReplicaSets("root.sg"))
+ .thenReturn(Collections.singletonList(regionReplicaSet));
+
+ procedure.executeRegionGroupDeletion(env);
+
+ assertEquals(
+ Collections.singletonList(new RemoveRegionGroupProcedure(regionReplicaSet)),
+ procedure.childProcedures);
+ }
+
+ @Test
+ public void testTransientConsensusFailuresRetryTheSameState() throws Exception {
+ final TestDeleteDatabaseProcedure procedure =
+ new TestDeleteDatabaseProcedure(new TDatabaseSchema("root.sg"));
+ final ConfigNodeProcedureEnv env = Mockito.mock(ConfigNodeProcedureEnv.class);
+ final TSStatus success = new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
+ final TSStatus failure = new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode());
+ Mockito.when(env.preDeleteDatabase(PreDeleteDatabasePlan.PreDeleteType.EXECUTE, "root.sg"))
+ .thenReturn(failure, success);
+ Mockito.when(env.invalidateCache("root.sg")).thenReturn(true);
+ Mockito.when(env.batchRemoveRegionCreateTasks("root.sg")).thenReturn(failure, success);
+
+ procedure.executeOneStep(env);
+ assertEquals(DeleteDatabaseState.PRE_DELETE_DATABASE, procedure.currentState());
+ procedure.executeOneStep(env);
+ assertEquals(DeleteDatabaseState.INVALIDATE_CACHE, procedure.currentState());
+ procedure.executeOneStep(env);
+ assertEquals(DeleteDatabaseState.INVALIDATE_CACHE, procedure.currentState());
+ procedure.executeOneStep(env);
+ assertEquals(DeleteDatabaseState.DELETE_DATABASE_SCHEMA, procedure.currentState());
+ }
}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java
new file mode 100644
index 0000000000000..366c58553ee7a
--- /dev/null
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.confignode.procedure.scheduler;
+
+import org.apache.iotdb.confignode.procedure.Procedure;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+public class DatabaseLifecycleLockManagerTest {
+
+ @Test
+ public void testRequestLocksAreScopedByDatabaseName() throws Exception {
+ final DatabaseLifecycleLockManager lockManager =
+ new DatabaseLifecycleLockManager(Mockito.mock(ProcedureScheduler.class));
+ final ExecutorService executor = Executors.newFixedThreadPool(2);
+ final CountDownLatch sameDatabaseAcquired = new CountDownLatch(1);
+ try (final DatabaseLock ignored = lockManager.acquireLocks(Collections.singleton("root.sg"))) {
+ final Future> sameDatabaseFuture =
+ executor.submit(
+ () -> {
+ try (final DatabaseLock sameDatabaseLock =
+ lockManager.acquireLocks(Collections.singleton("root.sg"))) {
+ sameDatabaseAcquired.countDown();
+ }
+ });
+ final Future> otherDatabaseFuture =
+ executor.submit(
+ () -> {
+ try (final DatabaseLock otherDatabaseLock =
+ lockManager.acquireLocks(Collections.singleton("root.other"))) {
+ // Acquiring a different database proves the lock is not cluster-global.
+ }
+ });
+
+ otherDatabaseFuture.get(10, TimeUnit.SECONDS);
+ Assert.assertFalse(sameDatabaseAcquired.await(200, TimeUnit.MILLISECONDS));
+ Assert.assertFalse(sameDatabaseFuture.isDone());
+ } finally {
+ executor.shutdownNow();
+ }
+ Assert.assertTrue(sameDatabaseAcquired.await(10, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testProcedureAndRequestShareTheSameLock() throws Exception {
+ final ProcedureScheduler scheduler = Mockito.mock(ProcedureScheduler.class);
+ final DatabaseLifecycleLockManager lockManager = new DatabaseLifecycleLockManager(scheduler);
+ final Procedure> owner = procedure(1);
+ final Procedure> waiter = procedure(2);
+ final Set databases = Collections.singleton("root.sg");
+
+ Assert.assertNull(lockManager.tryLock(owner, databases));
+ Assert.assertEquals("root.sg", lockManager.tryLock(waiter, databases));
+ lockManager.waitProcedure(waiter, "root.sg");
+
+ final ExecutorService executor = Executors.newSingleThreadExecutor();
+ final CountDownLatch requestAcquired = new CountDownLatch(1);
+ try {
+ executor.submit(
+ () -> {
+ try (final DatabaseLock ignored = lockManager.acquireLocks(databases)) {
+ requestAcquired.countDown();
+ }
+ });
+ Assert.assertFalse(requestAcquired.await(200, TimeUnit.MILLISECONDS));
+
+ lockManager.releaseLocks(owner, databases);
+ Mockito.verify(scheduler).addFront(waiter);
+ Assert.assertTrue(requestAcquired.await(10, TimeUnit.SECONDS));
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ @Test
+ public void testFailedMultiDatabaseAcquisitionDoesNotLeakPartialLocks() {
+ final DatabaseLifecycleLockManager lockManager =
+ new DatabaseLifecycleLockManager(Mockito.mock(ProcedureScheduler.class));
+ final Procedure> firstProcedure = procedure(1);
+ final Procedure> secondProcedure = procedure(2);
+
+ Assert.assertNull(lockManager.tryLock(firstProcedure, Collections.singleton("root.b")));
+ Assert.assertEquals("root.b", lockManager.tryLock(secondProcedure, Set.of("root.a", "root.b")));
+
+ try (final DatabaseLock ignored = lockManager.acquireLocks(Collections.singleton("root.a"))) {
+ // The partial root.a acquisition of secondProcedure must have been released.
+ }
+ lockManager.releaseLocks(firstProcedure, Collections.singleton("root.b"));
+ }
+
+ @Test
+ public void testRequestLocksAreReentrantOnTheOwningThread() {
+ final DatabaseLifecycleLockManager lockManager =
+ new DatabaseLifecycleLockManager(Mockito.mock(ProcedureScheduler.class));
+ final Procedure> procedure = procedure(1);
+ final Set databases = Collections.singleton("root.sg");
+
+ try (final DatabaseLock outerLock = lockManager.acquireLocks(databases)) {
+ try (final DatabaseLock innerLock = lockManager.acquireLocks(databases)) {
+ Assert.assertEquals("root.sg", lockManager.tryLock(procedure, databases));
+ }
+ Assert.assertEquals("root.sg", lockManager.tryLock(procedure, databases));
+ }
+ Assert.assertNull(lockManager.tryLock(procedure, databases));
+ lockManager.releaseLocks(procedure, databases);
+ }
+
+ private static Procedure> procedure(final long procedureId) {
+ final Procedure> procedure = Mockito.mock(Procedure.class);
+ Mockito.when(procedure.getProcId()).thenReturn(procedureId);
+ return procedure;
+ }
+}
From 78a28c533488244abf51dc434487db5b17205350 Mon Sep 17 00:00:00 2001
From: Yongzao <532741407@qq.com>
Date: Wed, 22 Jul 2026 22:44:26 +0800
Subject: [PATCH 2/5] [ConfigNode] Address lifecycle lock review feedback
---
.../confignode/i18n/ManagerMessages.java | 2 -
.../confignode/i18n/ManagerMessages.java | 2 -
.../confignode/manager/ProcedureManager.java | 131 ++++++++----
.../manager/partition/PartitionManager.java | 118 ++++++-----
.../persistence/partition/PartitionInfo.java | 23 +-
.../impl/AbstractDatabaseProcedure.java | 6 +-
.../impl/schema/DeleteDatabaseProcedure.java | 200 ++++++++++--------
.../DatabaseLifecycleLockManager.java | 99 +++++++--
.../DatabaseLifecycleLockManagerTest.java | 36 +++-
9 files changed, 379 insertions(+), 238 deletions(-)
diff --git a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 45361a98d8053..8eb35035b1754 100644
--- a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -522,8 +522,6 @@ public final class ManagerMessages {
"Unexpected interruption during waiting for configNode leader ready.";
public static final String UNEXPECTED_INTERRUPTION_DURING_WAITING_FOR_GET_CLUSTER_ID =
"Unexpected interruption during waiting for get cluster id.";
- public static final String UNEXPECTED_NON_CREATE_REGION_MAINTAIN_TASK_SKIPPED =
- "Unexpected non-create task in the RegionMaintainer queue; skipping it (the queue only recreates region replicas now, and region deletion is handled by RemoveRegionGroupProcedure).";
public static final String UNEXPECTED_NULL_PROCEDURE_PARAMETERS_FOR_WAITINGPROCEDUREFINISHED =
"Unexpected null procedure parameters for waitingProcedureFinished";
public static final String UNKNOWN_DATAPARTITION_ALLOCATION_STRATEGY_USING_INHERIT_STRATEGY_BY_DEFAULT =
diff --git a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 5ed644c0f6dab..44009361febe4 100644
--- a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -517,8 +517,6 @@ public final class ManagerMessages {
"等待 configNode leader 就绪过程中发生意外中断。";
public static final String UNEXPECTED_INTERRUPTION_DURING_WAITING_FOR_GET_CLUSTER_ID =
"等待获取 cluster id 过程中发生意外中断。";
- public static final String UNEXPECTED_NON_CREATE_REGION_MAINTAIN_TASK_SKIPPED =
- "RegionMaintainer 队列中出现意外的非 create 任务;跳过处理(该队列目前仅用于重建 region 副本,region 删除由 RemoveRegionGroupProcedure 处理)。";
public static final String UNEXPECTED_NULL_PROCEDURE_PARAMETERS_FOR_WAITINGPROCEDUREFINISHED =
"waitingProcedureFinished 的 procedure 参数为空";
public static final String UNKNOWN_DATAPARTITION_ALLOCATION_STRATEGY_USING_INHERIT_STRATEGY_BY_DEFAULT =
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
index c76615d1fc6d9..de0d311102e7a 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
@@ -193,6 +193,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@@ -303,54 +304,96 @@ public TSStatus testSubProcedure() {
public TSStatus deleteDatabases(
final List deleteSgSchemaList, final boolean isGeneratedByPipe) {
final List procedures = new ArrayList<>();
- final long startCheckTimeForProcedures = System.currentTimeMillis();
+ final long deadlineNanos =
+ System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(PROCEDURE_WAIT_TIME_OUT);
for (final TDatabaseSchema databaseSchema : deleteSgSchemaList) {
- final String database = databaseSchema.getName();
- boolean hasOverlappedTask = false;
- while (executor.isRunning()
- && System.currentTimeMillis() - startCheckTimeForProcedures < PROCEDURE_WAIT_TIME_OUT) {
- try (final DatabaseLock ignored = acquireDatabaseLifecycleLock(database)) {
- synchronized (this) {
- final Pair procedureIdDuplicatePair =
- checkDuplicateTableTask(
- database, null, null, null, null, ProcedureType.DELETE_DATABASE_PROCEDURE);
- hasOverlappedTask = procedureIdDuplicatePair.getRight();
-
- if (Boolean.FALSE.equals(procedureIdDuplicatePair.getRight())) {
- final DeleteDatabaseProcedure procedure =
- new DeleteDatabaseProcedure(databaseSchema, isGeneratedByPipe);
- this.executor.submitProcedure(procedure);
- procedures.add(procedure);
- }
- }
- }
- if (!hasOverlappedTask) {
- break;
- }
- synchronized (this) {
- try {
- wait(PROCEDURE_WAIT_RETRY_TIMEOUT);
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- }
- if (hasOverlappedTask) {
- return RpcUtils.getStatus(
- TSStatusCode.OVERLAP_WITH_EXISTING_TASK,
- String.format(
- "Some other task is operating table under the database %s, please retry after the procedure finishes.",
- database));
+ if (!submitDeleteDatabaseProcedure(
+ databaseSchema, isGeneratedByPipe, procedures, deadlineNanos)) {
+ return getDeleteDatabaseOverlapStatus(databaseSchema.getName());
}
}
- List results = new ArrayList<>(procedures.size());
+
+ final List results = new ArrayList<>(procedures.size());
procedures.forEach(procedure -> results.add(waitingProcedureFinished(procedure)));
if (results.stream()
.allMatch(result -> result.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode())) {
return StatusUtils.OK;
- } else {
- return RpcUtils.getStatus(results);
}
+ return RpcUtils.getStatus(results);
+ }
+
+ private boolean submitDeleteDatabaseProcedure(
+ final TDatabaseSchema databaseSchema,
+ final boolean isGeneratedByPipe,
+ final List procedures,
+ final long deadlineNanos) {
+ while (executor.isRunning()) {
+ final long remainingNanos = deadlineNanos - System.nanoTime();
+ if (remainingNanos <= 0) {
+ return false;
+ }
+
+ final DatabaseLock databaseLock;
+ try {
+ databaseLock =
+ env.getDatabaseLifecycleLockManager()
+ .tryAcquireLocks(
+ Collections.singleton(databaseSchema.getName()),
+ remainingNanos,
+ TimeUnit.NANOSECONDS);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ if (databaseLock == null) {
+ return false;
+ }
+
+ try (databaseLock) {
+ synchronized (this) {
+ final Pair procedureIdDuplicatePair =
+ checkDuplicateTableTask(
+ databaseSchema.getName(),
+ null,
+ null,
+ null,
+ null,
+ ProcedureType.DELETE_DATABASE_PROCEDURE);
+ if (Boolean.FALSE.equals(procedureIdDuplicatePair.getRight())) {
+ final DeleteDatabaseProcedure procedure =
+ new DeleteDatabaseProcedure(databaseSchema, isGeneratedByPipe);
+ executor.submitProcedure(procedure);
+ procedures.add(procedure);
+ return true;
+ }
+ }
+ }
+
+ if (!waitForDeleteDatabaseRetry()) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private boolean waitForDeleteDatabaseRetry() {
+ synchronized (this) {
+ try {
+ wait(PROCEDURE_WAIT_RETRY_TIMEOUT);
+ return true;
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+ }
+
+ private TSStatus getDeleteDatabaseOverlapStatus(final String database) {
+ return RpcUtils.getStatus(
+ TSStatusCode.OVERLAP_WITH_EXISTING_TASK,
+ String.format(
+ "Some other task is operating table under the database %s, please retry after the procedure finishes.",
+ database));
}
public TSStatus alterEncodingCompressor(
@@ -1553,10 +1596,10 @@ public boolean hasUnfinishedDatabaseLifecycleProcedure(final String database) {
.filter(procedure -> !procedure.isFinished())
.anyMatch(
procedure ->
- (procedure instanceof DeleteDatabaseProcedure
- && database.equals(((DeleteDatabaseProcedure) procedure).getDatabase()))
- || (procedure instanceof CreateRegionGroupsProcedure
- && ((CreateRegionGroupsProcedure) procedure).containsDatabase(database)));
+ (procedure instanceof DeleteDatabaseProcedure deleteProcedure
+ && database.equals(deleteProcedure.getDatabase()))
+ || (procedure instanceof CreateRegionGroupsProcedure createProcedure
+ && createProcedure.containsDatabase(database)));
}
/**
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
index 95325f268c983..d7ab72cbf6afe 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
@@ -86,7 +86,6 @@
import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
import org.apache.iotdb.confignode.persistence.partition.PartitionInfo;
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
-import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainTask;
import org.apache.iotdb.confignode.procedure.impl.partition.DataPartitionTableIntegrityCheckProcedure;
import org.apache.iotdb.confignode.procedure.impl.region.CreateRegionGroupsProcedure;
import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
@@ -1415,7 +1414,7 @@ public void maintainRegionReplicas() {
.map(RegionCreateTask::getStorageGroup)
.distinct()
.sorted()
- .collect(Collectors.toList());
+ .toList();
for (final String database : databases) {
try (final DatabaseLock ignored =
getProcedureManager().acquireDatabaseLifecycleLock(database)) {
@@ -1430,16 +1429,15 @@ public void maintainRegionReplicas() {
}
private void maintainRegionReplicasUnderLock(final String database) {
- final List persistedTasks =
+ final List persistedTasks =
partitionInfo.getRegionMaintainEntryList().stream()
.filter(RegionCreateTask.class::isInstance)
.map(RegionCreateTask.class::cast)
.filter(task -> database.equals(task.getStorageGroup()))
- .collect(Collectors.toList());
+ .toList();
final Map invalidTaskCountByRegion = new HashMap<>();
- for (RegionMaintainTask task : persistedTasks) {
- if (!(task instanceof RegionCreateTask)
- || !isRegionCreateTaskRegionValid((RegionCreateTask) task)) {
+ for (final RegionCreateTask task : persistedTasks) {
+ if (!isRegionCreateTaskRegionValid(task)) {
invalidTaskCountByRegion.merge(task.getRegionId(), 1, Integer::sum);
}
}
@@ -1458,21 +1456,10 @@ private void maintainRegionReplicasUnderLock(final String database) {
// RegionCreateTasks now (delete tasks are filtered out at the PartitionInfo ingestion points),
// and a region may carry several of them when more than one of its replicas failed to create.
final Map> tasksByRegion = new LinkedHashMap<>();
- for (RegionMaintainTask task : persistedTasks) {
- if (invalidRegionIds.contains(task.getRegionId())) {
- continue;
+ for (final RegionCreateTask task : persistedTasks) {
+ if (!invalidRegionIds.contains(task.getRegionId())) {
+ tasksByRegion.computeIfAbsent(task.getRegionId(), ignored -> new LinkedList<>()).add(task);
}
- if (!(task instanceof RegionCreateTask)) {
- // Unreachable: the queue only holds create tasks now (legacy delete tasks are dropped at
- // the
- // PartitionInfo ingestion points). Guard against a regression so an unexpected task type
- // cannot silently stall the loop.
- LOGGER.warn(ManagerMessages.UNEXPECTED_NON_CREATE_REGION_MAINTAIN_TASK_SKIPPED);
- continue;
- }
- tasksByRegion
- .computeIfAbsent(task.getRegionId(), k -> new LinkedList<>())
- .add((RegionCreateTask) task);
}
final Set invalidHeadRegionIds = new HashSet<>();
@@ -1480,25 +1467,9 @@ private void maintainRegionReplicasUnderLock(final String database) {
new EnumMap<>(TConsensusGroupType.class);
final Map> selectedCountByTypeAndDataNode =
new EnumMap<>(TConsensusGroupType.class);
- for (Queue queue : tasksByRegion.values()) {
- final RegionCreateTask head = queue.peek();
- if (!isRegionCreateTaskTargetValid(head)) {
- invalidHeadRegionIds.add(head.getRegionId());
- continue;
- }
- if (isRegionCreateTargetInBackoff(head)) {
- continue;
- }
- final TConsensusGroupType type = head.getRegionId().getType();
- final int dataNodeId = head.getTargetDataNode().getDataNodeId();
- final Map selectedCountByDataNode =
- selectedCountByTypeAndDataNode.computeIfAbsent(type, ignored -> new HashMap<>());
- final int selectedCount = selectedCountByDataNode.getOrDefault(dataNodeId, 0);
- if (selectedCount >= getRegionCreateBatchSize(type)) {
- continue;
- }
- selectedCountByDataNode.put(dataNodeId, selectedCount + 1);
- headsByType.computeIfAbsent(type, ignored -> new ArrayList<>()).add(head);
+ for (final Queue queue : tasksByRegion.values()) {
+ selectRegionCreateTask(
+ queue.peek(), invalidHeadRegionIds, headsByType, selectedCountByTypeAndDataNode);
}
// A target-specific stale task only removes the head of its Region queue. A following task of
@@ -1518,6 +1489,30 @@ private void maintainRegionReplicasUnderLock(final String database) {
}
}
+ private void selectRegionCreateTask(
+ final RegionCreateTask task,
+ final Set invalidRegionIds,
+ final Map> tasksByType,
+ final Map> selectedCountByTypeAndDataNode) {
+ if (!isRegionCreateTaskTargetValid(task)) {
+ invalidRegionIds.add(task.getRegionId());
+ return;
+ }
+ if (isRegionCreateTargetInBackoff(task)) {
+ return;
+ }
+
+ final TConsensusGroupType type = task.getRegionId().getType();
+ final int dataNodeId = task.getTargetDataNode().getDataNodeId();
+ final Map selectedCountByDataNode =
+ selectedCountByTypeAndDataNode.computeIfAbsent(type, ignored -> new HashMap<>());
+ final int selectedCount = selectedCountByDataNode.getOrDefault(dataNodeId, 0);
+ if (selectedCount < getRegionCreateBatchSize(type)) {
+ selectedCountByDataNode.put(dataNodeId, selectedCount + 1);
+ tasksByType.computeIfAbsent(type, ignored -> new ArrayList<>()).add(task);
+ }
+ }
+
private boolean writeRegionCreateTaskPlan(ConfigPhysicalPlan plan) {
try {
return getConsensusManager().write(plan).getCode()
@@ -1631,29 +1626,36 @@ private void collectSuccessfulRegions(
tasksByDataNode
.computeIfAbsent(task.getTargetDataNode().getDataNodeId(), ignored -> new ArrayList<>())
.add(task);
- final TSStatus status = responseMap.get(task.getRegionId().getId());
- if (status != null && status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ if (isRegionCreateSuccessful(responseMap.get(task.getRegionId().getId()))) {
successfulRegions.add(task.getRegionId());
}
}
- for (List dataNodeTasks : tasksByDataNode.values()) {
- RegionCreateTask failedTask = null;
- TSStatus failedStatus = null;
- for (RegionCreateTask task : dataNodeTasks) {
- final TSStatus status = responseMap.get(task.getRegionId().getId());
- if (status == null || status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- if (failedTask == null || isDirectMemoryFailure(status)) {
- failedTask = task;
- failedStatus = status;
- }
- }
- }
- if (failedTask != null) {
- recordRegionCreateFailure(failedTask, failedStatus);
- } else {
- clearRegionCreateBackoff(dataNodeTasks.get(0));
+ for (final List dataNodeTasks : tasksByDataNode.values()) {
+ updateRegionCreateBackoff(responseMap, dataNodeTasks);
+ }
+ }
+
+ private void updateRegionCreateBackoff(
+ final Map responseMap, final List dataNodeTasks) {
+ RegionCreateTask failedTask = null;
+ TSStatus failedStatus = null;
+ for (final RegionCreateTask task : dataNodeTasks) {
+ final TSStatus status = responseMap.get(task.getRegionId().getId());
+ if (!isRegionCreateSuccessful(status)
+ && (failedTask == null || isDirectMemoryFailure(status))) {
+ failedTask = task;
+ failedStatus = status;
}
}
+ if (failedTask == null) {
+ clearRegionCreateBackoff(dataNodeTasks.get(0));
+ } else {
+ recordRegionCreateFailure(failedTask, failedStatus);
+ }
+ }
+
+ private boolean isRegionCreateSuccessful(final TSStatus status) {
+ return status != null && status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode();
}
private boolean isRegionCreateTargetInBackoff(RegionCreateTask task) {
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
index 47685a60fcfe5..54a0bb497ca15 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
@@ -203,9 +203,8 @@ public TSStatus createRegionGroups(CreateRegionGroupsPlan plan) {
plan.getRegionGroupMap()
.forEach(
- (database, regionReplicaSets) -> {
- databasePartitionTables.get(database).createRegionGroups(regionReplicaSets);
- });
+ (database, regionReplicaSets) ->
+ databasePartitionTables.get(database).createRegionGroups(regionReplicaSets));
return new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
}
@@ -274,19 +273,16 @@ public TSStatus offerRegionMaintainTasks(
// owned by RemoveRegionGroupProcedure. Drop any legacy DELETE task that an upgraded node may
// replay from an old consensus log, so it cannot get stuck in the queue and block the
// recreation of that region's other replicas.
- for (RegionMaintainTask task : offerRegionMaintainTasksPlan.getRegionMaintainTaskList()) {
+ for (final RegionMaintainTask task :
+ offerRegionMaintainTasksPlan.getRegionMaintainTaskList()) {
if (RegionMaintainType.DELETE.equals(task.getType())) {
LOGGER.info(
ConfigNodeMessages
.MESSAGE_DROPPING_LEGACY_REGION_DELETE_TASK_FOR_ARG_WHILE_REPLAYING_OFFER_PLAN_REGION_DELETION_IS_NOW_HANDLED_BY_REMOVEREGIONGROUPPROCEDURE_2A81A649,
task.getRegionId());
- continue;
- }
- final RegionCreateTask createTask = (RegionCreateTask) task;
- if (!isRegionCreateTaskOwnedByCurrentPartitionTable(createTask)) {
- continue;
- }
- if (existingTasks.add(task)) {
+ } else if (task instanceof RegionCreateTask createTask
+ && isRegionCreateTaskOwnedByCurrentPartitionTable(createTask)
+ && existingTasks.add(task)) {
regionMaintainTaskList.add(task);
}
}
@@ -343,9 +339,8 @@ public TSStatus batchRemoveRegionCreateTasks(BatchRemoveRegionCreateTasksPlan pl
}
regionMaintainTaskList.removeIf(
task ->
- task instanceof RegionCreateTask
- && Objects.equals(
- plan.getDatabase(), ((RegionCreateTask) task).getStorageGroup()));
+ task instanceof RegionCreateTask createTask
+ && Objects.equals(plan.getDatabase(), createTask.getStorageGroup()));
return RpcUtils.SUCCESS_STATUS;
}
}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/AbstractDatabaseProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/AbstractDatabaseProcedure.java
index fc41b9afbc5a3..9d7bbaf91ff6d 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/AbstractDatabaseProcedure.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/AbstractDatabaseProcedure.java
@@ -25,10 +25,10 @@
import java.util.Set;
/** A procedure that holds exclusive lifecycle locks for its databases until it finishes. */
-public abstract class AbstractDatabaseProcedure
- extends StateMachineProcedure {
+public abstract class AbstractDatabaseProcedure
+ extends StateMachineProcedure {
- private transient String waitingDatabase;
+ private String waitingDatabase;
protected AbstractDatabaseProcedure() {
super();
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
index c9240878c9dae..0a17bc61b2f5d 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
@@ -81,106 +81,19 @@ protected Flow executeFromState(final ConfigNodeProcedureEnv env, final DeleteDa
try {
switch (state) {
case PRE_DELETE_DATABASE:
- LOG.info(
- ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_PRE_DELETE_DATABASE_ARG_6A1FEACC,
- deleteDatabaseSchema.getName());
- final TSStatus preDeleteStatus =
- env.preDeleteDatabase(
- PreDeleteDatabasePlan.PreDeleteType.EXECUTE, deleteDatabaseSchema.getName());
- if (preDeleteStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- setNextState(DeleteDatabaseState.INVALIDATE_CACHE);
- } else if (getCycles() > RETRY_THRESHOLD) {
- setFailure(
- new ProcedureException(
- ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
- } else {
- setNextState(DeleteDatabaseState.PRE_DELETE_DATABASE);
- }
+ executePreDeleteDatabase(env);
break;
case INVALIDATE_CACHE:
- LOG.info(
- ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_INVALIDATE_CACHE_DATABASE_ARG_299FC9BC,
- deleteDatabaseSchema.getName());
- if (env.invalidateCache(deleteDatabaseSchema.getName())) {
- final TSStatus removeTasksStatus =
- env.batchRemoveRegionCreateTasks(deleteDatabaseSchema.getName());
- if (removeTasksStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- setNextState(DeleteDatabaseState.DELETE_DATABASE_SCHEMA);
- } else if (getCycles() > RETRY_THRESHOLD) {
- setFailure(
- new ProcedureException(
- ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
- } else {
- setNextState(DeleteDatabaseState.INVALIDATE_CACHE);
- }
- } else {
- setFailure(
- new ProcedureException(
- ProcedureMessages.DELETEDATABASEPROCEDURE_INVALIDATE_CACHE_FAILED));
- }
+ executeInvalidateCache(env);
break;
case DELETE_DATABASE_SCHEMA:
- LOG.info(
- ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_ARG_A49A47AC,
- deleteDatabaseSchema.getName());
-
- // Delete every RegionGroup as a child procedure. This procedure keeps the database lock
- // while the children run, so neither same-name recreation nor a delayed Region creation
- // can overtake cleanup. Each child carries its own replica-set copy and survives leader
- // change or restart.
- //
- // Submission is intentionally NOT guarded by isStateDeserialized(): the executor persists
- // a procedure at a state BEFORE that state's body has run (it advances the state on the
- // previous cycle, then may stop at the inter-state boundary on a leader switch — see
- // ProcedureExecutor#executeProcedure). So a recovery that lands on this state means the
- // submission has NOT happened yet; skipping it would drop every region group's cleanup
- // while the next state still drops the partition table, orphaning the region peers/data
- // on disk with no record of where they live. Adding the children again on recovery is
- // safe because RegionGroup deletion is idempotent.
- final List regionReplicaSets =
- env.getAllReplicaSets(deleteDatabaseSchema.getName());
- regionReplicaSets.forEach(
- regionReplicaSet -> {
- // Clear heartbeat cache along the way
- env.getConfigManager()
- .getLoadManager()
- .removeRegionGroupRelatedCache(regionReplicaSet.getRegionId());
- addChildProcedure(new RemoveRegionGroupProcedure(regionReplicaSet));
- });
- setNextState(DeleteDatabaseState.DELETE_DATABASE_CONFIG);
+ executeDeleteDatabaseSchema(env);
break;
case DELETE_DATABASE_CONFIG:
- env.getConfigManager()
- .getLoadManager()
- .clearDataPartitionPolicyTable(deleteDatabaseSchema.getName());
- LOG.info(
- ProcedureMessages
- .LOG_DELETEDATABASEPROCEDURE_DATA_PARTITION_POLICY_TABLE_DATABASE_ARG_CLEARED_7A32E28A,
- deleteDatabaseSchema.getName());
-
- // Delete Database metrics
- PartitionMetrics.unbindDatabaseRelatedMetricsWhenUpdate(
- MetricService.getInstance(), deleteDatabaseSchema.getName());
- PartitionMetrics.unbindDatabaseTableMetrics(
- MetricService.getInstance(), deleteDatabaseSchema.getName());
-
- // Delete DatabasePartitionTable
- final TSStatus deleteConfigResult =
- env.deleteDatabaseConfig(deleteDatabaseSchema.getName(), isGeneratedByPipe);
-
- if (deleteConfigResult.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- LOG.info(
- ProcedureMessages
- .LOG_DELETEDATABASEPROCEDURE_DATABASE_ARG_DELETED_SUCCESSFULLY_3A4E9202,
- deleteDatabaseSchema.getName());
+ if (executeDeleteDatabaseConfig(env)) {
return Flow.NO_MORE_STATE;
- } else if (getCycles() > RETRY_THRESHOLD) {
- setFailure(
- new ProcedureException(
- ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
- } else {
- setNextState(DeleteDatabaseState.DELETE_DATABASE_CONFIG);
}
+ break;
}
} catch (final TException | IOException e) {
if (isRollbackSupported(state)) {
@@ -209,6 +122,109 @@ protected Flow executeFromState(final ConfigNodeProcedureEnv env, final DeleteDa
return Flow.HAS_MORE_STATE;
}
+ private void executePreDeleteDatabase(final ConfigNodeProcedureEnv env) {
+ LOG.info(
+ ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_PRE_DELETE_DATABASE_ARG_6A1FEACC,
+ deleteDatabaseSchema.getName());
+ final TSStatus preDeleteStatus =
+ env.preDeleteDatabase(
+ PreDeleteDatabasePlan.PreDeleteType.EXECUTE, deleteDatabaseSchema.getName());
+ if (preDeleteStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ setNextState(DeleteDatabaseState.INVALIDATE_CACHE);
+ } else {
+ retryOrFail(DeleteDatabaseState.PRE_DELETE_DATABASE);
+ }
+ }
+
+ private void executeInvalidateCache(final ConfigNodeProcedureEnv env)
+ throws IOException, TException {
+ LOG.info(
+ ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_INVALIDATE_CACHE_DATABASE_ARG_299FC9BC,
+ deleteDatabaseSchema.getName());
+ if (!env.invalidateCache(deleteDatabaseSchema.getName())) {
+ setFailure(
+ new ProcedureException(
+ ProcedureMessages.DELETEDATABASEPROCEDURE_INVALIDATE_CACHE_FAILED));
+ return;
+ }
+
+ final TSStatus removeTasksStatus =
+ env.batchRemoveRegionCreateTasks(deleteDatabaseSchema.getName());
+ if (removeTasksStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ setNextState(DeleteDatabaseState.DELETE_DATABASE_SCHEMA);
+ } else {
+ retryOrFail(DeleteDatabaseState.INVALIDATE_CACHE);
+ }
+ }
+
+ private void executeDeleteDatabaseSchema(final ConfigNodeProcedureEnv env) {
+ LOG.info(
+ ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_ARG_A49A47AC,
+ deleteDatabaseSchema.getName());
+
+ // Delete every RegionGroup as a child procedure. This procedure keeps the database lock while
+ // the children run, so neither same-name recreation nor a delayed Region creation can overtake
+ // cleanup. Each child carries its own replica-set copy and survives leader change or restart.
+ //
+ // Submission is intentionally NOT guarded by isStateDeserialized(): the executor persists a
+ // procedure at a state BEFORE that state's body has run (it advances the state on the previous
+ // cycle, then may stop at the inter-state boundary on a leader switch — see
+ // ProcedureExecutor#executeProcedure). So a recovery that lands on this state means the
+ // submission has NOT happened yet; skipping it would drop every region group's cleanup while
+ // the next state still drops the partition table, orphaning the region peers/data on disk with
+ // no record of where they live. Adding the children again on recovery is safe because
+ // RegionGroup deletion is idempotent.
+ final List regionReplicaSets =
+ env.getAllReplicaSets(deleteDatabaseSchema.getName());
+ regionReplicaSets.forEach(
+ regionReplicaSet -> {
+ // Clear heartbeat cache along the way
+ env.getConfigManager()
+ .getLoadManager()
+ .removeRegionGroupRelatedCache(regionReplicaSet.getRegionId());
+ addChildProcedure(new RemoveRegionGroupProcedure(regionReplicaSet));
+ });
+ setNextState(DeleteDatabaseState.DELETE_DATABASE_CONFIG);
+ }
+
+ private boolean executeDeleteDatabaseConfig(final ConfigNodeProcedureEnv env) {
+ env.getConfigManager()
+ .getLoadManager()
+ .clearDataPartitionPolicyTable(deleteDatabaseSchema.getName());
+ LOG.info(
+ ProcedureMessages
+ .LOG_DELETEDATABASEPROCEDURE_DATA_PARTITION_POLICY_TABLE_DATABASE_ARG_CLEARED_7A32E28A,
+ deleteDatabaseSchema.getName());
+
+ // Delete Database metrics
+ PartitionMetrics.unbindDatabaseRelatedMetricsWhenUpdate(
+ MetricService.getInstance(), deleteDatabaseSchema.getName());
+ PartitionMetrics.unbindDatabaseTableMetrics(
+ MetricService.getInstance(), deleteDatabaseSchema.getName());
+
+ // Delete DatabasePartitionTable
+ final TSStatus deleteConfigResult =
+ env.deleteDatabaseConfig(deleteDatabaseSchema.getName(), isGeneratedByPipe);
+ if (deleteConfigResult.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ LOG.info(
+ ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_DATABASE_ARG_DELETED_SUCCESSFULLY_3A4E9202,
+ deleteDatabaseSchema.getName());
+ return true;
+ }
+ retryOrFail(DeleteDatabaseState.DELETE_DATABASE_CONFIG);
+ return false;
+ }
+
+ private void retryOrFail(final DeleteDatabaseState state) {
+ if (getCycles() > RETRY_THRESHOLD) {
+ setFailure(
+ new ProcedureException(
+ ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
+ } else {
+ setNextState(state);
+ }
+ }
+
@Override
protected void rollbackState(final ConfigNodeProcedureEnv env, final DeleteDatabaseState state)
throws IOException, InterruptedException {
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
index 31f75950eb43d..54e5786872f87 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
@@ -28,6 +28,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@@ -59,12 +60,34 @@ public DatabaseLock acquireLocks(final Set databaseNames) {
while (!canAcquireRequestLocks(owner, orderedDatabases)) {
lockReleased.awaitUninterruptibly();
}
- orderedDatabases.forEach(
- database ->
- lockStateMap
- .computeIfAbsent(database, ignored -> new DatabaseLockState())
- .acquireRequestLock(owner));
- return new DatabaseLock(this, orderedDatabases, owner);
+ return acquireRequestLocks(orderedDatabases, owner);
+ } finally {
+ stateLock.unlock();
+ }
+ }
+
+ /**
+ * Tries to acquire database locks for a synchronous manager request within the given timeout.
+ *
+ * @return the acquired lock, or null if the timeout elapsed before every lock became available
+ */
+ public DatabaseLock tryAcquireLocks(
+ final Set databaseNames, final long timeout, final TimeUnit timeUnit)
+ throws InterruptedException {
+ final List orderedDatabases = orderedDatabases(databaseNames);
+ final Thread owner = Thread.currentThread();
+ long remainingNanos = timeUnit.toNanos(timeout);
+ if (!stateLock.tryLock(remainingNanos, TimeUnit.NANOSECONDS)) {
+ return null;
+ }
+ try {
+ while (!canAcquireRequestLocks(owner, orderedDatabases)) {
+ if (remainingNanos <= 0) {
+ return null;
+ }
+ remainingNanos = lockReleased.awaitNanos(remainingNanos);
+ }
+ return acquireRequestLocks(orderedDatabases, owner);
} finally {
stateLock.unlock();
}
@@ -78,11 +101,13 @@ public DatabaseLock acquireLocks(final Set databaseNames) {
public String tryLock(final Procedure> procedure, final Set databaseNames) {
stateLock.lock();
try {
+ final List orderedDatabases = orderedDatabases(databaseNames);
final List acquiredDatabases = new ArrayList<>();
- for (final String database : orderedDatabases(databaseNames)) {
+ for (final String database : orderedDatabases) {
final DatabaseLockState lockState =
lockStateMap.computeIfAbsent(database, ignored -> new DatabaseLockState());
- if (!lockState.canAcquireProcedureLock(procedure)) {
+ final boolean hasWaiterPriority = lockState.isHeadWaiter(procedure);
+ if (!lockState.canAcquireProcedureLock(procedure, hasWaiterPriority)) {
acquiredDatabases.forEach(
acquiredDatabase -> releaseProcedureLock(procedure, acquiredDatabase));
return database;
@@ -90,6 +115,9 @@ public String tryLock(final Procedure> procedure, final Set databaseNa
if (lockState.acquireProcedureLock(procedure)) {
acquiredDatabases.add(database);
}
+ if (hasWaiterPriority) {
+ lockState.removeHeadWaiter(procedure);
+ }
}
return null;
} finally {
@@ -102,7 +130,7 @@ public void waitProcedure(final Procedure> procedure, final String databaseNam
try {
final DatabaseLockState lockState =
lockStateMap.computeIfAbsent(databaseName, ignored -> new DatabaseLockState());
- if (lockState.isUnlocked()) {
+ if (lockState.isUnlocked() && !lockState.hasWaitingProcedures()) {
scheduler.addFront(procedure);
removeIfIdle(databaseName, lockState);
} else {
@@ -133,6 +161,16 @@ private boolean canAcquireRequestLocks(final Thread owner, final List or
return true;
}
+ private DatabaseLock acquireRequestLocks(
+ final List orderedDatabases, final Thread owner) {
+ orderedDatabases.forEach(
+ database ->
+ lockStateMap
+ .computeIfAbsent(database, ignored -> new DatabaseLockState())
+ .acquireRequestLock(owner));
+ return new DatabaseLock(this, orderedDatabases, owner);
+ }
+
private void releaseRequestLocks(final List orderedDatabases, final Thread requestOwner) {
stateLock.lock();
try {
@@ -157,8 +195,9 @@ private void releaseProcedureLock(final Procedure> procedure, final String dat
}
private void wakeWaiters(final DatabaseLockState lockState) {
- lockState.wakeWaitingProcedures(scheduler);
- lockReleased.signalAll();
+ if (!lockState.wakeNextWaitingProcedure(scheduler)) {
+ lockReleased.signalAll();
+ }
}
private void removeIfIdle(final String database, final DatabaseLockState lockState) {
@@ -214,6 +253,8 @@ private void acquireRequestLock(final Thread owner) {
}
/**
+ * Releases one request lock hold.
+ *
* @return true when the lock became fully released
*/
private boolean releaseRequestLock(final Thread owner) {
@@ -228,12 +269,16 @@ private boolean releaseRequestLock(final Thread owner) {
return false;
}
- private boolean canAcquireProcedureLock(final Procedure> procedure) {
+ private boolean canAcquireProcedureLock(
+ final Procedure> procedure, final boolean hasWaiterPriority) {
return requestOwner == null
- && (procedureOwner == null || procedureOwner.getProcId() == procedure.getProcId());
+ && (procedureOwner == null || procedureOwner.getProcId() == procedure.getProcId())
+ && (hasWaiterPriority || waitingProcedures.isEmpty());
}
/**
+ * Acquires the procedure lock when it is not already held by the same procedure.
+ *
* @return true when this invocation newly acquired the lock
*/
private boolean acquireProcedureLock(final Procedure> procedure) {
@@ -245,6 +290,8 @@ private boolean acquireProcedureLock(final Procedure> procedure) {
}
/**
+ * Releases the procedure lock.
+ *
* @return true when the lock was released
*/
private boolean releaseProcedureLock(final Procedure> procedure) {
@@ -262,10 +309,30 @@ private void waitProcedure(final Procedure> procedure) {
}
}
- private void wakeWaitingProcedures(final ProcedureScheduler procedureScheduler) {
- while (!waitingProcedures.isEmpty()) {
- procedureScheduler.addFront(waitingProcedures.pollFirst());
+ private boolean removeHeadWaiter(final Procedure> procedure) {
+ if (isHeadWaiter(procedure)) {
+ waitingProcedures.pollFirst();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isHeadWaiter(final Procedure> procedure) {
+ return !waitingProcedures.isEmpty()
+ && waitingProcedures.peekFirst().getProcId() == procedure.getProcId();
+ }
+
+ private boolean wakeNextWaitingProcedure(final ProcedureScheduler procedureScheduler) {
+ final Procedure> waitingProcedure = waitingProcedures.peekFirst();
+ if (waitingProcedure == null) {
+ return false;
}
+ procedureScheduler.addFront(waitingProcedure);
+ return true;
+ }
+
+ private boolean hasWaitingProcedures() {
+ return !waitingProcedures.isEmpty();
}
private boolean isUnlocked() {
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java
index 366c58553ee7a..e30f4cde7909b 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java
@@ -70,7 +70,7 @@ public void testRequestLocksAreScopedByDatabaseName() throws Exception {
}
@Test
- public void testProcedureAndRequestShareTheSameLock() throws Exception {
+ public void testWaitingProcedureCannotBeOvertakenByRequest() throws Exception {
final ProcedureScheduler scheduler = Mockito.mock(ProcedureScheduler.class);
final DatabaseLifecycleLockManager lockManager = new DatabaseLifecycleLockManager(scheduler);
final Procedure> owner = procedure(1);
@@ -84,22 +84,44 @@ public void testProcedureAndRequestShareTheSameLock() throws Exception {
final ExecutorService executor = Executors.newSingleThreadExecutor();
final CountDownLatch requestAcquired = new CountDownLatch(1);
try {
- executor.submit(
- () -> {
- try (final DatabaseLock ignored = lockManager.acquireLocks(databases)) {
- requestAcquired.countDown();
- }
- });
+ final Future> requestFuture =
+ executor.submit(
+ () -> {
+ try (final DatabaseLock ignored = lockManager.acquireLocks(databases)) {
+ requestAcquired.countDown();
+ }
+ });
Assert.assertFalse(requestAcquired.await(200, TimeUnit.MILLISECONDS));
lockManager.releaseLocks(owner, databases);
Mockito.verify(scheduler).addFront(waiter);
+ Assert.assertFalse(requestAcquired.await(200, TimeUnit.MILLISECONDS));
+
+ Assert.assertNull(lockManager.tryLock(waiter, databases));
+ Assert.assertFalse(requestAcquired.await(200, TimeUnit.MILLISECONDS));
+ lockManager.releaseLocks(waiter, databases);
Assert.assertTrue(requestAcquired.await(10, TimeUnit.SECONDS));
+ requestFuture.get(10, TimeUnit.SECONDS);
} finally {
executor.shutdownNow();
}
}
+ @Test
+ public void testTimedRequestLockAcquisitionHonorsTimeout() throws Exception {
+ final DatabaseLifecycleLockManager lockManager =
+ new DatabaseLifecycleLockManager(Mockito.mock(ProcedureScheduler.class));
+ final Procedure> owner = procedure(1);
+ final Set databases = Collections.singleton("root.sg");
+ Assert.assertNull(lockManager.tryLock(owner, databases));
+
+ final long startNanos = System.nanoTime();
+ Assert.assertNull(lockManager.tryAcquireLocks(databases, 100, TimeUnit.MILLISECONDS));
+ Assert.assertTrue(TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startNanos) < 5);
+
+ lockManager.releaseLocks(owner, databases);
+ }
+
@Test
public void testFailedMultiDatabaseAcquisitionDoesNotLeakPartialLocks() {
final DatabaseLifecycleLockManager lockManager =
From 8a6ce6be03f27c52acee80b646e588658c2c5664 Mon Sep 17 00:00:00 2001
From: Yongzao <532741407@qq.com>
Date: Wed, 22 Jul 2026 23:04:10 +0800
Subject: [PATCH 3/5] [ConfigNode] Address remaining Sonar findings
---
.../iotdb/confignode/manager/ProcedureManager.java | 14 ++++++--------
.../scheduler/DatabaseLifecycleLockManager.java | 1 +
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
index de0d311102e7a..08bd6c77a0ba7 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
@@ -377,14 +377,12 @@ private boolean submitDeleteDatabaseProcedure(
}
private boolean waitForDeleteDatabaseRetry() {
- synchronized (this) {
- try {
- wait(PROCEDURE_WAIT_RETRY_TIMEOUT);
- return true;
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- return false;
- }
+ try {
+ TimeUnit.MILLISECONDS.sleep(PROCEDURE_WAIT_RETRY_TIMEOUT);
+ return true;
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
}
}
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
index 54e5786872f87..57e0cb2e0e142 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
@@ -70,6 +70,7 @@ public DatabaseLock acquireLocks(final Set databaseNames) {
* Tries to acquire database locks for a synchronous manager request within the given timeout.
*
* @return the acquired lock, or null if the timeout elapsed before every lock became available
+ * @throws InterruptedException if interrupted while waiting for the locks
*/
public DatabaseLock tryAcquireLocks(
final Set databaseNames, final long timeout, final TimeUnit timeUnit)
From 7255c45847328e59dded46be6138ad591b410ca2 Mon Sep 17 00:00:00 2001
From: Yongzao <532741407@qq.com>
Date: Wed, 22 Jul 2026 23:10:48 +0800
Subject: [PATCH 4/5] [ConfigNode] Clean up Sonar line length findings
---
.../persistence/partition/PartitionInfo.java | 25 ++++++++++---------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
index 54a0bb497ca15..585bf0d070c6a 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
@@ -107,6 +107,9 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
+import static org.apache.iotdb.confignode.i18n.ConfigNodeMessages.MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_AF0F2440;
+import static org.apache.iotdb.confignode.i18n.ConfigNodeMessages.MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_651DB780;
+
/**
* The {@link PartitionInfo} stores cluster PartitionTable.
*
@@ -218,24 +221,22 @@ public TSStatus validateCreateRegionGroups(final CreateRegionGroupsPlan plan) {
ConfigNodeMessages
.LOG_REJECT_CREATEREGIONGROUPSPLAN_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_616E0CDE,
database);
- return new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode())
- .setMessage(
- String.format(
- ConfigNodeMessages
- .MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_AF0F2440,
- database));
+ final String message =
+ String.format(
+ MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_DOES_NOT_EXIST_AF0F2440,
+ database);
+ return new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode()).setMessage(message);
}
if (!databasePartitionTable.isNotPreDeleted()) {
LOGGER.warn(
ConfigNodeMessages
.LOG_REJECT_CREATEREGIONGROUPSPLAN_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_C085AC01,
database);
- return new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode())
- .setMessage(
- String.format(
- ConfigNodeMessages
- .MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_651DB780,
- database));
+ final String message =
+ String.format(
+ MESSAGE_CREATE_REGIONGROUPS_FAILED_BECAUSE_DATABASE_ARG_IS_BEING_DELETED_651DB780,
+ database);
+ return new TSStatus(TSStatusCode.DATABASE_NOT_EXIST.getStatusCode()).setMessage(message);
}
}
From be7e3610a1995dfaa6cba6f771a7b16c5f7efca4 Mon Sep 17 00:00:00 2001
From: Yongzao <532741407@qq.com>
Date: Thu, 23 Jul 2026 11:13:43 +0800
Subject: [PATCH 5/5] [ConfigNode] Split region group creation by database
---
.../confignode/manager/ProcedureManager.java | 6 +-
.../manager/partition/PartitionManager.java | 39 +++--
.../manager/schema/ClusterSchemaManager.java | 2 +-
.../procedure/env/ConfigNodeProcedureEnv.java | 16 +--
...ockManager.java => DatabaseLockQueue.java} | 14 +-
.../manager/ClusterSchemaManagerTest.java | 11 +-
.../partition/PartitionManagerTest.java | 136 ++++++++++++++++++
...erTest.java => DatabaseLockQueueTest.java} | 68 ++++-----
8 files changed, 218 insertions(+), 74 deletions(-)
rename iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/{DatabaseLifecycleLockManager.java => DatabaseLockQueue.java} (96%)
create mode 100644 iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/partition/PartitionManagerTest.java
rename iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/{DatabaseLifecycleLockManagerTest.java => DatabaseLockQueueTest.java} (65%)
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
index 08bd6c77a0ba7..8735bff7cc0b6 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
@@ -134,7 +134,7 @@
import org.apache.iotdb.confignode.procedure.impl.testonly.CreateManyDatabasesProcedure;
import org.apache.iotdb.confignode.procedure.impl.trigger.CreateTriggerProcedure;
import org.apache.iotdb.confignode.procedure.impl.trigger.DropTriggerProcedure;
-import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLockQueue.DatabaseLock;
import org.apache.iotdb.confignode.procedure.scheduler.ProcedureScheduler;
import org.apache.iotdb.confignode.procedure.scheduler.SimpleProcedureScheduler;
import org.apache.iotdb.confignode.procedure.store.ConfigProcedureStore;
@@ -336,7 +336,7 @@ private boolean submitDeleteDatabaseProcedure(
final DatabaseLock databaseLock;
try {
databaseLock =
- env.getDatabaseLifecycleLockManager()
+ env.getDatabaseLockQueue()
.tryAcquireLocks(
Collections.singleton(databaseSchema.getName()),
remainingNanos,
@@ -1586,7 +1586,7 @@ public DatabaseLock acquireDatabaseLifecycleLock(final String database) {
}
public DatabaseLock acquireDatabaseLifecycleLocks(final Set databases) {
- return env.getDatabaseLifecycleLockManager().acquireLocks(databases);
+ return env.getDatabaseLockQueue().acquireLocks(databases);
}
public boolean hasUnfinishedDatabaseLifecycleProcedure(final String database) {
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
index d7ab72cbf6afe..8f92464e2f913 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
@@ -88,7 +88,7 @@
import org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
import org.apache.iotdb.confignode.procedure.impl.partition.DataPartitionTableIntegrityCheckProcedure;
import org.apache.iotdb.confignode.procedure.impl.region.CreateRegionGroupsProcedure;
-import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLockQueue.DatabaseLock;
import org.apache.iotdb.confignode.rpc.thrift.TCountTimeSlotListReq;
import org.apache.iotdb.confignode.rpc.thrift.TGetRegionGroupsByTimeReq;
import org.apache.iotdb.confignode.rpc.thrift.TGetRegionIdReq;
@@ -123,6 +123,7 @@
import java.util.Queue;
import java.util.Set;
import java.util.StringJoiner;
+import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
@@ -749,29 +750,39 @@ private TSStatus autoExtendRegionGroupIfNecessary(
return generateAndAllocateRegionGroups(allotmentMap, consensusGroupType);
}
- private TSStatus generateAndAllocateRegionGroups(
+ TSStatus generateAndAllocateRegionGroups(
final Map allotmentMap, final TConsensusGroupType consensusGroupType)
throws NotEnoughDataNodeException, DatabaseNotExistsException {
- if (!allotmentMap.isEmpty()) {
- final CreateRegionGroupsProcedure procedure;
- // Cover both ID allocation and submission with the same per-database locks used by database
- // creation and deletion. A delayed plan therefore cannot become invisible between deleting
- // and recreating a database with the same name.
+ final List procedures = new ArrayList<>(allotmentMap.size());
+ for (final Map.Entry entry : new TreeMap<>(allotmentMap).entrySet()) {
+ final String database = entry.getKey();
+ // Cover both ID allocation and submission with the same database lock used by database
+ // creation and deletion. Submit every database before waiting so each Procedure can make
+ // progress independently of slow or deleting databases.
try (final DatabaseLock ignored =
- getProcedureManager().acquireDatabaseLifecycleLocks(allotmentMap.keySet())) {
+ getProcedureManager().acquireDatabaseLifecycleLock(database)) {
final CreateRegionGroupsPlan createRegionGroupsPlan =
- getLoadManager().allocateRegionGroups(allotmentMap, consensusGroupType);
+ getLoadManager()
+ .allocateRegionGroups(
+ Collections.singletonMap(database, entry.getValue()), consensusGroupType);
LOGGER.info(
ManagerMessages.CREATEREGIONGROUPS_STARTING_TO_CREATE_THE_FOLLOWING_REGIONGROUPS);
createRegionGroupsPlan.planLog(LOGGER);
- procedure =
+ procedures.add(
getProcedureManager()
- .submitCreateRegionGroups(consensusGroupType, createRegionGroupsPlan);
+ .submitCreateRegionGroups(consensusGroupType, createRegionGroupsPlan));
}
- return getProcedureManager().waitCreateRegionGroups(procedure);
- } else {
- return RpcUtils.SUCCESS_STATUS;
}
+
+ TSStatus result = RpcUtils.SUCCESS_STATUS;
+ for (final CreateRegionGroupsProcedure procedure : procedures) {
+ final TSStatus status = getProcedureManager().waitCreateRegionGroups(procedure);
+ if (result.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()
+ && status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ result = status;
+ }
+ }
+ return result;
}
/**
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
index d850c6d4f3482..21e33a3e08029 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java
@@ -100,7 +100,7 @@
import org.apache.iotdb.confignode.manager.partition.RegionGroupExtensionPolicy;
import org.apache.iotdb.confignode.persistence.schema.ClusterSchemaInfo;
import org.apache.iotdb.confignode.persistence.schema.ConfigSchemaStatistics;
-import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLockQueue.DatabaseLock;
import org.apache.iotdb.confignode.rpc.thrift.TDatabaseInfo;
import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
import org.apache.iotdb.confignode.rpc.thrift.TDescTable4InformationSchemaResp;
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
index 2124761521c75..3ff7a7f856804 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
@@ -56,7 +56,7 @@
import org.apache.iotdb.confignode.procedure.Procedure;
import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
import org.apache.iotdb.confignode.procedure.impl.schema.SchemaUtils;
-import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLockQueue;
import org.apache.iotdb.confignode.procedure.scheduler.LockQueue;
import org.apache.iotdb.confignode.procedure.scheduler.ProcedureScheduler;
import org.apache.iotdb.confignode.rpc.thrift.TAddConsensusGroupReq;
@@ -117,7 +117,7 @@ public class ConfigNodeProcedureEnv {
/** Add or remove node lock. */
private final LockQueue nodeLock = new LockQueue();
- private final DatabaseLifecycleLockManager databaseLifecycleLockManager;
+ private final DatabaseLockQueue databaseLockQueue;
private final ReentrantLock schedulerLock = new ReentrantLock(true);
@@ -136,7 +136,7 @@ public class ConfigNodeProcedureEnv {
public ConfigNodeProcedureEnv(ConfigManager configManager, ProcedureScheduler scheduler) {
this.configManager = configManager;
this.scheduler = scheduler;
- this.databaseLifecycleLockManager = new DatabaseLifecycleLockManager(scheduler);
+ this.databaseLockQueue = new DatabaseLockQueue(scheduler);
this.regionMaintainHandler = new RegionMaintainHandler(configManager);
this.removeDataNodeHandler = new RemoveDataNodeHandler(configManager);
this.removeConfigNodeLock = new ReentrantLock();
@@ -1154,19 +1154,19 @@ public LockQueue getNodeLock() {
* @return the first database whose lock is unavailable, or null when all locks are acquired
*/
public String tryLockDatabases(final Procedure> procedure, final Set databaseNames) {
- return databaseLifecycleLockManager.tryLock(procedure, databaseNames);
+ return databaseLockQueue.tryLock(procedure, databaseNames);
}
public void waitDatabaseLock(final Procedure> procedure, final String databaseName) {
- databaseLifecycleLockManager.waitProcedure(procedure, databaseName);
+ databaseLockQueue.waitProcedure(procedure, databaseName);
}
public void releaseDatabaseLocks(final Procedure> procedure, final Set databaseNames) {
- databaseLifecycleLockManager.releaseLocks(procedure, databaseNames);
+ databaseLockQueue.releaseLocks(procedure, databaseNames);
}
- public DatabaseLifecycleLockManager getDatabaseLifecycleLockManager() {
- return databaseLifecycleLockManager;
+ public DatabaseLockQueue getDatabaseLockQueue() {
+ return databaseLockQueue;
}
public ProcedureScheduler getScheduler() {
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLockQueue.java
similarity index 96%
rename from iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
rename to iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLockQueue.java
index 57e0cb2e0e142..78ce645e2fbe5 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManager.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLockQueue.java
@@ -40,14 +40,14 @@
* database deletion cannot bypass one another. Procedure ownership is identified by procedure id
* rather than worker thread because a procedure can resume on a different executor thread.
*/
-public class DatabaseLifecycleLockManager {
+public class DatabaseLockQueue {
private final ProcedureScheduler scheduler;
private final ReentrantLock stateLock = new ReentrantLock(true);
private final Condition lockReleased = stateLock.newCondition();
private final Map lockStateMap = new HashMap<>();
- public DatabaseLifecycleLockManager(final ProcedureScheduler scheduler) {
+ public DatabaseLockQueue(final ProcedureScheduler scheduler) {
this.scheduler = scheduler;
}
@@ -212,16 +212,14 @@ private static List orderedDatabases(final Set databaseNames) {
}
public static final class DatabaseLock implements AutoCloseable {
- private final DatabaseLifecycleLockManager lockManager;
+ private final DatabaseLockQueue lockQueue;
private final List databaseNames;
private final Thread owner;
private boolean closed;
private DatabaseLock(
- final DatabaseLifecycleLockManager lockManager,
- final List databaseNames,
- final Thread owner) {
- this.lockManager = lockManager;
+ final DatabaseLockQueue lockQueue, final List databaseNames, final Thread owner) {
+ this.lockQueue = lockQueue;
this.databaseNames = databaseNames;
this.owner = owner;
}
@@ -230,7 +228,7 @@ private DatabaseLock(
public void close() {
if (!closed) {
closed = true;
- lockManager.releaseRequestLocks(databaseNames, owner);
+ lockQueue.releaseRequestLocks(databaseNames, owner);
}
}
}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
index db40647f2377b..5cb999687a520 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java
@@ -24,8 +24,8 @@
import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
import org.apache.iotdb.confignode.manager.schema.ClusterSchemaQuotaStatistics;
import org.apache.iotdb.confignode.persistence.schema.ClusterSchemaInfo;
-import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager;
-import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLockQueue;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLockQueue.DatabaseLock;
import org.apache.iotdb.confignode.procedure.scheduler.SimpleProcedureScheduler;
import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
import org.apache.iotdb.rpc.TSStatusCode;
@@ -62,8 +62,7 @@ public void testSetDatabaseWaitsForLifecycleAdmissionBeforeCheckingProcedures()
final String database = "root.sg";
final IManager configManager = Mockito.mock(IManager.class);
final ProcedureManager procedureManager = Mockito.mock(ProcedureManager.class);
- final DatabaseLifecycleLockManager lockManager =
- new DatabaseLifecycleLockManager(new SimpleProcedureScheduler());
+ final DatabaseLockQueue lockQueue = new DatabaseLockQueue(new SimpleProcedureScheduler());
final CountDownLatch admissionAttempted = new CountDownLatch(1);
final AtomicBoolean unfinishedProcedure = new AtomicBoolean(false);
Mockito.when(configManager.getProcedureManager()).thenReturn(procedureManager);
@@ -71,7 +70,7 @@ public void testSetDatabaseWaitsForLifecycleAdmissionBeforeCheckingProcedures()
.thenAnswer(
ignored -> {
admissionAttempted.countDown();
- return lockManager.acquireLocks(Collections.singleton(database));
+ return lockQueue.acquireLocks(Collections.singleton(database));
});
Mockito.when(procedureManager.hasUnfinishedDatabaseLifecycleProcedure(database))
.thenAnswer(ignored -> unfinishedProcedure.get());
@@ -87,7 +86,7 @@ public void testSetDatabaseWaitsForLifecycleAdmissionBeforeCheckingProcedures()
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future statusFuture;
- try (final DatabaseLock ignored = lockManager.acquireLocks(Collections.singleton(database))) {
+ try (final DatabaseLock ignored = lockQueue.acquireLocks(Collections.singleton(database))) {
statusFuture = executor.submit(() -> schemaManager.setDatabase(plan, false));
Assert.assertTrue(admissionAttempted.await(10, TimeUnit.SECONDS));
unfinishedProcedure.set(true);
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/partition/PartitionManagerTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/partition/PartitionManagerTest.java
new file mode 100644
index 0000000000000..a4c2fd2ee9ec7
--- /dev/null
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/partition/PartitionManagerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.confignode.manager.partition;
+
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupId;
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
+import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
+import org.apache.iotdb.confignode.manager.IManager;
+import org.apache.iotdb.confignode.manager.ProcedureManager;
+import org.apache.iotdb.confignode.manager.load.LoadManager;
+import org.apache.iotdb.confignode.persistence.partition.PartitionInfo;
+import org.apache.iotdb.confignode.procedure.impl.region.CreateRegionGroupsProcedure;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLockQueue;
+import org.apache.iotdb.confignode.procedure.scheduler.ProcedureScheduler;
+import org.apache.iotdb.rpc.RpcUtils;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class PartitionManagerTest {
+
+ @Test
+ public void testCreateRegionGroupsProceduresAreSubmittedPerDatabaseBeforeWaiting()
+ throws Exception {
+ final IManager configManager = Mockito.mock(IManager.class);
+ final LoadManager loadManager = Mockito.mock(LoadManager.class);
+ final ProcedureManager procedureManager = Mockito.mock(ProcedureManager.class);
+ final DatabaseLockQueue databaseLockQueue =
+ new DatabaseLockQueue(Mockito.mock(ProcedureScheduler.class));
+ Mockito.when(configManager.getLoadManager()).thenReturn(loadManager);
+ Mockito.when(configManager.getProcedureManager()).thenReturn(procedureManager);
+ Mockito.when(procedureManager.acquireDatabaseLifecycleLock(Mockito.anyString()))
+ .thenAnswer(
+ invocation ->
+ databaseLockQueue.acquireLocks(
+ Collections.singleton((String) invocation.getArgument(0))));
+
+ final CreateRegionGroupsPlan databaseAPlan = new CreateRegionGroupsPlan();
+ final CreateRegionGroupsPlan databaseBPlan = new CreateRegionGroupsPlan();
+ databaseAPlan.addRegionGroup(
+ "root.a",
+ new TRegionReplicaSet(
+ new TConsensusGroupId(TConsensusGroupType.DataRegion, 1), Collections.emptyList()));
+ databaseBPlan.addRegionGroup(
+ "root.b",
+ new TRegionReplicaSet(
+ new TConsensusGroupId(TConsensusGroupType.DataRegion, 2), Collections.emptyList()));
+ final CreateRegionGroupsProcedure databaseAProcedure =
+ Mockito.mock(CreateRegionGroupsProcedure.class);
+ final CreateRegionGroupsProcedure databaseBProcedure =
+ Mockito.mock(CreateRegionGroupsProcedure.class);
+ Mockito.when(
+ loadManager.allocateRegionGroups(
+ Collections.singletonMap("root.a", 1), TConsensusGroupType.DataRegion))
+ .thenReturn(databaseAPlan);
+ Mockito.when(
+ loadManager.allocateRegionGroups(
+ Collections.singletonMap("root.b", 2), TConsensusGroupType.DataRegion))
+ .thenReturn(databaseBPlan);
+ Mockito.when(
+ procedureManager.submitCreateRegionGroups(
+ TConsensusGroupType.DataRegion, databaseAPlan))
+ .thenReturn(databaseAProcedure);
+ Mockito.when(
+ procedureManager.submitCreateRegionGroups(
+ TConsensusGroupType.DataRegion, databaseBPlan))
+ .thenReturn(databaseBProcedure);
+ Mockito.when(procedureManager.waitCreateRegionGroups(databaseAProcedure))
+ .thenReturn(new TSStatus(TSStatusCode.CREATE_REGION_ERROR.getStatusCode()));
+ Mockito.when(procedureManager.waitCreateRegionGroups(databaseBProcedure))
+ .thenReturn(RpcUtils.SUCCESS_STATUS);
+
+ final PartitionManager partitionManager =
+ new PartitionManager(configManager, Mockito.mock(PartitionInfo.class));
+ try {
+ final Map allotmentMap = new LinkedHashMap<>();
+ allotmentMap.put("root.b", 2);
+ allotmentMap.put("root.a", 1);
+
+ final TSStatus status =
+ partitionManager.generateAndAllocateRegionGroups(
+ allotmentMap, TConsensusGroupType.DataRegion);
+
+ Assert.assertEquals(TSStatusCode.CREATE_REGION_ERROR.getStatusCode(), status.getCode());
+ final InOrder inOrder = Mockito.inOrder(loadManager, procedureManager);
+ inOrder.verify(procedureManager).acquireDatabaseLifecycleLock("root.a");
+ inOrder
+ .verify(loadManager)
+ .allocateRegionGroups(
+ Collections.singletonMap("root.a", 1), TConsensusGroupType.DataRegion);
+ inOrder
+ .verify(procedureManager)
+ .submitCreateRegionGroups(TConsensusGroupType.DataRegion, databaseAPlan);
+ inOrder.verify(procedureManager).acquireDatabaseLifecycleLock("root.b");
+ inOrder
+ .verify(loadManager)
+ .allocateRegionGroups(
+ Collections.singletonMap("root.b", 2), TConsensusGroupType.DataRegion);
+ inOrder
+ .verify(procedureManager)
+ .submitCreateRegionGroups(TConsensusGroupType.DataRegion, databaseBPlan);
+ inOrder.verify(procedureManager).waitCreateRegionGroups(databaseAProcedure);
+ inOrder.verify(procedureManager).waitCreateRegionGroups(databaseBProcedure);
+ Mockito.verify(procedureManager, Mockito.never())
+ .acquireDatabaseLifecycleLocks(Mockito.anySet());
+ } finally {
+ partitionManager.getRegionMaintainer().shutdownNow();
+ }
+ }
+}
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLockQueueTest.java
similarity index 65%
rename from iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java
rename to iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLockQueueTest.java
index e30f4cde7909b..09990f1961033 100644
--- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLifecycleLockManagerTest.java
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/scheduler/DatabaseLockQueueTest.java
@@ -20,7 +20,7 @@
package org.apache.iotdb.confignode.procedure.scheduler;
import org.apache.iotdb.confignode.procedure.Procedure;
-import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLifecycleLockManager.DatabaseLock;
+import org.apache.iotdb.confignode.procedure.scheduler.DatabaseLockQueue.DatabaseLock;
import org.junit.Assert;
import org.junit.Test;
@@ -34,20 +34,20 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-public class DatabaseLifecycleLockManagerTest {
+public class DatabaseLockQueueTest {
@Test
public void testRequestLocksAreScopedByDatabaseName() throws Exception {
- final DatabaseLifecycleLockManager lockManager =
- new DatabaseLifecycleLockManager(Mockito.mock(ProcedureScheduler.class));
+ final DatabaseLockQueue lockQueue =
+ new DatabaseLockQueue(Mockito.mock(ProcedureScheduler.class));
final ExecutorService executor = Executors.newFixedThreadPool(2);
final CountDownLatch sameDatabaseAcquired = new CountDownLatch(1);
- try (final DatabaseLock ignored = lockManager.acquireLocks(Collections.singleton("root.sg"))) {
+ try (final DatabaseLock ignored = lockQueue.acquireLocks(Collections.singleton("root.sg"))) {
final Future> sameDatabaseFuture =
executor.submit(
() -> {
try (final DatabaseLock sameDatabaseLock =
- lockManager.acquireLocks(Collections.singleton("root.sg"))) {
+ lockQueue.acquireLocks(Collections.singleton("root.sg"))) {
sameDatabaseAcquired.countDown();
}
});
@@ -55,7 +55,7 @@ public void testRequestLocksAreScopedByDatabaseName() throws Exception {
executor.submit(
() -> {
try (final DatabaseLock otherDatabaseLock =
- lockManager.acquireLocks(Collections.singleton("root.other"))) {
+ lockQueue.acquireLocks(Collections.singleton("root.other"))) {
// Acquiring a different database proves the lock is not cluster-global.
}
});
@@ -72,14 +72,14 @@ public void testRequestLocksAreScopedByDatabaseName() throws Exception {
@Test
public void testWaitingProcedureCannotBeOvertakenByRequest() throws Exception {
final ProcedureScheduler scheduler = Mockito.mock(ProcedureScheduler.class);
- final DatabaseLifecycleLockManager lockManager = new DatabaseLifecycleLockManager(scheduler);
+ final DatabaseLockQueue lockQueue = new DatabaseLockQueue(scheduler);
final Procedure> owner = procedure(1);
final Procedure> waiter = procedure(2);
final Set databases = Collections.singleton("root.sg");
- Assert.assertNull(lockManager.tryLock(owner, databases));
- Assert.assertEquals("root.sg", lockManager.tryLock(waiter, databases));
- lockManager.waitProcedure(waiter, "root.sg");
+ Assert.assertNull(lockQueue.tryLock(owner, databases));
+ Assert.assertEquals("root.sg", lockQueue.tryLock(waiter, databases));
+ lockQueue.waitProcedure(waiter, "root.sg");
final ExecutorService executor = Executors.newSingleThreadExecutor();
final CountDownLatch requestAcquired = new CountDownLatch(1);
@@ -87,19 +87,19 @@ public void testWaitingProcedureCannotBeOvertakenByRequest() throws Exception {
final Future> requestFuture =
executor.submit(
() -> {
- try (final DatabaseLock ignored = lockManager.acquireLocks(databases)) {
+ try (final DatabaseLock ignored = lockQueue.acquireLocks(databases)) {
requestAcquired.countDown();
}
});
Assert.assertFalse(requestAcquired.await(200, TimeUnit.MILLISECONDS));
- lockManager.releaseLocks(owner, databases);
+ lockQueue.releaseLocks(owner, databases);
Mockito.verify(scheduler).addFront(waiter);
Assert.assertFalse(requestAcquired.await(200, TimeUnit.MILLISECONDS));
- Assert.assertNull(lockManager.tryLock(waiter, databases));
+ Assert.assertNull(lockQueue.tryLock(waiter, databases));
Assert.assertFalse(requestAcquired.await(200, TimeUnit.MILLISECONDS));
- lockManager.releaseLocks(waiter, databases);
+ lockQueue.releaseLocks(waiter, databases);
Assert.assertTrue(requestAcquired.await(10, TimeUnit.SECONDS));
requestFuture.get(10, TimeUnit.SECONDS);
} finally {
@@ -109,50 +109,50 @@ public void testWaitingProcedureCannotBeOvertakenByRequest() throws Exception {
@Test
public void testTimedRequestLockAcquisitionHonorsTimeout() throws Exception {
- final DatabaseLifecycleLockManager lockManager =
- new DatabaseLifecycleLockManager(Mockito.mock(ProcedureScheduler.class));
+ final DatabaseLockQueue lockQueue =
+ new DatabaseLockQueue(Mockito.mock(ProcedureScheduler.class));
final Procedure> owner = procedure(1);
final Set databases = Collections.singleton("root.sg");
- Assert.assertNull(lockManager.tryLock(owner, databases));
+ Assert.assertNull(lockQueue.tryLock(owner, databases));
final long startNanos = System.nanoTime();
- Assert.assertNull(lockManager.tryAcquireLocks(databases, 100, TimeUnit.MILLISECONDS));
+ Assert.assertNull(lockQueue.tryAcquireLocks(databases, 100, TimeUnit.MILLISECONDS));
Assert.assertTrue(TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startNanos) < 5);
- lockManager.releaseLocks(owner, databases);
+ lockQueue.releaseLocks(owner, databases);
}
@Test
public void testFailedMultiDatabaseAcquisitionDoesNotLeakPartialLocks() {
- final DatabaseLifecycleLockManager lockManager =
- new DatabaseLifecycleLockManager(Mockito.mock(ProcedureScheduler.class));
+ final DatabaseLockQueue lockQueue =
+ new DatabaseLockQueue(Mockito.mock(ProcedureScheduler.class));
final Procedure> firstProcedure = procedure(1);
final Procedure> secondProcedure = procedure(2);
- Assert.assertNull(lockManager.tryLock(firstProcedure, Collections.singleton("root.b")));
- Assert.assertEquals("root.b", lockManager.tryLock(secondProcedure, Set.of("root.a", "root.b")));
+ Assert.assertNull(lockQueue.tryLock(firstProcedure, Collections.singleton("root.b")));
+ Assert.assertEquals("root.b", lockQueue.tryLock(secondProcedure, Set.of("root.a", "root.b")));
- try (final DatabaseLock ignored = lockManager.acquireLocks(Collections.singleton("root.a"))) {
+ try (final DatabaseLock ignored = lockQueue.acquireLocks(Collections.singleton("root.a"))) {
// The partial root.a acquisition of secondProcedure must have been released.
}
- lockManager.releaseLocks(firstProcedure, Collections.singleton("root.b"));
+ lockQueue.releaseLocks(firstProcedure, Collections.singleton("root.b"));
}
@Test
public void testRequestLocksAreReentrantOnTheOwningThread() {
- final DatabaseLifecycleLockManager lockManager =
- new DatabaseLifecycleLockManager(Mockito.mock(ProcedureScheduler.class));
+ final DatabaseLockQueue lockQueue =
+ new DatabaseLockQueue(Mockito.mock(ProcedureScheduler.class));
final Procedure> procedure = procedure(1);
final Set databases = Collections.singleton("root.sg");
- try (final DatabaseLock outerLock = lockManager.acquireLocks(databases)) {
- try (final DatabaseLock innerLock = lockManager.acquireLocks(databases)) {
- Assert.assertEquals("root.sg", lockManager.tryLock(procedure, databases));
+ try (final DatabaseLock outerLock = lockQueue.acquireLocks(databases)) {
+ try (final DatabaseLock innerLock = lockQueue.acquireLocks(databases)) {
+ Assert.assertEquals("root.sg", lockQueue.tryLock(procedure, databases));
}
- Assert.assertEquals("root.sg", lockManager.tryLock(procedure, databases));
+ Assert.assertEquals("root.sg", lockQueue.tryLock(procedure, databases));
}
- Assert.assertNull(lockManager.tryLock(procedure, databases));
- lockManager.releaseLocks(procedure, databases);
+ Assert.assertNull(lockQueue.tryLock(procedure, databases));
+ lockQueue.releaseLocks(procedure, databases);
}
private static Procedure> procedure(final long procedureId) {