]> git.arvados.org - arvados.git/blob - sdk/go/httpserver/httpserver.go
22360: Render dummy lines for first time DE skeleton loader
[arvados.git] / sdk / go / httpserver / httpserver.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package httpserver
6
7 import (
8         "log"
9         "net"
10         "net/http"
11         "os"
12         "strings"
13         "sync"
14         "time"
15 )
16
17 type Server struct {
18         http.Server
19         Addr     string // host:port where the server is listening.
20         err      error
21         cond     *sync.Cond
22         running  bool
23         listener *net.TCPListener
24         wantDown bool
25 }
26
27 // Start is essentially (*http.Server)ListenAndServe() with two more
28 // features: (1) by the time Start() returns, Addr is changed to the
29 // address:port we ended up listening to -- which makes listening on
30 // ":0" useful in test suites -- and (2) the server can be shut down
31 // without killing the process -- which is useful in test cases, and
32 // makes it possible to shut down gracefully on SIGTERM without
33 // killing active connections.
34 func (srv *Server) Start() error {
35         addr, err := net.ResolveTCPAddr("tcp", srv.Addr)
36         if err != nil {
37                 return err
38         }
39         srv.listener, err = listenTCP("tcp", addr)
40         if err != nil {
41                 return err
42         }
43         srv.Addr = srv.listener.Addr().String()
44
45         mutex := &sync.RWMutex{}
46         srv.cond = sync.NewCond(mutex.RLocker())
47         srv.running = true
48         go func() {
49                 lnr := tcpKeepAliveListener{srv.listener}
50                 if srv.TLSConfig != nil {
51                         err = srv.ServeTLS(lnr, "", "")
52                 } else {
53                         err = srv.Serve(lnr)
54                 }
55                 if !srv.wantDown {
56                         srv.err = err
57                 }
58                 mutex.Lock()
59                 srv.running = false
60                 srv.cond.Broadcast()
61                 mutex.Unlock()
62         }()
63         return nil
64 }
65
66 // Close shuts down the server and returns when it has stopped.
67 func (srv *Server) Close() error {
68         srv.wantDown = true
69         srv.listener.Close()
70         return srv.Wait()
71 }
72
73 // Wait returns when the server has shut down.
74 func (srv *Server) Wait() error {
75         if srv.cond == nil {
76                 return nil
77         }
78         srv.cond.L.Lock()
79         defer srv.cond.L.Unlock()
80         for srv.running {
81                 srv.cond.Wait()
82         }
83         return srv.err
84 }
85
86 // tcpKeepAliveListener is copied from net/http because not exported.
87 type tcpKeepAliveListener struct {
88         *net.TCPListener
89 }
90
91 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
92         tc, err := ln.AcceptTCP()
93         if err != nil {
94                 return
95         }
96         tc.SetKeepAlive(true)
97         tc.SetKeepAlivePeriod(3 * time.Minute)
98         return tc, nil
99 }
100
101 // net.ListenTCP, but retry after "address already in use" for up to 5
102 // minutes if running inside the arvados test suite.
103 func listenTCP(network string, addr *net.TCPAddr) (*net.TCPListener, error) {
104         if os.Getenv("ARVADOS_TEST_API_HOST") == "" {
105                 return net.ListenTCP("tcp", addr)
106         }
107         timeout := 5 * time.Minute
108         deadline := time.Now().Add(timeout)
109         logged := false
110         for {
111                 ln, err := net.ListenTCP("tcp", addr)
112                 if err != nil && strings.Contains(err.Error(), "address already in use") && time.Now().Before(deadline) {
113                         if !logged {
114                                 log.Printf("listenTCP: retrying up to %v after error: %s", timeout, err)
115                                 logged = true
116                         }
117                         time.Sleep(time.Second)
118                         continue
119                 }
120                 return ln, err
121         }
122 }