Last Updated: June 13, 2021
·
22.42K
· rickhanlonii

Setting Supervisor to *really* stop Django runserver

If you're using Supervisor and adding Django to it via

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

then you may notice that when you stop the some_django program (or kill the process) there is still a process running from your runserver command. And if you try to start the server back up, you'll raise an error because the port is in use!

If you look close enough, you'll see that this process's parent ID is the one which is now dead. What we have here is a rogue orphan.

Many thanks to Simon Pantzare for getting me on the right track. He recommends adding:

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

I tried and tested it by executing

supervisorctl stop some_django

but the orphaned process was still there. Looking into the docs, I found what I needed. You see, killasgroup kills all processes in the group when Supervisor resorts to sending a SIGKILL. The idea is that when the process is playing nicely, it should be allowed to handle stoping it's children on its own. But when it misbehaves and needs a bullet to the bits, you may need an option which that gives you the power to drag the whole family to the gallows for execution as well. From 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.

But when I ran the stop command, and even through the stopsignal was set to KILL, Supervisor wasn't inside the blocks that consider the killasgroup setting.

After digging through the docs and source, I found that I needed to use stopasgroup (which was added after killasgroup). From CHANGES.txt again:

- 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.  

So, the settings that works for running Django's runserver command in Supervisor is:

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

A few closing notes:

  1. Setting stopasgroup to true sets killasgroup to true as well. See options.py:

    stopasgroup = boolean(
            get(section, 'stopasgroup', 'false'))
    killasgroup = boolean(
            get(section, 'killasgroup', stopasgroup))  
  2. Setting stopasgroup to true and killasgroup to false raises an error. From options.py again:

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

15 Responses
Add your response

Really cool you shared how you got to your solution. Thanks!

over 1 year ago ·

I had trouble with this some time ago, if you use the "--noreload" argument Django will create a single process.

over 1 year ago ·

cannot stop all processes, after adding lines, still one process is keeps running, how to solve this problem ? any solution ?

over 1 year ago ·

Can you post your config file yspanchal?

over 1 year ago ·

hi all i have the same problem, where i add --noreload??

this is my config file:

[program:xxx]
command=path/.virtualenvs/dev_env/bin/python2.7 mypath/xxx/manage.py runserver 0.0.0.0:8082
directory=mypath/Proxy/xxx

stdoutlogfile=mypath/logs/xxxoutput.log
redirect_stderr=true

autostart=false
autorestart=false
priority=991

stopsignal=KILL
killasgroup=true
stopasgroup=true

over 1 year ago ·

@zoubydazarkouna can you fix the formatting for your log file?

over 1 year ago ·

thanks for your reponse i fix my prob :
in command line i add --noreload option :

manage.py runserver 0.0.0.0:8082 directory=mypath/Proxy/xxx --noreload

over 1 year ago ·

My Supervisord cannot stop django-celery processes even with the 'stopasgroup=true' config.

over 1 year ago ·

+1 on the --noreload

over 1 year ago ·

solved my problem.

over 1 year ago ·

Hi! You are the man! Thanks.

over 1 year ago ·

Hi! You are the man! Thanks.

over 1 year ago ·

It works with my Flask app! Thanks!

over 1 year ago ·

Thank you. It does help for my Flask app.

over 1 year ago ·

Thank you for this.

over 1 year ago ·