Skip to content

[DSIP-18454][Scheduler] Add schedule missed fire policy - #18464

Open
liang-wenjie wants to merge 8 commits into
apache:devfrom
liang-wenjie:2dev/feat/add-schedule-missed-fire-policy-v2
Open

[DSIP-18454][Scheduler] Add schedule missed fire policy#18464
liang-wenjie wants to merge 8 commits into
apache:devfrom
liang-wenjie:2dev/feat/add-schedule-missed-fire-policy-v2

Conversation

@liang-wenjie

@liang-wenjie liang-wenjie commented Aug 4, 2026

Copy link
Copy Markdown

Was this PR generated or assisted by AI?

YES. AI assisted with reviewing the previous implementation, refining domain naming and compatibility behavior, adding focused tests, and preparing this pull request. The changes were reviewed and validated by the contributor.

Purpose of the pull request

This pull request adds a schedule-level missed fire policy for Cron schedules as an independent part of DSIP #18454.

It is split from the closed PR #18458 so that missed-fire handling can be reviewed separately from the fixed-interval trigger design. Fixed-interval scheduling is intentionally out of scope for this pull request.

The policy uses scheduler-domain terminology instead of Quartz-specific names. DolphinScheduler currently uses Quartz 2.3.2 and explicitly calls withMisfireHandlingInstructionIgnoreMisfires() when building Cron triggers, so the default is FIRE_ALL_MISSED to preserve the existing behavior.

Related to #18454.
Supersedes the missed-fire policy portion of #18458.

Brief change log

  • Add ScheduleMissedFirePolicy with SKIP_MISSED, FIRE_ONCE_NOW, and FIRE_ALL_MISSED.
  • Persist missedFirePolicy in schedule API models and the missed_fire_policy database column.
  • Add CronScheduleBuilderFactory with separate implementations for all three policies.
  • Default missing or legacy policy values to FIRE_ALL_MISSED to preserve the current IgnoreMisfires behavior.
  • Add schedule form options and English/Chinese locale text in the UI.
  • Add unit tests for factory selection, all three Quartz mappings, and the null/default behavior.

Verify this pull request

This change added tests and can be verified as follows:

  • Added CronScheduleBuilderFactoryTest covering all policies and the default behavior.
  • Maven Spotless checks passed for the affected DAO, Quartz scheduler, and API modules.
  • License headers were added to both new upgrade SQL files.
  • UI Prettier checks passed.
  • UI ESLint checks passed.
  • vue-tsc --noEmit passed.
  • Java unit tests could not be executed locally because the local environment provides a JRE without javac; they are expected to run in CI.

Pull Request Notice

Pull Request Notice

This pull request does not introduce an incompatible change. Existing schedules and requests that omit the new field retain the current Quartz IgnoreMisfires behavior through the FIRE_ALL_MISSED default.

private Date endTime;
private String crontab;
private String timezoneId;
private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ONCE_NOW;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, the default misfire policy is FIRE_ALL_MISSED?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous implementation did not set an explicit policy, so Quartz used SMART_POLICY. In Quartz 2.3.2, CronTriggerImpl.updateAfterMisfire() explicitly translates SMART_POLICY to MISFIRE_INSTRUCTION_FIRE_ONCE_NOW. Therefore, I kept FIRE_ONCE_NOW as the default to preserve the current behavior rather than changing existing schedules to FIRE_ALL_MISSED.

@ruanwenjun ruanwenjun Aug 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which version are you using? ds use withMisfireHandlingInstructionIgnoreMisfires to set the policy.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. DolphinScheduler uses Quartz 2.3.2, and the current dev implementation explicitly calls withMisfireHandlingInstructionIgnoreMisfires(), which maps to MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY. I had incorrectly reasoned from Quartz SMART_POLICY instead of checking the existing builder call. Updated in 2868f09: API/database/UI defaults and null fallback now use FIRE_ALL_MISSED, and the default factory test verifies the existing IgnoreMisfires behavior.


import org.quartz.CronScheduleBuilder;

