Ir al contenido

Scheduler

El scheduler ejecuta procesos usando un diseño de work-stealing. Los workers mantienen deques locales y roban de otros cuando están idle.

El scheduler trabaja con cualquier tipo que implemente la interfaz Process:

type Process interface {
Init(ctx context.Context, method string, input payload.Payloads) error
Step(events []Event, out *StepOutput) error
Close()
}
MétodoPropósito
InitPreparar proceso con nombre de método de entrada y argumentos de entrada
StepAvanzar máquina de estados con eventos entrantes, escribir yields a salida
CloseLiberar recursos

El parámetro method en Init especifica qué punto de entrada invocar. Una instancia de proceso puede exponer múltiples puntos de entrada, y el llamador selecciona cuál ejecutar. Esto también sirve como verificación de que el scheduler está iniciando el proceso correctamente.

El scheduler llama Step() repetidamente, pasando eventos (completaciones de yield, mensajes) y recolectando yields (comandos a despachar). El proceso escribe su estado y cualquier yield al buffer StepOutput.

type Event struct {
Type EventType // EventYieldComplete o EventMessage
Tag uint64 // Tag de correlación para completaciones de yield
Data any // Datos de resultado o payload de mensaje
Error error // Error si yield falló
}

El scheduler genera GOMAXPROCS workers por defecto. Cada worker tiene un deque local para acceso LIFO amigable con cache. Una cola FIFO global maneja nuevos envíos y transferencias entre workers. Los procesos se rastrean por PID para routing de mensajes.

flowchart TD
W[Worker necesita trabajo] --> L{Deque local?}
L -->|tiene items| LP[Pop desde fondo LIFO]
L -->|vacío| G{Cola global?}
G -->|tiene items| GP[Pop + transferencia batch hasta 16]
G -->|vacía| S[Robar de víctima aleatoria]
S --> SH[StealHalfInto deque de víctima]

Workers verifican fuentes en orden de prioridad:

PrioridadFuentePatrón
1Deque localPop LIFO, sin lock, amigable con cache
2Cola globalPop FIFO con transferencia batch
3Otros workersRobar mitad del deque de víctima

Al hacer pop de global, workers toman un item y transfieren en batch hasta 16 más a su deque local.

Cada worker posee un deque Chase-Lev de work-stealing:

type Deque struct {
buffer atomic.Pointer[dequeBuffer]
top atomic.Int64 // Ladrones roban desde aquí (CAS)
bottom atomic.Int64 // Dueño push/pop aquí
}

El dueño hace push y pop desde el fondo (LIFO) sin sincronización. Los ladrones roban desde arriba (FIFO) usando CAS. Esto da al dueño acceso amigable con cache a items recientemente pusheados mientras distribuye trabajo más viejo a stealers.

StealHalfInto toma la mitad de los items en una operación CAS, reduciendo contención.

Antes de bloquear en la variable de condición, workers giran adaptativamente:

Contador de SpinAcción
< 4Loop cerrado
4-15Yield thread (runtime.Gosched)
>= 16Bloquear en variable de condición
stateDiagram-v2
[*] --> Ready: Submit
Ready --> Running: CAS por worker
Running --> Complete: done
Running --> Blocked: yields comandos
Running --> Idle: esperando mensajes
Blocked --> Ready: CompleteYield
Idle --> Ready: Send llega
EstadoDescripción
ReadyEncolado para ejecución
RunningWorker está ejecutando Step()
BlockedEsperando completación de yield
IdleEsperando mensajes
CompleteEjecución terminada

Un flag de wakeup maneja races: si un handler llama CompleteYield mientras el worker todavía posee el proceso (Running), establece el flag. El worker verifica el flag después de despachar y re-encola si está establecido.

Cada proceso tiene una cola de eventos MPSC (multi-producer, single-consumer):

  • Productores: Handlers de comandos (CompleteYield), remitentes de mensajes (Send)
  • Consumidor: Worker drena eventos en Step()

El scheduler implementa relay.Receiver para enrutar mensajes a procesos. Cuando Send() es llamado, busca el PID destino en el mapa byPID, pushea el mensaje como evento a la cola del proceso, y despierta el proceso si está idle pusheándolo a la cola global.

En shutdown, el scheduler envía eventos de cancelación a todos los procesos en ejecución y espera que completen o timeout. Workers salen una vez que no queda trabajo.