Skip to content

Conversation

@christinoleo
Copy link

@christinoleo christinoleo commented Dec 27, 2025

Summary

Enables sub-minute scheduling by adding SecondOptional to the cron parser. This allows schedules like */5 * * * * * to run every 5 seconds.

Changes

  • Add cron.SecondOptional flag to parser in spec/schedule.go
  • Update scheduler tick precision from minute to second
  • Update NextTick to advance by seconds instead of minutes
  • Update dagrunjob to use second-level truncation

Motivation

As discussed in #676, the underlying robfig/cron library already supports seconds parsing via the SecondOptional flag. This PR enables that functionality.

Usage

Standard 5-field cron expressions continue to work as before:

schedule: "*/5 * * * *"  # Every 5 minutes

With this change, 6-field expressions with seconds are now supported:

schedule: "*/5 * * * * *"  # Every 5 seconds

Fixes #676

Summary by CodeRabbit

  • New Features

    • Scheduling granularity increased from minute-based to second-based, enabling more frequent task execution. Cron expressions now support optional seconds fields for enhanced scheduling precision and control.
  • Chores

    • Updated configuration files.

✏️ Tip: You can customize this high-level summary in your review settings.

Enables sub-minute scheduling by adding SecondOptional to the cron parser.
This allows schedules like '*/5 * * * * *' to run every 5 seconds.

Changes:
- Add cron.SecondOptional flag to parser in spec/schedule.go
- Update scheduler tick precision from minute to second
- Update NextTick to advance by seconds instead of minutes
- Update dagrunjob to use second-level truncation

Fixes dagu-org#676
@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

📝 Walkthrough

Walkthrough

The changes enable second-level precision in cron expressions by adding cron.SecondOptional to the cron parser configuration and updating scheduler tick granularity from minute-based to second-based alignment, allowing jobs to be scheduled at sub-minute intervals.

Changes

Cohort / File(s) Summary
Cron Parser Configuration
internal/core/spec/schedule.go, internal/service/scheduler/scheduler_test.go
Added cron.SecondOptional flag to enable optional seconds field parsing in cron expressions; test initialization updated to match production parser configuration.
Scheduler Tick Granularity
internal/service/scheduler/scheduler.go
Changed tick alignment from minute-based (truncate to minute boundary) to second-based (truncate to second boundary); NextTick calculation now advances by 1 second instead of 60 seconds.
Job Timing Precision
internal/service/scheduler/dagrunjob.go
Modified latestStartedAt normalization from minute precision to second precision, affecting timing comparisons in Ready method.
Metadata
.gitignore
Added "dagu" to Go-related ignore rules.

Poem

🐰 A second-hand sprint through the cron-parser hall,
Where minutes once marched, now seconds enthrall!
Five-second health checks now dance in the queue,
Dagu keeps the beat, precise and so true! ✨

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature being added: optional seconds field support in cron schedule parsing.
Linked Issues check ✅ Passed The PR fulfills issue #676's requirement by adding SecondOptional flag to the cron parser, enabling sub-minute scheduling with second-level precision as requested.
Out of Scope Changes check ✅ Passed All changes are scoped to enabling seconds-precision scheduling: .gitignore, cron parser configuration, and scheduler tick calculations. No unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 140afba and bd9a3c6.

📒 Files selected for processing (5)
  • .gitignore
  • internal/core/spec/schedule.go
  • internal/service/scheduler/dagrunjob.go
  • internal/service/scheduler/scheduler.go
  • internal/service/scheduler/scheduler_test.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/core/spec/schedule.go
  • internal/service/scheduler/scheduler.go
  • internal/service/scheduler/scheduler_test.go
  • internal/service/scheduler/dagrunjob.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/service/scheduler/scheduler_test.go
🔇 Additional comments (7)
.gitignore (1)

41-41: LGTM! Standard build artifact ignore.

Adding the dagu binary to .gitignore prevents committing the built executable.

internal/service/scheduler/dagrunjob.go (1)

96-96: LGTM! Necessary precision adjustment.

Changing from time.Minute to time.Second truncation aligns with the scheduler's new second-level tick granularity, ensuring correct comparisons for sub-minute schedules.

internal/service/scheduler/scheduler_test.go (2)

84-84: LGTM! Test parser aligned with production.

Correctly updated to include cron.SecondOptional flag, matching the production parser configuration.


195-195: LGTM! Test parser aligned with production.

Correctly updated to include cron.SecondOptional flag, matching the production parser configuration.

internal/service/scheduler/scheduler.go (2)

309-311: LGTM! Correct second-based tick advancement.

The NextTick implementation now correctly advances by one second instead of one minute, aligning with the second-level scheduling granularity introduced by cron.SecondOptional.


283-283: Verify performance impact of second-level tick granularity.

Changing from time.Minute to time.Second truncation means the scheduler now ticks 60 times more frequently. This is necessary for sub-minute scheduling but increases CPU usage.

