@@ -9,8 +9,10 @@ import (
99 "fmt"
1010 "net/http"
1111 "os"
12+ "os/signal"
1213 "strconv"
1314 "strings"
15+ "syscall"
1416 "time"
1517 "unicode"
1618
@@ -58,8 +60,8 @@ func initLogging() {
5860
5961//+kubebuilder:rbac:groups="coordination.k8s.io",resources="leases",verbs={get,create,update,watch}
6062
61- func initManager () (runtime.Options , error ) {
62- log := logging .FromContext (context . Background () )
63+ func initManager (ctx context. Context ) (runtime.Options , error ) {
64+ log := logging .FromContext (ctx )
6365
6466 options := runtime.Options {}
6567 options .Cache .SyncPeriod = initialize .Pointer (time .Hour )
@@ -120,39 +122,63 @@ func initManager() (runtime.Options, error) {
120122}
121123
122124func main () {
123- // This context is canceled by SIGINT, SIGTERM, or by calling shutdown.
124- ctx , shutdown := context . WithCancel ( runtime . SignalHandler () )
125+ starting , stopStarting := context . WithCancel ( context . Background ())
126+ defer stopStarting ( )
125127
126- otelFlush , err := initOpenTelemetry ()
127- assertNoError (err )
128- defer otelFlush ()
128+ running , stopRunning := context .WithCancel (context .Background ())
129+ defer stopRunning ()
129130
130131 initLogging ()
131-
132- log := logging .FromContext (ctx )
132+ log := logging .FromContext (starting )
133133 log .V (1 ).Info ("debug flag set to true" )
134134
135+ // Start a goroutine that waits for SIGINT or SIGTERM.
136+ {
137+ signals := []os.Signal {os .Interrupt , syscall .SIGTERM }
138+ receive := make (chan os.Signal , len (signals ))
139+ signal .Notify (receive , signals ... )
140+ go func () {
141+ // Wait for a signal then immediately restore the default signal handlers.
142+ // After this, a SIGHUP, SIGINT, or SIGTERM causes the program to exit.
143+ // - https://pkg.go.dev/os/signal#hdr-Default_behavior_of_signals_in_Go_programs
144+ s := <- receive
145+ signal .Stop (receive )
146+
147+ log .Info ("received signal from OS" , "signal" , s .String ())
148+
149+ // Cancel the "starting" and "running" contexts.
150+ stopStarting ()
151+ stopRunning ()
152+ }()
153+ }
154+
135155 features := feature .NewGate ()
136156 assertNoError (features .Set (os .Getenv ("PGO_FEATURE_GATES" )))
137157 log .Info ("feature gates enabled" , "PGO_FEATURE_GATES" , features .String ())
138158
159+ // Initialize OpenTelemetry and flush data when there is a panic.
160+ otelFinish , err := initOpenTelemetry (starting )
161+ assertNoError (err )
162+ defer otelFinish (starting )
163+
139164 cfg , err := runtime .GetConfig ()
140165 assertNoError (err )
141166
142167 cfg .Wrap (otelTransportWrapper ())
143168
169+ // TODO(controller-runtime): Set config.WarningHandler instead after v0.19.0.
144170 // Configure client-go to suppress warnings when warning headers are encountered. This prevents
145171 // warnings from being logged over and over again during reconciliation (e.g. this will suppress
146172 // deprecation warnings when using an older version of a resource for backwards compatibility).
147173 rest .SetDefaultWarningHandler (rest.NoWarnings {})
148174
149175 k8s , err := kubernetes .NewDiscoveryRunner (cfg )
150176 assertNoError (err )
151- assertNoError (k8s .Read (ctx ))
177+ assertNoError (k8s .Read (starting ))
152178
153- log .Info ("Connected to Kubernetes" , "api" , k8s .Version ().String (), "openshift" , k8s .IsOpenShift ())
179+ log .Info ("connected to Kubernetes" , "api" , k8s .Version ().String (), "openshift" , k8s .IsOpenShift ())
154180
155- options , err := initManager ()
181+ options , err := initManager (starting )
156182 assertNoError (err )
157183
158184 // Add to the Context that Manager passes to Reconciler.Start, Runnable.Start,
@@ -168,7 +194,7 @@ func main() {
168194 assertNoError (err )
169195 assertNoError (mgr .Add (k8s ))
170196
171- registrar , err := registration .NewRunner (os .Getenv ("RSA_KEY" ), os .Getenv ("TOKEN_PATH" ), shutdown )
197+ registrar , err := registration .NewRunner (os .Getenv ("RSA_KEY" ), os .Getenv ("TOKEN_PATH" ), stopRunning )
172198 assertNoError (err )
173199 assertNoError (mgr .Add (registrar ))
174200 token , _ := registrar .CheckToken ()
@@ -206,10 +232,30 @@ func main() {
206232 assertNoError (mgr .AddHealthzCheck ("health" , healthz .Ping ))
207233 assertNoError (mgr .AddReadyzCheck ("check" , healthz .Ping ))
208234
209- log .Info ("starting controller runtime manager and will wait for signal to exit" )
235+ // Start the manager and wait for its context to be canceled.
236+ stopped := make (chan error , 1 )
237+ go func () { stopped <- mgr .Start (running ) }()
238+ <- running .Done ()
239+
240+ // Set a deadline for graceful termination.
241+ log .Info ("shutting down" )
242+ stopping , cancel := context .WithTimeout (context .Background (), 20 * time .Second )
243+ defer cancel ()
244+
245+ // Wait for the manager to return or the deadline to pass.
246+ select {
247+ case err = <- stopped :
248+ case <- stopping .Done ():
249+ err = stopping .Err ()
250+ }
210251
211- assertNoError (mgr .Start (ctx ))
212- log .Info ("signal received, exiting" )
252+ // Flush any telemetry with the remaining time we have.
253+ otelFinish (stopping )
254+ if err != nil {
255+ log .Error (err , "shutdown failed" )
256+ } else {
257+ log .Info ("shutdown complete" )
258+ }
213259}
214260
215261// addControllersToManager adds all PostgreSQL Operator controllers to the provided controller
0 commit comments