fix(jobs): a disabled client-Java job stays disabled (#6626) - #6627
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
ScheduledClassConsumerre-registers its jobs on every class load, and the registration hard-coded the flag:A class load happens at every server start and on every client-Java rebuild — the compile is one registry-wide batch, so publishing any
.javaanywhere 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 effectJobService.savesees a disabled→enabled transition and mails the job's subscribers the "job enabled" notification every time.Two paths lost the choice, both fixed
enabledoff 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.scheduleJobalready refuses to schedule a disabled job and deletes an existing Quartz job for it.onClassLoadedcalledunregister(fqn)first, which unschedules andjobService.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@Scheduledmethod).onClassUnloadedstill removes everything..jobartefacts were never affectedWorth recording, because it makes this a divergence rather than platform-wide behaviour — I initially assumed the opposite.
SynchronizationProcessor.parseDefinitionsonly callssynchronizer.parse(...)for a definition in stateNEW/MODIFIED/BROKEN/DELETED; an unchanged one isPARSEDand goes throughretrieve(...).Definitionis persisted (DIRIGIBLE_DEFINITIONS), so an unchanged.jobstaysPARSEDacross a restart andJobSynchronizer.parseImpl— which rebuilds the row from the file — never runs. A.jobkeeps 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 wholeengine-javasuite is green (76), andformatter:validateplus 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