Pular para o conteúdo

Um pool de recursos

O caso que junta tudo: emprestar, devolver, e o que acontece quando ninguém devolve.

Um pool é o exemplo canônico de posse: o recurso pertence ao pool, quem usa empresta, e devolver não é opcional — um empréstimo que não volta é uma conexão a menos, para sempre.

dataforge
adopt Arcane.Posse as Posse

blueprint Pool:
    action setup(quantas):
        self.livres := []
        self.emprestadas := 0
        self.criadas := 0
        cycle i from 1 to quantas:
            self.criadas := self.criadas + 1
            self.livres.append({"id": self.criadas})

    action com(acao):
        given len(self.livres) is 0:
            trigger "o pool acabou — todas as conexões estão emprestadas"
        conexao := self.livres.pop(len(self.livres) - 1)
        self.emprestadas := self.emprestadas + 1
        // O 'defer' é o que torna a devolução impossível de esquecer:
        // ele roda mesmo quando a ação falha no meio.
        defer:
            self.livres.append(conexao)
            self.emprestadas := self.emprestadas - 1
        yield acao(conexao)

p := spawn Pool(2)
assert p.com(lambda c => c["id"]) is 2
assert len(p.livres) is 2          // devolvida

// E mesmo quando o corpo falha:
monitor:
    p.com(lambda c => 1 / 0)
handle Error:
    out "a ação falhou"
assert len(p.livres) is 2
assert p.emprestadas is 0
out "devolvida mesmo com erro"

O que acontece quando o pool acaba#

dataforge
adopt Arcane.Posse as Posse

blueprint Pool:
    action setup(quantas):
        self.livres := [{"id": i} cycle i in range(1, quantas + 1)]

    action pegar():
        given len(self.livres) is 0:
            // Falhar RÁPIDO é melhor que esperar para sempre: uma
            // espera sem prazo vira um travamento sem mensagem, e
            // ninguém consegue distinguir isso de rede lenta.
            trigger "pool esgotado: aumente o tamanho ou reduza o tempo de uso"
        yield self.livres.pop(0)

p := spawn Pool(1)
primeira := p.pegar()

monitor:
    p.pegar()
    assert no
handle Error as e:
    out e.message

As quatro decisões de um pool#

DecisãoSem ela
devolver no deferuma falha no meio come uma conexão por vez, até o pool acabar
falhar quando esgota, com prazoespera infinita — e um travamento sem mensagem
um teto, e não crescer sem limiteo pool vira um jeito elaborado de abrir conexão demais no banco
conferir a conexão ao devolveruma conexão morta volta para o pool e quebra o próximo

E o pool de processos, que já existe#

Para trabalho de CPU, a linguagem já traz um: P.pool_processos() paga a partida uma vez — medido, 180 ms na primeira chamada e 82 ms na segunda. O fechar() é explícito porque o contrário deixa processos ociosos vivos.

dataforge
adopt Arcane.Concurrent as C

pool := C.pool_processos()
defer:
    pool.fechar()

assert pool is not void
out "o pool sobrevive entre chamadas — e o fechar é explícito"