15370: Fix flaky test.
[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         "net"
9         "net/http"
10         "sync"
11         "time"
12 )
13
14 type Server struct {
15         http.Server
16         Addr     string // host:port where the server is listening.
17         err      error
18         cond     *sync.Cond
19         running  bool
20         listener *net.TCPListener
21         wantDown bool
22 }
23
24 // Start is essentially (*http.Server)ListenAndServe() with two more
25 // features: (1) by the time Start() returns, Addr is changed to the
26 // address:port we ended up listening to -- which makes listening on
27 // ":0" useful in test suites -- and (2) the server can be shut down
28 // without killing the process -- which is useful in test cases, and
29 // makes it possible to shut down gracefully on SIGTERM without
30 // killing active connections.
31 func (srv *Server) Start() error {
32         addr, err := net.ResolveTCPAddr("tcp", srv.Addr)
33         if err != nil {
34                 return err
35         }
36         srv.listener, err = net.ListenTCP("tcp", addr)
37         if err != nil {
38                 return err
39         }
40         srv.Addr = srv.listener.Addr().String()
41
42         mutex := &sync.RWMutex{}
43         srv.cond = sync.NewCond(mutex.RLocker())
44         srv.running = true
45         go func() {
46                 lnr := tcpKeepAliveListener{srv.listener}
47                 if srv.TLSConfig != nil {
48                         err = srv.ServeTLS(lnr, "", "")
49                 } else {
50                         err = srv.Serve(lnr)
51                 }
52                 if !srv.wantDown {
53                         srv.err = err
54                 }
55                 mutex.Lock()
56                 srv.running = false
57                 srv.cond.Broadcast()
58                 mutex.Unlock()
59         }()
60         return nil
61 }
62
63 // Close shuts down the server and returns when it has stopped.
64 func (srv *Server) Close() error {
65         srv.wantDown = true
66         srv.listener.Close()
67         return srv.Wait()
68 }
69
70 // Wait returns when the server has shut down.
71 func (srv *Server) Wait() error {
72         if srv.cond == nil {
73                 return nil
74         }
75         srv.cond.L.Lock()
76         defer srv.cond.L.Unlock()
77         for srv.running {
78                 srv.cond.Wait()
79         }
80         return srv.err
81 }
82
83 // tcpKeepAliveListener is copied from net/http because not exported.
84 type tcpKeepAliveListener struct {
85         *net.TCPListener
86 }
87
88 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
89         tc, err := ln.AcceptTCP()
90         if err != nil {
91                 return
92         }
93         tc.SetKeepAlive(true)
94         tc.SetKeepAlivePeriod(3 * time.Minute)
95         return tc, nil
96 }