Code review comment: Not a safe way to new thread pool for each job. If user submit a lot export job, there will be lots of threads and thread pool.

Code under review:
protected void exec() {
        if (job.getState() == JobState.IN_QUEUE) {
            handleInQueueState();
        }

        if (job.getState() != ExportJob.JobState.EXPORTING) {
            return;
        }
        LOG.info("begin execute export job in exporting state. job: {}", job);

        synchronized (job) {
            if (job.getDoExportingThread() != null) {
                LOG.warn("export task is already being executed.");
                return;
            }
            job.setDoExportingThread(Thread.currentThread());
        }

        List<SelectStmt> selectStmtList = job.getSelectStmtList();
        int completeTaskNum = 0;
        List<ExportJob.OutfileInfo> outfileInfoList = Lists.newArrayList();

        int parallelNum = selectStmtList.size();
        ThreadPoolExecutor exportExecPool = ThreadPoolManager.newDaemonFixedThreadPool(parallelNum,
                parallelNum * 2, "export-job-pool-" + job.getLabel(), false);
        CompletionService<ExportResult> completionService = new ExecutorCompletionService<>(exportExecPool);

        
        for (int i = 0; i < parallelNum; ++i) {
            final int idx = i;
            completionService.submit(() -> {
                
                if (job.getState() != JobState.EXPORTING) {
                    return new ExportResult(true, null, null);
                }
                try {
                    Database db = Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException(
                            job.getTableName().getDb());
                    OlapTable table = db.getOlapTableOrAnalysisException(job.getTableName().getTbl());
                    table.readLock();
                    try {
                        SelectStmt selectStmt = selectStmtList.get(idx);
                        List<Long> tabletIds = selectStmt.getTableRefs().get(0).getSampleTabletIds();
                        for (Long tabletId : tabletIds) {
                            TabletMeta tabletMeta = Env.getCurrentEnv().getTabletInvertedIndex().getTabletMeta(
                                    tabletId);
                            Partition partition = table.getPartition(tabletMeta.getPartitionId());
                            long nowVersion = partition.getVisibleVersion();
                            long oldVersion = job.getPartitionToVersion().get(partition.getName());
                            if (nowVersion != oldVersion) {
                                LOG.warn("Tablet {} has changed version, old version = {}, now version = {}",
                                        tabletId, oldVersion, nowVersion);
                                return new ExportResult(true, new ExportFailMsg(
                                        ExportFailMsg.CancelType.RUN_FAIL,
                                        "Tablet {" + tabletId + "} has changed"), null);
                            }
                        }
                    } finally {
                        table.readUnlock();
                    }
                } catch (AnalysisException e) {
                    return new ExportResult(true,
                            new ExportFailMsg(ExportFailMsg.CancelType.RUN_FAIL, e.getMessage()), null);
                }
                try (AutoCloseConnectContext r = buildConnectContext()) {
                    this.stmtExecutor = new StmtExecutor(r.connectContext, selectStmtList.get(idx));
                    this.stmtExecutor.execute();
                    if (r.connectContext.getState().getStateType() == MysqlStateType.ERR) {
                        return new ExportResult(true, new ExportFailMsg(ExportFailMsg.CancelType.RUN_FAIL,
                                r.connectContext.getState().getErrorMessage()), null);
                    }
                    ExportJob.OutfileInfo outfileInfo = getOutFileInfo(r.connectContext.getResultAttachedInfo());
                    return new ExportResult(false, null, outfileInfo);
                } catch (Exception e) {
                    return new ExportResult(true, new ExportFailMsg(ExportFailMsg.CancelType.RUN_FAIL,
                            e.getMessage()),
                            null);
                } finally {
                    this.stmtExecutor.addProfileToSpan();
                }
            });
        }

        Boolean isFailed = false;
        ExportFailMsg failMsg = new ExportFailMsg();
        try {
            for (int i = 0; i < parallelNum; ++i) {
                Future<ExportResult> future = completionService.take();
                ExportResult result = future.get();
                if (!result.isFailed) {
                    outfileInfoList.add(result.getOutfileInfo());
                    ++completeTaskNum;
                    int progress = completeTaskNum * 100 / selectStmtList.size();
                    if (progress >= 100) {
                        progress = 99;
                    }
                    job.setProgress(progress);
                    LOG.info("Export Job {} finished {} outfile export and it's progress is {}%", job.getId(),
                            completeTaskNum, progress);
                } else {
                    isFailed = true;
                    failMsg.setCancelType(result.failMsg.getCancelType());
                    failMsg.setMsg(result.failMsg.getMsg());
                    LOG.warn("Exporting task failed because: {}", result.failMsg.getMsg());
                    break;
                }
            }
        } catch (Exception e) {
            isFailed = true;
            failMsg.setCancelType(CancelType.RUN_FAIL);
            failMsg.setMsg(e.getMessage());
        } finally {
            exportExecPool.shutdownNow();
        }

        if (isFailed) {
            job.cancel(failMsg.getCancelType(), failMsg.getMsg());
            return;
        }

        if (job.finish(outfileInfoList)) {
            LOG.info("export job success. job: {}", job);
            
            
            
            
            
            
            
            
            
        }

        synchronized (this) {
            job.setDoExportingThread(null);
        }
    }

