Pular para o conteúdo

O arquivo, o soquete e a conexão

O que a posse protege num mundo com coletor — e o que ela não protege.

A primeira coisa a dizer, porque ela evita a leitura errada de tudo o mais: num mundo com coletor, a memória nunca esteve em risco. O que Arcane.Posse protege é o protocolo de um recurso — soltar uma vez, não usar depois de soltar, não escrever no meio de uma leitura.

O problemaSem posseCom posse
usar depois de fecharerro do sistema operacional, três camadas longePosseMovidaError, na linha que usou
fechar duas vezeserro, ou pior: fecha o descritor de outrosoltar é idempotente
esquecer de fecharo arquivo fica aberto até o processo morrero escopo solta, e o check avisa
dois donos do mesmo arquivoquem fecha primeiro estraga o outroum dono, e mover é explícito
dataforge
adopt Arcane.Posse as Posse

// O dono carrega o valor E o que fazer ao soltar.
fechados := []
arquivo := Posse.dono({"nome": "dados.csv"},
                      lambda v => fechados.append(v["nome"]),
                      "arquivo")

// Usar é sempre por dentro de uma ação: não há como guardar a
// referência crua e usá-la depois de soltar.
assert arquivo.usar(lambda v => v["nome"]) is "dados.csv"
assert arquivo.vivo() is yes

arquivo.soltar()
assert fechados is ["dados.csv"]
assert arquivo.solto() is yes

// E soltar de novo NÃO é erro.
assert arquivo.soltar() is no

Por que `soltar` é idempotente#

Um close() escrito no defer e no caminho de erro é a forma mais comum de fechar recurso — e se o segundo soltar levantasse, a disciplina atrapalharia em vez de ajudar. A regra: soltar é pedir um estado final, e nesse ponto já não importa se ele já estava lá.

Usar depois de soltar é erro, e o erro diz onde#

dataforge
adopt Arcane.Posse as Posse

conexao := Posse.dono({"banco": "loja"})
conexao.soltar()

monitor:
    conexao.usar(lambda v => v["banco"])
    assert no
handle Error as e:
    out e.message

O de fora solta o que possuía#

Um dono que guarda outro dono precisa soltá-lo — é a drop glue. Sem isso, soltar o de fora deixaria o de dentro aberto, que é exatamente o vazamento que a peça existe para evitar:

dataforge
adopt Arcane.Posse as Posse

soltos := []

action abrir(nome):
    yield Posse.dono({"nome": nome}, lambda v => soltos.append(v["nome"]), nome)

// Um 'Escopo' guarda vários, e solta todos na ordem inversa da
// abertura — como a pilha de um bloco.
escopo := Posse.escopo()
escopo.guardar(abrir("conexao"))
escopo.guardar(abrir("transacao"))
escopo.guardar(abrir("arquivo"))
assert escopo.quantos() is 3

escopo.soltar()
assert soltos is ["arquivo", "transacao", "conexao"]
out soltos

A ordem inversa não é estética: a transação foi aberta sobre a conexão, e fechar a conexão primeiro deixaria a transação sem onde confirmar.

E o `defer`, que já existia#

deferPosse
quando rodana saída da açãoquando o dono é solto, ou o escopo fecha
quem garanteo interpretadorquem escreveu
protege de usar depoisnãosim — e é a diferença que importa
atravessa fronteiranãosim: o dono pode ser movido
custo para quem não usazerozero

Os dois convivem, e o mais comum é usar defer para o caso simples e Posse quando o recurso atravessa — vai para dentro de uma estrutura, é devolvido por uma ação, ou tem mais de um candidato a dono.