Torna atômica a reivindicação da notificação de atraso - #92
Merged
Merged
Conversation
A rake task selecionava as demandas com atraso_notificado_em nulo, enviava, e só depois gravava a marca. O README promete "uma vez por atraso", e isso valia só no caminho feliz de execução única. Duas janelas de duplicação vinham dessa ordem: duas execuções simultâneas — cron disparado duas vezes, retry do scheduler, uma réplica a mais — selecionavam a mesma demanda antes de qualquer gravação e notificavam as duas; e um processo que morresse entre o envio e o update_column fazia a execução seguinte notificar de novo. Agora a task reivindica antes de enviar, com `UPDATE ... WHERE atraso_notificado_em IS NULL`: a checagem e a escrita acontecem no mesmo comando, o banco decide quem venceu, e só quem recebe 1 linha afetada envia. A garantia deixa de depender da disciplina de quem agenda. Uma variação em relação ao que a issue propunha: uma falha DECLARADA do envio (notify_atraso devolveu false) devolve a demanda para a próxima execução. A issue aceitava perder a notificação nesse caso, mas não é preciso — o retry já existia, e mantê-lo não reabre a corrida, porque a execução concorrente já perdeu na reivindicação. O que continua sendo gasto sem sair é a demanda cujo processo morra entre reivindicar e enviar; essa é a troca consciente de marcar antes, e está comentada como tal. Sobra uma janela estreita: um transporte que devolva false depois de a mensagem ter sido entregue (timeout na leitura da resposta) faz a próxima execução notificar de novo. É estritamente menor que a janela anterior e o modo de falha é o mesmo que já existia. Os dois specs novos discriminam de verdade: um mede se a marca já está gravada no momento em que o envio acontece, e o outro simula a execução concorrente tentando reivindicar durante o envio e verifica que ela recebe 0 linhas. Ambos passariam por vacuidade contra o código anterior — não passam: com a ordem antiga o primeiro veria nil e o segundo, 1. O agendamento ganhou seção própria no README, como a issue pedia: um deploy sem cron tem a funcionalidade desligada e nada avisa isso. Registrei também que desde a #77 existe uma terceira via (config/ recurring.yml, com o worker já de pé) que dispensaria o scheduler externo — não foi feita, exigiria embrulhar a task num job. Verificado no container: RuboCop 110 arquivos sem ofensas, zeitwerk:check limpo, RSpec 394 exemplos e 0 falhas (eram 391). Closes #80 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fecha a #80.
A rake task selecionava as demandas com
atraso_notificado_emnulo, enviava, e só então gravava a marca. O README promete "uma vez por atraso" — e isso valia só no caminho feliz de execução única.Duas janelas de duplicação vinham dessa ordem:
Agora a task reivindica antes de enviar, com
UPDATE ... WHERE atraso_notificado_em IS NULL. A checagem e a escrita acontecem no mesmo comando: o banco decide quem venceu, e só quem recebe 1 linha afetada envia. A garantia deixa de depender da disciplina de quem agenda.Onde me afastei da issue
A issue propunha aceitar que qualquer falha "gastasse" a notificação. Fiz diferente: uma falha declarada do envio (
notify_atrasodevolveufalse) devolve a demanda para a próxima execução.Não é preciso perder o retry para consertar a corrida — a execução concorrente já perdeu na reivindicação, então devolver não reabre nada. O que continua sendo gasto sem sair é a demanda cujo processo morra entre reivindicar e enviar; essa sim é a troca consciente de marcar antes, e está comentada como tal na task.
Sobra uma janela estreita: um transporte que devolva
falsedepois de a mensagem ter sido entregue (timeout na leitura da resposta) faz a próxima execução notificar de novo. É estritamente menor que a janela anterior, e o modo de falha é o mesmo que já existia.Os specs discriminam de verdade
Os dois novos falhariam contra o código anterior, que é o teste que importa:
reivindica a demanda antes de enviar, e não depois— mede se a marca já está gravada no instante em que o envio acontece. Com a ordem antiga:nil.faz uma execução concorrente perder a corrida— simula outra execução tentando reivindicar durante o envio e verifica que ela recebe 0 linhas. Com a ordem antiga: 1.O spec pré-existente
não marca atraso_notificado_em quando o envio falhacontinua valendo sem alteração — a devolução preserva exatamente o estado que ele já afirmava.Agendamento
Ganhou seção própria no README, como a issue pedia. O ponto que faltava estar escrito: um deploy sem cron tem a funcionalidade desligada, e nada na aplicação avisa isso.
Registrei também que desde a #77 existe uma terceira via —
config/recurring.yml, com o processoworkerjá de pé — que dispensaria o scheduler externo. É a evolução natural, exigiria embrulhar a task num job, e não foi feita aqui.Verificação
No container: RuboCop 110 arquivos / 0 ofensas,
zeitwerk:checkAll is good!, RSpec 394 exemplos / 0 falhas (eram 391).Nada de JS, CSS, HAML ou CSP mudou.