TerminationHandler implements OutputHandler { @VisibleForTesting boolean retryConditionMet(RunState state, Optional<Integer> exitCode, String retryCondition) { var context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("exitCode", exitCode.orElse(null)); context.setVariable("tries", state.data().tries()); context.setVariable("triggerType", state.data().trigger().map(TriggerUtil::triggerType).orElse(null)); context.setVariable("consecutiveFailures", state.data().consecutiveFailures()); final Expression expression; try { expression = retryConditionExpressionCache.get(retryCondition, () -> expressionParser.parseRaw(retryCondition)); } catch (ExecutionException | UncheckedExecutionException e) { LOG.debug("Failed to parse retry condition `{}`", retryCondition, e.getCause()); return false; } try { return TRUE.equals(expression.getValue(context, Boolean.class)); } catch (EvaluationException e) { LOG.debug("Failed to evaluate retry condition `{}`", retryCondition, e); return false; } } TerminationHandler(RetryUtil retryUtil, Supplier<Map<WorkflowId, Workflow>> workflows); @Override void transitionInto(RunState state, EventRouter eventRouter); static final int MISSING_DEPS_RETRY_DELAY_MINUTES; }
@Test(timeout = 2000) public void shouldEvaluateToTrueUnderMultiThreadEnv() { var forkJoinPool = new ForkJoinPool(32); var results = forkJoinPool.submit(() -> IntStream.range(0, 10000) .parallel() .mapToObj(i -> terminationHandler.retryConditionMet( RunState.create(WORKFLOW_INSTANCE, FAILED, StateData.newBuilder() .tries(3) .consecutiveFailures(2) .trigger(Trigger.natural()) .build()), Optional.of(1), "#exitCode == 1 && (#tries < 3 || #consecutiveFailures < 4) && #triggerType == \"natural\""))) .join() .collect(toList()); forkJoinPool.shutdownNow(); results.forEach(result -> assertThat(result, is(true))); }
@Test public void shouldFailToParse() { var result = terminationHandler.retryConditionMet( RunState.create(WORKFLOW_INSTANCE, FAILED, StateData.zero()), Optional.of(1), "foo -> bar"); assertThat(result, is(false)); }
@Test public void shouldFailToEvaluate() { var result = terminationHandler.retryConditionMet( RunState.create(WORKFLOW_INSTANCE, FAILED, StateData.zero()), Optional.of(1), "\"foo\""); assertThat(result, is(false)); }
@Test public void shouldEvaluateToFalseWhenMissingExitCode() { var result = terminationHandler.retryConditionMet( RunState.create(WORKFLOW_INSTANCE, FAILED, StateData.zero()), Optional.empty(), "#exitCode == 1"); assertThat(result, is(false)); }
@Test public void shouldEvaluateToFalseWhenMissingTriggerType() { var result = terminationHandler.retryConditionMet( RunState.create(WORKFLOW_INSTANCE, FAILED, StateData.newBuilder() .tries(3) .consecutiveFailures(2) .build()), Optional.of(1), "#exitCode == 1 && (#tries < 3 || #consecutiveFailures < 4) && #triggerType == \"natural\""); assertThat(result, is(false)); }
@Test public void shouldEvaluateToFalseIfRetryConditionIsNotBooleanExpression() { var result = terminationHandler.retryConditionMet( RunState.create(WORKFLOW_INSTANCE, FAILED, StateData.zero()), Optional.of(1), "21 * 2"); assertThat(result, is(false)); }