Created
March 10, 2025 09:51
-
-
Save Maximilan4/84679393fd6ab4af907ea897f0209fb7 to your computer and use it in GitHub Desktop.
Reloadable go app with tableflip and systemd notifications
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
module tft | |
go 1.23 | |
require ( | |
github.com/cloudflare/tableflip v1.2.3 // indirect | |
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect | |
) |
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
github.com/cloudflare/tableflip v1.2.3 h1:8I+B99QnnEWPHOY3fWipwVKxS70LGgUsslG7CSfmHMw= | |
github.com/cloudflare/tableflip v1.2.3/go.mod h1:P4gRehmV6Z2bY5ao5ml9Pd8u6kuEnlB37pUFMmv7j2E= | |
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= | |
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= | |
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
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
package main | |
import ( | |
"context" | |
"flag" | |
"fmt" | |
"log" | |
"net/http" | |
"os" | |
"os/signal" | |
"strconv" | |
"syscall" | |
"time" | |
"github.com/cloudflare/tableflip" | |
"github.com/coreos/go-systemd/daemon" | |
) | |
func main() { | |
var ( | |
listenAddr = flag.String("listen", "localhost:8081", "`Address` to listen on") | |
pidFile = flag.String("pid-file", "", "`Path` to pid file") | |
) | |
flag.Parse() | |
log.SetPrefix(fmt.Sprintf("[%d] ", os.Getpid())) | |
upg, err := tableflip.New(tableflip.Options{ | |
PIDFile: *pidFile, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
defer upg.Stop() | |
done := make(chan struct{}, 1) | |
// Do an upgrade on SIGHUP | |
go func() { | |
sig := make(chan os.Signal, 1) | |
signal.Notify(sig, syscall.SIGUSR2) | |
for sg := range sig { | |
switch sg { | |
case syscall.SIGUSR2: | |
log.Println("got signal", sg.String()) | |
uErr := upg.Upgrade() | |
if uErr != nil { | |
log.Println("Upgrade failed:", err) | |
} | |
done <- struct{}{} | |
ok, sdErr := daemon.SdNotify(false, daemon.SdNotifyReloading) | |
log.Println("sd notify:", ok, sdErr) | |
case syscall.SIGTERM: | |
ok, sdErr := daemon.SdNotify(false, daemon.SdNotifyStopping) | |
log.Println("sd notify:", ok, sdErr) | |
} | |
} | |
}() | |
// Listen must be called before Ready | |
ln, err := upg.Listen("tcp", *listenAddr) | |
if err != nil { | |
log.Fatalln("Can't listen:", err) | |
} | |
server := http.Server{ | |
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
log.Println(r.Method, r.URL.String()) | |
w.Write([]byte("{}")) | |
return | |
}), | |
} | |
go func() { | |
log.Println("Listening on", ln.Addr()) | |
err := server.Serve(ln) | |
if err != http.ErrServerClosed { | |
log.Println("HTTP server:", err) | |
} | |
}() | |
if err = upg.Ready(); err != nil { | |
panic(err) | |
} | |
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | |
defer cancel() | |
if err = upg.WaitForParent(ctx); err != nil { | |
log.Println("WaitForParent:", err) | |
} | |
log.Printf("running") | |
ok, sdErr := daemon.SdNotify(false, daemon.SdNotifyReady) | |
log.Println("sd notify:", ok, sdErr) | |
if upg.HasParent() { | |
os.Setenv("WATCHDOG_PID", strconv.Itoa(os.Getpid())) | |
} | |
if wdInt, wdErr := daemon.SdWatchdogEnabled(false); wdErr == nil && wdInt != 0 { | |
log.Println("Watchdog started:", os.Environ()) | |
log.Println(os.Getenv("WATCHDOG_USEC"), os.Getenv("WATCHDOG_PID")) | |
timer := time.NewTimer(time.Duration(wdInt) / 2) | |
defer timer.Stop() | |
go func() { | |
var ( | |
wdOk bool | |
tick time.Time | |
) | |
for { | |
select { | |
case <-done: | |
return | |
case tick = <-timer.C: | |
wdOk, wdErr = daemon.SdNotify(false, daemon.SdNotifyWatchdog) | |
log.Println("sd wd notify:", tick, wdOk, wdErr) | |
timer.Reset(time.Duration(wdInt) / 2) | |
} | |
} | |
}() | |
} else { | |
log.Println("Watchdog skipped:", wdInt, sdErr, os.Environ()) | |
log.Println(os.Getenv("WATCHDOG_USEC"), os.Getenv("WATCHDOG_PID")) | |
} | |
<-upg.Exit() | |
// Make sure to set a deadline on exiting the process | |
// after upg.Exit() is closed. No new upgrades can be | |
// performed if the parent doesn't exit. | |
time.AfterFunc(30*time.Second, func() { | |
log.Println("Graceful shutdown timed out") | |
os.Exit(1) | |
}) | |
// Wait for connections to drain. | |
server.Shutdown(context.Background()) | |
time.Sleep(1 * time.Second) | |
log.Println("server shutdown") | |
} |
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
[Unit] | |
Description=test daemon. | |
After=network.target | |
After=nss-lookup.target | |
[Service] | |
Type=notify | |
ExecStart=/usr/bin/tft --pid-file=/run/tft.pid | |
ExecReload=/bin/kill -USR2 $MAINPID | |
ExecStop=/bin/kill -15 $MAINPID | |
Restart=always | |
PIDFile=/run/tft.pid | |
WatchdogSec=30 | |
[Install] | |
WantedBy=multi-user.target |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment