diff --git a/src/utils/BackoffStrategy.ts b/src/utils/BackoffStrategy.ts new file mode 100644 index 0000000000000000000000000000000000000000..c10ad0a314f7f651c828a85cdccd20d4882e5c78 --- /dev/null +++ b/src/utils/BackoffStrategy.ts @@ -0,0 +1,70 @@ +abstract class BackoffStrategy { + protected currentVal: number; + protected iteration: number; + protected reachedMax: boolean; + protected maxValue: number; + + constructor(maxValue?: number) { + this.currentVal = 0; + this.iteration = 0; + this.reachedMax = false; + this.maxValue = maxValue ?? 300; + } + + protected abstract computeNextValue(): number; + + public reset() { + this.iteration = 0; + this.currentVal = 0; + this.reachedMax = false; + } + + public *generator(): Generator<number> { + while (true) { + this.iteration++; + if (this.reachedMax) { + yield this.maxValue; + } else { + this.currentVal = this.computeNextValue(); + if (0 < this.maxValue && this.maxValue < this.currentVal) { + this.reachedMax = true; + yield this.maxValue; + } else { + yield this.currentVal; + } + } + } + } +} + +export class ExponentialBackoff extends BackoffStrategy { + protected computeNextValue(): number { + return 2 << (this.iteration - 1); + } +} + +export class NoBackoff extends BackoffStrategy { + private readonly constantValue: number; + + constructor(constantValue?: number) { + super(); + this.constantValue = constantValue ?? 30; + } + + protected computeNextValue(): number { + return this.constantValue; + } +} + +export class LinearBackoff extends BackoffStrategy { + private readonly backoff: number; + + constructor(backoff?: number) { + super(); + this.backoff = backoff ?? 5; + } + + protected computeNextValue(): number { + return this.currentVal + this.backoff; + } +} diff --git a/src/views/workflows/ListWorkflowExecutionsView.vue b/src/views/workflows/ListWorkflowExecutionsView.vue index ea395aee68394923ecbcd377ad6df8b38d15ca12..cbf7b1f06fd45f9af5a399a853ea5a78e7f20db7 100644 --- a/src/views/workflows/ListWorkflowExecutionsView.vue +++ b/src/views/workflows/ListWorkflowExecutionsView.vue @@ -8,12 +8,14 @@ import DeleteModal from "@/components/modals/DeleteModal.vue"; import { useWorkflowStore } from "@/stores/workflows"; import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; import ParameterModal from "@/components/workflows/modals/ParameterModal.vue"; +import { ExponentialBackoff } from "@/utils/BackoffStrategy"; const workflowRepository = useWorkflowStore(); const executionRepository = useWorkflowExecutionStore(); +const backoff = new ExponentialBackoff(); let refreshTimeout: NodeJS.Timeout | undefined = undefined; -let intervalId: NodeJS.Timer | undefined = undefined; +let pollingTimeout: NodeJS.Timeout | undefined = undefined; const executionsState = reactive<{ loading: boolean; @@ -80,9 +82,17 @@ const deleteModalString = computed<string>(() => { // Functions // ----------------------------------------------------------------------------- function updateExecutions() { - executionRepository.fetchExecutions(() => { - executionsState.loading = false; - }); + backoff.reset(); + clearTimeout(pollingTimeout); + executionRepository + .fetchExecutions(() => { + executionsState.loading = false; + }) + .then(() => { + if (runningExecutions.value.length > 0) { + refreshRunningWorkflowExecutionTimer(); + } + }); } function refreshExecutions() { @@ -106,25 +116,37 @@ function cancelWorkflowExecution(executionId: string) { executionRepository.cancelExecution(executionId); } +const runningExecutions = computed<WorkflowExecutionOut[]>(() => + executionRepository.executions.filter((execution) => + workflowExecutionCancelable(execution), + ), +); + function refreshRunningWorkflowExecution() { Promise.all( - executionRepository.executions - .filter((execution) => workflowExecutionCancelable(execution)) - .map((execution) => - executionRepository.fetchExecution(execution.execution_id), - ), + runningExecutions.value.map((execution) => + executionRepository.fetchExecution(execution.execution_id), + ), ); } +async function refreshRunningWorkflowExecutionTimer() { + for (const sleep of backoff.generator()) { + await new Promise((resolve) => { + pollingTimeout = setTimeout(resolve, sleep * 1000); + }); + refreshRunningWorkflowExecution(); + } +} + onMounted(() => { updateExecutions(); workflowRepository.fetchWorkflows(); - intervalId = setInterval(refreshRunningWorkflowExecution, 5000); new Tooltip("#refreshExecutionsButton"); }); onUnmounted(() => { - clearInterval(intervalId); + clearTimeout(pollingTimeout); }); </script>