Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Scheduler Feature for Running Cron Jobs #735

Merged
merged 21 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci-generators.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ jobs:
REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}}
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres_test

- name: scheduler
run: cargo run -- generate scheduler
Copy link
Contributor

Choose a reason for hiding this comment

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

i would add some more value for example

$ cargo run -- generate job send_emails --cron '* * * * ..'

Added `send_emails` to scheduler jobs
Total 12 jobs in `config/development.yaml` under `scheduler:`.

This will upsert a schedule record.

You can also experiment with english-to-cron libraries

$ cargo run -- generate job send_emails --at 'every sunday at 1pm'

Cron schedule: 0 * * * 1 *
Added `send_emails` to jobs
Total 12 jobs in `config/development.yaml` under `scheduler:`.

Notice we generate a job
If we say scheduler it is the thing that runs jobs

Its also a good opportunity to centralize and sharpen our concepts and terminology:

  • task is a one time, you run it any way you like
  • job is a repeat on a schedule, run by the scheduler
  • worker is something that performs a background job from a queue

working-directory: ./examples/demo
env:
REDIS_URL: redis://localhost:${{job.services.redis.ports[6379]}}
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres_test

- name: docker deployment
run: cargo run -- generate deployment
working-directory: ./examples/demo
Expand Down
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ tracing = "0.1.40"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
tracing-appender = "0.2.3"

# cli/migrations
duct = "0.13.6"
duct = { version = "0.13.6" }
duct_sh = { version = "0.13.7" }

tower-http = { workspace = true }
byte-unit = "4.0.19"
Expand Down Expand Up @@ -123,6 +123,9 @@ object_store = { version = "0.10.2", default-features = false }
# cache
moka = { version = "0.12.7", features = ["sync"], optional = true }

# Scheduler
tokio-cron-scheduler = { version = "0.11.0", features = ["signal"] }

[workspace.dependencies]
async-trait = { version = "0.1.74" }
axum = { version = "0.7.5", features = ["macros"] }
Expand Down
18 changes: 9 additions & 9 deletions docs-site/content/docs/getting-started/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ The one-person framework for Rust
Usage: blo-cli [OPTIONS] <COMMAND>

Commands:
start Start an app
db Perform DB operations
routes Describe all application endpoints
task Run a custom task
generate code generation creates a set of files and code templates based on a predefined set of rules
doctor Validate and diagnose configurations
version Display the app version
help Print this message or the help of the given subcommand(s)
start Start an app
db Perform DB operations
routes Describe all application endpoints
task Run a custom task
scheduler Run the scheduler
generate code generation creates a set of files and code templates based on a predefined set of rules
doctor Validate and diagnose configurations
version Display the app version
help Print this message or the help of the given subcommand(s)

Options:
-e, --environment <ENVIRONMENT> Specify the environment [default: development]
-h, --help Print help
-V, --version Print version

```
<!-- </snip> -->

Expand Down
50 changes: 49 additions & 1 deletion examples/demo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions examples/demo/config/development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,35 @@ auth:
expiration: 604800 # 7 days
# </snip>

# Scheduler Jobs Configuration
# <snip id="configuration-scheduler">
scheduler:
# Location of shipping the command stdout and stderr.
output: stdout
# A list of jobs to be scheduled.
jobs:
# The name of the job.
- name: "Run command"
Copy link
Contributor

Choose a reason for hiding this comment

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

use a hashmap instead of vec, id instead of name

jobs:
   send_emails:
         ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agree.
changed: 19d2a0f

# The type of job.
shell:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it should be just:

run: "...command.."
shell: true | false

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: f85601b

command: "echo loco >> ./scheduler.txt"
# The cron expression defining the job's schedule.
cron: "*/1 * * * * *"
output: silent
tags:
Copy link
Contributor

Choose a reason for hiding this comment

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

can we do tags: ['base', 'infra'] in YAML example?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. 667ce38

- base
- infra

- name: "Run command"
task:
name: "foo"
cron: "*/5 * * * * *"

- name: "list if users"
task:
name: "user_report"
cron: "*/7 * * * * *"
tags:
- base
- users
# </snip>
23 changes: 23 additions & 0 deletions examples/demo/config/scheduler.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
jobs:
- name: "add text"
shell:
command: "echo loco >> ./scheduler.txt"
cron: "*/1 * * * * *"
tags:
- base
- infra

- name: "Run command"
task:
name: "foo"
vars:
path: /tmp/scheduler.txt
cron: "*/5 * * * * *"

- name: "list if users"
task:
name: "user_report"
cron: "*/7 * * * * *"
tags:
- base
- users
23 changes: 23 additions & 0 deletions examples/demo/config/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,26 @@ auth:
secret: PqRwLF2rhHe8J22oBeHy
# Token expiration time in seconds
expiration: 604800 # 7 days

scheduler:
jobs:
- name: "Run command"
shell:
command: "echo loco >> ./scheduler.txt"
cron: "*/1 * * * * *"
tags:
- base
- infra

- name: "Run command"
task:
name: "foo"
cron: "*/5 * * * * *"

- name: "list if users"
task:
name: "user_report"
cron: "*/7 * * * * *"
tags:
- base
- users
18 changes: 10 additions & 8 deletions examples/demo/tests/cmd/cli.trycmd
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ The one-person framework for Rust
Usage: blo-cli [OPTIONS] <COMMAND>

Commands:
start Start an app
db Perform DB operations
routes Describe all application endpoints
task Run a custom task
generate code generation creates a set of files and code templates based on a predefined set of rules
doctor Validate and diagnose configurations
version Display the app version
help Print this message or the help of the given subcommand(s)
start Start an app
db Perform DB operations
routes Describe all application endpoints
task Run a custom task
scheduler Run the scheduler
generate code generation creates a set of files and code templates based on a predefined set of rules
doctor Validate and diagnose configurations
version Display the app version
help Print this message or the help of the given subcommand(s)

Options:
-e, --environment <ENVIRONMENT> Specify the environment [default: development]
Expand Down Expand Up @@ -59,6 +60,7 @@ Commands:
scaffold Generates a CRUD scaffold, model and controller
controller Generate a new controller with the given controller name, and test file
task Generate a Task based on the given name
scheduler Generate a scheduler jobs configuration template
worker Generate worker
mailer Generate mailer
deployment Generate a deployment infrastructure
Expand Down
19 changes: 19 additions & 0 deletions examples/demo/tests/cmd/scheduler.trycmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
```console
$ LOCO_ENV=test blo-cli scheduler --list
# job_name cron tags kind
1 Run command */1 * * * * * base, infra Shell { command: "echo loco >> ./scheduler.txt" }
2 Run command */5 * * * * * - Task { name: "foo", vars: None }
3 list if users */7 * * * * * base, users Task { name: "user_report", vars: None }


