From a7f4538ffa85520f2281bd9fcb7da2ddb8b57739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de> Date: Tue, 21 Nov 2023 14:21:25 +0100 Subject: [PATCH] Use exponential backoff when polling running executions #85 --- src/utils/BackoffStrategy.ts | 70 +++++++++++++++++++ .../workflows/ListWorkflowExecutionsView.vue | 44 +++++++++--- 2 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 src/utils/BackoffStrategy.ts diff --git a/src/utils/BackoffStrategy.ts b/src/utils/BackoffStrategy.ts new file mode 100644 index 0000000..c10ad0a --- /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 ea395ae..cbf7b1f 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> -- GitLab