Rework shutdown mechanism

This commit is contained in:
Jan-Lukas Else 2021-05-07 16:14:15 +02:00
parent 45b1921663
commit 705f02b7ef
8 changed files with 103 additions and 76 deletions

View File

@ -36,6 +36,10 @@ func initDatabase() (err error) {
if err != nil {
return err
}
addShutdownFunc(func() {
_ = closeDb()
log.Println("Closed database")
})
vacuumDb()
err = migrateDb()
if err != nil {

13
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/boombuler/barcode v1.0.1 // indirect
github.com/caddyserver/certmagic v0.13.0
github.com/caddyserver/certmagic v0.13.1
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/dgraph-io/ristretto v0.0.4-0.20210311064603-e4f298c8aa88
@ -25,7 +25,6 @@ require (
github.com/gorilla/handlers v1.5.1
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/joncrlsn/dque v0.0.0-20200702023911-3e80e3146ce5
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kyokomi/emoji/v2 v2.2.8
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
@ -35,7 +34,7 @@ require (
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattn/go-sqlite3 v1.14.7
github.com/microcosm-cc/bluemonday v1.0.9
github.com/miekg/dns v1.1.41 // indirect
github.com/miekg/dns v1.1.42 // indirect
github.com/mitchellh/go-server-timing v1.0.1
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.9.0 // indirect
@ -54,14 +53,14 @@ require (
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
github.com/yuin/goldmark v1.3.5
github.com/yuin/goldmark-emoji v1.0.1
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
golang.org/x/mod v0.4.1 // indirect
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
golang.org/x/net v0.0.0-20210505214959-0714010a04ed
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect
golang.org/x/sys v0.0.0-20210507014357-30e306a8bba5 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.62.0 // indirect

29
go.sum
View File

@ -41,8 +41,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/caddyserver/certmagic v0.13.0 h1:ky0rntZvIFiUKFdIikYxj31WN+Ts0Od6Wjz83iTzxfc=
github.com/caddyserver/certmagic v0.13.0/go.mod h1:dNOzF4iOB7H9E51xTooMB90vs+2XNVtpnx0liQNsQY4=
github.com/caddyserver/certmagic v0.13.1 h1:A5qLxh9J6/CYWEOHaj135IWAjCY0193ONxEy8jbOlPw=
github.com/caddyserver/certmagic v0.13.1/go.mod h1:+zhQtEgLOyXRA/KRduHXNhGGdTeqRM4ePj8eBGD/2CQ=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
@ -180,9 +180,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid v1.2.5/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -232,8 +231,8 @@ github.com/microcosm-cc/bluemonday v1.0.9 h1:dpCwruVKoyrULicJwhuY76jB+nIxRVKv/e2
github.com/microcosm-cc/bluemonday v1.0.9/go.mod h1:B2riunDr9benLHghZB7hjIgdwSUzzs0pjCxFrWYEZFU=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-server-timing v1.0.1 h1:f00/aIe8T3MrnLhQHu3tSWvnwc5GV/p5eutuu3hF/tE=
@ -352,8 +351,8 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
@ -368,8 +367,8 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -416,8 +415,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210505214959-0714010a04ed h1:V9kAVxLvz1lkufatrpHuUVyJ/5tR3Ms7rk951P4mI98=
golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -454,8 +453,8 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210507014357-30e306a8bba5 h1:cez+MEm4+A0CG7ik1Qzj3bmK9DFoouuLom9lwM+Ijow=
golang.org/x/sys v0.0.0-20210507014357-30e306a8bba5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@ -21,3 +21,11 @@ func healthcheck() bool {
_, _ = io.Copy(io.Discard, resp.Body)
return resp.StatusCode == 200
}
func healthcheckExitCode() int {
if healthcheck() {
return 0
} else {
return 1
}
}

21
http.go
View File

@ -81,11 +81,7 @@ func startServer() (err error) {
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
}
go onShutdown(func() {
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
_ = s.Shutdown(toc)
c()
})
addShutdownFunc(shutdownServer(s, "main server"))
if appConfig.Server.PublicHTTPS {
// Configure
certmagic.Default.Storage = &certmagic.FileStorage{Path: "data/https"}
@ -98,11 +94,7 @@ func startServer() (err error) {
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
}
go onShutdown(func() {
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
_ = httpServer.Shutdown(toc)
c()
})
addShutdownFunc(shutdownServer(httpServer, "http server"))
go func() {
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Println("Failed to start HTTP server:", err.Error())
@ -130,6 +122,15 @@ func startServer() (err error) {
return nil
}
func shutdownServer(s *http.Server, name string) func() {
return func() {
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
_ = s.Shutdown(toc)
c()
log.Println("Stopped server:", name)
}
}
func redirectToHttps(w http.ResponseWriter, r *http.Request) {
requestHost, _, err := net.SplitHostPort(r.Host)
if err != nil {

59
main.go
View File

@ -21,12 +21,12 @@ func main() {
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
log.Fatalln("could not create CPU profile: ", err)
return
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
log.Fatalln("could not start CPU profile: ", err)
return
}
defer pprof.StopCPUProfile()
@ -50,17 +50,16 @@ func main() {
// Initialize config
log.Println("Initialize configuration...")
if err = initConfig(); err != nil {
log.Fatalln("Failed to init config:", err.Error())
logErrAndQuit("Failed to init config:", err.Error())
return
}
// Healthcheck tool
if len(os.Args) >= 2 && os.Args[1] == "healthcheck" {
// Connect to public address + "/ping" and exit with 0 when successful
if health := healthcheck(); health {
os.Exit(0)
} else {
os.Exit(1)
}
health := healthcheckExitCode()
shutdown()
os.Exit(health)
return
}
@ -71,10 +70,11 @@ func main() {
AccountName: appConfig.User.Nick,
})
if err != nil {
log.Fatalln(err.Error())
logErrAndQuit(err.Error())
return
}
log.Println("TOTP-Secret:", key.Secret())
shutdown()
return
}
@ -87,7 +87,7 @@ func main() {
// Initialize database and markdown
log.Println("Initialize database...")
if err = initDatabase(); err != nil {
log.Fatalln("Failed to init database:", err.Error())
logErrAndQuit("Failed to init database:", err.Error())
return
}
log.Println("Initialize server components...")
@ -96,45 +96,42 @@ func main() {
// Link check tool after init of markdown
if len(os.Args) >= 2 && os.Args[1] == "check" {
checkAllExternalLinks()
if err = closeDb(); err != nil {
log.Fatalln("Failed to close DB:", err.Error())
return
}
shutdown()
return
}
// More initializations
initMinify()
if err = initTemplateAssets(); err != nil { // Needs minify
log.Fatalln("Failed to init template assets:", err.Error())
logErrAndQuit("Failed to init template assets:", err.Error())
return
}
if err = initTemplateStrings(); err != nil {
log.Fatalln("Failed to init template translations:", err.Error())
logErrAndQuit("Failed to init template translations:", err.Error())
return
}
if err = initRendering(); err != nil { // Needs assets and minify
log.Fatalln("Failed to init HTML rendering:", err.Error())
logErrAndQuit("Failed to init HTML rendering:", err.Error())
return
}
if err = initCache(); err != nil {
log.Fatalln("Failed to init HTTP cache:", err.Error())
logErrAndQuit("Failed to init HTTP cache:", err.Error())
return
}
if err = initRegexRedirects(); err != nil {
log.Fatalln("Failed to init redirects:", err.Error())
logErrAndQuit("Failed to init redirects:", err.Error())
return
}
if err = initHTTPLog(); err != nil {
log.Fatal("Failed to init HTTP logging:", err.Error())
logErrAndQuit("Failed to init HTTP logging:", err.Error())
return
}
if err = initActivityPub(); err != nil {
log.Fatalln("Failed to init ActivityPub:", err.Error())
logErrAndQuit("Failed to init ActivityPub:", err.Error())
return
}
if err = initWebmention(); err != nil {
log.Fatalln("Failed to init webmention support:", err.Error())
logErrAndQuit("Failed to init webmention support:", err.Error())
return
}
initTelegram()
@ -143,22 +140,20 @@ func main() {
startHourlyHooks()
// Start the server
log.Println("Starting server...")
log.Println("Starting server(s)...")
err = startServer()
if err != nil {
log.Fatalln("Failed to start server:", err.Error())
logErrAndQuit("Failed to start server(s):", err.Error())
return
}
log.Println("Stopped server(s)")
// Wait till everything is shutdown
waitForShutdown()
// Close DB
if err = closeDb(); err != nil {
log.Fatalln("Failed to close DB:", err.Error())
return
}
log.Println("Closed Database")
}
func logErrAndQuit(v ...interface{}) {
log.Println(v...)
shutdown()
os.Exit(1)
}

View File

@ -7,15 +7,40 @@ import (
"syscall"
)
var shutdownWg sync.WaitGroup
var (
quit = make(chan os.Signal, 1)
shutdownFuncs = []func(){}
shutdownWg sync.WaitGroup
shutdownFuncMapMutex sync.Mutex
)
func onShutdown(f func()) {
defer shutdownWg.Done()
func init() {
signal.Notify(quit,
os.Interrupt,
syscall.SIGINT,
syscall.SIGTERM, // e.g. Docker stop
)
go func() {
<-quit
shutdown()
}()
}
func addShutdownFunc(f func()) {
shutdownWg.Add(1)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-quit
f()
shutdownFuncMapMutex.Lock()
shutdownFuncs = append(shutdownFuncs, f)
shutdownFuncMapMutex.Unlock()
}
func shutdown() {
for _, f := range shutdownFuncs {
go func(f func()) {
defer shutdownWg.Done()
f()
}(f)
}
shutdownWg.Wait()
}
func waitForShutdown() {

6
tor.go
View File

@ -81,11 +81,7 @@ func startOnionService(h http.Handler) error {
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
}
go onShutdown(func() {
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
_ = s.Shutdown(toc)
c()
})
addShutdownFunc(shutdownServer(s, "tor"))
if err = s.Serve(onion); err != nil && err != http.ErrServerClosed {
return err
}