Files

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