Line commented on: ThreadPoolExecutor exportExecPool = ThreadPoolManager.newDaemonFixedThreadPool(parallelNum,

Revised code:
protected void exec() {
        if (job.getState() == JobState.IN_QUEUE) {
            handleInQueueState();
        }

        if (job.getState() != ExportJob.JobState.EXPORTING) {
            return;
        }
        LOG.info("begin execute export job in exporting state. job: {}", job);

        synchronized (job) {
            if (job.getDoExportingThread() != null) {
                LOG.warn("export task is already being executed.");
                return;
            }
            job.setDoExportingThread(Thread.currentThread());
        }

        List<SelectStmt> selectStmtList = job.getSelectStmtList();
        int completeTaskNum = 0;
        List<ExportJob.OutfileInfo> outfileInfoList = Lists.newArrayList();

        int parallelNum = selectStmtList.size();
        CompletionService<ExportResult> completionService = new ExecutorCompletionService<>(exportExecPool);

        
        for (int i = 0; i < parallelNum; ++i) {
            final int idx = i;
            completionService.submit(() -> {
                
                if (job.getState() != JobState.EXPORTING) {
                    return new ExportResult(true, null, null);
                }
                try {
                    Database db = Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException(
                            job.getTableName().getDb());
                    OlapTable table = db.getOlapTableOrAnalysisException(job.getTableName().getTbl());
                    table.readLock();
                    try {
                        SelectStmt selectStmt = selectStmtList.get(idx);
                        List<Long> tabletIds = selectStmt.getTableRefs().get(0).getSampleTabletIds();
                        for (Long tabletId : tabletIds) {
                            TabletMeta tabletMeta = Env.getCurrentEnv().getTabletInvertedIndex().getTabletMeta(
                                    tabletId);
                            Partition partition = table.getPartition(tabletMeta.getPartitionId());
                            long nowVersion = partition.getVisibleVersion();
                            long oldVersion = job.getPartitionToVersion().get(partition.getName());
                            if (nowVersion != oldVersion) {
                                LOG.warn("Tablet {} has changed version, old version = {}, now version = {}",
                                        tabletId, oldVersion, nowVersion);
                                return new ExportResult(true, new ExportFailMsg(
                                        ExportFailMsg.CancelType.RUN_FAIL,
                                        "Tablet {" + tabletId + "} has changed"), null);
                            }
                        }
                    } finally {
                        table.readUnlock();
                    }
                } catch (AnalysisException e) {
                    return new ExportResult(true,
                            new ExportFailMsg(ExportFailMsg.CancelType.RUN_FAIL, e.getMessage()), null);
                }
                try (AutoCloseConnectContext r = buildConnectContext()) {
                    StmtExecutor stmtExecutor = new StmtExecutor(r.connectContext, selectStmtList.get(idx));
                    job.setStmtExecutor(idx, stmtExecutor);
                    stmtExecutor.execute();
                    if (r.connectContext.getState().getStateType() == MysqlStateType.ERR) {
                        return new ExportResult(true, new ExportFailMsg(ExportFailMsg.CancelType.RUN_FAIL,
                                r.connectContext.getState().getErrorMessage()), null);
                    }
                    ExportJob.OutfileInfo outfileInfo = getOutFileInfo(r.connectContext.getResultAttachedInfo());
                    return new ExportResult(false, null, outfileInfo);
                } catch (Exception e) {
                    return new ExportResult(true, new ExportFailMsg(ExportFailMsg.CancelType.RUN_FAIL,
                            e.getMessage()),
                            null);
                } finally {
                    job.getStmtExecutor(idx).addProfileToSpan();
                }
            });
        }

        Boolean isFailed = false;
        ExportFailMsg failMsg = new ExportFailMsg();
        try {
            for (int i = 0; i < parallelNum; ++i) {
                Future<ExportResult> future = completionService.take();
                ExportResult result = future.get();
                if (!result.isFailed) {
                    outfileInfoList.add(result.getOutfileInfo());
                    ++completeTaskNum;
                    int progress = completeTaskNum * 100 / selectStmtList.size();
                    if (progress >= 100) {
                        progress = 99;
                    }
                    job.setProgress(progress);
                    LOG.info("Export Job {} finished {} outfile export and it's progress is {}%", job.getId(),
                            completeTaskNum, progress);
                } else {
                    isFailed = true;
                    failMsg.setCancelType(result.failMsg.getCancelType());
                    failMsg.setMsg(result.failMsg.getMsg());
                    LOG.warn("Exporting task failed because: {}", result.failMsg.getMsg());
                    break;
                }
            }
        } catch (Exception e) {
            isFailed = true;
            failMsg.setCancelType(CancelType.RUN_FAIL);
            failMsg.setMsg(e.getMessage());
        } finally {
            
            if (isFailed) {
                for (int idx = 0; idx < parallelNum; ++idx) {
                    job.getStmtExecutor(idx).cancel();
                }
            }
            exportExecPool.shutdownNow();
        }

        if (isFailed) {
            job.cancel(failMsg.getCancelType(), failMsg.getMsg());
            return;
        }

        if (job.finish(outfileInfoList)) {
            LOG.info("export job success. job: {}", job);
            
            
            
            
            
            
            
            
            
        }

        synchronized (this) {
            job.setDoExportingThread(null);
        }
    }