Configurando o Supervisor para * realmente * parar o Django runserver

Se você estiver usando o Supervisor e adicionando Django a ele via

[program:some_django]
command
=python manage.py runserver
directory
=/dir/to/app

então você pode notar que quando você para o programa some_django (ou finaliza o processo), ainda há um processo em execução a partir do comando runserver. E se você tentar reiniciar o servidor, gerará um erro porque a porta está em uso!

Se você olhar bem de perto, verá que o ID pai deste processo é aquele que agora está morto. O que temos aqui é um órfão desonesto.

Muito obrigado a Simon Pantzare por me colocar no caminho certo . Ele recomenda adicionar:

[program:some_django]
...
stopsignal
=KILL
killasgroup
=true

Eu tentei e testei executando

supervisorctl stop some_django

mas o processo órfão ainda estava lá. Olhando para os documentos, encontrei o que precisava. Veja, killasgroup elimina todos os processos no grupo quando o Supervisor recorre ao envio de um SIGKILL. A ideia é que, quando o processo está funcionando bem, ele deve ter permissão para interromper seus filhos por conta própria. Mas quando ele se comporta mal e precisa de uma bala para os pedaços, você pode precisar de uma opção que lhe dê o poder de arrastar toda a família para a forca para execução também. De CHANGES.txt :

- Add a boolean program option `killasgroup`, defaulting to false, if true when resorting to send SIGKILL to stop/terminate the process send it to its whole process group instead to take care of possible children as well and not leave them behind.  Patch by Samuele Pedroni.

Mas quando executei o comando de parada, e mesmo com o sinal de parada configurado para KILL, o Supervisor não estava dentro dos blocos que consideram a configuração killasgroup.

Depois de vasculhar os documentos e a fonte, descobri que precisava usar stopasgroup (que foi adicionado após killasgroup). De CHANGES.txt novamente :

- Add a boolean program option `stopasgroup`, defaulting to false. When true, the flag causes supervisor to send the stop signal to the whole process group.  This is useful for programs, such as Flask in debug mode, that do not propagate stop signals to their children, leaving them orphaned.  

Portanto, as configurações que funcionam para executar o comando runserver do Django no Supervisor são:

[program:some_django]
command
=python manage.py runserver
directory
=/dir/to/app
stopasgroup
=true

Algumas notas finais:

  1. Definir stopasgroup como true também define killasgroup como true. Veja options.py :

    stopasgroup = boolean(
    get(section, 'stopasgroup', 'false'))
    killasgroup
    = boolean(
    get(section, 'killasgroup', stopasgroup))
  2. Definir stopasgroup como true e killasgroup como false gera um erro. De options.py novamente:

    if stopasgroup and not killasgroup:
    raise ValueError(
    "Cannot set stopasgroup=true and killasgroup=false"
    )