final class QuartzScheduleMissedFirePolicyApplier {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't add this util, it's better to create CronScheduleBuilderFactory and add three different implementation.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in c73327e. I replaced the util with CronScheduleBuilderFactory and added three separate implementations: SkipMissedCronScheduleBuilderFactory, FireOnceNowCronScheduleBuilderFactory, and FireAllMissedCronScheduleBuilderFactory. The focused test now verifies both factory selection and the Quartz misfire instruction for each policy.

start_time: '开始时间',
end_time: '结束时间',
crontab: 'Crontab',
missed_fire_policy: '错过触发策略',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
missed_fire_policy: '错过触发策略',
missed_fire_policy: '定时错过策略',

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to 定时错过策略 in c73327e. Thanks for the suggestion.

@liang-wenjie
liang-wenjie requested a review from ruanwenjun August 5, 2026 03:56
@liang-wenjie

Copy link
Copy Markdown
Author

The latest commit 2868f09 addresses the review feedback and the previous CI issues. The Backend, Frontend, Test, API-Test, E2E, Docs, and CodeQL workflows currently show action_required with no jobs, so they appear to be waiting for a maintainer to approve the fork workflows. When convenient, could a maintainer please approve the workflow runs and continue the review? The PR also still needs a milestone and a valid type label for the milestone-label check. Thank you.

@ruanwenjun ruanwenjun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also update incompatible.md

Comment on lines +406 to +408
private ScheduleMissedFirePolicy defaultMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) {
return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED : missedFirePolicy;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? Your already set a initialize value at scheduleParam.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the redundant fallback helper. SchedulerServiceImpl now uses ScheduleParam#getMissedFirePolicy directly, since ScheduleParam already initializes the default value.

Comment on lines +25 to +28
public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) {
return CronScheduleBuilder.cronSchedule(cronExpression)
.withMisfireHandlingInstructionFireAndProceed();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) {
return CronScheduleBuilder.cronSchedule(cronExpression)
.withMisfireHandlingInstructionFireAndProceed();
}
public CronScheduleBuilder createCronScheduleBuilder(Schedule schedule) {
return CronScheduleBuilder.cronSchedule(cronExpression)
.withMisfireHandlingInstructionFireAndProceed()
.inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId()));
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated CronScheduleBuilderFactory to accept the complete Schedule. All three implementations now create the cron schedule from Schedule#getCrontab and apply the schedule timezone internally. The factory tests also verify the configured timezone for every policy.

@liang-wenjie

Copy link
Copy Markdown
Author

Addressed the latest review feedback in 060743f:

  • removed the redundant SchedulerServiceImpl default-policy helper;
  • changed CronScheduleBuilderFactory and all implementations to accept Schedule and configure the timezone internally;
  • added timezone assertions to CronScheduleBuilderFactoryTest;
  • documented the 3.5.0 missed_fire_policy schema change in the English and Chinese incompatible guides;
  • moved the upgrade DDL from the incorrect 3.3.2_schema post files into the current 3.5.0_schema MySQL/PostgreSQL scripts, which addresses the historical-version schema-check failures.

Local verification:

  • spotless:check for the changed backend modules: passed
  • CronScheduleBuilderFactoryTest: 4 tests passed
  • git diff --check: passed

The previous dead-link failure is unrelated to this PR content: lychee v0.24.0 rejects the existing repository setting include_fragments = false (it expects a string or table). The milestone-label check still requires a maintainer-added milestone/type label.

ruanwenjun
ruanwenjun previously approved these changes Aug 7, 2026

@ruanwenjun ruanwenjun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ruanwenjun ruanwenjun added this to the 3.5.0 milestone Aug 7, 2026
@ruanwenjun ruanwenjun added the feature new feature label Aug 7, 2026
@liang-wenjie

Copy link
Copy Markdown
Author

Thank you for the approval. I checked the remaining failed checks on commit 060743f:

  • Every Unit-Test matrix job failed before Maven started because Maven Central returned HTTP 429 while mvnw was downloading apache-maven-3.8.4-bin.zip. This is an external rate-limit failure, not a test failure.
  • milestone-label-check ran before milestone 3.5.0 was assigned. The PR now has milestone 3.5.0 and valid labels, so that check should pass when rerun.
  • dead-link still fails while parsing the repository existing lychee.toml with lychee v0.24.0 (include_fragments = false is rejected); it does not reach or check this PR documentation links.

