Skip to content

Commit 0dd9315

Browse files
authored
Add configurable enabled flag for scheduling tasks (helidon-io#10812) (helidon-io#10825)
* Add configurable enabled flag for scheduling tasks (helidon-io#10812) Adds support for disabling scheduled tasks via configuration, similar to Spring's @scheduled(enabled = false). Changes: - Add 'enabled' property to TaskConfigBlueprint (default: true) - Update CronTask to check enabled flag before scheduling - Update FixedRateTask to check enabled flag before scheduling - Create cancelled dummy future for disabled tasks to maintain compatibility with existing close() logic - Add tests for disabled Cron and FixedRate tasks Usage: Cron.builder() .expression("0 45 9 ? * *") .enabled(false) .build(); Or via configuration: my-task: enabled: false expression: "0 45 9 ? * *" * Simplify disabled task handling in scheduling Replace unnecessary scheduling and cancellation with CompletableFuture.completedFuture(null) for disabled tasks. Update future type from ScheduledFuture to Future for better flexibility. Ensure task manager cleanup always occurs in close() method. Based on the code review suggestion
1 parent 67f5aa3 commit 0dd9315

File tree

5 files changed

+90
-17
lines changed

5 files changed

+90
-17
lines changed

scheduling/src/main/java/io/helidon/scheduling/CronTask.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
import java.util.Locale;
2323
import java.util.Optional;
2424
import java.util.UUID;
25+
import java.util.concurrent.CompletableFuture;
26+
import java.util.concurrent.Future;
2527
import java.util.concurrent.ScheduledExecutorService;
26-
import java.util.concurrent.ScheduledFuture;
2728
import java.util.concurrent.TimeUnit;
2829
import java.util.concurrent.atomic.AtomicLong;
2930
import java.util.concurrent.locks.ReentrantLock;
@@ -53,7 +54,7 @@ class CronTask implements Cron {
5354
private ZonedDateTime lastNext = null;
5455

5556
private volatile boolean stopped;
56-
private volatile ScheduledFuture<?> future;
57+
private volatile Future<?> future;
5758

5859
CronTask(CronConfig config) {
5960
this.config = config;
@@ -68,7 +69,13 @@ class CronTask implements Cron {
6869
executionTime = ExecutionTime.forCron(cron);
6970

7071
config.taskManager().register(this);
71-
scheduleNext();
72+
73+
if (config.enabled()) {
74+
scheduleNext();
75+
} else {
76+
future = CompletableFuture.completedFuture(null);
77+
LOGGER.log(Level.INFO, "Task " + taskId + " is disabled and will not be scheduled");
78+
}
7279
}
7380

7481
@Override
@@ -128,8 +135,8 @@ public void close() {
128135
stopped = true;
129136
if (future != null) {
130137
future.cancel(false);
131-
config.taskManager().remove(this);
132138
}
139+
config.taskManager().remove(this);
133140
} finally {
134141
scheduleNextLock.unlock();
135142
}

scheduling/src/main/java/io/helidon/scheduling/FixedRateTask.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
import java.lang.System.Logger.Level;
2020
import java.time.Duration;
2121
import java.util.UUID;
22+
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.Future;
2224
import java.util.concurrent.ScheduledExecutorService;
23-
import java.util.concurrent.ScheduledFuture;
2425
import java.util.concurrent.TimeUnit;
2526
import java.util.concurrent.atomic.AtomicLong;
2627

@@ -33,7 +34,7 @@ class FixedRateTask implements FixedRate {
3334
private final Duration initialDelay;
3435
private final Duration interval;
3536
private final ScheduledConsumer<FixedRateInvocation> actualTask;
36-
private final ScheduledFuture<?> future;
37+
private final Future<?> future;
3738
private final FixedRateConfig config;
3839
private final String taskId;
3940

@@ -46,16 +47,21 @@ class FixedRateTask implements FixedRate {
4647
this.executorService = config.executor();
4748
this.taskId = config.id().orElseGet(() -> UUID.randomUUID().toString());
4849

49-
this.future = switch (config.delayType()) {
50-
case SINCE_PREVIOUS_START -> executorService.scheduleAtFixedRate(this::run,
51-
initialDelay.toMillis(),
52-
interval.toMillis(),
53-
TimeUnit.MILLISECONDS);
54-
case SINCE_PREVIOUS_END -> executorService.scheduleWithFixedDelay(this::run,
55-
initialDelay.toMillis(),
56-
interval.toMillis(),
57-
TimeUnit.MILLISECONDS);
58-
};
50+
if (config.enabled()) {
51+
this.future = switch (config.delayType()) {
52+
case SINCE_PREVIOUS_START -> executorService.scheduleAtFixedRate(this::run,
53+
initialDelay.toMillis(),
54+
interval.toMillis(),
55+
TimeUnit.MILLISECONDS);
56+
case SINCE_PREVIOUS_END -> executorService.scheduleWithFixedDelay(this::run,
57+
initialDelay.toMillis(),
58+
interval.toMillis(),
59+
TimeUnit.MILLISECONDS);
60+
};
61+
} else {
62+
this.future = CompletableFuture.completedFuture(null);
63+
LOGGER.log(Level.INFO, "Task " + taskId + " is disabled and will not be scheduled");
64+
}
5965

6066
config.taskManager().register(this);
6167
}
@@ -81,7 +87,9 @@ public ScheduledExecutorService executor() {
8187

8288
@Override
8389
public void close() {
84-
future.cancel(false);
90+
if (future != null) {
91+
future.cancel(false);
92+
}
8593
config.taskManager().remove(this);
8694
}
8795

scheduling/src/main/java/io/helidon/scheduling/TaskConfigBlueprint.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,14 @@ interface TaskConfigBlueprint {
4848
*/
4949
@Option.Configured
5050
Optional<String> id();
51+
52+
/**
53+
* Whether the task is enabled. If disabled, the task will not be scheduled.
54+
* Default value is {@code true}.
55+
*
56+
* @return true if the task should be scheduled
57+
*/
58+
@Option.Configured
59+
@Option.DefaultBoolean(true)
60+
boolean enabled();
5161
}

scheduling/src/test/java/io/helidon/scheduling/CronSchedulingTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,4 +305,31 @@ void cronMissingTask() {
305305
}
306306
}
307307

308+
@Test
309+
void cronDisabledProgrammatically() throws InterruptedException {
310+
ScheduledExecutorService executorService = ScheduledThreadPoolSupplier.create().get();
311+
IntervalMeter meter = new IntervalMeter();
312+
Cron cron = null;
313+
314+
try {
315+
cron = Cron.builder()
316+
.id("disabledCronTest")
317+
.executor(executorService)
318+
.expression("* * * * * ? *") // Every second
319+
.enabled(false) // Disabled
320+
.task(cronInvocation -> meter.start().end())
321+
.build();
322+
323+
// Wait and verify task never executed
324+
Thread.sleep(3000);
325+
assertThat(meter.size(), Matchers.equalTo(0));
326+
assertThat(taskManager.tasks(), hasItem(cron));
327+
} finally {
328+
if (cron != null) {
329+
cron.close();
330+
}
331+
executorService.shutdownNow();
332+
}
333+
}
334+
308335
}

scheduling/src/test/java/io/helidon/scheduling/FixedRateSchedulingTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,25 @@ void fixedRateMissingTask() {
307307
executorService.shutdownNow();
308308
}
309309
}
310+
311+
@Test
312+
void fixedRateDisabled() throws InterruptedException {
313+
ScheduledExecutorService executorService = ScheduledThreadPoolSupplier.create().get();
314+
IntervalMeter meter = new IntervalMeter();
315+
316+
try {
317+
FixedRate.builder()
318+
.executor(executorService)
319+
.interval(Duration.ofSeconds(1))
320+
.enabled(false) // Disabled
321+
.task(inv -> meter.start().end())
322+
.build();
323+
324+
// Wait and verify task never executed
325+
Thread.sleep(3000);
326+
assertThat(meter.size(), Matchers.equalTo(0));
327+
} finally {
328+
executorService.shutdownNow();
329+
}
330+
}
310331
}

0 commit comments

Comments
 (0)