|
41 | 41 | import java.time.ZonedDateTime; |
42 | 42 | import java.util.ArrayList; |
43 | 43 | import java.util.Collections; |
| 44 | +import java.util.HashSet; |
44 | 45 | import java.util.List; |
| 46 | +import java.util.Set; |
45 | 47 | import java.util.stream.Collectors; |
46 | 48 |
|
47 | 49 | import lombok.extern.slf4j.Slf4j; |
|
55 | 57 | @Component |
56 | 58 | public class BackfillWorkflowExecutorDelegate implements IExecutorDelegate<BackfillWorkflowDTO, List<Integer>> { |
57 | 59 |
|
| 60 | + private static final ThreadLocal<Set<Long>> BACKFILL_VISITING_WORKFLOWS = new ThreadLocal<>(); |
| 61 | + |
58 | 62 | @Autowired |
59 | 63 | private CommandDao commandDao; |
60 | 64 |
|
@@ -183,89 +187,104 @@ private Integer doBackfillWorkflow(final BackfillWorkflowDTO backfillWorkflowDTO |
183 | 187 |
|
184 | 188 | private void doBackfillDependentWorkflow(final BackfillWorkflowDTO backfillWorkflowDTO, |
185 | 189 | final List<String> backfillTimeList) { |
186 | | - // 1) Query downstream dependent workflows for the current workflow |
187 | | - final WorkflowDefinition upstreamWorkflow = backfillWorkflowDTO.getWorkflowDefinition(); |
188 | | - final long upstreamWorkflowCode = upstreamWorkflow.getCode(); |
189 | | - |
190 | | - List<DependentWorkflowDefinition> downstreamDefinitions = |
191 | | - workflowLineageService.queryDownstreamDependentWorkflowDefinitions(upstreamWorkflowCode); |
192 | | - |
193 | | - if (downstreamDefinitions == null || downstreamDefinitions.isEmpty()) { |
194 | | - log.info("No downstream dependent workflows found for workflow code {}", upstreamWorkflowCode); |
195 | | - return; |
| 190 | + final boolean isRootCall = BACKFILL_VISITING_WORKFLOWS.get() == null; |
| 191 | + if (isRootCall) { |
| 192 | + final Set<Long> visitedCodes = new HashSet<>(); |
| 193 | + visitedCodes.add(backfillWorkflowDTO.getWorkflowDefinition().getCode()); |
| 194 | + BACKFILL_VISITING_WORKFLOWS.set(visitedCodes); |
196 | 195 | } |
| 196 | + try { |
| 197 | + final Set<Long> visitedCodes = BACKFILL_VISITING_WORKFLOWS.get(); |
197 | 198 |
|
198 | | - // 2) Convert upstream backfill time from string to ZonedDateTime as the base business dates for downstream |
199 | | - // backfill |
200 | | - final List<ZonedDateTime> upstreamBackfillDates = backfillTimeList.stream() |
201 | | - .map(DateUtils::stringToZoneDateTime) |
202 | | - .collect(Collectors.toList()); |
| 199 | + // 1) Query downstream dependent workflows for the current workflow |
| 200 | + final WorkflowDefinition upstreamWorkflow = backfillWorkflowDTO.getWorkflowDefinition(); |
| 201 | + final long upstreamWorkflowCode = upstreamWorkflow.getCode(); |
203 | 202 |
|
204 | | - // 3) Iterate downstream workflows and build/trigger corresponding BackfillWorkflowDTO |
205 | | - for (DependentWorkflowDefinition dependentWorkflowDefinition : downstreamDefinitions) { |
206 | | - long downstreamCode = dependentWorkflowDefinition.getWorkflowDefinitionCode(); |
| 203 | + List<DependentWorkflowDefinition> downstreamDefinitions = |
| 204 | + workflowLineageService.queryDownstreamDependentWorkflowDefinitions(upstreamWorkflowCode); |
207 | 205 |
|
208 | | - // Prevent self dependency |
209 | | - if (downstreamCode == upstreamWorkflowCode) { |
210 | | - log.warn("Skip self dependent workflow {}", downstreamCode); |
211 | | - continue; |
| 206 | + if (downstreamDefinitions == null || downstreamDefinitions.isEmpty()) { |
| 207 | + log.info("No downstream dependent workflows found for workflow code {}", upstreamWorkflowCode); |
| 208 | + return; |
212 | 209 | } |
213 | 210 |
|
214 | | - WorkflowDefinition downstreamWorkflow = |
215 | | - workflowDefinitionDao.queryByCode(downstreamCode).orElse(null); |
216 | | - if (downstreamWorkflow == null) { |
217 | | - log.warn("Skip dependent workflow {}, definition not found", downstreamCode); |
218 | | - continue; |
| 211 | + // 2) Convert upstream backfill time from string to ZonedDateTime as the base business dates for downstream |
| 212 | + // backfill |
| 213 | + final List<ZonedDateTime> upstreamBackfillDates = backfillTimeList.stream() |
| 214 | + .map(DateUtils::stringToZoneDateTime) |
| 215 | + .collect(Collectors.toList()); |
| 216 | + |
| 217 | + // 3) Iterate downstream workflows and build/trigger corresponding BackfillWorkflowDTO |
| 218 | + for (DependentWorkflowDefinition dependentWorkflowDefinition : downstreamDefinitions) { |
| 219 | + long downstreamCode = dependentWorkflowDefinition.getWorkflowDefinitionCode(); |
| 220 | + |
| 221 | + // Prevent self-dependency and circular dependency chains |
| 222 | + if (visitedCodes.contains(downstreamCode)) { |
| 223 | + log.warn("Skip circular dependent workflow {}", downstreamCode); |
| 224 | + continue; |
| 225 | + } |
| 226 | + |
| 227 | + WorkflowDefinition downstreamWorkflow = |
| 228 | + workflowDefinitionDao.queryByCode(downstreamCode).orElse(null); |
| 229 | + if (downstreamWorkflow == null) { |
| 230 | + log.warn("Skip dependent workflow {}, definition not found", downstreamCode); |
| 231 | + continue; |
| 232 | + } |
| 233 | + |
| 234 | + if (downstreamWorkflow.getReleaseState() != ReleaseState.ONLINE) { |
| 235 | + log.warn("Skip dependent workflow {}, release state is not ONLINE", downstreamCode); |
| 236 | + continue; |
| 237 | + } |
| 238 | + |
| 239 | + // Currently, reuse the same business date list as upstream for downstream backfill; |
| 240 | + // later we can refine the dates based on dependency cycle configuration in dependentWorkflowDefinition |
| 241 | + // (taskParams). |
| 242 | + BackfillWorkflowDTO.BackfillParamsDTO originalParams = backfillWorkflowDTO.getBackfillParams(); |
| 243 | + boolean allLevelDependent = originalParams.isAllLevelDependent(); |
| 244 | + ComplementDependentMode downstreamDependentMode = |
| 245 | + allLevelDependent ? originalParams.getBackfillDependentMode() : ComplementDependentMode.OFF_MODE; |
| 246 | + |
| 247 | + BackfillWorkflowDTO.BackfillParamsDTO dependentParams = BackfillWorkflowDTO.BackfillParamsDTO.builder() |
| 248 | + .runMode(originalParams.getRunMode()) |
| 249 | + .backfillDateList(upstreamBackfillDates) |
| 250 | + .expectedParallelismNumber(originalParams.getExpectedParallelismNumber()) |
| 251 | + // Control whether downstream will continue triggering its own dependencies based on |
| 252 | + // allLevelDependent flag |
| 253 | + .backfillDependentMode(downstreamDependentMode) |
| 254 | + .allLevelDependent(allLevelDependent) |
| 255 | + .executionOrder(originalParams.getExecutionOrder()) |
| 256 | + .build(); |
| 257 | + |
| 258 | + BackfillWorkflowDTO dependentBackfillDTO = BackfillWorkflowDTO.builder() |
| 259 | + .loginUser(backfillWorkflowDTO.getLoginUser()) |
| 260 | + .workflowDefinition(downstreamWorkflow) |
| 261 | + .startNodes(null) |
| 262 | + .failureStrategy(backfillWorkflowDTO.getFailureStrategy()) |
| 263 | + .taskDependType(backfillWorkflowDTO.getTaskDependType()) |
| 264 | + .execType(backfillWorkflowDTO.getExecType()) |
| 265 | + .warningType(backfillWorkflowDTO.getWarningType()) |
| 266 | + .warningGroupId(downstreamWorkflow.getWarningGroupId()) |
| 267 | + .runMode(dependentParams.getRunMode()) |
| 268 | + .workflowInstancePriority(backfillWorkflowDTO.getWorkflowInstancePriority()) |
| 269 | + .workerGroup(backfillWorkflowDTO.getWorkerGroup()) |
| 270 | + .tenantCode(backfillWorkflowDTO.getTenantCode()) |
| 271 | + .environmentCode(backfillWorkflowDTO.getEnvironmentCode()) |
| 272 | + .startParamList(backfillWorkflowDTO.getStartParamList()) |
| 273 | + .dryRun(backfillWorkflowDTO.getDryRun()) |
| 274 | + .backfillParams(dependentParams) |
| 275 | + .build(); |
| 276 | + |
| 277 | + log.info("Trigger dependent workflow {} for upstream workflow {} with backfill dates {}", |
| 278 | + downstreamCode, upstreamWorkflowCode, backfillTimeList); |
| 279 | + |
| 280 | + // 4) Mark as visiting before recursive trigger to detect cycles |
| 281 | + visitedCodes.add(downstreamCode); |
| 282 | + execute(dependentBackfillDTO); |
219 | 283 | } |
220 | | - |
221 | | - if (downstreamWorkflow.getReleaseState() != ReleaseState.ONLINE) { |
222 | | - log.warn("Skip dependent workflow {}, release state is not ONLINE", downstreamCode); |
223 | | - continue; |
| 284 | + } finally { |
| 285 | + if (isRootCall) { |
| 286 | + BACKFILL_VISITING_WORKFLOWS.remove(); |
224 | 287 | } |
225 | | - |
226 | | - // Currently, reuse the same business date list as upstream for downstream backfill; |
227 | | - // later we can refine the dates based on dependency cycle configuration in dependentWorkflowDefinition |
228 | | - // (taskParams). |
229 | | - BackfillWorkflowDTO.BackfillParamsDTO originalParams = backfillWorkflowDTO.getBackfillParams(); |
230 | | - boolean allLevelDependent = originalParams.isAllLevelDependent(); |
231 | | - ComplementDependentMode downstreamDependentMode = |
232 | | - allLevelDependent ? originalParams.getBackfillDependentMode() : ComplementDependentMode.OFF_MODE; |
233 | | - |
234 | | - BackfillWorkflowDTO.BackfillParamsDTO dependentParams = BackfillWorkflowDTO.BackfillParamsDTO.builder() |
235 | | - .runMode(originalParams.getRunMode()) |
236 | | - .backfillDateList(upstreamBackfillDates) |
237 | | - .expectedParallelismNumber(originalParams.getExpectedParallelismNumber()) |
238 | | - // Control whether downstream will continue triggering its own dependencies based on |
239 | | - // allLevelDependent flag |
240 | | - .backfillDependentMode(downstreamDependentMode) |
241 | | - .allLevelDependent(allLevelDependent) |
242 | | - .executionOrder(originalParams.getExecutionOrder()) |
243 | | - .build(); |
244 | | - |
245 | | - BackfillWorkflowDTO dependentBackfillDTO = BackfillWorkflowDTO.builder() |
246 | | - .loginUser(backfillWorkflowDTO.getLoginUser()) |
247 | | - .workflowDefinition(downstreamWorkflow) |
248 | | - .startNodes(backfillWorkflowDTO.getStartNodes()) |
249 | | - .failureStrategy(backfillWorkflowDTO.getFailureStrategy()) |
250 | | - .taskDependType(backfillWorkflowDTO.getTaskDependType()) |
251 | | - .execType(backfillWorkflowDTO.getExecType()) |
252 | | - .warningType(backfillWorkflowDTO.getWarningType()) |
253 | | - .warningGroupId(downstreamWorkflow.getWarningGroupId()) |
254 | | - .runMode(dependentParams.getRunMode()) |
255 | | - .workflowInstancePriority(backfillWorkflowDTO.getWorkflowInstancePriority()) |
256 | | - .workerGroup(backfillWorkflowDTO.getWorkerGroup()) |
257 | | - .tenantCode(backfillWorkflowDTO.getTenantCode()) |
258 | | - .environmentCode(backfillWorkflowDTO.getEnvironmentCode()) |
259 | | - .startParamList(backfillWorkflowDTO.getStartParamList()) |
260 | | - .dryRun(backfillWorkflowDTO.getDryRun()) |
261 | | - .backfillParams(dependentParams) |
262 | | - .build(); |
263 | | - |
264 | | - log.info("Trigger dependent workflow {} for upstream workflow {} with backfill dates {}", |
265 | | - downstreamCode, upstreamWorkflowCode, backfillTimeList); |
266 | | - |
267 | | - // 4) Recursively call execute to trigger downstream backfill |
268 | | - execute(dependentBackfillDTO); |
269 | 288 | } |
270 | 289 | } |
271 | 290 | } |
0 commit comments