I do not have repository admin permission to rerun these jobs. Could a maintainer please rerun the failed Test and Mergeable checks (and handle/rerun dead-link as appropriate)? The remaining Backend/API-Test/E2E/CodeQL workflows are still in progress.

@SbloodyS SbloodyS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The selected missed-fire policy is never submitted and updates reset it

The new selector writes its value to timingForm.missedFirePolicy, but use-modal.ts#getTimingData only serializes startTime, endTime, crontab, and timezoneId into the schedule request.

Consequently:

  • Creating a schedule with SKIP_MISSED or FIRE_ONCE_NOW always stores the default FIRE_ALL_MISSED; the selector currently has no effect.
  • Updating a schedule with a non-default policy omits the field, after which ScheduleParam supplies FIRE_ALL_MISSED and SchedulerServiceImpl#updateSchedule overwrites the existing policy.

Please include missedFirePolicy in the schedule JSON constructed by getTimingData.

For backward compatibility, the backend should also distinguish creation defaults from an omitted update field: an omitted field during creation may use FIRE_ALL_MISSED, while an omitted field during update should preserve the currently stored policy. Please add create/update regression tests covering all three policies and an update request from a legacy client that omits the field.

@SbloodyS SbloodyS added the first time contributor First-time contributor label Aug 7, 2026
…y on update

Address review feedback (SbloodyS):
- Frontend: include missedFirePolicy in the schedule create/update payload
  so the selected policy is actually persisted to the backend.
- Backend: distinguish an omitted JSON field from an explicit value. A new
  missedFirePolicySet marker tracks field presence, because Jackson cannot
  tell omission from an explicit null.
  - create: omitted or explicit null falls back to FIRE_ALL_MISSED
  - update: when the client omits the field (e.g. an older client), the
    existing stored policy is preserved instead of being overwritten
- Add unit tests covering create/update semantics and JSON presence detection.

Co-Authored-By: WorkBuddy <workbuddy@tencent.com>
@liang-wenjie

Copy link
Copy Markdown
Author

Thanks for the review, @SbloodyS. I've addressed both points:

  1. Frontend serializationmissedFirePolicy is now included in the schedule create/update payload in use-modal.ts (getTimingData), so the selected policy is actually persisted. The timing form already defaults to FIRE_ALL_MISSED and the modal binds/loads it correctly on edit.

  2. Backward-compatible update — Jackson cannot distinguish an omitted JSON field from an explicit null, so I added a missedFirePolicySet presence marker in ScheduleParam. The setter flips it only when missedFirePolicy is present in the JSON:

    • Create: omitted or explicit null falls back to FIRE_ALL_MISSED (safe default for new schedules).
    • Update: the stored policy is overwritten only when the field is present and non-null; when an older client omits it, the existing policy is preserved.

    Added unit tests covering create (all 3 policies + default fallback) and update (all 3 policies + preserve-on-omit), plus a test asserting the presence marker distinguishes omitted vs explicit values.

Let me know if you'd prefer a different default or behavior.

@SbloodyS

SbloodyS commented Aug 7, 2026

Copy link
Copy Markdown
Member

You should check failed tests. @liang-wenjie

@liang-wenjie

Copy link
Copy Markdown
Author

Thanks, @SbloodyS. I checked and fixed the failed schedule API tests in c804e50cf5 (test(api): fix schedule insert mock). The branch has also been updated with dev; current head is cd5a311164.

The relevant checks are now green:

  • Unit-Test (dolphinscheduler-api | Java 8) — passed
  • Unit-Test (dolphinscheduler-api | Java 11) — passed
  • API-Test / Backend / Frontend / E2E / schema checks — passed

The only remaining failed check is dead-link. It fails before checking any links because the workflow installs lychee v0.24.0, while the repository's existing .github/workflows/lychee.toml has include_fragments = false; v0.24.0 expects a string or table for this option. This is unrelated to the files changed by this PR.

Could you please re-review the missed-fire-policy changes when convenient? The requested frontend serialization, backward-compatible update behavior, and create/update regression coverage are all included in the current branch. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend document feature new feature first time contributor First-time contributor test UI ui and front end related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants