foxtive-cron 0.5.0

Foxtive Cron
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# 🦊 Foxtive Cron

A production-ready, asynchronous cron-based job scheduler for Rust powered by Tokio.

Schedule async tasks with cron expressions, manage them dynamically at runtime, control concurrency, handle failures with retry policies, and persist state - all with type-safe guarantees and comprehensive observability.

## Features

### Cron Expression Builder (NEW in 0.5.0)
- **Fluent builder API** - Build cron expressions programmatically with type safety
- **Type-safe enums** - `Month` and `Weekday` enums prevent invalid values
- **Field composition** - Intervals, ranges, lists, and single values for all fields
- **Common presets** - `hourly()`, `daily()`, `weekly()`, `monthly()` shortcuts
- **Timezone support** - Schedule jobs in any timezone via `chrono-tz`
- **Blackout dates** - Exclude specific dates (holidays, maintenance windows)
- **Execution jitter** - Add random delays to prevent thundering herd problems
- **Compile-time validation** - Invalid expressions caught at build time

### Core Scheduling
- Standard 7-field cron expressions (with seconds and year support)
- **Validated at registration time** - no silent runtime failures
- Timezone support via `chrono-tz` (schedule in local time, not just UTC)
- One-time jobs (run once at specific DateTime)
- Delayed start (recurring jobs with initial delay)
- Per-job priorities (higher priority executes first when concurrent)

### Dynamic Job Management
- Add jobs at runtime via `add_job()`, `add_job_fn()`, `add_blocking_job_fn()`
- Remove jobs at runtime via `remove_job(id)` - stops execution and cleans up resources
- Trigger jobs immediately via `trigger_job(id)` without affecting schedule
- Introspection API via `list_job_ids()` to see all scheduled jobs
- Internal job registry for O(1) lookup by ID

### Advanced Execution Control
- **Global concurrency limits** - prevent resource exhaustion
- **Per-job concurrency limits** - fine-grained control
- **Execution timeouts** - automatically terminate long-running jobs
- **Graceful shutdown** - controlled termination with cleanup
- Jobs run concurrently in independent `tokio::spawn` tasks

### Reliability & Resilience
- **Misfire policies**: Skip, FireOnce, FireAll (handle missed executions)
- **Retry strategies**: Fixed interval, Exponential backoff (with overflow protection)
- **Persistence layer** via `JobStore` trait
- **In-memory store** included (`InMemoryJobStore`)
- State tracking (last run, success/failure counts)

### Observability
- **Event hooks** - listen to lifecycle events (Started, Finished, Failed, Retrying)
- **Metrics integration** - counters and histograms via `MetricsExporter` trait
- **Structured logging** via `tracing` crate
- Health checks and monitoring support

## Installation

```toml
[dependencies]
foxtive-cron = "0.4"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
async-trait = "0.1"
```

### Optional Features

- `tokio-macros` - Enable tokio macros for examples

## Quick Start

### Using the Cron Expression Builder (Recommended)

```rust
use foxtive_cron::builder::{CronExpression, Weekday};
use chrono_tz::US::Eastern;
use std::time::Duration;

// Build a cron expression using the fluent API
let schedule = CronExpression::builder()
    .weekdays_only()
    .hours_range(9, 17)
    .minutes_interval(30)
    .with_timezone(Eastern)
    .with_jitter(Duration::from_secs(30))
    .build();

println!("Generated expression: {}", schedule);
// Output: "0 */30 9-17 * * 1-5 *"
```

### Basic async job

```rust
use foxtive_cron::Cron;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut cron = Cron::new();

    // Run every 5 seconds
    cron.add_job_fn("ping", "Ping Service", "*/5 * * * * * *", || async {
        println!("Pinging at {}", chrono::Utc::now());
        Ok(())
    })?;

    // Run scheduler in background
    tokio::spawn(async move { cron.run().await });

    // Wait for shutdown signal
    tokio::signal::ctrl_c().await?;
    Ok(())
}
```

### Blocking job (CPU-intensive)

```rust
cron.add_blocking_job_fn("backup", "Backup", "0 0 * * * * *", || {
    std::fs::write("/var/backup/snapshot", "data")?;
    Ok(())
})?;
```

Blocking functions run inside `tokio::task::spawn_blocking` so they never block the async runtime.

### With concurrency limit and event listener

```rust
use foxtive_cron::{Cron, contracts::{JobEventListener, JobEvent}};
use std::sync::Arc;

struct MyListener;

#[async_trait::async_trait]
impl JobEventListener for MyListener {
    async fn on_event(&self, event: JobEvent) {
        match event {
            JobEvent::Started { id, name } => {
                println!("Job {} started", name);
            }
            JobEvent::Finished { id, name, duration } => {
                println!("Job {} completed in {:?}", name, duration);
            }
            JobEvent::Failed { id, name, error } => {
                eprintln!("Job {} failed: {}", name, error);
            }
            _ => {}
        }
    }
}

let mut cron = Cron::builder()
    .with_global_concurrency_limit(5)  // Max 5 concurrent jobs
    .with_listener(Arc::new(MyListener))
    .build();

cron.add_job_fn("job1", "Job 1", "*/10 * * * * * *", || async {
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
    Ok(())
})?;
```

## Custom Jobs via `JobContract`

For full control, implement `JobContract` on your struct:

```rust
use foxtive_cron::{CronResult, Cron};
use foxtive_cron::contracts::{JobContract, ValidatedSchedule, RetryPolicy, MisfirePolicy};
use async_trait::async_trait;
use std::borrow::Cow;
use std::sync::Arc;

struct DatabaseCleanupJob {
    schedule: ValidatedSchedule,
}

impl DatabaseCleanupJob {
    fn new() -> CronResult<Self> {
        Ok(Self {
            schedule: ValidatedSchedule::parse("0 0 2 * * * *")?,  // Daily at 2 AM
        })
    }
}

#[async_trait]
impl JobContract for DatabaseCleanupJob {
    fn id(&self) -> Cow<'_, str> { Cow::Borrowed("db-cleanup") }
    fn name(&self) -> Cow<'_, str> { Cow::Borrowed("Database Cleanup") }
    fn schedule(&self) -> &ValidatedSchedule { &self.schedule }
    
    // Optional: configure retry policy
    fn retry_policy(&self) -> RetryPolicy {
        RetryPolicy::Exponential {
            initial_interval: std::time::Duration::from_secs(5),
            max_interval: std::time::Duration::from_secs(60),
            max_attempts: 3,
        }
    }
    
    // Optional: configure misfire policy
    fn misfire_policy(&self) -> MisfirePolicy {
        MisfirePolicy::Skip
    }

    async fn run(&self) -> CronResult<()> {
        println!("Cleaning up database at {}", chrono::Utc::now());
        // Your cleanup logic here
        Ok(())
    }

    // Optional lifecycle hooks:
    async fn on_start(&self) {
        tracing::info!("Starting database cleanup");
    }
    
    async fn on_complete(&self) {
        tracing::info!("Database cleanup completed successfully");
    }
    
    async fn on_error(&self, err: &anyhow::Error) {
        tracing::error!("Database cleanup failed: {}", err);
    }
}

#[tokio::main]
async fn main() -> CronResult<()> {
    let mut cron = Cron::new();
    cron.add_job(Arc::new(DatabaseCleanupJob::new()?))?;

    tokio::spawn(async move { cron.run().await });
    tokio::signal::ctrl_c().await?;
    Ok(())
}
```

## Advanced Features

### Retry Policies

```rust
use foxtive_cron::contracts::RetryPolicy;
use std::time::Duration;

// Fixed retry: retry every 5 seconds, up to 3 times
fn retry_policy() -> RetryPolicy {
    RetryPolicy::Fixed {
        interval: Duration::from_secs(5),
        max_attempts: 3,
    }
}

// Exponential backoff: 2s -> 4s -> 8s -> ... max 60s
fn exponential_backoff() -> RetryPolicy {
    RetryPolicy::Exponential {
        initial_interval: Duration::from_secs(2),
        max_interval: Duration::from_secs(60),
        max_attempts: 10,
    }
}
```

### Misfire Handling

```rust
use foxtive_cron::contracts::MisfirePolicy;

// Skip missed runs if scheduler was down
MisfirePolicy::Skip

// Run once ASAP, then resume normal schedule
MisfirePolicy::FireOnce

// Run all missed executions
MisfirePolicy::FireAll
```

### Timezone Support

```rust
use chrono_tz::America::New_York;

// Schedule in local time, not UTC
cron.add_job_fn(
    "daily-report",
    "Daily Report",
    "0 30 9 * * * *",  // 9:30 AM
).timezone(New_York)?;  // Runs at 9:30 AM Eastern Time
```

### Builder API with Advanced Features

```rust
use foxtive_cron::builder::{CronExpression, Month, Weekday};
use chrono::NaiveDate;
use chrono_tz::Europe::London;
use std::time::Duration;

// Complex schedule with multiple features
let schedule = CronExpression::builder()
    .weekdays_only()                          // Monday-Friday only
    .hours_range(9, 17)                      // Business hours
    .minutes_interval(30)                    // Every 30 minutes
    .with_timezone(London)                   // London timezone
    .with_jitter(Duration::from_secs(60))   // Β±60s random delay
    .exclude_date(NaiveDate::from_ymd_opt(2024, 12, 25).unwrap()) // Christmas
    .exclude_date(NaiveDate::from_ymd_opt(2024, 12, 26).unwrap()) // Boxing Day
    .build();

println!("Schedule: {}", schedule);
```

### Common Builder Patterns

