2021-05-02 22:49:16 +00:00
|
|
|
package gounicorn
|
|
|
|
|
|
|
|
import (
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2021-10-05 20:19:33 +00:00
|
|
|
"net/http"
|
2021-05-02 22:49:16 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
2021-11-06 12:54:59 +00:00
|
|
|
"runtime"
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2021-11-06 12:54:59 +00:00
|
|
|
"syscall"
|
2021-10-05 20:19:33 +00:00
|
|
|
"time"
|
2021-05-02 22:49:16 +00:00
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
|
2021-11-15 13:04:10 +00:00
|
|
|
"goauthentik.io/internal/config"
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
"goauthentik.io/internal/utils"
|
2022-06-20 09:54:10 +00:00
|
|
|
"goauthentik.io/internal/utils/web"
|
2021-05-02 22:49:16 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type GoUnicorn struct {
|
2021-11-04 12:16:53 +00:00
|
|
|
HealthyCallback func()
|
|
|
|
|
2021-07-17 14:59:31 +00:00
|
|
|
log *log.Entry
|
|
|
|
p *exec.Cmd
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
pidFile *string
|
2021-07-17 14:59:31 +00:00
|
|
|
started bool
|
2021-07-17 15:02:24 +00:00
|
|
|
killed bool
|
2021-10-05 20:19:33 +00:00
|
|
|
alive bool
|
2021-05-02 22:49:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 09:54:11 +00:00
|
|
|
func New() *GoUnicorn {
|
2021-10-11 15:51:49 +00:00
|
|
|
logger := log.WithField("logger", "authentik.router.unicorn")
|
2021-07-17 14:59:31 +00:00
|
|
|
g := &GoUnicorn{
|
2021-11-04 12:16:53 +00:00
|
|
|
log: logger,
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
pidFile: nil,
|
2021-11-04 12:16:53 +00:00
|
|
|
started: false,
|
|
|
|
killed: false,
|
|
|
|
alive: false,
|
|
|
|
HealthyCallback: func() {},
|
2021-07-17 14:59:31 +00:00
|
|
|
}
|
|
|
|
g.initCmd()
|
|
|
|
return g
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GoUnicorn) initCmd() {
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
pidFile, _ := os.CreateTemp("", "authentik-gunicorn.*.pid")
|
|
|
|
g.pidFile = func() *string { s := pidFile.Name(); return &s }()
|
2021-05-02 22:49:16 +00:00
|
|
|
command := "gunicorn"
|
2021-11-15 15:32:56 +00:00
|
|
|
args := []string{"-c", "./lifecycle/gunicorn.conf.py", "authentik.root.asgi:application"}
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
if g.pidFile != nil {
|
|
|
|
args = append(args, "--pid", *g.pidFile)
|
|
|
|
}
|
2022-07-26 09:33:35 +00:00
|
|
|
if config.Get().Debug {
|
2021-11-15 13:04:10 +00:00
|
|
|
command = "./manage.py"
|
|
|
|
args = []string{"runserver"}
|
|
|
|
}
|
2021-07-17 14:59:31 +00:00
|
|
|
g.log.WithField("args", args).WithField("cmd", command).Debug("Starting gunicorn")
|
|
|
|
g.p = exec.Command(command, args...)
|
2021-07-17 15:02:24 +00:00
|
|
|
g.p.Env = os.Environ()
|
2021-07-17 14:59:31 +00:00
|
|
|
g.p.Stdout = os.Stdout
|
|
|
|
g.p.Stderr = os.Stderr
|
2021-06-23 18:40:51 +00:00
|
|
|
}
|
|
|
|
|
2021-10-05 20:19:33 +00:00
|
|
|
func (g *GoUnicorn) IsRunning() bool {
|
|
|
|
return g.alive
|
|
|
|
}
|
|
|
|
|
2021-06-23 18:40:51 +00:00
|
|
|
func (g *GoUnicorn) Start() error {
|
2021-07-17 14:59:31 +00:00
|
|
|
if g.started {
|
|
|
|
g.initCmd()
|
|
|
|
}
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
g.killed = false
|
2021-07-17 14:59:31 +00:00
|
|
|
g.started = true
|
2021-10-05 20:19:33 +00:00
|
|
|
go g.healthcheck()
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
return g.p.Start()
|
2021-06-23 18:40:51 +00:00
|
|
|
}
|
|
|
|
|
2021-10-05 20:19:33 +00:00
|
|
|
func (g *GoUnicorn) healthcheck() {
|
|
|
|
g.log.Debug("starting healthcheck")
|
|
|
|
h := &http.Client{
|
2022-06-20 09:54:10 +00:00
|
|
|
Transport: web.NewUserAgentTransport("goauthentik.io/proxy/healthcheck", http.DefaultTransport),
|
2021-10-05 20:19:33 +00:00
|
|
|
}
|
|
|
|
check := func() bool {
|
|
|
|
res, err := h.Get("http://localhost:8000/-/health/live/")
|
|
|
|
if err == nil && res.StatusCode == 204 {
|
|
|
|
g.alive = true
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default healthcheck is every 1 second on startup
|
|
|
|
// once we've been healthy once, increase to 30 seconds
|
2022-04-29 11:15:54 +00:00
|
|
|
for range time.Tick(time.Second) {
|
|
|
|
if check() {
|
|
|
|
g.log.Info("backend is alive, backing off with healthchecks")
|
|
|
|
g.HealthyCallback()
|
|
|
|
break
|
2021-10-05 20:19:33 +00:00
|
|
|
}
|
2022-04-29 11:15:54 +00:00
|
|
|
g.log.Debug("backend not alive yet")
|
|
|
|
}
|
2021-10-05 20:19:33 +00:00
|
|
|
for range time.Tick(30 * time.Second) {
|
|
|
|
check()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
func (g *GoUnicorn) Reload() {
|
|
|
|
g.log.WithField("method", "reload").Info("reloading gunicorn")
|
|
|
|
err := g.p.Process.Signal(syscall.SIGHUP)
|
|
|
|
if err != nil {
|
|
|
|
g.log.WithError(err).Warning("failed to reload gunicorn")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GoUnicorn) Restart() {
|
|
|
|
g.log.WithField("method", "restart").Info("restart gunicorn")
|
|
|
|
if g.pidFile == nil {
|
|
|
|
g.log.Warning("pidfile is non existent, cannot restart")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err := g.p.Process.Signal(syscall.SIGUSR2)
|
|
|
|
if err != nil {
|
|
|
|
g.log.WithError(err).Warning("failed to restart gunicorn")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
newPidFile := fmt.Sprintf("%s.2", *g.pidFile)
|
|
|
|
|
|
|
|
// Wait for the new PID file to be created
|
|
|
|
for range time.Tick(1 * time.Second) {
|
|
|
|
_, err = os.Stat(newPidFile)
|
|
|
|
if err == nil || !os.IsNotExist(err) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
g.log.Debugf("waiting for new gunicorn pidfile to appear at %s", newPidFile)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
g.log.WithError(err).Warning("failed to find the new gunicorn process, aborting")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
newPidB, err := ioutil.ReadFile(newPidFile)
|
|
|
|
if err != nil {
|
|
|
|
g.log.WithError(err).Warning("failed to find the new gunicorn process, aborting")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
newPidS := strings.TrimSpace(string(newPidB[:]))
|
|
|
|
newPid, err := strconv.Atoi(newPidS)
|
|
|
|
if err != nil {
|
|
|
|
g.log.WithError(err).Warning("failed to find the new gunicorn process, aborting")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
g.log.Warningf("new gunicorn PID is %d", newPid)
|
|
|
|
|
|
|
|
newProcess, err := utils.FindProcess(newPid)
|
|
|
|
if newProcess == nil || err != nil {
|
|
|
|
g.log.WithError(err).Warning("failed to find the new gunicorn process, aborting")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// The new process has started, let's gracefully kill the old one
|
|
|
|
g.log.Warningf("killing old gunicorn")
|
|
|
|
err = g.p.Process.Signal(syscall.SIGTERM)
|
|
|
|
if err != nil {
|
|
|
|
g.log.Warning("failed to kill old instance of gunicorn")
|
|
|
|
}
|
|
|
|
|
|
|
|
g.p.Process = newProcess
|
|
|
|
|
|
|
|
// No need to close any files and the .2 pid file is deleted by Gunicorn
|
|
|
|
}
|
|
|
|
|
2021-07-18 14:12:57 +00:00
|
|
|
func (g *GoUnicorn) Kill() {
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
if !g.started {
|
|
|
|
return
|
|
|
|
}
|
2021-11-06 12:54:59 +00:00
|
|
|
var err error
|
|
|
|
if runtime.GOOS == "darwin" {
|
|
|
|
g.log.WithField("method", "kill").Warning("stopping gunicorn")
|
|
|
|
err = g.p.Process.Kill()
|
|
|
|
} else {
|
|
|
|
g.log.WithField("method", "sigterm").Warning("stopping gunicorn")
|
|
|
|
err = syscall.Kill(g.p.Process.Pid, syscall.SIGTERM)
|
|
|
|
}
|
2021-07-18 14:12:57 +00:00
|
|
|
if err != nil {
|
2021-11-06 12:54:59 +00:00
|
|
|
g.log.WithError(err).Warning("failed to stop gunicorn")
|
2021-07-18 14:12:57 +00:00
|
|
|
}
|
root: handle SIGHUP and SIGUSR2
This is the first step to handle configuration reloading. With those
changes, it is already possible to do so, by sending a SIGUSR2 signal to
the Go server process. The next step would be to watch for changes to
configuration files and call the Restart function of the GoUnicorn
instance.
SIGHUP is catched by the go server and forwarded as-is to gunicorn,
which causes it to restart its workers. However, that does not trigger
a reload of the Django settings, probably because they are already
loaded in the master, before creating any of the worker instances.
SIGUSR2, however, can be used to spawn a new gunicorn master process,
but handling it is a bit trickier. Please refer to Gunicorn's
documentation[0] for details, especially the "Upgrading to a new binary
on the fly" section.
As we are now effectively killing the gunicorn processed launched by the
server, we need to handle some sort of check to make sure it is still
running. That's done by using the already existing healthchecks, making
them useful not only for the application start, but also for its
lifetime. If a check is failed too many times in a given time period,
the gunicorn processed is killed (if necessary) and then restarted.
[0] https://docs.gunicorn.org/en/20.1.0/signals.html
Other relevant links and documentation:
Python library handling the processing swaping upon a SIGUSR2:
https://github.com/flupke/rainbow-saddle/
Golang cannot easily check if a process exists on Unix systems:
https://github.com/golang/go/issues/34396
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2023-04-28 21:52:02 +00:00
|
|
|
if g.pidFile != nil {
|
|
|
|
os.Remove(*g.pidFile)
|
|
|
|
}
|
|
|
|
g.killed = true
|
2021-05-02 22:49:16 +00:00
|
|
|
}
|