Skip to content

fix(jobs): a disabled client-Java job stays disabled (#6626) - #6627

Merged
delchev merged 1 commit into
masterfrom
fix/preserve-client-java-job-enabled
Aug 9, 2026
Merged

fix(jobs): a disabled client-Java job stays disabled (#6626)#6627
delchev merged 1 commit into
masterfrom
fix/preserve-client-java-job-enabled

Conversation

@delchev

@delchev delchev commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closes #6626. Noticed while fixing #6305 / #6625.

The problem

A client-Java scheduled job can be disabled from the Jobs perspective like any other job — and then quietly turns itself back on. ScheduledClassConsumer re-registers its jobs on every class load, and the registration hard-coded the flag:

job.setEnabled(true);

A class load happens at every server start and on every client-Java rebuild — the compile is one registry-wide batch, so publishing any .java anywhere reloads all client classes. So an operator who switches a misbehaving job off finds it firing again after the next restart or the next unrelated publish, with no way to keep it off short of deleting the code. As a side effect JobService.save sees a disabled→enabled transition and mails the job's subscribers the "job enabled" notification every time.

Two paths lost the choice, both fixed

  1. Registration overwrote the flag. It now reads enabled off the existing row and carries it over. A brand-new job still starts enabled, like every other artefact-defined one. No scheduling change is needed: JobsManager.scheduleJob already refuses to schedule a disabled job and deletes an existing Quartz job for it.
  2. A reload deleted the row. onClassLoaded called unregister(fqn) first, which unschedules and jobService.deletes the rows — so re-registration created a fresh row that defaulted to enabled, and preserving the flag in (1) alone would not have helped. It now reconciles: register what the class declares now, then drop only the previously registered names it no longer declares (a renamed or removed @Scheduled method). onClassUnloaded still removes everything.

.job artefacts were never affected

Worth recording, because it makes this a divergence rather than platform-wide behaviour — I initially assumed the opposite. SynchronizationProcessor.parseDefinitions only calls synchronizer.parse(...) for a definition in state NEW / MODIFIED / BROKEN / DELETED; an unchanged one is PARSED and goes through retrieve(...). Definition is persisted (DIRIGIBLE_DEFINITIONS), so an unchanged .job stays PARSED across a restart and JobSynchronizer.parseImpl — which rebuilds the row from the file — never runs. A .job keeps its Disable until the file itself changes.

Tests

ScheduledClassConsumerTest (new): a new job starts enabled; a disabled one stays disabled when registered again; a reload updates the row instead of deleting it; an unloaded class still loses its job. Both new tests fail on the code before this change (expected: <false> but was: <true>, and the delete/unschedule the reload used to perform). The whole engine-java suite is green (76), and formatter:validate plus the release javadoc pass are clean.

The engine-java guide's job bullet is rewritten in #6625 (the trigger-now fix), so I left it alone here to keep the two PRs conflict-free — the class javadoc in this PR carries the same note.

🤖 Generated with Claude Code

A client-Java scheduled job (`@Component implements JobHandler`, or a
`@Scheduled` method) could be disabled from the Jobs perspective, but the
toggle did not stick: `ScheduledClassConsumer` re-registers a job on every
class load - at every server start, and on every client-Java rebuild, which
is one registry-wide batch, so publishing any `.java` anywhere reloads all
of them - and the registration hard-coded `enabled = true`. The job quietly
started firing again, and `JobService.save` mailed the job's subscribers a
"job enabled" notification each time.

Two paths dropped the operator's choice, and both are fixed:

- registration overwrote `enabled` on the existing row; it now carries the
  stored flag over. A brand-new job still starts enabled, like every other
  artefact-defined one. No scheduling change is needed - `JobsManager`
  already refuses to schedule a disabled job and deletes its Quartz job.
- a reload unregistered everything first, DELETING the row, so what came
  back was a fresh one that defaulted to enabled. `onClassLoaded` now
  reconciles instead: it registers what the class declares now, then drops
  only the names it no longer declares (a renamed or removed `@Scheduled`
  method). `onClassUnloaded` still removes everything.

`.job` artefacts were never affected - `SynchronizationProcessor` only
re-parses a definition that is NEW/MODIFIED/BROKEN/DELETED, and `Definition`
is persisted, so an unchanged `.job` is never rebuilt from its file. This was
a client-Java divergence, not platform-wide behaviour.

ScheduledClassConsumerTest covers both halves; both new tests fail on the
code before this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev merged commit f3978fc into master Aug 9, 2026
10 checks passed
@delchev
delchev deleted the fix/preserve-client-java-job-enabled branch August 9, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Disabling a client-Java scheduled job does not stick — it re-enables on the next class load

1 participant