```

```console
$ LOCO_ENV=test blo-cli scheduler --config ./config/scheduler.yaml --list
# job_name cron tags kind
1 add text */1 * * * * * base, infra Shell { command: "echo loco >> ./scheduler.txt" }
Copy link
Contributor

Choose a reason for hiding this comment

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

there are some libraries that do "english to cron" and "cron to english"
might be good to add a "cron to english" description here, based on the cron expression that's saved in the configuration

2 Run command */5 * * * * * - Task { name: "foo", vars: Some({"path": "/tmp/scheduler.txt"}) }
3 list if users */7 * * * * * base, users Task { name: "user_report", vars: None }


```
18 changes: 18 additions & 0 deletions snipdoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,21 @@ snippets:
migrate-down-n-command:
content: cargo loco db down 2
path: ./snipdoc.yml
scheduler-generate-command:
content: cargo loco generate scheduler
path: ./snipdoc.yml
scheduler-list-command:
content: cargo loco scheduler --list
path: ./snipdoc.yml
scheduler-list-from-file-command:
content: cargo loco scheduler --path config/scheduler.yaml --list
path: ./snipdoc.yml
scheduler-list-from-env-setting-command:
content: LOCO_ENV=production cargo loco scheduler --list
path: ./snipdoc.yml
scheduler-run-job-by-name-command:
content: LOCO_ENV=production cargo loco scheduler --name 'JOB_NAME'
path: ./snipdoc.yml
scheduler-run-job-by-tag-command:
content: LOCO_ENV=production cargo loco scheduler --tag 'TAG'
path: ./snipdoc.yml
47 changes: 47 additions & 0 deletions src/boot.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! # Application Bootstrapping and Logic
//! This module contains functions and structures for bootstrapping and running
//! your application.
use std::path::PathBuf;

use axum::Router;
#[cfg(feature = "with-db")]
use sea_orm_migration::MigratorTrait;
Expand All @@ -18,6 +20,7 @@ use crate::{
errors::Error,
mailer::{EmailSender, MailerWorker},
redis,
scheduler::{self, Scheduler},
storage::{self, Storage},
task::{self, Tasks},
worker::{self, AppWorker, Pool, Processor, RedisConnectionManager},
Expand Down Expand Up @@ -121,6 +124,50 @@ pub async fn run_task<H: Hooks>(
Ok(())
}

/// Runs the scheduler with the given configuration and context. in case if list args is true
/// prints scheduler job configuration
///
/// This function initializes the scheduler, registers tasks through the provided [`Hooks`],
/// and executes the scheduler based on the specified configuration or context. The scheduler
/// continuously runs, managing and executing scheduled tasks until a signal is received to shut down.
/// Upon receiving this signal, the function gracefully shuts down all running tasks and exits safely.
///
/// # Errors
///
/// When running could not run the scheduler.
pub async fn run_scheduler<H: Hooks>(
app_context: &AppContext,
config: Option<&PathBuf>,
name: Option<String>,
tag: Option<String>,
list: bool,
) -> Result<()> {
let mut tasks = Tasks::default();
H::register_tasks(&mut tasks);

let task_span = tracing::span!(tracing::Level::DEBUG, "scheduler_jobs");
let _guard = task_span.enter();

let scheduler = match config {
Some(path) => Scheduler::from_config::<H>(path, &app_context.environment)?,
None => {
if let Some(config) = &app_context.config.scheduler {
Scheduler::new::<H>(config, &app_context.environment)?
} else {
return Err(Error::Scheduler(scheduler::Error::Empty));
}
}
};

let scheduler = scheduler.by_spec(&scheduler::Spec { name, tag });
if list {
println!("{scheduler}");
Ok(())
} else {
Ok(scheduler.run().await?)
}
}

/// Represents commands for handling database-related operations.
#[derive(Debug)]
pub enum RunDbCommand {
Expand Down
Loading
Loading