Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
pull_request:
branches: [master]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -92,7 +96,15 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Build wheels (with postgres)
if: runner.os == 'Linux'
uses: PyO3/maturin-action@v1
with:
args: --release --out dist --features postgres
manylinux: auto

- name: Build wheels
if: runner.os != 'Linux'
uses: PyO3/maturin-action@v1
with:
args: --release --out dist
Expand Down
47 changes: 47 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Deploy Docs

on:
push:
branches: [master]
paths:
- "docs/**"
- "zensical.toml"
- ".github/workflows/docs.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v5

- name: Install dependencies
run: uv sync --group docs

- name: Build docs
run: uv run zensical build

- uses: actions/upload-pages-artifact@v3
with:
path: site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
37 changes: 37 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
repos:
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --all --check
language: system
types: [rust]
pass_filenames: false

- id: cargo-clippy
name: cargo clippy
entry: cargo clippy --all-targets --all-features -- -D warnings
language: system
types: [rust]
pass_filenames: false

- id: ruff-check
name: ruff check
entry: uv run ruff check .
language: system
types: [python]
pass_filenames: false

- id: ruff-format
name: ruff format
entry: uv run ruff format --check .
language: system
types: [python]
pass_filenames: false

- id: mypy
name: mypy
entry: uv run mypy py_src/
language: system
types: [python]
pass_filenames: false
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ resolver = "2"
[workspace.dependencies]
diesel = { version = "2.2", features = ["sqlite", "r2d2", "returning_clauses_for_sqlite_3_35"] }
libsqlite3-sys = { version = "0.30", features = ["bundled"] }
pq-sys = { version = "0.6", features = ["bundled"] }
uuid = { version = "1", features = ["v7"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ chord([download.s(u) for u in urls], merge.s()).apply()
### Periodic Tasks

```python
@queue.periodic(cron="0 */6 * * *")
@queue.periodic(cron="0 0 */6 * * *")
def cleanup_temp_files():
...
```
Expand Down
5 changes: 5 additions & 0 deletions crates/taskito-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ name = "taskito-core"
version = "0.2.2"
edition = "2021"

[features]
default = []
postgres = ["diesel/postgres", "pq-sys"]

[dependencies]
diesel = { workspace = true }
libsqlite3-sys = { workspace = true }
pq-sys = { workspace = true, optional = true }
uuid = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions crates/taskito-core/src/job.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use uuid::Uuid;

use crate::storage::models::JobRow;
Expand Down Expand Up @@ -142,6 +142,6 @@ impl NewJob {
pub fn now_millis() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time went backwards")
.unwrap_or(Duration::ZERO)
.as_millis() as i64
}
9 changes: 6 additions & 3 deletions crates/taskito-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
pub mod job;
pub mod error;
pub mod storage;
pub mod scheduler;
pub mod job;
pub mod periodic;
pub mod resilience;
pub mod scheduler;
pub mod storage;

// Primary public API — the types most consumers need.
pub use error::QueueError;
pub use job::{Job, JobStatus, NewJob};
pub use scheduler::{Scheduler, SchedulerConfig};
#[cfg(feature = "postgres")]
pub use storage::postgres::PostgresStorage;
pub use storage::sqlite::SqliteStorage;
pub use storage::Storage;
pub use storage::StorageBackend;
8 changes: 3 additions & 5 deletions crates/taskito-core/src/periodic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ use crate::error::{QueueError, Result};
/// Compute the next run time (in UNIX milliseconds) for a cron expression,
/// starting from `after_ms` (also UNIX milliseconds).
pub fn next_cron_time(cron_expr: &str, after_ms: i64) -> Result<i64> {
let schedule = Schedule::from_str(cron_expr).map_err(|e| {
QueueError::Config(format!("invalid cron expression '{cron_expr}': {e}"))
})?;
let schedule = Schedule::from_str(cron_expr)
.map_err(|e| QueueError::Config(format!("invalid cron expression '{cron_expr}': {e}")))?;

let after_dt = chrono::DateTime::from_timestamp_millis(after_ms)
.unwrap_or_else(Utc::now);
let after_dt = chrono::DateTime::from_timestamp_millis(after_ms).unwrap_or_else(Utc::now);

let next = schedule
.after(&after_dt)
Expand Down
6 changes: 3 additions & 3 deletions crates/taskito-core/src/resilience/circuit_breaker.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::error::Result;
use crate::job::now_millis;
use crate::storage::models::CircuitBreakerRow;
use crate::storage::sqlite::SqliteStorage;
use crate::storage::{Storage, StorageBackend};

/// Circuit breaker states.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -40,11 +40,11 @@ pub struct CircuitBreakerConfig {

/// Circuit breaker manager backed by SQLite.
pub struct CircuitBreaker {
storage: SqliteStorage,
storage: StorageBackend,
}

impl CircuitBreaker {
pub fn new(storage: SqliteStorage) -> Self {
pub fn new(storage: StorageBackend) -> Self {
Self { storage }
}

Expand Down
6 changes: 3 additions & 3 deletions crates/taskito-core/src/resilience/dlq.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::error::Result;
use crate::job::Job;
use crate::storage::sqlite::{SqliteStorage, DeadJob};
use crate::storage::{DeadJob, Storage, StorageBackend};

/// Dead letter queue manager.
pub struct DeadLetterQueue {
storage: SqliteStorage,
storage: StorageBackend,
}

impl DeadLetterQueue {
pub fn new(storage: SqliteStorage) -> Self {
pub fn new(storage: StorageBackend) -> Self {
Self { storage }
}

Expand Down
15 changes: 9 additions & 6 deletions crates/taskito-core/src/resilience/rate_limiter.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::error::Result;
use crate::storage::sqlite::SqliteStorage;
use crate::storage::{Storage, StorageBackend};

/// Token bucket rate limiter backed by SQLite for persistence.
pub struct RateLimiter {
storage: SqliteStorage,
storage: StorageBackend,
}

/// Parsed rate limit configuration (e.g., "100/m" → 100 tokens, refill 1.667/s).
Expand Down Expand Up @@ -37,7 +37,7 @@ impl RateLimitConfig {
}

impl RateLimiter {
pub fn new(storage: SqliteStorage) -> Self {
pub fn new(storage: StorageBackend) -> Self {
Self { storage }
}

Expand Down Expand Up @@ -71,7 +71,8 @@ mod tests {

#[test]
fn test_token_bucket() {
let storage = SqliteStorage::in_memory().unwrap();
let storage =
StorageBackend::Sqlite(crate::storage::sqlite::SqliteStorage::in_memory().unwrap());
let limiter = RateLimiter::new(storage);
let config = RateLimitConfig {
max_tokens: 3.0,
Expand All @@ -89,13 +90,15 @@ mod tests {

#[test]
fn test_concurrent_token_acquisition() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::Barrier;

let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("rate_limit_test.db");
let storage = SqliteStorage::new(db_path.to_str().unwrap()).unwrap();
let storage = StorageBackend::Sqlite(
crate::storage::sqlite::SqliteStorage::new(db_path.to_str().unwrap()).unwrap(),
);
let limiter = Arc::new(RateLimiter::new(storage));
let config = RateLimitConfig {
max_tokens: 10.0,
Expand Down
4 changes: 2 additions & 2 deletions crates/taskito-core/src/resilience/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_retries: 3,
base_delay_ms: 1_000, // 1 second
max_delay_ms: 300_000, // 5 minutes
base_delay_ms: 1_000, // 1 second
max_delay_ms: 300_000, // 5 minutes
}
}
}
Expand Down
Loading