fad4006f60
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
125 lines
3.7 KiB
Markdown
125 lines
3.7 KiB
Markdown
---
|
|
id: "0007d"
|
|
title: "Scheduler: cron parser y ticker"
|
|
status: completado
|
|
type: feature
|
|
domain: []
|
|
scope: multi-app
|
|
priority: media
|
|
depends: []
|
|
blocks: []
|
|
related: []
|
|
created: 2026-05-17
|
|
updated: 2026-05-17
|
|
tags: []
|
|
---
|
|
# 0007d — Scheduler: cron parser y ticker
|
|
|
|
## Metadata
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **ID** | 0007d |
|
|
| **Estado** | pendiente |
|
|
| **Prioridad** | media |
|
|
| **Tipo** | feature |
|
|
|
|
## Dependencias
|
|
|
|
| ID | Título | Estado | Requerido |
|
|
|----|--------|--------|-----------|
|
|
| 0007a | Funciones core del DAG engine | pendiente | Si |
|
|
| 0007c | Execution store | pendiente | Si |
|
|
|
|
**Bloqueada por:** `#0007a, #0007c`
|
|
|
|
**Desbloquea:** `#0007e`
|
|
|
|
---
|
|
|
|
## Objetivo
|
|
|
|
Funciones para parsear expresiones cron, calcular proximas ejecuciones, y un ticker que dispara DAGs segun su schedule. Es lo que reemplaza el scheduler de Dagu.
|
|
|
|
## Contexto
|
|
|
|
- Las expresiones cron de Dagu son estandar (5 campos: min hour dom mon dow)
|
|
- El ticker es un loop infinito que cada minuto evalua que DAGs deben lanzarse
|
|
- Funciones puras para parseo y calculo, impura solo el ticker
|
|
|
|
## Arquitectura
|
|
|
|
```
|
|
functions/core/
|
|
├── cron_parse.go — NEW: string → CronExpression
|
|
├── cron_parse.md
|
|
├── cron_next.go — NEW: CronExpression + time → proxima ejecucion
|
|
├── cron_next.md
|
|
├── cron_match.go — NEW: CronExpression + time → bool (coincide?)
|
|
├── cron_match.md
|
|
|
|
functions/infra/
|
|
├── dag_ticker.go — NEW: loop que evalua schedules y lanza DAGs
|
|
├── dag_ticker.md
|
|
|
|
types/core/
|
|
├── cron_expression.md — NEW: minute, hour, dom, month, dow (cada uno []int o wildcard)
|
|
```
|
|
|
|
### Patron pure core / impure shell
|
|
|
|
- `core/` — `cron_parse`, `cron_next`, `cron_match` son puras
|
|
- `infra/` — `dag_ticker` es impuro (time.Sleep, lanza ejecuciones)
|
|
|
|
## Tareas
|
|
|
|
### Fase 1: Tipos
|
|
|
|
- [ ] **1.1** Definir `CronExpression` — campos parseados con soporte para *, ranges (1-5), lists (1,3,5), intervals (*/5)
|
|
|
|
### Fase 2: Funciones puras
|
|
|
|
- [ ] **2.1** `cron_parse` — "0 9 * * *" → CronExpression. Soportar: *, N, N-M, N/M, listas
|
|
- [ ] **2.2** `cron_next` — dada una CronExpression y un time.Time, retorna el proximo time.Time que coincide
|
|
- [ ] **2.3** `cron_match` — dada una CronExpression y un time.Time, retorna true si coincide (para el ticker)
|
|
- [ ] **2.4** Tests exhaustivos: wildcards, ranges, listas, intervalos, edge cases (fin de mes, febrero)
|
|
|
|
### Fase 3: Ticker
|
|
|
|
- [ ] **3.1** `dag_ticker` — recibe lista de (DagDefinition, path), cada minuto evalua cron_match para cada uno, lanza los que coinciden
|
|
- [ ] **3.2** Soporte para cancelacion (context.Context) y graceful shutdown
|
|
|
|
### Fase 4: Cleanup
|
|
|
|
- [ ] `fn index` y verificar IDs
|
|
|
|
---
|
|
|
|
## Ejemplo de uso
|
|
|
|
```go
|
|
// Puro
|
|
expr, _ := cron_parse("*/5 9-17 * * 1-5") // cada 5 min, 9-17h, lun-vie
|
|
next := cron_next(expr, time.Now()) // proxima ejecucion
|
|
matches := cron_match(expr, time.Now()) // true si ahora coincide
|
|
|
|
// Impuro (el ticker)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
dag_ticker(ctx, dags, executor) // loop infinito hasta cancel
|
|
```
|
|
|
|
## Decisiones de diseno
|
|
|
|
- **No usar libreria cron externa**: las expresiones son simples, implementar desde cero es ~100 lineas y evita dependencias
|
|
- **Separar parse/next/match**: parse es costoso, match es barato — parsear una vez, match cada minuto
|
|
- **Ticker como funcion, no como goroutine**: el caller decide como lanzarlo
|
|
|
|
## Criterios de aceptacion
|
|
|
|
- [ ] Parsea todas las expresiones cron de los DAGs existentes en `~/dagu/dags/`
|
|
- [ ] `cron_next` calcula correctamente la proxima ejecucion
|
|
- [ ] `cron_match` coincide correctamente para el minuto actual
|
|
- [ ] Ticker lanza DAGs en el momento correcto
|
|
- [ ] Tests pasan
|
|
- [ ] Indexado en registry.db
|