From b4e385e926b98b2a0e1ef24aa7530c92b9f0d767 Mon Sep 17 00:00:00 2001 From: Mingyu Chen Date: Sun, 8 Oct 2023 11:33:27 +0800 Subject: [PATCH] [fix](export) fix ConcurrentModificationException in export (#25096) The session variable in export job should be copied from session variable in connection context. Because both session variable in connection context and in export job may be modified at same time, cause ConcurrentModificationException like: 2023-10-07 22:56:12,818 WARN (mysql-nio-pool-2|249) [ConnectProcessor.handleQueryException():396] Process one query failed because unknown reason: java.util.ConcurrentModificationException: null at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437) ~[?:1.8.0_131] at java.util.HashMap$KeyIterator.next(HashMap.java:1461) ~[?:1.8.0_131] at org.apache.doris.qe.VariableMgr.revertSessionValue(VariableMgr.java:238) ~[doris-fe.jar:1.2-SNAPSHOT] at org.apache.doris.qe.StmtExecutor.execute(StmtExecutor.java:474) ~[doris-fe.jar:1.2-SNAPSHOT] at org.apache.doris.qe.StmtExecutor.execute(StmtExecutor.java:438) ~[doris-fe.jar:1.2-SNAPSHOT] at org.apache.doris.qe.ConnectProcessor.handleQuery(ConnectProcessor.java:353) ~[doris-fe.jar:1.2-SNAPSHOT] at org.apache.doris.qe.ConnectProcessor.dispatch(ConnectProcessor.java:501) ~[doris-fe.jar:1.2-SNAPSHOT] at org.apache.doris.qe.ConnectProcessor.processOnce(ConnectProcessor.java:752) ~[doris-fe.jar:1.2-SNAPSHOT] at org.apache.doris.mysql.ReadListener.lambda$handleEvent$0(ReadListener.java:52) ~[doris-fe.jar:1.2-SNAPSHOT] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[?:1.8.0_131] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[?:1.8.0_131] at java.lang.Thread.run(Thread.java:748) ~[?:1.8.0_131] This error is reported by external_table_p0/export/test_export_external_table.groovy --- .../nereids/trees/plans/commands/ExportCommand.java | 10 +++++----- .../src/main/java/org/apache/doris/qe/VariableMgr.java | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExportCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExportCommand.java index a48753a119..2be6e84938 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExportCommand.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExportCommand.java @@ -304,11 +304,11 @@ public class ExportCommand extends Command implements ForwardWithSync { exportJob.setQualifiedUser(ctx.getQualifiedUser()); exportJob.setUserIdentity(ctx.getCurrentUserIdentity()); - Optional optionalSessionVariable = Optional.ofNullable( - ConnectContext.get().getSessionVariable()); - exportJob.setSessionVariables(optionalSessionVariable.orElse(VariableMgr.getDefaultSessionVariable())); - exportJob.setTimeoutSecond(optionalSessionVariable.orElse(VariableMgr.getDefaultSessionVariable()) - .getQueryTimeoutS()); + // Must copy session variable, because session variable may be changed during export job running. + SessionVariable clonedSessionVariable = VariableMgr.cloneSessionVariable(Optional.ofNullable( + ConnectContext.get().getSessionVariable()).orElse(VariableMgr.getDefaultSessionVariable())); + exportJob.setSessionVariables(clonedSessionVariable); + exportJob.setTimeoutSecond(clonedSessionVariable.getQueryTimeoutS()); // exportJob generate outfile sql exportJob.generateOutfileLogicalPlans(RelationUtil.getQualifierName(ctx, this.nameParts)); diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/VariableMgr.java b/fe/fe-core/src/main/java/org/apache/doris/qe/VariableMgr.java index 1d1edbb213..14afc7a1bc 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/VariableMgr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/VariableMgr.java @@ -245,12 +245,16 @@ public class VariableMgr { public static SessionVariable newSessionVariable() { wlock.lock(); try { - return (SessionVariable) SerializationUtils.clone(defaultSessionVariable); + return cloneSessionVariable(defaultSessionVariable); } finally { wlock.unlock(); } } + public static SessionVariable cloneSessionVariable(SessionVariable var) { + return SerializationUtils.clone(var); + } + // Check if this setVar can be set correctly // Do not use ErrorReport.reportDdlException to throw exeception, it will set the query state in connection context. // But in some case, we do not want to set the query state and need to ignore that error.