This is an automated email from the ASF dual-hosted git repository.
Caideyipi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 27f89fa72fc [Feature] Make DROP TABLE asynchronous and expose deletion
progress (#18806)
27f89fa72fc is described below
commit 27f89fa72fc6f8ed4c478b8892bdc5fc669200b9
Author: Caideyipi <[email protected]>
AuthorDate: Tue Oct 13 14:16:56 2026 +0800
[Feature] Make DROP TABLE asynchronous and expose deletion progress (#18806)
* [Feature] Make DROP TABLE asynchronous and expose deletion progress
* [Test] Wait for asynchronous DROP TABLE completion in deletion IT
* [Fix] Address asynchronous DROP TABLE review comments
* [Fix] Disable RPC timeout for asynchronous DROP TABLE deletion
---
.../relational/it/db/it/IoTDBDeletionTableIT.java | 20 +++
.../relational/it/schema/IoTDBDatabaseIT.java | 4 +-
.../iotdb/relational/it/schema/IoTDBTableIT.java | 66 ++++++++
.../CnToDnInternalServiceAsyncRequestManager.java | 8 +-
.../iotdb/confignode/manager/ConfigManager.java | 10 ++
.../apache/iotdb/confignode/manager/IManager.java | 3 +
.../iotdb/confignode/manager/ProcedureManager.java | 99 ++++++++++--
.../confignode/procedure/ProcedureExecutor.java | 106 +++++++++---
.../procedure/TimeoutExecutorThread.java | 2 +-
.../impl/schema/table/DropTableProcedure.java | 8 +
.../thrift/ConfigNodeRPCServiceProcessor.java | 6 +
...ToDnInternalServiceAsyncRequestManagerTest.java | 114 +++++++++++++
.../impl/schema/table/DropTableProcedureTest.java | 177 +++++++++++++++++++++
.../iotdb/db/protocol/client/ConfigNodeClient.java | 7 +
.../InformationSchemaContentSupplierFactory.java | 68 ++++++++
.../DataNodeLocationSupplierFactory.java | 1 +
.../commons/client/AbstractConfigNodeClient.java | 6 +
.../schema/column/ColumnHeaderConstant.java | 3 +
.../commons/schema/table/InformationSchema.java | 20 +++
.../src/main/thrift/confignode.thrift | 15 ++
20 files changed, 711 insertions(+), 32 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java
index f8732645926..c6063a2c59d 100644
---
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java
@@ -29,6 +29,7 @@ import org.apache.iotdb.itbase.category.ManualIT;
import org.apache.iotdb.itbase.category.TableClusterIT;
import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
import org.apache.iotdb.itbase.env.BaseEnv;
+import org.apache.iotdb.itbase.exception.InconsistentDataException;
import org.apache.iotdb.itbase.exception.ParallelRequestTimeoutException;
import org.apache.iotdb.rpc.IoTDBConnectionException;
import org.apache.iotdb.rpc.StatementExecutionException;
@@ -1361,6 +1362,25 @@ public class IoTDBDeletionTableIT {
statement.execute("DROP TABLE vehicle" + testNum);
+ // DROP TABLE acknowledges submission before the table has been fully
removed.
+ Awaitility.await()
+ .atMost(60, TimeUnit.SECONDS)
+ .pollInSameThread()
+ // Progress snapshots can differ between cluster nodes while
deletion is running.
+ .ignoreException(InconsistentDataException.class)
+ .untilAsserted(
+ () -> {
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ "SELECT state, error_message FROM
information_schema.drop_table_procedures "
+ + "WHERE database = 'test' AND table_name =
'vehicle"
+ + testNum
+ + "' ORDER BY procedure_id DESC LIMIT 1")) {
+ assertTrue(resultSet.next());
+ assertEquals(resultSet.getString(2), "SUCCESS",
resultSet.getString(1));
+ }
+ });
+
try (ResultSet ignored = statement.executeQuery("SELECT * FROM vehicle"
+ testNum)) {
fail("Exception expected");
} catch (SQLException e) {
diff --git
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
index be42be99298..3fc22722078 100644
---
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
@@ -532,6 +532,7 @@ public class IoTDBDatabaseIT {
"current_queries,INF,",
"data_nodes,INF,",
"databases,INF,",
+ "drop_table_procedures,INF,",
"functions,INF,",
"keywords,INF,",
"nodes,INF,",
@@ -829,6 +830,7 @@ public class IoTDBDatabaseIT {
Arrays.asList(
"information_schema,databases,INF,USING,null,SYSTEM
VIEW,false,",
"information_schema,tables,INF,USING,null,SYSTEM
VIEW,false,",
+
"information_schema,drop_table_procedures,INF,USING,null,SYSTEM VIEW,false,",
"information_schema,columns,INF,USING,null,SYSTEM
VIEW,false,",
"information_schema,queries,INF,USING,null,SYSTEM
VIEW,false,",
"information_schema,regions,INF,USING,null,SYSTEM
VIEW,false,",
@@ -854,7 +856,7 @@ public class IoTDBDatabaseIT {
TestUtils.assertResultSetEqual(
statement.executeQuery("count devices from tables where status =
'USING'"),
"count(devices),",
- Collections.singleton("24,"));
+ Collections.singleton("25,"));
TestUtils.assertResultSetEqual(
statement.executeQuery(
"select * from columns where table_name = 'queries' or database
= 'test'"),
diff --git
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java
index 4c13f660972..4589ec5d49a 100644
---
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java
@@ -546,6 +546,7 @@ public class IoTDBTableIT {
Collections.singleton("null,"));
statement.execute("drop table table2");
+ awaitDropProcedure(statement, "test2", "table2");
try {
statement.executeQuery("describe table2");
fail();
@@ -1229,4 +1230,69 @@ public class IoTDBTableIT {
}
}
}
+
+ @Test
+ public void testAsyncDropTableProgress() throws Exception {
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ final Statement statement = connection.createStatement()) {
+ statement.execute("create database async_drop_db");
+ statement.execute("use async_drop_db");
+ statement.execute("create table drop_target (device string tag, reading
int32)");
+ statement.execute("drop table drop_target");
+
+ // The drop may finish before the first query. Its completed result
remains visible until
+ // the procedure cleaner evicts it, so verify that result after waiting
for completion.
+ awaitDropProcedure(statement, "async_drop_db", "drop_target");
+ TestUtils.assertResultSetEqual(
+ statement.executeQuery("show tables from async_drop_db"),
+ "TableName,TTL(ms),",
+ Collections.emptySet());
+ try (final ResultSet resultSet =
+ statement.executeQuery(
+ "select procedure_id, state, progress, error_message "
+ + "from information_schema.drop_table_procedures "
+ + "where database = 'async_drop_db' and table_name =
'drop_target'")) {
+ assertTrue(resultSet.next());
+ assertTrue(resultSet.getLong(1) >= 0);
+ assertEquals("SUCCESS", resultSet.getString(2));
+ assertEquals("SUCCESS", resultSet.getString(3));
+ Assert.assertNull(resultSet.getString(4));
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+
+ private static void awaitDropProcedure(
+ final Statement statement, final String database, final String tableName)
+ throws SQLException {
+ final long deadline = System.currentTimeMillis() + 60_000;
+ while (System.currentTimeMillis() < deadline) {
+ try (final ResultSet resultSet =
+ statement.executeQuery(
+ "select state, error_message from
information_schema.drop_table_procedures "
+ + "where database = '"
+ + database
+ + "' and table_name = '"
+ + tableName
+ + "'")) {
+ if (resultSet.next()) {
+ final String state = resultSet.getString(1);
+ if ("SUCCESS".equals(state)) {
+ return;
+ }
+ if ("ROLLEDBACK".equals(state)) {
+ throw new SQLException(resultSet.getString(2));
+ }
+ }
+ }
+ try {
+ Thread.sleep(20);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new SQLException(e);
+ }
+ }
+ throw new SQLException("Timed out waiting for drop table procedure");
+ }
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
index 7ca698afa2c..e73573ea0e8 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
@@ -558,11 +558,15 @@ public class CnToDnInternalServiceAsyncRequestManager
@Override
protected void adjustClientTimeoutIfNecessary(
- CnToDnAsyncRequestType CnToDnAsyncRequestType,
+ CnToDnAsyncRequestType requestType,
AsyncDataNodeInternalServiceClient client,
Long timeoutInMs) {
- if
(CnToDnAsyncRequestType.SUBMIT_TEST_CONNECTION_TASK.equals(CnToDnAsyncRequestType))
{
+ if
(CnToDnAsyncRequestType.SUBMIT_TEST_CONNECTION_TASK.equals(requestType)) {
client.setTimeoutTemporarily(TestConnectionUtils.calculateCnLeaderToAllDnMaxTime());
+ } else if
(CnToDnAsyncRequestType.DELETE_DATA_FOR_DROP_TABLE.equals(requestType)
+ ||
CnToDnAsyncRequestType.DELETE_DEVICES_FOR_DROP_TABLE.equals(requestType)) {
+ // Asynchronous DROP TABLE deletion can take arbitrarily long to finish.
+ client.setTimeoutTemporarily(-1);
} else if (timeoutInMs != null) {
client.setTimeoutTemporarily(timeoutInMs);
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
index 98c0a1bb9fc..f82d8187fab 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
@@ -243,6 +243,7 @@ import
org.apache.iotdb.confignode.rpc.thrift.TShowConfigNodesResp;
import
org.apache.iotdb.confignode.rpc.thrift.TShowDataNodes4InformationSchemaResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDataNodesResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDatabaseResp;
+import org.apache.iotdb.confignode.rpc.thrift.TShowDropTableProceduresResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipePluginReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeResp;
@@ -3401,6 +3402,15 @@ public class ConfigManager implements IManager {
: new TShowTable4InformationSchemaResp(status);
}
+ @Override
+ public TShowDropTableProceduresResp showDropTableProcedures() {
+ final TSStatus status = confirmLeader();
+ return status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()
+ ? new TShowDropTableProceduresResp(status)
+
.setDropTableProcedureInfoList(procedureManager.getDropTableProcedures())
+ : new TShowDropTableProceduresResp(status);
+ }
+
@Override
public TDescTableResp describeTable(
final String database, final String tableName, final boolean isDetails) {
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/IManager.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/IManager.java
index cc203caeeba..37cc7c72247 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/IManager.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/IManager.java
@@ -151,6 +151,7 @@ import
org.apache.iotdb.confignode.rpc.thrift.TShowConfigNodesResp;
import
org.apache.iotdb.confignode.rpc.thrift.TShowDataNodes4InformationSchemaResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDataNodesResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDatabaseResp;
+import org.apache.iotdb.confignode.rpc.thrift.TShowDropTableProceduresResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipePluginReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeResp;
@@ -924,6 +925,8 @@ public interface IManager {
TShowTable4InformationSchemaResp showTables4InformationSchema();
+ TShowDropTableProceduresResp showDropTableProcedures();
+
TDescTableResp describeTable(
final String database, final String tableName, final boolean isDetails);
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 9531f31f1b8..b9b84f20b65 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
@@ -32,12 +32,14 @@ import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.conf.IoTDBConstant;
import org.apache.iotdb.commons.exception.IoTDBException;
import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.exception.table.TableNotExistsException;
import org.apache.iotdb.commons.path.MeasurementPath;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.path.PathDeserializeUtil;
import org.apache.iotdb.commons.path.PathPatternTree;
import org.apache.iotdb.commons.pipe.agent.plugin.meta.PipePluginMeta;
import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
+import org.apache.iotdb.commons.schema.table.TableNodeStatus;
import org.apache.iotdb.commons.schema.table.TreeViewSchema;
import org.apache.iotdb.commons.schema.table.TsTable;
import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchemaUtil;
@@ -60,6 +62,7 @@ import
org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGr
import org.apache.iotdb.confignode.i18n.ManagerMessages;
import org.apache.iotdb.confignode.i18n.ProcedureMessages;
import org.apache.iotdb.confignode.manager.partition.PartitionManager;
+import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
import
org.apache.iotdb.confignode.manager.subscription.SubscriptionCoordinator;
import org.apache.iotdb.confignode.persistence.ProcedureInfo;
import org.apache.iotdb.confignode.procedure.PartitionTableAutoCleaner;
@@ -159,6 +162,7 @@ import
org.apache.iotdb.confignode.rpc.thrift.TDeleteLogicalViewReq;
import org.apache.iotdb.confignode.rpc.thrift.TDeleteTableDeviceReq;
import org.apache.iotdb.confignode.rpc.thrift.TDeleteTableDeviceResp;
import org.apache.iotdb.confignode.rpc.thrift.TDropPipePluginReq;
+import org.apache.iotdb.confignode.rpc.thrift.TDropTableProcedureInfo;
import org.apache.iotdb.confignode.rpc.thrift.TExtendRegionReq;
import org.apache.iotdb.confignode.rpc.thrift.TMigrateRegionReq;
import org.apache.iotdb.confignode.rpc.thrift.TReconstructRegionReq;
@@ -2378,15 +2382,63 @@ public class ProcedureManager {
public TSStatus dropTable(final TAlterOrDropTableReq req) {
final boolean isView = req.isSetIsView() && req.isIsView();
- return executeWithoutDuplicate(
- req.database,
- null,
- req.tableName,
- req.queryId,
- isView ? ProcedureType.DROP_VIEW_PROCEDURE :
ProcedureType.DROP_TABLE_PROCEDURE,
- isView
- ? new DropViewProcedure(req.database, req.tableName, req.queryId,
false)
- : new DropTableProcedure(req.database, req.tableName, req.queryId,
false));
+ if (isView) {
+ return executeWithoutDuplicate(
+ req.database,
+ null,
+ req.tableName,
+ req.queryId,
+ ProcedureType.DROP_VIEW_PROCEDURE,
+ new DropViewProcedure(req.database, req.tableName, req.queryId,
false));
+ }
+ synchronized (this) {
+ if (!executor.isRunning()) {
+ return RpcUtils.getStatus(TSStatusCode.CONFIG_NODE_LEADER_WARMING_UP);
+ }
+ final Pair<Long, Boolean> duplicate =
+ checkDuplicateTableTask(
+ req.database,
+ null,
+ req.tableName,
+ null,
+ req.queryId,
+ ProcedureType.DROP_TABLE_PROCEDURE);
+ if (duplicate.getLeft() != -1) {
+ return StatusUtils.OK;
+ }
+ if (Boolean.TRUE.equals(duplicate.getRight())) {
+ return RpcUtils.getStatus(TSStatusCode.OVERLAP_WITH_EXISTING_TASK);
+ }
+ try {
+ final Optional<Pair<TsTable, TableNodeStatus>> table =
+ configManager
+ .getClusterSchemaManager()
+ .getTableAndStatusIfExists(req.database, req.tableName);
+ if (table.isEmpty()) {
+ final TableNotExistsException e =
+ new TableNotExistsException(req.database, req.tableName);
+ return RpcUtils.getStatus(e.getErrorCode(), e.getMessage());
+ }
+ if (table.get().getRight() == TableNodeStatus.PRE_DELETE) {
+ return RpcUtils.getStatus(TSStatusCode.OVERLAP_WITH_EXISTING_TASK);
+ }
+ final Optional<Pair<TSStatus, TsTable>> viewCheck =
+ ClusterSchemaManager.checkTable4View(req.database,
table.get().getLeft(), false);
+ if (viewCheck.isPresent()) {
+ return viewCheck.get().getLeft();
+ }
+ } catch (final MetadataException e) {
+ return RpcUtils.getStatus(e.getErrorCode(), e.getMessage());
+ }
+ final Procedure<ConfigNodeProcedureEnv> procedure =
+ new DropTableProcedure(req.database, req.tableName, req.queryId,
false);
+ try {
+ executor.submitProcedureAndPersist(procedure);
+ return StatusUtils.OK;
+ } catch (final Exception e) {
+ return RpcUtils.getStatus(TSStatusCode.INTERNAL_SERVER_ERROR,
e.getMessage());
+ }
+ }
}
public TSStatus renameTable(final TAlterOrDropTableReq req) {
@@ -2486,6 +2538,35 @@ public class ProcedureManager {
return result;
}
+ /** Returns running drops and completed results retained by the executor's
shared TTL cleaner. */
+ public List<TDropTableProcedureInfo> getDropTableProcedures() {
+ final Map<Long, DropTableProcedure> dropProcedures = new HashMap<>();
+ Stream.concat(
+ executor.getProcedures().values().stream(),
executor.getCompletedProcedures().stream())
+ .filter(
+ procedure ->
+ procedure instanceof DropTableProcedure
+ && !(procedure instanceof DropViewProcedure))
+ .map(procedure -> (DropTableProcedure) procedure)
+ .forEach(procedure -> dropProcedures.put(procedure.getProcId(),
procedure));
+ return dropProcedures.values().stream()
+ .map(
+ procedure -> {
+ final TDropTableProcedureInfo info =
+ new TDropTableProcedureInfo(
+ procedure.getDatabase(),
+ procedure.getTableName(),
+ procedure.getProcId(),
+ procedure.getState().name(),
+ procedure.getDropProgress());
+ if (procedure.hasException()) {
+ info.setErrorMessage(procedure.getException().getMessage());
+ }
+ return info;
+ })
+ .collect(Collectors.toList());
+ }
+
public TSStatus executeWithoutDuplicate(
final String database,
final TsTable table,
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/ProcedureExecutor.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/ProcedureExecutor.java
index dbbf33325a9..82b217b93ea 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/ProcedureExecutor.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/ProcedureExecutor.java
@@ -24,6 +24,8 @@ import org.apache.iotdb.commons.utils.RetryUtils;
import org.apache.iotdb.commons.utils.TestOnly;
import org.apache.iotdb.confignode.i18n.ProcedureMessages;
import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
+import
org.apache.iotdb.confignode.procedure.impl.schema.table.DropTableProcedure;
+import
org.apache.iotdb.confignode.procedure.impl.schema.table.view.DropViewProcedure;
import org.apache.iotdb.confignode.procedure.scheduler.ProcedureScheduler;
import
org.apache.iotdb.confignode.procedure.scheduler.SimpleProcedureScheduler;
import org.apache.iotdb.confignode.procedure.state.ProcedureLockState;
@@ -71,6 +73,7 @@ public class ProcedureExecutor<Env> {
// Metrics may be scraped before init() and concurrently with initialization
during a ConfigNode
// leader transition.
private volatile CopyOnWriteArrayList<WorkerThread> workerThreads;
+ private volatile CopyOnWriteArrayList<WorkerThread> dropTableWorkerThreads;
private TimeoutExecutorThread<Env> timeoutExecutor;
@@ -89,6 +92,9 @@ public class ProcedureExecutor<Env> {
private CompletedProcedureRecycler<Env> completedProcedureRecycler;
private final ProcedureScheduler scheduler;
+ // Drop table can spend a long time deleting data on DataNodes. Keep its
worker separate so
+ // unrelated procedures can still make progress.
+ private final ProcedureScheduler dropTableScheduler = new
SimpleProcedureScheduler();
private final AtomicLong workId = new AtomicLong(0);
private final AtomicInteger activeExecutorCount = new AtomicInteger(0);
@@ -122,7 +128,7 @@ public class ProcedureExecutor<Env> {
return PROCEDURE_EXECUTION_CONTEXT.get();
}
- public void init(int numThreads) {
+ public synchronized void init(int numThreads) {
this.corePoolSize = numThreads;
this.maxPoolSize = 10 * numThreads;
this.timeoutExecutor =
@@ -134,12 +140,15 @@ public class ProcedureExecutor<Env> {
workId.set(0);
workerThreads = new CopyOnWriteArrayList<>();
for (int i = 0; i < corePoolSize; i++) {
- workerThreads.add(new WorkerThread(threadGroup));
+ workerThreads.add(new WorkerThread(threadGroup, scheduler));
}
+ dropTableWorkerThreads = new CopyOnWriteArrayList<>();
// Add worker monitor
workerMonitorExecutor.add(new WorkerMonitor());
+ dropTableScheduler.clear();
scheduler.start();
+ dropTableScheduler.start();
recover();
}
@@ -225,9 +234,10 @@ public class ProcedureExecutor<Env> {
waitingTimeoutList.forEach(timeoutExecutor::add);
- failedList.forEach(scheduler::addBack);
- runnableList.forEach(scheduler::addBack);
+ failedList.forEach(procedure ->
getScheduler(procedure).addBack(procedure));
+ runnableList.forEach(procedure ->
getScheduler(procedure).addBack(procedure));
scheduler.signalAll();
+ dropTableScheduler.signalAll();
}
private List<Procedure<Env>> getProcedureListFromDifferentVersion() {
@@ -295,7 +305,7 @@ public class ProcedureExecutor<Env> {
});
}
- public void startWorkers() {
+ public synchronized void startWorkers() {
if (!running.compareAndSet(false, true)) {
LOG.warn(ProcedureMessages.ALREADY_RUNNING);
return;
@@ -305,6 +315,9 @@ public class ProcedureExecutor<Env> {
for (WorkerThread workerThread : workerThreads) {
workerThread.start();
}
+ for (WorkerThread workerThread : dropTableWorkerThreads) {
+ workerThread.start();
+ }
LOG.info(ProcedureMessages.PROCEDURE_WORKERS_ARE_STARTED,
workerThreads.size());
}
@@ -392,7 +405,7 @@ public class ProcedureExecutor<Env> {
break;
case LOCK_YIELD_WAIT:
rootProcStack.unsetRollback();
- scheduler.yield(lockStateResult.procedure);
+
getScheduler(lockStateResult.procedure).yield(lockStateResult.procedure);
break;
default:
throw new UnsupportedOperationException();
@@ -409,7 +422,7 @@ public class ProcedureExecutor<Env> {
lockEventWaitProcedure = proc;
break;
case LOCK_YIELD_WAIT:
- scheduler.yield(proc);
+ getScheduler(proc).yield(proc);
break;
default:
throw new UnsupportedOperationException();
@@ -560,7 +573,7 @@ public class ProcedureExecutor<Env> {
RetryUtils.executeWithEndlessBackoffRetry(
() -> store.update(parent), "count down children procedure");
// do not add this procedure when exception occurred
- scheduler.addFront(parent);
+ getScheduler(parent).addFront(parent);
LOG.info(
ProcedureMessages.LOG_FINISHED_SUBPROCEDURE_PID_ARG_RESUME_PROCESSING_PPID_ARG_93ED990B,
proc.getProcId(),
@@ -577,7 +590,7 @@ public class ProcedureExecutor<Env> {
for (Procedure<Env> subproc : subprocs) {
subproc.updateMetricsOnSubmit(getEnvironment());
procedures.put(subproc.getProcId(), subproc);
- scheduler.addFront(subproc);
+ getScheduler(subproc).addFront(subproc);
LOG.info(ProcedureMessages.SUB_PROCEDURE_PID_HAS_BEEN_SUBMITTED,
subproc.getProcId());
}
}
@@ -665,7 +678,7 @@ public class ProcedureExecutor<Env> {
private void yieldProcedure(Procedure<Env> proc) {
releaseLock(proc, false);
- scheduler.yield(proc);
+ getScheduler(proc).yield(proc);
}
/**
@@ -848,7 +861,7 @@ public class ProcedureExecutor<Env> {
}
rollbackStack.put(currentProcId, stack);
procedures.put(currentProcId, procedure);
- scheduler.addBack(procedure);
+ getScheduler(procedure).addBack(procedure);
return procedure.getProcId();
}
@@ -862,22 +875,25 @@ public class ProcedureExecutor<Env> {
}
private class WorkerThread extends StoppableThread {
+ private final ProcedureScheduler workerScheduler;
private final AtomicLong startTime = new AtomicLong(Long.MAX_VALUE);
private final AtomicReference<Procedure<Env>> activeProcedure = new
AtomicReference<>();
protected long keepAliveTime = -1;
- public WorkerThread(ThreadGroup threadGroup) {
- this(threadGroup, "ProcedureCoreWorker-");
+ public WorkerThread(ThreadGroup threadGroup, ProcedureScheduler
workerScheduler) {
+ this(threadGroup, workerScheduler, "ProcedureCoreWorker-");
}
- public WorkerThread(ThreadGroup threadGroup, String prefix) {
+ public WorkerThread(
+ ThreadGroup threadGroup, ProcedureScheduler workerScheduler, String
prefix) {
super(threadGroup, prefix + workId.incrementAndGet());
+ this.workerScheduler = workerScheduler;
setDaemon(true);
}
@Override
public void sendStopSignal() {
- scheduler.signalAll();
+ workerScheduler.signalAll();
}
@Override
@@ -885,7 +901,7 @@ public class ProcedureExecutor<Env> {
long lastUpdated = System.currentTimeMillis();
try {
while (isRunning() && keepAlive(lastUpdated)) {
- Procedure<Env> procedure = scheduler.poll(keepAliveTime,
TimeUnit.MILLISECONDS);
+ Procedure<Env> procedure = workerScheduler.poll(keepAliveTime,
TimeUnit.MILLISECONDS);
if (procedure == null) {
Thread.sleep(1000);
continue;
@@ -942,7 +958,11 @@ public class ProcedureExecutor<Env> {
} finally {
LOG.info(ProcedureMessages.PROCEDURE_WORKER_TERMINATED, getName());
}
- workerThreads.remove(this);
+ if (workerScheduler == dropTableScheduler) {
+ dropTableWorkerThreads.remove(this);
+ } else {
+ workerThreads.remove(this);
+ }
}
protected boolean keepAlive(long lastUpdated) {
@@ -968,7 +988,7 @@ public class ProcedureExecutor<Env> {
private final class TemporaryWorkerThread extends WorkerThread {
public TemporaryWorkerThread(ThreadGroup group) {
- super(group, "ProcedureTemporaryWorker-");
+ super(group, scheduler, "ProcedureTemporaryWorker-");
this.keepAliveTime = TimeUnit.SECONDS.toMillis(10);
}
@@ -1047,6 +1067,12 @@ public class ProcedureExecutor<Env> {
return workers == null ? 0 : workers.size();
}
+ @TestOnly
+ public int getDropTableWorkerThreadCount() {
+ final CopyOnWriteArrayList<WorkerThread> workers = dropTableWorkerThreads;
+ return workers == null ? 0 : workers.size();
+ }
+
public long getActiveWorkerThreadCount() {
final CopyOnWriteArrayList<WorkerThread> workers = workerThreads;
return workers == null
@@ -1058,12 +1084,13 @@ public class ProcedureExecutor<Env> {
return running.get();
}
- public void stop() {
+ public synchronized void stop() {
if (!running.getAndSet(false)) {
return;
}
LOG.info(ProcedureMessages.STOPPING);
scheduler.stop();
+ dropTableScheduler.stop();
timeoutExecutor.sendStopSignal();
}
@@ -1073,6 +1100,9 @@ public class ProcedureExecutor<Env> {
for (WorkerThread workerThread : workerThreads) {
workerThread.awaitTermination();
}
+ for (WorkerThread workerThread : dropTableWorkerThreads) {
+ workerThread.awaitTermination();
+ }
}
public boolean isStarted(long procId) {
@@ -1091,6 +1121,12 @@ public class ProcedureExecutor<Env> {
return procedures;
}
+ public List<Procedure<Env>> getCompletedProcedures() {
+ final List<Procedure<Env>> result = new ArrayList<>();
+ completed.values().forEach(container ->
result.add(container.getProcedure()));
+ return result;
+ }
+
// -----------------------------CLIENT
IMPLEMENTATION-----------------------------------
/**
* Submit a new root-procedure to the executor, called by client.
@@ -1116,10 +1152,42 @@ public class ProcedureExecutor<Env> {
return pushProcedure(procedure);
}
+ /** Submit only after the procedure has been stored, for requests
acknowledged asynchronously. */
+ public long submitProcedureAndPersist(final Procedure<Env> procedure) throws
Exception {
+ Preconditions.checkArgument(procedure.getState() ==
ProcedureState.INITIALIZING);
+ Preconditions.checkArgument(
+ !procedure.hasParent(),
ProcedureMessages.EXCEPTION_UNEXPECTED_PARENT_444B4289, procedure);
+ procedure.setProcId(store.getNextProcId());
+ procedure.setProcRunnable();
+ store.update(procedure);
+ LOG.debug(ProcedureMessages.IS_STORED, procedure);
+ return pushProcedure(procedure);
+ }
+
public ProcedureScheduler getScheduler() {
return scheduler;
}
+ public ProcedureScheduler getScheduler(final Procedure<?> procedure) {
+ return procedure instanceof DropTableProcedure && !(procedure instanceof
DropViewProcedure)
+ ? getDropTableScheduler()
+ : scheduler;
+ }
+
+ private synchronized ProcedureScheduler getDropTableScheduler() {
+ // Recovery can enqueue drops before startWorkers(), while new submissions
arrive after it.
+ // Share the lifecycle lock so the first drop creates and starts exactly
one worker.
+ if (dropTableWorkerThreads.isEmpty()) {
+ final WorkerThread worker =
+ new WorkerThread(threadGroup, dropTableScheduler,
"DropTableProcedureWorker-");
+ dropTableWorkerThreads.add(worker);
+ if (isRunning()) {
+ worker.start();
+ }
+ }
+ return dropTableScheduler;
+ }
+
public Env getEnvironment() {
return environment;
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/TimeoutExecutorThread.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/TimeoutExecutorThread.java
index d8e16ca1b1f..23cfb6c089f 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/TimeoutExecutorThread.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/TimeoutExecutorThread.java
@@ -104,7 +104,7 @@ public class TimeoutExecutorThread<Env> extends
StoppableThread {
// procedure.
LOGGER.warn(ProcedureMessages.FAILED_TO_UPDATE_PROCEDURE,
procedure, e);
}
- executor.getScheduler().addFront(procedure);
+ executor.getScheduler(procedure).addFront(procedure);
}
}
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DropTableProcedure.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DropTableProcedure.java
index 37f3b85c675..64ea7a04be1 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DropTableProcedure.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DropTableProcedure.java
@@ -70,6 +70,14 @@ public class DropTableProcedure extends
AbstractAlterOrDropTableProcedure<DropTa
super(database, tableName, queryId, isGeneratedByPipe);
}
+ public String getDropProgress() {
+ if (isFinished()) {
+ return getState().name();
+ }
+ final DropTableState state = getCurrentState();
+ return state == null ? getState().name() : state.name();
+ }
+
@Override
protected String getActionMessage() {
return "drop table";
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java
index b6fe2c0fc21..1f89e39d448 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java
@@ -210,6 +210,7 @@ import
org.apache.iotdb.confignode.rpc.thrift.TShowConfigNodesResp;
import
org.apache.iotdb.confignode.rpc.thrift.TShowDataNodes4InformationSchemaResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDataNodesResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDatabaseResp;
+import org.apache.iotdb.confignode.rpc.thrift.TShowDropTableProceduresResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipePluginReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeResp;
@@ -1498,6 +1499,11 @@ public class ConfigNodeRPCServiceProcessor implements
IConfigNodeRPCService.Ifac
return configManager.showTables4InformationSchema();
}
+ @Override
+ public TShowDropTableProceduresResp showDropTableProcedures() {
+ return configManager.showDropTableProcedures();
+ }
+
@Override
public TDescTableResp describeTable(
final String database, final String tableName, final boolean isDetails) {
diff --git
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManagerTest.java
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManagerTest.java
new file mode 100644
index 00000000000..3769782bf12
--- /dev/null
+++
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManagerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.client.async;
+
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.commons.client.ClientManager;
+import
org.apache.iotdb.commons.client.async.AsyncDataNodeInternalServiceClient;
+import org.apache.iotdb.commons.client.property.ThriftClientProperty;
+
+import org.apache.thrift.TException;
+import org.apache.thrift.async.TAsyncClientManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class CnToDnInternalServiceAsyncRequestManagerTest {
+
+ private static final int DEFAULT_TIMEOUT_MS = 1_000;
+
+ private final CnToDnInternalServiceAsyncRequestManager requestManager =
+ CnToDnInternalServiceAsyncRequestManager.getInstance();
+ private final TEndPoint endpoint = new TEndPoint("127.0.0.1", 9000);
+ private ClientManager<TEndPoint, AsyncDataNodeInternalServiceClient>
clientManager;
+ private AsyncDataNodeInternalServiceClient client;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ clientManager = mock(ClientManager.class);
+ client =
+ new AsyncDataNodeInternalServiceClient(
+ new ThriftClientProperty.Builder()
+ .setConnectionTimeoutMs(DEFAULT_TIMEOUT_MS)
+ .setPrintLogWhenEncounterException(false)
+ .build(),
+ endpoint,
+ mock(TAsyncClientManager.class),
+ clientManager);
+ }
+
+ @After
+ public void tearDown() {
+ client.invalidate();
+ }
+
+ @Test
+ public void
dropTableDeletionDisablesDeadlineAndRestoresTimeoutOnCompletion() {
+ for (final CnToDnAsyncRequestType requestType :
+ new CnToDnAsyncRequestType[] {
+ CnToDnAsyncRequestType.DELETE_DATA_FOR_DROP_TABLE,
+ CnToDnAsyncRequestType.DELETE_DEVICES_FOR_DROP_TABLE
+ }) {
+ requestManager.adjustClientTimeoutIfNecessary(requestType, client, null);
+ assertFalse(client.hasTimeout());
+
+ client.onComplete();
+ assertTrue(client.hasTimeout());
+ assertEquals(DEFAULT_TIMEOUT_MS, client.getTimeout());
+ }
+ verify(clientManager, times(2)).returnClient(endpoint, client);
+ }
+
+ @Test
+ public void dropTableDeletionRestoresTimeoutOnError() {
+ requestManager.adjustClientTimeoutIfNecessary(
+ CnToDnAsyncRequestType.DELETE_DATA_FOR_DROP_TABLE, client, null);
+ assertFalse(client.hasTimeout());
+
+ client.onError(new TException("Deletion failed"));
+ assertTrue(client.hasTimeout());
+ assertEquals(DEFAULT_TIMEOUT_MS, client.getTimeout());
+ verify(clientManager).returnClient(endpoint, client);
+ }
+
+ @Test
+ public void cacheInvalidationKeepsDefaultAndExplicitTimeouts() {
+ requestManager.adjustClientTimeoutIfNecessary(
+ CnToDnAsyncRequestType.PRE_DELETE_TABLE, client, null);
+ assertTrue(client.hasTimeout());
+ assertEquals(DEFAULT_TIMEOUT_MS, client.getTimeout());
+
+ requestManager.adjustClientTimeoutIfNecessary(
+ CnToDnAsyncRequestType.PRE_DELETE_TABLE, client, 5_000L);
+ assertTrue(client.hasTimeout());
+ assertEquals(5_000L, client.getTimeout());
+
+ client.onComplete();
+ assertEquals(DEFAULT_TIMEOUT_MS, client.getTimeout());
+ }
+}
diff --git
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DropTableProcedureTest.java
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DropTableProcedureTest.java
index 7da83c02a9d..fb141aab270 100644
---
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DropTableProcedureTest.java
+++
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/table/DropTableProcedureTest.java
@@ -20,7 +20,14 @@
package org.apache.iotdb.confignode.procedure.impl.schema.table;
import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.confignode.procedure.NoopProcedureStore;
+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.impl.schema.table.view.DropViewProcedure;
+import org.apache.iotdb.confignode.procedure.state.schema.DropTableState;
import org.apache.iotdb.confignode.procedure.store.ProcedureType;
+import org.apache.iotdb.confignode.procedure.util.ProcedureTestUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -29,8 +36,178 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+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 DropTableProcedureTest {
+ @Test
+ public void dropTableDoesNotOccupyRegularProcedureWorker() throws Exception {
+ final NoopProcedureStore store = new NoopProcedureStore();
+ final ProcedureExecutor<ConfigNodeProcedureEnv> executor = new
ProcedureExecutor<>(null, store);
+ final CountDownLatch dropStarted = new CountDownLatch(1);
+ final CountDownLatch releaseDrop = new CountDownLatch(1);
+ final CountDownLatch regularCompleted = new CountDownLatch(1);
+ Assert.assertEquals(0, executor.getDropTableWorkerThreadCount());
+ store.start();
+ executor.init(1);
+ Assert.assertEquals(0, executor.getDropTableWorkerThreadCount());
+ executor.startWorkers();
+ try {
+ Assert.assertSame(
+ executor.getScheduler(), executor.getScheduler(new
DropViewProcedure(false)));
+ Assert.assertEquals(0, executor.getDropTableWorkerThreadCount());
+ final DropTableProcedure dropProcedure =
+ new DropTableProcedure("db", "table", "drop", false) {
+ @Override
+ protected Flow executeFromState(
+ final ConfigNodeProcedureEnv env, final DropTableState state)
+ throws InterruptedException {
+ dropStarted.countDown();
+ releaseDrop.await();
+ return Flow.NO_MORE_STATE;
+ }
+ };
+ executor.submitProcedureAndPersist(dropProcedure);
+ Assert.assertTrue(dropStarted.await(10, TimeUnit.SECONDS));
+ Assert.assertEquals(1, executor.getDropTableWorkerThreadCount());
+ Assert.assertEquals(
+ DropTableState.CHECK_AND_INVALIDATE_TABLE.name(),
dropProcedure.getDropProgress());
+
+ executor.submitProcedure(
+ new Procedure<ConfigNodeProcedureEnv>() {
+ @Override
+ protected Procedure<ConfigNodeProcedureEnv>[] execute(
+ final ConfigNodeProcedureEnv env) {
+ regularCompleted.countDown();
+ return null;
+ }
+
+ @Override
+ protected void rollback(final ConfigNodeProcedureEnv env) {}
+ });
+ Assert.assertTrue(regularCompleted.await(10, TimeUnit.SECONDS));
+ } finally {
+ releaseDrop.countDown();
+ executor.stop();
+ executor.join();
+ store.stop();
+ }
+ Assert.assertEquals(0, executor.getDropTableWorkerThreadCount());
+ }
+
+ @Test
+ public void recoveredDropStartsWorkerAndCompletedDropDoesNot() throws
Exception {
+ final CountDownLatch dropStarted = new CountDownLatch(1);
+ final CountDownLatch releaseDrop = new CountDownLatch(1);
+ final DropTableProcedure dropProcedure =
+ new DropTableProcedure("db", "table", "drop", false) {
+ @Override
+ protected Flow executeFromState(
+ final ConfigNodeProcedureEnv env, final DropTableState state)
+ throws InterruptedException {
+ dropStarted.countDown();
+ releaseDrop.await();
+ return Flow.NO_MORE_STATE;
+ }
+ };
+ dropProcedure.setProcId(0);
+ dropProcedure.setProcRunnable();
+ final NoopProcedureStore store =
+ new NoopProcedureStore() {
+ @Override
+ public List<Procedure> load() {
+ return Collections.singletonList(dropProcedure);
+ }
+ };
+ final ProcedureExecutor<ConfigNodeProcedureEnv> executor = new
ProcedureExecutor<>(null, store);
+ store.start();
+ executor.init(1);
+ try {
+ Assert.assertEquals(1, executor.getDropTableWorkerThreadCount());
+ Assert.assertEquals(1, dropStarted.getCount());
+ executor.startWorkers();
+ Assert.assertTrue(dropStarted.await(10, TimeUnit.SECONDS));
+ releaseDrop.countDown();
+ ProcedureTestUtil.waitForProcedure(executor, dropProcedure.getProcId());
+ Assert.assertTrue(dropProcedure.isSuccess());
+ executor.stop();
+ executor.join();
+
+ executor.init(1);
+ executor.startWorkers();
+ Assert.assertEquals(0, executor.getDropTableWorkerThreadCount());
+ Assert.assertEquals(
+ Collections.singletonList(dropProcedure),
executor.getCompletedProcedures());
+ } finally {
+ releaseDrop.countDown();
+ executor.stop();
+ executor.join();
+ store.stop();
+ }
+ }
+
+ @Test
+ public void concurrentFirstDropsShareOneWorker() throws Exception {
+ final NoopProcedureStore store = new NoopProcedureStore();
+ final ProcedureExecutor<ConfigNodeProcedureEnv> executor = new
ProcedureExecutor<>(null, store);
+ final ExecutorService submitters = Executors.newFixedThreadPool(4);
+ final CountDownLatch submittersReady = new CountDownLatch(4);
+ final CountDownLatch startSubmissions = new CountDownLatch(1);
+ final CountDownLatch dropStarted = new CountDownLatch(1);
+ final CountDownLatch releaseDrops = new CountDownLatch(1);
+ store.start();
+ executor.init(1);
+ executor.startWorkers();
+ try {
+ final List<Future<Long>> submissions = new ArrayList<>();
+ for (int i = 0; i < 4; i++) {
+ final DropTableProcedure dropProcedure =
+ new DropTableProcedure("db", "table" + i, "drop" + i, false) {
+ @Override
+ protected Flow executeFromState(
+ final ConfigNodeProcedureEnv env, final DropTableState state)
+ throws InterruptedException {
+ dropStarted.countDown();
+ releaseDrops.await();
+ return Flow.NO_MORE_STATE;
+ }
+ };
+ submissions.add(
+ submitters.submit(
+ () -> {
+ submittersReady.countDown();
+ startSubmissions.await();
+ return executor.submitProcedureAndPersist(dropProcedure);
+ }));
+ }
+ Assert.assertTrue(submittersReady.await(10, TimeUnit.SECONDS));
+ startSubmissions.countDown();
+ for (final Future<Long> submission : submissions) {
+ submission.get(10, TimeUnit.SECONDS);
+ }
+ Assert.assertTrue(dropStarted.await(10, TimeUnit.SECONDS));
+ Assert.assertEquals(1, executor.getDropTableWorkerThreadCount());
+ releaseDrops.countDown();
+ for (final Future<Long> submission : submissions) {
+ ProcedureTestUtil.waitForProcedure(executor, submission.get());
+ }
+ Assert.assertEquals(4, executor.getCompletedProcedures().size());
+ } finally {
+ startSubmissions.countDown();
+ releaseDrops.countDown();
+ submitters.shutdownNow();
+ executor.stop();
+ executor.join();
+ store.stop();
+ }
+ }
+
@Test
public void serializeDeserializeTest() throws IllegalPathException,
IOException {
final DropTableProcedure dropTableProcedure =
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java
index a9945869fbf..9f1e90fda10 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java
@@ -164,6 +164,7 @@ import
org.apache.iotdb.confignode.rpc.thrift.TShowConfigNodesResp;
import
org.apache.iotdb.confignode.rpc.thrift.TShowDataNodes4InformationSchemaResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDataNodesResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDatabaseResp;
+import org.apache.iotdb.confignode.rpc.thrift.TShowDropTableProceduresResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipePluginReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeResp;
@@ -1274,6 +1275,12 @@ public class ConfigNodeClient extends
AbstractConfigNodeClient<ConfigNodeClient>
() -> client.showTables4InformationSchema(), resp ->
!updateConfigNodeLeader(resp.status));
}
+ @Override
+ public TShowDropTableProceduresResp showDropTableProcedures() throws
TException {
+ return executeRemoteCallWithRetry(
+ () -> client.showDropTableProcedures(), resp ->
!updateConfigNodeLeader(resp.status));
+ }
+
@Override
public TDescTableResp describeTable(
final String database, final String tableName, final boolean isDetails)
throws TException {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java
index f57fc00154e..5b92f259373 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java
@@ -59,11 +59,13 @@ import
org.apache.iotdb.confignode.rpc.thrift.TConfigNodeInfo4InformationSchema;
import org.apache.iotdb.confignode.rpc.thrift.TDataNodeInfo4InformationSchema;
import org.apache.iotdb.confignode.rpc.thrift.TDatabaseInfo;
import org.apache.iotdb.confignode.rpc.thrift.TDescTable4InformationSchemaResp;
+import org.apache.iotdb.confignode.rpc.thrift.TDropTableProcedureInfo;
import org.apache.iotdb.confignode.rpc.thrift.TGetDatabaseReq;
import org.apache.iotdb.confignode.rpc.thrift.TGetUdfTableReq;
import org.apache.iotdb.confignode.rpc.thrift.TNodeVersionInfo;
import org.apache.iotdb.confignode.rpc.thrift.TRegionInfo;
import org.apache.iotdb.confignode.rpc.thrift.TShowClusterResp;
+import org.apache.iotdb.confignode.rpc.thrift.TShowDropTableProceduresResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeInfo;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowRegionReq;
@@ -193,6 +195,8 @@ public class InformationSchemaContentSupplierFactory {
return new DatabaseSupplier(dataTypes, userEntity);
case InformationSchema.TABLES:
return new TableSupplier(dataTypes, userEntity);
+ case InformationSchema.DROP_TABLE_PROCEDURES:
+ return new DropTableProceduresSupplier(dataTypes, userEntity);
case InformationSchema.COLUMNS:
return new ColumnSupplier(dataTypes, userEntity);
case InformationSchema.REGIONS:
@@ -486,6 +490,70 @@ public class InformationSchemaContentSupplierFactory {
}
}
+ private static class DropTableProceduresSupplier extends TsBlockSupplier {
+ private final Iterator<TDropTableProcedureInfo> procedures;
+ private final UserEntity userEntity;
+ private TDropTableProcedureInfo currentProcedure;
+
+ private DropTableProceduresSupplier(
+ final List<TSDataType> dataTypes, final UserEntity userEntity) throws
Exception {
+ super(dataTypes);
+ this.userEntity = userEntity;
+ try (final ConfigNodeClient client =
+
ConfigNodeClientManager.getInstance().borrowClient(ConfigNodeInfo.CONFIG_REGION_ID))
{
+ final TShowDropTableProceduresResp response =
client.showDropTableProcedures();
+ if (response.getStatus().getCode() !=
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ throw new IoTDBRuntimeException(response.getStatus());
+ }
+ final List<TDropTableProcedureInfo> result =
response.getDropTableProcedureInfoList();
+ procedures =
+ (result == null ? Collections.<TDropTableProcedureInfo>emptyList()
: result).iterator();
+ }
+ }
+
+ @Override
+ protected void constructLine() {
+ columnBuilders[0].writeBinary(
+ new Binary(currentProcedure.getDatabase(),
TSFileConfig.STRING_CHARSET));
+ columnBuilders[1].writeBinary(
+ new Binary(currentProcedure.getTableName(),
TSFileConfig.STRING_CHARSET));
+ columnBuilders[2].writeLong(currentProcedure.getProcedureId());
+ columnBuilders[3].writeBinary(
+ new Binary(currentProcedure.getState(),
TSFileConfig.STRING_CHARSET));
+ columnBuilders[4].writeBinary(
+ new Binary(currentProcedure.getProgress(),
TSFileConfig.STRING_CHARSET));
+ if (currentProcedure.isSetErrorMessage()) {
+ columnBuilders[5].writeBinary(
+ new Binary(currentProcedure.getErrorMessage(),
TSFileConfig.STRING_CHARSET));
+ } else {
+ columnBuilders[5].appendNull();
+ }
+ resultBuilder.declarePosition();
+ currentProcedure = null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (currentProcedure != null) {
+ return true;
+ }
+ while (procedures.hasNext()) {
+ final TDropTableProcedureInfo procedure = procedures.next();
+ if (canShowDB(accessControl, userEntity.getUsername(),
procedure.getDatabase(), userEntity)
+ && canShowTable(
+ accessControl,
+ userEntity.getUsername(),
+ procedure.getDatabase(),
+ procedure.getTableName(),
+ userEntity)) {
+ currentProcedure = procedure;
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
public static class ColumnSupplier extends TsBlockSupplier {
private final Iterator<Map.Entry<String, Map<String,
TableColumnDetailInfo>>> dbIterator;
private Iterator<Map.Entry<String, TableColumnDetailInfo>>
tableInfoIterator;
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/DataNodeLocationSupplierFactory.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/DataNodeLocationSupplierFactory.java
index b4d8e8d7a43..fdb6d2c26bf 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/DataNodeLocationSupplierFactory.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/DataNodeLocationSupplierFactory.java
@@ -146,6 +146,7 @@ public class DataNodeLocationSupplierFactory {
return getReadableDataNodeLocations();
case InformationSchema.DATABASES:
case InformationSchema.TABLES:
+ case InformationSchema.DROP_TABLE_PROCEDURES:
case InformationSchema.COLUMNS:
case InformationSchema.REGIONS:
case InformationSchema.PIPES:
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/AbstractConfigNodeClient.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/AbstractConfigNodeClient.java
index a95bf954888..86bc416653e 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/AbstractConfigNodeClient.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/client/AbstractConfigNodeClient.java
@@ -162,6 +162,7 @@ import
org.apache.iotdb.confignode.rpc.thrift.TShowConfigNodesResp;
import
org.apache.iotdb.confignode.rpc.thrift.TShowDataNodes4InformationSchemaResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDataNodesResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowDatabaseResp;
+import org.apache.iotdb.confignode.rpc.thrift.TShowDropTableProceduresResp;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipePluginReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeReq;
import org.apache.iotdb.confignode.rpc.thrift.TShowPipeResp;
@@ -1302,6 +1303,11 @@ public abstract class AbstractConfigNodeClient<C extends
AbstractConfigNodeClien
return null;
}
+ @Override
+ public TShowDropTableProceduresResp showDropTableProcedures() throws
TException {
+ return null;
+ }
+
@Override
public TDescTableResp describeTable(String database, String tableName,
boolean isDetails)
throws TException {
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
index 239478b13fe..03ba898f77f 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
@@ -305,6 +305,9 @@ public class ColumnHeaderConstant {
public static final String PIPE_PROCESSOR_TABLE_MODEL = "pipe_processor";
public static final String PIPE_SINK_TABLE_MODEL = "pipe_sink";
public static final String EXCEPTION_MESSAGE_TABLE_MODEL =
"exception_message";
+ public static final String PROCEDURE_ID_TABLE_MODEL = "procedure_id";
+ public static final String PROGRESS_TABLE_MODEL = "progress";
+ public static final String ERROR_MESSAGE_TABLE_MODEL = "error_message";
public static final String REMAINING_EVENT_COUNT_TABLE_MODEL =
"remaining_event_count";
public static final String ESTIMATED_REMAINING_SECONDS_TABLE_MODEL =
"estimated_remaining_seconds";
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
index 00ef05c5afa..9f3bf0bb2df 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
@@ -47,6 +47,7 @@ public class InformationSchema {
public static final String QUERIES = "queries";
public static final String DATABASES = "databases";
public static final String TABLES = "tables";
+ public static final String DROP_TABLE_PROCEDURES = "drop_table_procedures";
public static final String COLUMNS = "columns";
public static final String REGIONS = "regions";
public static final String PIPES = "pipes";
@@ -148,6 +149,25 @@ public class InformationSchema {
tableTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
schemaTables.put(TABLES, tableTable);
+ final TsTable dropTableProceduresTable = new
TsTable(DROP_TABLE_PROCEDURES);
+ dropTableProceduresTable.addColumnSchema(
+ new TagColumnSchema(
+ ColumnHeaderConstant.DATABASE.toLowerCase(Locale.ENGLISH),
TSDataType.STRING));
+ dropTableProceduresTable.addColumnSchema(
+ new TagColumnSchema(ColumnHeaderConstant.TABLE_NAME_TABLE_MODEL,
TSDataType.STRING));
+ dropTableProceduresTable.addColumnSchema(
+ new
AttributeColumnSchema(ColumnHeaderConstant.PROCEDURE_ID_TABLE_MODEL,
TSDataType.INT64));
+ dropTableProceduresTable.addColumnSchema(
+ new AttributeColumnSchema(
+ ColumnHeaderConstant.STATE.toLowerCase(Locale.ENGLISH),
TSDataType.STRING));
+ dropTableProceduresTable.addColumnSchema(
+ new AttributeColumnSchema(ColumnHeaderConstant.PROGRESS_TABLE_MODEL,
TSDataType.STRING));
+ dropTableProceduresTable.addColumnSchema(
+ new AttributeColumnSchema(
+ ColumnHeaderConstant.ERROR_MESSAGE_TABLE_MODEL,
TSDataType.STRING));
+ dropTableProceduresTable.removeColumnSchema(TsTable.TIME_COLUMN_NAME);
+ schemaTables.put(DROP_TABLE_PROCEDURES, dropTableProceduresTable);
+
final TsTable columnTable = new TsTable(COLUMNS);
columnTable.addColumnSchema(
new TagColumnSchema(
diff --git a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
index 48921651ee2..e2f2595fa7d 100644
--- a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
+++ b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
@@ -1314,6 +1314,20 @@ struct TShowTable4InformationSchemaResp {
2: optional map<string, list<TTableInfo>> databaseTableInfoMap
}
+struct TShowDropTableProceduresResp {
+ 1: required common.TSStatus status
+ 2: optional list<TDropTableProcedureInfo> dropTableProcedureInfoList
+}
+
+struct TDropTableProcedureInfo {
+ 1: required string database
+ 2: required string tableName
+ 3: required i64 procedureId
+ 4: required string state
+ 5: required string progress
+ 6: optional string errorMessage
+}
+
struct TDescTableResp {
1: required common.TSStatus status
2: optional binary tableInfo
@@ -2142,6 +2156,7 @@ service IConfigNodeRPCService {
TShowTableResp showTables(string database, bool isDetails)
TShowTable4InformationSchemaResp showTables4InformationSchema()
+ TShowDropTableProceduresResp showDropTableProcedures()
TDescTableResp describeTable(string database, string tableName, bool
isDetails)