2019-10-15 14:39:51 +01:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// This code is highly inspired by endless go
package graceful
import (
"crypto/tls"
"net"
"os"
"strings"
"sync"
2020-02-07 09:08:09 +00:00
"sync/atomic"
2019-10-15 14:39:51 +01:00
"syscall"
"time"
"code.gitea.io/gitea/modules/log"
2021-06-10 22:25:25 +01:00
"code.gitea.io/gitea/modules/setting"
2019-10-15 14:39:51 +01:00
)
var (
// DefaultReadTimeOut default read timeout
DefaultReadTimeOut time . Duration
// DefaultWriteTimeOut default write timeout
DefaultWriteTimeOut time . Duration
// DefaultMaxHeaderBytes default max header bytes
DefaultMaxHeaderBytes int
2021-06-10 22:25:25 +01:00
// PerWriteWriteTimeout timeout for writes
PerWriteWriteTimeout = 30 * time . Second
// PerWriteWriteTimeoutKbTime is a timeout taking account of how much there is to be written
PerWriteWriteTimeoutKbTime = 10 * time . Second
2019-10-15 14:39:51 +01:00
)
func init ( ) {
DefaultMaxHeaderBytes = 0 // use http.DefaultMaxHeaderBytes - which currently is 1 << 20 (1MB)
}
// ServeFunction represents a listen.Accept loop
type ServeFunction = func ( net . Listener ) error
// Server represents our graceful server
type Server struct {
2021-06-10 22:25:25 +01:00
network string
address string
listener net . Listener
wg sync . WaitGroup
state state
lock * sync . RWMutex
BeforeBegin func ( network , address string )
OnShutdown func ( )
PerWriteTimeout time . Duration
PerWritePerKbTimeout time . Duration
2019-10-23 16:32:19 +01:00
}
2019-10-15 14:39:51 +01:00
// NewServer creates a server on network at provided address
2021-03-08 02:43:59 +00:00
func NewServer ( network , address , name string ) * Server {
2019-12-15 09:51:28 +00:00
if GetManager ( ) . IsChild ( ) {
2021-03-08 02:43:59 +00:00
log . Info ( "Restarting new %s server: %s:%s on PID: %d" , name , network , address , os . Getpid ( ) )
2019-10-15 14:39:51 +01:00
} else {
2021-03-08 02:43:59 +00:00
log . Info ( "Starting new %s server: %s:%s on PID: %d" , name , network , address , os . Getpid ( ) )
2019-10-15 14:39:51 +01:00
}
srv := & Server {
2021-06-10 22:25:25 +01:00
wg : sync . WaitGroup { } ,
state : stateInit ,
lock : & sync . RWMutex { } ,
network : network ,
address : address ,
PerWriteTimeout : setting . PerWriteTimeout ,
PerWritePerKbTimeout : setting . PerWritePerKbTimeout ,
2019-10-15 14:39:51 +01:00
}
srv . BeforeBegin = func ( network , addr string ) {
log . Debug ( "Starting server on %s:%s (PID: %d)" , network , addr , syscall . Getpid ( ) )
}
return srv
}
// ListenAndServe listens on the provided network address and then calls Serve
// to handle requests on incoming connections.
func ( srv * Server ) ListenAndServe ( serve ServeFunction ) error {
2019-11-21 18:32:02 +00:00
go srv . awaitShutdown ( )
2019-10-15 14:39:51 +01:00
l , err := GetListener ( srv . network , srv . address )
if err != nil {
log . Error ( "Unable to GetListener: %v" , err )
return err
}
srv . listener = newWrappedListener ( l , srv )
srv . BeforeBegin ( srv . network , srv . address )
return srv . Serve ( serve )
}
// ListenAndServeTLSConfig listens on the provided network address and then calls
// Serve to handle requests on incoming TLS connections.
func ( srv * Server ) ListenAndServeTLSConfig ( tlsConfig * tls . Config , serve ServeFunction ) error {
2019-11-21 18:32:02 +00:00
go srv . awaitShutdown ( )
2019-10-15 14:39:51 +01:00
2021-11-20 06:12:43 +00:00
if tlsConfig . MinVersion == 0 {
tlsConfig . MinVersion = tls . VersionTLS12
}
2020-09-02 23:37:49 +01:00
2019-10-15 14:39:51 +01:00
l , err := GetListener ( srv . network , srv . address )
if err != nil {
log . Error ( "Unable to get Listener: %v" , err )
return err
}
wl := newWrappedListener ( l , srv )
srv . listener = tls . NewListener ( wl , tlsConfig )
srv . BeforeBegin ( srv . network , srv . address )
return srv . Serve ( serve )
}
// Serve accepts incoming HTTP connections on the wrapped listener l, creating a new
// service goroutine for each. The service goroutines read requests and then call
// handler to reply to them. Handler is typically nil, in which case the
// DefaultServeMux is used.
//
// In addition to the standard Serve behaviour each connection is added to a
// sync.Waitgroup so that all outstanding connections can be served before shutting
// down the server.
func ( srv * Server ) Serve ( serve ServeFunction ) error {
defer log . Debug ( "Serve() returning... (PID: %d)" , syscall . Getpid ( ) )
srv . setState ( stateRunning )
2019-12-15 09:51:28 +00:00
GetManager ( ) . RegisterServer ( )
2019-10-15 14:39:51 +01:00
err := serve ( srv . listener )
log . Debug ( "Waiting for connections to finish... (PID: %d)" , syscall . Getpid ( ) )
srv . wg . Wait ( )
srv . setState ( stateTerminate )
2019-12-15 09:51:28 +00:00
GetManager ( ) . ServerDone ( )
2019-10-15 14:39:51 +01:00
// use of closed means that the listeners are closed - i.e. we should be shutting down - return nil
2020-10-19 22:03:08 +01:00
if err == nil || strings . Contains ( err . Error ( ) , "use of closed" ) || strings . Contains ( err . Error ( ) , "http: Server closed" ) {
2019-10-15 14:39:51 +01:00
return nil
}
return err
}
func ( srv * Server ) getState ( ) state {
srv . lock . RLock ( )
defer srv . lock . RUnlock ( )
return srv . state
}
func ( srv * Server ) setState ( st state ) {
srv . lock . Lock ( )
defer srv . lock . Unlock ( )
srv . state = st
}
2019-11-21 18:32:02 +00:00
type filer interface {
File ( ) ( * os . File , error )
}
2019-10-15 14:39:51 +01:00
type wrappedListener struct {
net . Listener
stopped bool
server * Server
}
func newWrappedListener ( l net . Listener , srv * Server ) * wrappedListener {
return & wrappedListener {
Listener : l ,
server : srv ,
}
}
func ( wl * wrappedListener ) Accept ( ) ( net . Conn , error ) {
var c net . Conn
// Set keepalive on TCPListeners connections.
if tcl , ok := wl . Listener . ( * net . TCPListener ) ; ok {
tc , err := tcl . AcceptTCP ( )
if err != nil {
return nil , err
}
_ = tc . SetKeepAlive ( true ) // see http.tcpKeepAliveListener
_ = tc . SetKeepAlivePeriod ( 3 * time . Minute ) // see http.tcpKeepAliveListener
c = tc
} else {
var err error
c , err = wl . Listener . Accept ( )
if err != nil {
return nil , err
}
}
2020-02-07 09:08:09 +00:00
closed := int32 ( 0 )
2021-10-12 21:55:18 +01:00
c = & wrappedConn {
2021-06-10 22:25:25 +01:00
Conn : c ,
server : wl . server ,
closed : & closed ,
perWriteTimeout : wl . server . PerWriteTimeout ,
perWritePerKbTimeout : wl . server . PerWritePerKbTimeout ,
2019-10-15 14:39:51 +01:00
}
wl . server . wg . Add ( 1 )
return c , nil
}
func ( wl * wrappedListener ) Close ( ) error {
if wl . stopped {
return syscall . EINVAL
}
wl . stopped = true
return wl . Listener . Close ( )
}
func ( wl * wrappedListener ) File ( ) ( * os . File , error ) {
// returns a dup(2) - FD_CLOEXEC flag *not* set so the listening socket can be passed to child processes
return wl . Listener . ( filer ) . File ( )
}
type wrappedConn struct {
net . Conn
2021-06-10 22:25:25 +01:00
server * Server
closed * int32
deadline time . Time
perWriteTimeout time . Duration
perWritePerKbTimeout time . Duration
2019-10-15 14:39:51 +01:00
}
2021-10-12 21:55:18 +01:00
func ( w * wrappedConn ) Write ( p [ ] byte ) ( n int , err error ) {
2021-06-10 22:25:25 +01:00
if w . perWriteTimeout > 0 {
minTimeout := time . Duration ( len ( p ) / 1024 ) * w . perWritePerKbTimeout
minDeadline := time . Now ( ) . Add ( minTimeout ) . Add ( w . perWriteTimeout )
w . deadline = w . deadline . Add ( minTimeout )
if minDeadline . After ( w . deadline ) {
w . deadline = minDeadline
}
_ = w . Conn . SetWriteDeadline ( w . deadline )
2021-05-14 13:26:03 +01:00
}
return w . Conn . Write ( p )
}
2021-10-12 21:55:18 +01:00
func ( w * wrappedConn ) Close ( ) error {
2020-02-07 09:08:09 +00:00
if atomic . CompareAndSwapInt32 ( w . closed , 0 , 1 ) {
2020-04-27 01:01:06 +01:00
defer func ( ) {
if err := recover ( ) ; err != nil {
select {
case <- GetManager ( ) . IsHammer ( ) :
// Likely deadlocked request released at hammertime
log . Warn ( "Panic during connection close! %v. Likely there has been a deadlocked request which has been released by forced shutdown." , err )
default :
log . Error ( "Panic during connection close! %v" , err )
}
}
} ( )
2019-10-15 14:39:51 +01:00
w . server . wg . Done ( )
}
2020-02-07 09:08:09 +00:00
return w . Conn . Close ( )
2019-10-15 14:39:51 +01:00
}