introduction
This article is the third article in the series [In-depth understanding of Go Standard Library]
Article 1:Starting of http server
Article 2:ServeMux usage and pattern matching
Article 3: Elegant closure of http server👈
This series will be updated continuously, welcome to follow 👏 Get real-time notifications
Remember how to start an HTTP Server?
package main import ( "net" "net/http" ) func main() { // Method 1 err := (":8080", nil) if err != nil { panic(err) } // Method 2 // server := &{Addr: ":8080"} // err := () // if err != nil { // panic(err) // } }
ListenAndServe
Without errors, it will be blocked in this location. How to stop such an HTTP Server?
CTRL+C
is a common way to end a process, andkill pid
orkill -l 15 pid
There is no difference in commands in essence, they are all sent to the processSIGTERM
Signal. Because the program is not setSIGTERM
signal handler, so the system default signal handler ends our process
What problems will this bring?
Our server may be processing the request and not completing when the server's process is killed. Therefore, an unexpected error was generated for the client
curl -v --max-time 4 127.0.0.1:8009/foo * Connection #0 to host 127.0.0.1 left intact * Trying 127.0.0.1:8009... * Connected to 127.0.0.1 (127.0.0.1) port 8009 (#0) > GET /foo HTTP/1.1 > Host: 127.0.0.1:8009 > User-Agent: curl/7.86.0 > Accept: */* > * Empty reply from server * Closing connection 0 curl: (52) Empty reply from server
If there is a nginx proxy, nginx will generate a 502 response because of the interruption of upstream.
curl -v --max-time 11 127.0.0.1:8010/foo * Trying 127.0.0.1:8010... * Connected to 127.0.0.1 (127.0.0.1) port 8010 (#0) > GET /foo HTTP/1.1 > Host: 127.0.0.1:8010 > User-Agent: curl/7.86.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 502 Bad Gateway < Server: nginx/1.25.3 < Date: Sat, 02 Dec 2023 10:14:33 GMT < Content-Type: text/html < Content-Length: 497 < Connection: keep-alive < ETag: "6537cac7-1f1"
The initial implementation of elegant closure
Graceful shutdown refers to the fact that our HTTP Server not only rejects new requests before shutting down, but also correctly handles the ongoing requests, and then the process exits. How to achieve it?
🌲 Start HTTP server asynchronously
becauseListenAndServe
It will block the goroutine. If we still need to let the code continue to execute, we need to put it into an asynchronous goroutine
go func() { if err := (); err != nil { panic(err) } }()
🌲 Step 2: Set up SIGTERM signal processor
The default signal processor of the operating system is to directly end the process, so to implement graceful shutdown, you need to set up the program's own signal processor.
In Go, the following method can be used to process signals.
To set the signal we want to monitor. Once a signal set by the program occurs, the signal will be written to the channel.
signalCh chan
We define a buffered channel, and the read operation will block when there is no data in the channel
signalCh := make(chan , 1) (signalCh, , ) sig := <-signalCh ("Received signal: %v\n", sig)
🌲 Step 3: Smoothly shut down HTTP Server
What to handle in a custom signal handler?
1. First, you need to close the listening of the port. At this time, the new request cannot establish a connection.
2. Close the idle connection
3. Waiting for the in-progress connection is completed and closed after it becomes an idle connection.
Before Go 1.8, implementing the above operations required a lot of code to be written, and there are also some third-party libraries (tylerstillwate/graceful, facebookarchive/grace, etc.) available for use. But after Go1.8, the standard library providesShutdown()
method
🌲 Implementation: The above three steps are as follows
func main() { mx := () ("/foo", func(w , r *) { (((10)) * ) ([]byte("Receive path foo\n")) }) srv := { Addr: ":8009", Handler: mx, } go func() { if err := (); err != nil { panic(err) } }() signalCh := make(chan , 1) (signalCh, , ) sig := <-signalCh ("Received signal: %v\n", sig) if err := (()); err != nil { ("Server shutdown failed: %v\n", err) } ("Server shutdown gracefully") }
Not receivedSIGINT
、SIGTERM
Before the signal, the main goroutine issignalCh
Read blocking
Once the signal is received,signalCh
The blocking is cancelled and the server will be executed downShutdown()
,Shutdown()
The function handles active and inactive connections and returns the result
Is there any problem with the above code?
Elegantly close the implementation details
🌲 WhenShutdown
When calledListenAndServe
Will return immediatelyError
go func() { if err := (); err != nil { panic(err) } }()
For the above code,Shutdown()
Just called,ListenAndServe
The goroutine where it is located throws panic, which also causes the main goroutine to be exited and not reach the run.Shutdown()
Expected results
If you still want to be rightListenAndServe
The error throws painc and needs to be ignoredError
go func() { err := () if err != nil && err != { panic(err) } }()
🌲 Shut down the server for a limited time
During the elegant closing process, wait for the in-progress request to complete. However, the process of request processing may be very time-consuming, or the request itself has fallen into an indefinitely state, so it is impossible for us to wait infinitely, so it is safer to set a closed upper limit time.
Shutdown()
Accept oneType parameters, we can use to set the timeout time
ctx, cancel := ((), 5*) defer cancel() if err := (ctx); err != nil { ("Server shutdown failed: %v\n", err) } ("Server shutdown gracefully")
pass()
It can distinguish whether the server shutdown is caused by timeout, so different reasons for exit
ctx, cancel := ((), 5*) defer cancel() if err := (ctx); err != nil { select { case <-(): // Since the timeout time is reached, the server is closed, the elegant shutdown is not completed ("timeout of 5 seconds.") default: // Service shutdown exception caused by other reasons, elegant shutdown is not completed ("Server shutdown failed: %v\n", err) } return } // Correctly perform elegant shutdown of the server("Server shutdown gracefully")
🌲 Release other resources
In addition to explicitly releasing resources, it is necessary for main goroutine to notify other goroutine processes to exit soon and do necessary processing.
For example, after starting our service, we will register with the service center, and then report its own status asynchronously.
In order for the registration center to realize that the service has been offline as soon as possible, it is necessary to actively cancel the service. Before canceling the service, you need to pause asynchronous timed reporting first
Let us do this easily
ctx, cancel := (()) defer func() { cancel() }() // You need to register at the registration center after the service is startedgo func() { tc := (5 * ) for { select { case <-: // Report status ("status update success") case <-(): // server closed, return () ("stop update success") return } } }()
There is a more complex utilization in the sample repositoryExample of exiting a child goroutine
🌲 Full picture
Combining all the details above, an elegantly closed http server code is as follows
func registerService(ctx ) { tc := (5 * ) for { select { case <-: // Report status ("status update success") case <-(): () ("stop update success") return } } } func destroyService() { ("destroy success") } func gracefulShutdown() { mainCtx, mainCancel := (()) // Use ctx to initialize resources, mysql, redis, etc. // ... defer func() { mainCancel() // Actively cancel the service destroyService() // Clean up resources, mysql, redis, etc. // ... }() mx := () ("/foo", func(w , r *) { (((10)) * ) ([]byte("Receive path foo\n")) }) srv := { Addr: ":8009", Handler: mx, } // ListenAndServe will also block, and you need to put it in a goroutine go func() { if err := (); err != nil && err != { panic(err) } }() // You need to register at the registration center after the service is started go registerService(mainCtx) signalCh := make(chan , 1) (signalCh, , ) // Wait for signal sig := <-signalCh ("Received signal: %v\n", sig) ctxTimeout, cancelTimeout := ((), 5*) defer cancelTimeout() if err := (ctxTimeout); err != nil { select { case <-(): // Since the timeout time is reached, the server is closed, the elegant shutdown is not completed ("timeout of 5 seconds.") default: // Service shutdown exception caused by other reasons, elegant shutdown is not completed ("Server shutdown failed: %v\n", err) } return } // Correctly perform elegant shutdown of the server ("Server shutdown gracefully") }
The above is the detailed content of the elegant closure of the Go standard library http server. For more information about the closure of the Go standard library http server, please pay attention to my other related articles!