Pular para o conteúdo

O escalonador

Ordem, prazo, cancelamento, contrapressão, o executor para o que bloqueia — e o erro que não pode derrubar o servidor inteiro.

O laço tem três filas, e cada uma resolve um problema diferente.

FilaO que resolve
poller (selectors)dormir até haver E/S, em vez de girar
prazos (heap)apos e a_cada sem uma thread por relógio
prontasa ordem de execução, e onde a contrapressão mora

Contrapressão#

Uma fila sem teto troca falha visível por morte por memória — que é muito pior de diagnosticar, porque acontece longe da causa. Com teto, agendar devolve no e quem chama decide o que fazer.

dataforge
adopt Arcane.Laco as L

laco := L.novo(2)                // teto de duas tarefas na fila

assert L.agendar(laco, lambda => void)
assert L.agendar(laco, lambda => void)
assert not L.agendar(laco, lambda => void)     // recusada, e diz que recusou
assert L.estatisticas(laco)["recusadas"] is 1

Um erro não derruba o laço#

Um reator que morre no primeiro erro derruba o servidor inteiro — e o erro costuma ser de uma conexão. Aqui ele é contado, guardado com o tipo e o texto, e o laço segue.

dataforge
adopt Arcane.Laco as L

laco := L.novo()
visto := []

L.agendar(laco, lambda => 1 / 0)
L.agendar(laco, lambda => visto.append("segui"))
L.rodar(laco)

assert visto is ["segui"]
assert L.estatisticas(laco)["erros"] is 1
assert L.falhas(laco)[0]["tipo"] is "DivisionByZeroError"

O que bloqueia vai para o pool#

Esta é a peça que mais falta num reator escrito à mão. Um trabalho que bloqueia dentro do laço trava tudo — não só aquela tarefa, mas toda conexão aberta. L.executar manda para um pool de threads e devolve o resultado pela fila.

dataforge
adopt Arcane.Laco as L
adopt Arcane.Time as T

laco := L.novo()
marcas := []

action pesado():
    T.sleep(0.05)
    yield "pronto"

L.executar(laco, pesado, lambda r => marcas.append(r))
L.apos(laco, 10, lambda => marcas.append("tique"))
L.rodar(laco)

// o tique rodou ENQUANTO o trabalho pesado corria
assert marcas is ["tique", "pronto"]

O que NÃO existe#

ItemPor quê
work stealing entre laçoscada laço é uma thread; roubar tarefa entre eles exigiria fila sem trava e afinidade — e o GIL come o ganho antes de ele aparecer
io_uringsó Linux, e pelo CPython exigiria uma extensão em C — fora de uma linguagem sem dependência externa
IOCP no Windowso selectors usa select ali, que tem teto de 512 descritores. É o limite do Windows nesta forma, e está dito
prioridade por tarefaa fila é FIFO. Prioridade sem inversão de prioridade é mais difícil do que parece, e ninguém pediu ainda