From 20be426125d1aa1f52a276177565a98a2eba0929 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 21 Sep 2025 19:10:01 -0400 Subject: [PATCH] feat: add support for locking in docker-local commands --- plugins/cron/cron.go | 2 +- plugins/run/internal-functions | 17 ++++++++- plugins/scheduler-docker-local/scheduler-run | 39 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/plugins/cron/cron.go b/plugins/cron/cron.go index aa62ed75c..89d19f552 100644 --- a/plugins/cron/cron.go +++ b/plugins/cron/cron.go @@ -75,7 +75,7 @@ func (t CronTask) DokkuRunCommand() string { return t.AltCommand } - return fmt.Sprintf("dokku run --cron-id %s %s %s", t.ID, t.App, t.Command) + return fmt.Sprintf("dokku run --concurrency-policy %s --cron-id %s %s %s", t.ConcurrencyPolicy, t.ID, t.App, t.Command) } // FetchCronTasksInput is the input for the FetchCronTasks function diff --git a/plugins/run/internal-functions b/plugins/run/internal-functions index b4ccfc2ce..fe45b1790 100755 --- a/plugins/run/internal-functions +++ b/plugins/run/internal-functions @@ -9,7 +9,7 @@ fn-run() { shift 1 declare APP="" - local CRON_ID + local CRON_ID CONCURRENCY_POLICY="allow" declare -a RUN_ENV RUN_ENV=() while [[ $# -gt 0 ]]; do @@ -19,6 +19,11 @@ fn-run() { CRON_ID="$arg" shift ;; + --concurrency-policy=*) + local arg=$(printf "%s" "$1" | sed -E 's/(^--concurrency-policy=)//g') + CONCURRENCY_POLICY="$arg" + shift + ;; --no-tty) export DOKKU_DISABLE_TTY=true shift @@ -35,6 +40,14 @@ fn-run() { CRON_ID="$2" shift 2 ;; + --concurrency-policy) + if [[ ! $2 ]]; then + dokku_log_warn "expected $1 to have an argument" + break + fi + CONCURRENCY_POLICY="$2" + shift 2 + ;; -e=* | --env=*) local arg=$(printf "%s" "$1" | sed -E 's/(^-e=)|(^--env=)//g') RUN_ENV+=("$arg") @@ -67,7 +80,7 @@ fn-run() { verify_app_name "$APP" local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") - DOKKU_CRON_ID="$CRON_ID" plugn trigger scheduler-run "$DOKKU_SCHEDULER" "$APP" "${#RUN_ENV[@]}" "${RUN_ENV[@]}" -- "$@" + DOKKU_CRON_ID="$CRON_ID" DOKKU_CONCURRENCY_POLICY="$CONCURRENCY_POLICY" plugn trigger scheduler-run "$DOKKU_SCHEDULER" "$APP" "${#RUN_ENV[@]}" "${RUN_ENV[@]}" -- "$@" } cmd-run() { diff --git a/plugins/scheduler-docker-local/scheduler-run b/plugins/scheduler-docker-local/scheduler-run index cb6bbfb74..096210dc3 100755 --- a/plugins/scheduler-docker-local/scheduler-run +++ b/plugins/scheduler-docker-local/scheduler-run @@ -5,6 +5,25 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-functions" +fn-scheduler-docker-local-scheduler-run-acquire-lock() { + declare desc="acquire advisory lock for use in docker-local cron jobs" + local APP="$1" CRON_ID="$2" + local LOCK_TYPE="exclusive" + local APP_DEPLOY_LOCK_FILE="$DOKKU_LIB_ROOT/data/scheduler-docker-local/$APP/cron-$CRON_ID.lock" + local LOCK_WAITING_MSG="$APP currently has a cron lock in place for $CRON_ID. Waiting..." + local LOCK_FAILED_MSG="$APP currently has a cron lock in place for $CRON_ID. Exiting..." + + acquire_advisory_lock "$APP_DEPLOY_LOCK_FILE" "$LOCK_TYPE" "$LOCK_WAITING_MSG" "$LOCK_FAILED_MSG" +} + +fn-scheduler-docker-local-scheduler-run-release-lock() { + declare desc="release advisory lock used in docker-local cron jobs" + local APP="$1" CRON_ID="$2" + local APP_DEPLOY_LOCK_FILE="$DOKKU_LIB_ROOT/data/scheduler-docker-local/$APP/cron-$CRON_ID.lock" + + release_advisory_lock "$APP_DEPLOY_LOCK_FILE" +} + trigger-scheduler-docker-local-scheduler-run() { declare desc="runs command in container based on app image" declare trigger="scheduler-run" @@ -39,9 +58,24 @@ trigger-scheduler-docker-local-scheduler-run() { [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true " local PROCESS_TYPE=run + local concurrency_policy="allow" if [[ -n "$DOKKU_CRON_ID" ]]; then PROCESS_TYPE=cron DOCKER_ARGS+=" --label=com.dokku.cron-id=$DOKKU_CRON_ID" + + if [[ -n "$DOKKU_CONCURRENCY_POLICY" ]]; then + concurrency_policy="$DOKKU_CONCURRENCY_POLICY" + fi + concurrency_policy="$DOKKU_CONCURRENCY_POLICY" + DOCKER_ARGS+=" --label=com.dokku.concurrency-policy=$concurrency_policy" + + if [[ "$concurrency_policy" == "forbid" ]]; then + fn-scheduler-docker-local-scheduler-run-acquire-lock "$APP" "$DOKKU_CRON_ID" + fi + + if [[ "$concurrency_policy" == "replace" ]]; then + dokku_log_warn "Concurrency policy 'replace' is not supported for docker-local cron jobs" + fi fi local DYNO_NUMBER="$RANDOM" @@ -138,6 +172,11 @@ trigger-scheduler-docker-local-scheduler-run() { DOKKU_CONTAINER_EXIT_CODE="$("$DOCKER_BIN" container wait "$CONTAINER_ID" 2>/dev/null || echo "$EXIT_CODE")" [[ -z "$DOKKU_CONTAINER_EXIT_CODE" ]] && DOKKU_CONTAINER_EXIT_CODE=0 fi + + if [[ -n "$DOKKU_CRON_ID" ]] && [[ "$concurrency_policy" == "forbid" ]]; then + fn-scheduler-docker-local-scheduler-run-release-lock "$APP" "$DOKKU_CRON_ID" + fi + plugn trigger scheduler-post-run "$DOKKU_SCHEDULER" "$APP" "$CONTAINER_ID" "$DOKKU_CONTAINER_EXIT_CODE" return "$DOKKU_CONTAINER_EXIT_CODE" }