sorentwo/oban

DynamicCron cannot insert jobs for Crontab entries that override `guaranteed`

Closed this issue ยท 5 comments

Hey, we are implementing DynamicCron in our company but we are facing some issues.

Environment

  • Oban Version 1.4.3

Current Behavior

For an Oban configured with:

{
   Oban.Pro.Plugins.DynamicCron,
   crontab: [
     {"* * * * *", Pruner, guaranteed: false}
   ],
   guaranteed: true,
   sync_mode: :automatic
}

Every time that the job would be inserted, the following error pops up:

** (Ecto.InvalidChangesetError) could not perform insert because changeset is invalid.

Errors

    %{base: [{\"unknown option :guaranteed provided\", []}]}

Applied changes

    %{
      args: %{},
      meta: %{
        uniq_key: 131720079,
        cron: true,
        cron_expr: \"* * * * *\",
        cron_name: \"Pruner\"
      },
      max_attempts: 3,
      queue: \"pruner\",
      worker: \"Pruner\",
      unique: %{
        timestamp: :inserted_at,
        keys: [],
        period: :infinity,
        fields: [:args, :queue, :worker],
        states: [:available, :scheduled, :executing, :retryable]
      }
    }

Params

    %{
      \"args\" => %{},
      \"guaranteed\" => true,
      \"max_attempts\" => 3,
      \"meta\" => %{
        cron: true,
        cron_expr: \"* * * * *\",
        cron_name: \"Pruner\"
      },
      \"queue\" => \"pruner\",
      \"unique\" => [
        period: :infinity,
        states: [:available, :scheduled, :executing, :retryable]
      ],
      \"worker\" => \"Pruner\"
    }

Changeset

    #Ecto.Changeset<
      action: :insert,
      changes: %{
        args: %{},
        meta: %{
          uniq_key: 131720079,
          cron: true,
          cron_expr: \"* * * * *\",
          cron_name: \"Pruner\"
        },
        max_attempts: 3,
        queue: \"pruner\",
        worker: \"Pruner\",
        unique: %{
          timestamp: :inserted_at,
          keys: [],
          period: :infinity,
          fields: [:args, :queue, :worker],
          states: [:available, :scheduled, :executing, :retryable]
        }
      },
      errors: [base: {\"unknown option :guaranteed provided\", []}],
      data: #Oban.Job<>,
      valid?: false
    >

In other words, the guaranteed option is being sent to Ecto Changeset, but the Oban.Job schema does not have that field.

The Oban guaranteed option is described here: https://getoban.pro/docs/pro/1.4.3/Oban.Pro.Plugins.DynamicCron.html#module-scheduling-guarantees

Another bug is that the last_match_at/1 function does not consider the timezone.

For example, let's consider a Cron entry as 20 17 * * * with the timezone Europe/Lisbon (UTC+1 currently).

Then, the function last_match_at is run at 17:30 WEST (16:30 UTC).

The expected result would be 17:20 WEST, but the actual result is 17:20 UTC of the day before.

This is caused because the datetime sent to Expression.now? is in UTC, and not in the selected timezone as it should.

This means that the guaranteed option would only schedule jobs that have been late for over a day.

A final issue with the guaranteed jobs is that when updating a Cron expression to run at a later time, Oban will always schedule a job as if it was missed.

If a Cron entry that sends emails with the following expression: 20 17 * * * is updated to be 30 17 * * *, it would trigger the " "missed" behaviour independently of the time the expression was modified.

This is troublesome as unexpected cron executions can have high costs.

One possible fix, for example, would be to clear the insertions when the expression is updated.

@thiagopromano Thank you for the excellent reports and analysis. All three issues are patched, but the last one will require some more thinking to compensate for config-level timezone changes.

Do you know when we can expect a patch @sorentwo ? We just ran into the invalid changeset error as well. :)

@TheMoonDawg Coming in the next day or two.