diff --git a/cylc/flow/etc/examples/expiry/.validate b/cylc/flow/etc/examples/expiry/.validate new file mode 100755 index 0000000000..2b1b913853 --- /dev/null +++ b/cylc/flow/etc/examples/expiry/.validate @@ -0,0 +1,110 @@ +#!/bin/bash +# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE. +# Copyright (C) NIWA & British Crown (Met Office) & Contributors. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set -euxo pipefail + +test_one () { + ID="$(< /dev/urandom tr -dc A-Za-z | head -c6 || true)" + + # start the workflow + cylc vip \ + --check-circular \ + --no-detach \ + --final-cycle-point=P0D \ + --no-run-name \ + --workflow-name "$ID" \ + ./one + + # the start task should have expired + grep 'start.*(internal)expired' "$HOME/cylc-run/$ID/log/scheduler/log" + + # the following task(s) should not have run + ! grep 'a.*running' "$HOME/cylc-run/$ID/log/scheduler/log" + ! grep 'b.*running' "$HOME/cylc-run/$ID/log/scheduler/log" + + # lint + cylc lint "$ID" + + # clean up + cylc clean "$ID" +} + + +test_two () { + ID="$(< /dev/urandom tr -dc A-Za-z | head -c6 || true)" + + # start the workflow + cylc vip \ + --check-circular \ + --no-detach \ + --final-cycle-point=P0D \ + --no-run-name \ + --workflow-name "$ID" \ + ./two + + # the start task should run + grep 'start.*running' "$HOME/cylc-run/$ID/log/scheduler/log" + + # some other task in the chain should expire + grep '(internal)expired' "$HOME/cylc-run/$ID/log/scheduler/log" + + # the housekeep task at the end of the cycle should not run + ! grep 'housekeep.*running' "$HOME/cylc-run/$ID/log/scheduler/log" + + # lint + cylc lint "$ID" + + # clean up + cylc clean "$ID" +} + + +test_three () { + ID="$(< /dev/urandom tr -dc A-Za-z | head -c6 || true)" + + # start the workflow + cylc vip \ + --check-circular \ + --no-detach \ + --final-cycle-point=P0D \ + --no-run-name \ + --workflow-name "$ID" \ + ./three + + # the start task should expire + grep 'start.*(internal)expired' "$HOME/cylc-run/$ID/log/scheduler/log" + [[ ! -f "$HOME/cylc-run/$ID/log/job/"*"/a/NN/job" ]] + + # only the "a" and "housekeep" tasks should run + [[ $(cd "$HOME/cylc-run/XOECeJ/log/job/"*; echo *) == 'a housekeep' ]] + + # tasks b, c and d should skip + grep '\/b.*run mode=skip' "$HOME/cylc-run/$ID/log/scheduler/log" + grep '\/c.*run mode=skip' "$HOME/cylc-run/$ID/log/scheduler/log" + grep '\/d.*run mode=skip' "$HOME/cylc-run/$ID/log/scheduler/log" + + # lint + cylc lint "$ID" + + # clean up + cylc clean "$ID" +} + + +test_one +test_two +test_three diff --git a/cylc/flow/etc/examples/expiry/index.rst b/cylc/flow/etc/examples/expiry/index.rst new file mode 100644 index 0000000000..a2a9f82c2f --- /dev/null +++ b/cylc/flow/etc/examples/expiry/index.rst @@ -0,0 +1,78 @@ +.. _examples.expiry: + +Expiring Tasks / Cycles +----------------------- + +Cylc is often used to write workflows which monitor real-world events. + +For example, this workflow will run the task ``foo`` every day at 00:00am: + +.. code-block:: cylc + + [scheduling] + initial cycle point = previous(T00) + [[graph]] + P1D = """ + @wall_clock => foo + """ + +Sometimes such workflows might get behind, e.g. due to failures or slow task +execution. In this situation, it might be necessary to skip a few tasks in +order for the workflow to catch up with the real-world time. + +Cylc has a concept called :ref:`expiry ` which allows tasks +to be automatcially "expired" if they are running behind schedule. + +.. seealso:: + + :ref:`ClockExpireTasks`. + + +Example 1: Skip a whole cycle of tasks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the workflow gets behind, skip whole cycles of tasks until it catches up. + +.. admonition:: Get a copy of this example + :class: hint + + .. code-block:: console + + $ cylc get-resources examples/expiry/one + +.. literalinclude:: one/flow.cylc + :language: cylc + + +Example 2: Skip the remainder of a cycle of tasks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the workflow gets behind, skip the remainder of the tasks in the cycle, +then skip whole cycles of tasks until it catches up. + +.. admonition:: Get a copy of this example + :class: hint + + .. code-block:: console + + $ cylc get-resources examples/expiry/two + +.. literalinclude:: two/flow.cylc + :language: cylc + + +Example 3: Skip selected tasks in a cycle +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the workflow gets behind, turn off selected tasks to allow it to catch up +more quickly. + +.. admonition:: Get a copy of this example + :class: hint + + .. code-block:: console + + $ cylc get-resources examples/expiry/three + +.. literalinclude:: three/flow.cylc + :language: cylc diff --git a/cylc/flow/etc/examples/expiry/one/flow.cylc b/cylc/flow/etc/examples/expiry/one/flow.cylc new file mode 100644 index 0000000000..528dbaa233 --- /dev/null +++ b/cylc/flow/etc/examples/expiry/one/flow.cylc @@ -0,0 +1,33 @@ +[meta] + description = """ + If the workflow runs slowly and the cycle time gets behind the real + world (wallclock) time, then it will skip cycles until it catches up. + + Either a cycle runs or it is skipped. + + When you start this workflow, the first cycle will be at 00:00am this + morning so will immediately expire causing the workflow to move onto + tomorrow's cycle. + """ + +[scheduler] + allow implicit tasks = True + +[scheduling] + # start the workflow at 00:00am this morning + initial cycle point = previous(T00) + + # the "start" task will "expire" if the cycle time falls behind + # the wallclock time + [[special tasks]] + clock-expire = start + + [[graph]] + P1D = """ + # the chain of tasks we want to run + start => a => b => c => d => housekeep + + # wait for the previous cycle to either complete or expire before + # continuing onto the next cycle + housekeep[-P1D] | start[-P1D]:expired? => start + """ diff --git a/cylc/flow/etc/examples/expiry/three/flow.cylc b/cylc/flow/etc/examples/expiry/three/flow.cylc new file mode 100644 index 0000000000..8613aafd6b --- /dev/null +++ b/cylc/flow/etc/examples/expiry/three/flow.cylc @@ -0,0 +1,40 @@ +[meta] + description = """ + If the workflow runs slowly and the cycle time gets behind the real + world (wallclock) time, then it will skip selected tasks until it + catches up. + + In this case, the tasks "b", "c" and "d" will be skipped to help the + workflow to catch up more quickly. + + When this workflow starts up, the first cycle will be at 00:00am today + so the "start" task will immediately expire. This will cause tasks + "b", "c" and "d" to be configured to "skip" rather than run. + """ + +[scheduler] + allow implicit tasks = True + +[scheduling] + # start the workflow at 00:00am this morning + initial cycle point = previous(T00) + final cycle point = +P0D + + # the "start" task will "expire" if the cycle time falls behind + # the wallclock time + [[special tasks]] + clock-expire = start + + [[graph]] + P1D = """ + # the chain of tasks we want to run + start | start:expired? => a => b => c => d => housekeep + """ + +[runtime] + [[start]] + # if this task expires, configure the tasks "b", "c" and "d" to + # "skip" rather than run + # Note: This task will also be "skipped" if it expires + [[[events]]] + expired handlers = cylc broadcast "%(workflow)s" -p "%(point)s" -n b -n c -n d -s "run mode = skip" diff --git a/cylc/flow/etc/examples/expiry/two/flow.cylc b/cylc/flow/etc/examples/expiry/two/flow.cylc new file mode 100644 index 0000000000..66d3fd6518 --- /dev/null +++ b/cylc/flow/etc/examples/expiry/two/flow.cylc @@ -0,0 +1,46 @@ +[meta] + description = """ + If the workflow runs slowly and the cycle time gets behind the real + world (wallclock) time, then it will skip tasks until it catches up. + + A cycle may be skipped part way through to allow the workflow to catch + up faster. + + When this workflow starts up, the first cycle will be one minute ahead + of the wallclock time. At some point in the cycle, the wallclock time + will overtake the cycle time and the next task in the chain will + expire. The workflow will then move onto the next cycle. + """ + +[scheduler] + allow implicit tasks = True + +[scheduling] + # start the workflow at 00:00am this morning + initial cycle point = PT1M + + # any task in the workflow will "expire" rather than run if the cycle + # time falls behind the wallclock time + [[special tasks]] + clock-expire = start, a, b, c, d, housekeep + + [[graph]] + P1D = """ + # the chain of tasks we want to run + start => a => b => c => d => housekeep + + # start the next cycle as soon as the previous cycle has finished + # OR and task in the previous cycle has expired + housekeep[-P1D] + | start[-P1D]:expire? + | a[-P1D]:expired? + | b[-P1D]:expired? + | c[-P1D]:expired? + | d[-P1D]:expired? + | housekeep[-P1D]:expired? + => start + """ + +[runtime] + [[root]] + script = sleep 12