The impact should be minimal when no jobs are scheduled for a given second, but for deployments with many DAGs, the increased frequency of invokeJobs and entryReader.Next calls could be noticeable.

Consider profiling the scheduler under production-like load to verify the performance impact is acceptable:

#!/bin/bash
# Compare CPU usage before/after this change with a representative DAG count
echo "Profile the scheduler with multiple DAGs scheduled at different intervals:"
echo "1. Set up test environment with 100+ DAGs (mix of minute and second schedules)"
echo "2. Monitor CPU usage over 5-10 minutes"
echo "3. Compare against baseline with minute-only schedules"
echo ""
echo "Use Go's pprof if available:"
echo "  go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30"
internal/core/spec/schedule.go (1)

10-12: The cron.SecondOptional flag correctly enables optional seconds parsing.

The addition of cron.SecondOptional to the parser flags allows both 5-field (minute precision) and 6-field (second precision) cron expressions while maintaining backward compatibility. This is the standard approach in robfig/cron v3.0.1 for supporting sub-minute scheduling as intended by the PR.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@christinoleo christinoleo marked this pull request as draft December 27, 2025 11:55
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/service/scheduler/scheduler_test.go (1)

71-80: Critical: Test expectations don't match new second-based behavior.

The NextTick test expects minute-granularity behavior (advancing from 01:00:50 to 01:01:00), but the implementation now uses second-granularity (NextTick returns now.Add(time.Second).Truncate(time.Second)), which would advance to 01:00:51.

This test should be failing unless the expected result is updated to match the new second-based tick advancement.

🔎 Proposed fix
 	t.Run("NextTick", func(t *testing.T) {
 		now := time.Date(2020, 1, 1, 1, 0, 50, 0, time.UTC)
 
 		th := test.SetupScheduler(t)
 		schedulerInstance, err := scheduler.New(th.Config, &mockJobManager{}, th.DAGRunMgr, th.DAGRunStore, th.QueueStore, th.ProcStore, th.ServiceRegistry, th.CoordinatorCli)
 		require.NoError(t, err)
 
 		next := schedulerInstance.NextTick(now)
-		require.Equal(t, time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC), next)
+		require.Equal(t, time.Date(2020, 1, 1, 1, 0, 51, 0, time.UTC), next)
 	})
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 140afba and bd9a3c6.

📒 Files selected for processing (5)
  • .gitignore
  • internal/core/spec/schedule.go
  • internal/service/scheduler/dagrunjob.go
  • internal/service/scheduler/scheduler.go
  • internal/service/scheduler/scheduler_test.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/core/spec/schedule.go
  • internal/service/scheduler/scheduler.go
  • internal/service/scheduler/scheduler_test.go
  • internal/service/scheduler/dagrunjob.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/service/scheduler/scheduler_test.go
🔇 Additional comments (7)
.gitignore (1)

41-41: LGTM! Standard build artifact ignore.

Adding the dagu binary to .gitignore prevents committing the built executable.

internal/service/scheduler/dagrunjob.go (1)

96-96: LGTM! Necessary precision adjustment.

Changing from time.Minute to time.Second truncation aligns with the scheduler's new second-level tick granularity, ensuring correct comparisons for sub-minute schedules.

internal/service/scheduler/scheduler_test.go (2)

84-84: LGTM! Test parser aligned with production.

Correctly updated to include cron.SecondOptional flag, matching the production parser configuration.


195-195: LGTM! Test parser aligned with production.

Correctly updated to include cron.SecondOptional flag, matching the production parser configuration.

internal/service/scheduler/scheduler.go (2)

309-311: LGTM! Correct second-based tick advancement.

The NextTick implementation now correctly advances by one second instead of one minute, aligning with the second-level scheduling granularity introduced by cron.SecondOptional.


283-283: Verify performance impact of second-level tick granularity.

Changing from time.Minute to time.Second truncation means the scheduler now ticks 60 times more frequently. This is necessary for sub-minute scheduling but increases CPU usage.

The impact should be minimal when no jobs are scheduled for a given second, but for deployments with many DAGs, the increased frequency of invokeJobs and entryReader.Next calls could be noticeable.

Consider profiling the scheduler under production-like load to verify the performance impact is acceptable:

#!/bin/bash
# Compare CPU usage before/after this change with a representative DAG count
echo "Profile the scheduler with multiple DAGs scheduled at different intervals:"
echo "1. Set up test environment with 100+ DAGs (mix of minute and second schedules)"
echo "2. Monitor CPU usage over 5-10 minutes"
echo "3. Compare against baseline with minute-only schedules"
echo ""
echo "Use Go's pprof if available:"
echo "  go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30"
internal/core/spec/schedule.go (1)

10-12: The cron.SecondOptional flag correctly enables optional seconds parsing.

The addition of cron.SecondOptional to the parser flags allows both 5-field (minute precision) and 6-field (second precision) cron expressions while maintaining backward compatibility. This is the standard approach in robfig/cron v3.0.1 for supporting sub-minute scheduling as intended by the PR.

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.

allow usage of seconds in cron

1 participant