Files
fn_registry/dev/issues/completed/0007d-scheduler.md
T

3.7 KiB

id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
id title status type domain scope priority depends blocks related created updated tags
0007d Scheduler: cron parser y ticker completado feature
multi-app media
2026-05-17 2026-05-17

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

// 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