```rust
// Daily backup at 2:30 AM
let daily_backup = CronExpression::builder()
    .daily()
    .hour(2)
    .minute(30)
    .build();

// Health check every 30 seconds
let health_check = CronExpression::builder()
    .seconds_interval(30)
    .build();

// Monthly report on 1st at 9 AM
let monthly_report = CronExpression::builder()
    .monthly()
    .hour(9)
    .build();

// Multiple specific times per day
let digest_emails = CronExpression::builder()
    .daily()
    .hours_list(&[8, 12, 18])  // 8 AM, 12 PM, 6 PM
    .minute(0)
    .build();
```

### Graceful Shutdown

```rust
let mut cron = Cron::new();
// ... add jobs ...

// Gracefully shutdown - waits for running jobs to complete
cron.shutdown().await;
```

## Cron Expression Format

Foxtive Cron uses a **7-field** cron format:

```
sec  min  hour  day  month  weekday  year
```

| Example              | Meaning                           |
|----------------------|-----------------------------------|
| `*/10 * * * * * *`   | Every 10 seconds                  |
| `0 * * * * * *`      | Every minute                      |
| `0 0 * * * * *`      | Every hour                        |
| `0 30 9 * * * *`     | Every day at 09:30:00             |
| `0 0 0 1 * * *`      | First day of every month          |
| `0 0 0 * * MON-FRI *`| Monday to Friday at midnight      |

Expressions are validated via `ValidatedSchedule::parse()` at registration time.
Invalid expressions return `Err` immediately rather than failing silently at runtime.

## Thread Safety & Concurrency

- Jobs execute in independent `tokio::spawn` tasks - slow jobs never block others
- Multiple jobs due at the same tick fire concurrently in the same iteration
- Jobs wrapped in `Arc<dyn JobContract>` must be `Send + Sync`
- Global concurrency limits prevent resource exhaustion
- Per-job concurrency limits for fine-grained control
- Priority-based execution when multiple jobs are due simultaneously

## Logging & Observability

Job execution is traced via the `tracing` crate:

- `INFO` level: Job start and successful completion
- `ERROR` level: Job failures (scheduler continues running)
- `WARN` level: Misfires, retries, and warnings

Integrate with any `tracing`-compatible subscriber:

```rust
use tracing_subscriber;

tracing_subscriber::fmt()
    .with_max_level(tracing::Level::INFO)
    .init();
```

### Custom Event Listeners

Implement `JobEventListener` to receive real-time notifications of all job lifecycle events.

## Examples

See the [`examples/`](examples/) directory for complete working examples:

### Builder API Examples (NEW)
- **builder_basic.rs** - Comprehensive cron expression builder demonstrations
- **builder_real_world_scenarios.rs** - Production-ready scheduling patterns
- **builder_timezone_advanced.rs** - Advanced timezone-aware scheduling
- **builder_blackout_and_jitter.rs** - Blackout dates and jitter strategies
- **builder_complex_composition.rs** - Complex field composition patterns
- **builder_devops_automation.rs** - DevOps and infrastructure automation
- **builder_edge_cases.rs** - Edge cases and validation scenarios

### Core Features
- **basic.rs** - Simple job scheduling
- **advanced.rs** - Custom jobs with full lifecycle hooks
- **concurrency.rs** - Managing concurrent job execution
- **persistence.rs** - Persisting job state with InMemoryJobStore
- **priority.rs** - Job priority handling
- **timezone.rs** - Scheduling in different timezones

Run examples with:

```bash
cargo run --example builder_basic
cargo run --example builder_real_world_scenarios --features tokio-macros
cargo run --example builder_timezone_advanced
cargo run --example builder_blackout_and_jitter
cargo run --example builder_complex_composition
cargo run --example builder_devops_automation
cargo run --example builder_edge_cases
cargo run --example basic --features tokio-macros
cargo run --example advanced --features tokio-macros
# ... etc
```

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Cron Scheduler                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Job Registry β”‚  β”‚ Priority Queueβ”‚  β”‚Semaphoresβ”‚  β”‚
β”‚  β”‚  (HashMap)   β”‚  β”‚ (BinaryHeap) β”‚  β”‚          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚         Job Execution Engine                 β”‚  β”‚
β”‚  β”‚  β€’ Concurrency Control                       β”‚  β”‚
β”‚  β”‚  β€’ Retry Logic                               β”‚  β”‚
β”‚  β”‚  β€’ Misfire Handling                          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚Event Listenersβ”‚ β”‚Metrics Exportβ”‚ β”‚Job Store β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

Key components:
- **Job Registry**: O(1) lookup by job ID
- **Priority Queue**: Efficient scheduling via BinaryHeap (min-heap by next_run)
- **Semaphores**: Concurrency control (global and per-job)
- **Event System**: Real-time lifecycle notifications
- **Metrics**: Counters and histograms for monitoring
- **Persistence**: Pluggable storage backends

## Contributing

Contributions, bug reports, and feature requests are welcome!

To contribute:
1. Fork the repository
2. Create a feature branch
3. Make your changes with tests
4. Ensure all tests pass: `cargo test`
5. Check for clippy warnings: `cargo clippy`
6. Submit a pull request

See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.

## License

MIT License