Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c22f0cb

Browse files
周曙光kevwan
周曙光
authored andcommittedJan 17, 2025··
fix: health ok after server start
1 parent 3d931d7 commit c22f0cb

File tree

8 files changed

+138
-27
lines changed

8 files changed

+138
-27
lines changed
 

‎rest/engine.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/zeromicro/go-zero/core/codec"
1212
"github.com/zeromicro/go-zero/core/load"
1313
"github.com/zeromicro/go-zero/core/stat"
14+
"github.com/zeromicro/go-zero/internal/health"
1415
"github.com/zeromicro/go-zero/rest/chain"
1516
"github.com/zeromicro/go-zero/rest/handler"
1617
"github.com/zeromicro/go-zero/rest/httpx"
@@ -305,7 +306,7 @@ func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain.Chai
305306
}, nil
306307
}
307308

308-
func (ng *engine) start(router httpx.Router, opts ...StartOption) error {
309+
func (ng *engine) start(router httpx.Router, probe health.Probe, opts ...StartOption) error {
309310
if err := ng.bindRoutes(router); err != nil {
310311
return err
311312
}
@@ -314,7 +315,7 @@ func (ng *engine) start(router httpx.Router, opts ...StartOption) error {
314315
opts = append([]StartOption{ng.withTimeout()}, opts...)
315316

316317
if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {
317-
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router, opts...)
318+
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router, probe, opts...)
318319
}
319320

320321
// make sure user defined options overwrite default options
@@ -327,7 +328,7 @@ func (ng *engine) start(router httpx.Router, opts ...StartOption) error {
327328
}, opts...)
328329

329330
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
330-
ng.conf.KeyFile, router, opts...)
331+
ng.conf.KeyFile, router, probe, opts...)
331332
}
332333

333334
func (ng *engine) use(middleware Middleware) {

‎rest/engine_test.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ Verbose: true
223223
}
224224
})
225225

226-
assert.NotNil(t, ng.start(mockedRouter{}, func(svr *http.Server) {
226+
assert.NotNil(t, ng.start(mockedRouter{}, mockProbe{}, func(svr *http.Server) {
227227
}))
228228

229229
timeout := time.Second * 3
@@ -414,7 +414,7 @@ func TestEngine_start(t *testing.T) {
414414
Host: "localhost",
415415
Port: -1,
416416
})
417-
assert.Error(t, ng.start(router.NewRouter()))
417+
assert.Error(t, ng.start(router.NewRouter(), mockProbe{}))
418418
})
419419

420420
t.Run("https", func(t *testing.T) {
@@ -425,10 +425,20 @@ func TestEngine_start(t *testing.T) {
425425
KeyFile: "bar",
426426
})
427427
ng.tlsConfig = &tls.Config{}
428-
assert.Error(t, ng.start(router.NewRouter()))
428+
assert.Error(t, ng.start(router.NewRouter(), mockProbe{}))
429429
})
430430
}
431431

432+
type mockProbe struct{}
433+
434+
func (m mockProbe) MarkReady() {}
435+
436+
func (m mockProbe) MarkNotReady() {}
437+
438+
func (m mockProbe) IsReady() bool { return false }
439+
440+
func (m mockProbe) Name() string { return "" }
441+
432442
type mockedRouter struct {
433443
}
434444

‎rest/internal/starter.go

+8-11
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,26 @@ import (
1111
"github.com/zeromicro/go-zero/internal/health"
1212
)
1313

14-
const probeNamePrefix = "rest"
15-
1614
// StartOption defines the method to customize http.Server.
1715
type StartOption func(svr *http.Server)
1816

1917
// StartHttp starts a http server.
20-
func StartHttp(host string, port int, handler http.Handler, opts ...StartOption) error {
21-
return start(host, port, handler, func(svr *http.Server) error {
18+
func StartHttp(host string, port int, handler http.Handler, probe health.Probe, opts ...StartOption) error {
19+
return start(host, port, handler, probe, func(svr *http.Server) error {
2220
return svr.ListenAndServe()
2321
}, opts...)
2422
}
2523

2624
// StartHttps starts a https server.
27-
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler,
25+
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler, probe health.Probe,
2826
opts ...StartOption) error {
29-
return start(host, port, handler, func(svr *http.Server) error {
27+
return start(host, port, handler, probe, func(svr *http.Server) error {
3028
// certFile and keyFile are set in buildHttpsServer
3129
return svr.ListenAndServeTLS(certFile, keyFile)
3230
}, opts...)
3331
}
3432

35-
func start(host string, port int, handler http.Handler, run func(svr *http.Server) error,
33+
func start(host string, port int, handler http.Handler, probe health.Probe, run func(svr *http.Server) error,
3634
opts ...StartOption) (err error) {
3735
server := &http.Server{
3836
Addr: fmt.Sprintf("%s:%d", host, port),
@@ -41,21 +39,20 @@ func start(host string, port int, handler http.Handler, run func(svr *http.Serve
4139
for _, opt := range opts {
4240
opt(server)
4341
}
44-
healthManager := health.NewHealthManager(fmt.Sprintf("%s-%s:%d", probeNamePrefix, host, port))
4542

4643
waitForCalled := proc.AddShutdownListener(func() {
47-
healthManager.MarkNotReady()
4844
if e := server.Shutdown(context.Background()); e != nil {
4945
logx.Error(e)
5046
}
5147
})
5248
defer func() {
5349
if errors.Is(err, http.ErrServerClosed) {
50+
probe.MarkNotReady()
5451
waitForCalled()
5552
}
5653
}()
5754

58-
healthManager.MarkReady()
59-
health.AddProbe(healthManager)
55+
probe.MarkReady()
56+
6057
return run(server)
6158
}

‎rest/internal/starter_test.go

+51-2
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ import (
66
"strconv"
77
"strings"
88
"testing"
9+
"time"
910

1011
"github.com/stretchr/testify/assert"
1112
"github.com/zeromicro/go-zero/core/proc"
13+
"github.com/zeromicro/go-zero/core/syncx"
1214
)
1315

1416
func TestStartHttp(t *testing.T) {
1517
svr := httptest.NewUnstartedServer(http.NotFoundHandler())
1618
fields := strings.Split(svr.Listener.Addr().String(), ":")
1719
port, err := strconv.Atoi(fields[1])
1820
assert.Nil(t, err)
19-
err = StartHttp(fields[0], port, http.NotFoundHandler(), func(svr *http.Server) {
21+
err = StartHttp(fields[0], port, http.NotFoundHandler(), &mockProbe{}, func(svr *http.Server) {
2022
svr.IdleTimeout = 0
2123
})
2224
assert.NotNil(t, err)
@@ -28,9 +30,56 @@ func TestStartHttps(t *testing.T) {
2830
fields := strings.Split(svr.Listener.Addr().String(), ":")
2931
port, err := strconv.Atoi(fields[1])
3032
assert.Nil(t, err)
31-
err = StartHttps(fields[0], port, "", "", http.NotFoundHandler(), func(svr *http.Server) {
33+
err = StartHttps(fields[0], port, "", "", http.NotFoundHandler(), &mockProbe{}, func(svr *http.Server) {
3234
svr.IdleTimeout = 0
3335
})
3436
assert.NotNil(t, err)
3537
proc.WrapUp()
3638
}
39+
func TestStartWithShutdownListener(t *testing.T) {
40+
probe := &mockProbe{}
41+
shutdownCalled := make(chan struct{})
42+
serverStarted := make(chan struct{})
43+
serverClosed := make(chan struct{})
44+
45+
run := func(svr *http.Server) error {
46+
close(serverStarted)
47+
<-shutdownCalled
48+
return http.ErrServerClosed
49+
}
50+
51+
go func() {
52+
err := start("localhost", 8888, http.NotFoundHandler(), probe, run)
53+
assert.Equal(t, http.ErrServerClosed, err)
54+
close(serverClosed)
55+
}()
56+
57+
select {
58+
case <-serverStarted:
59+
assert.True(t, probe.IsReady(), "server should be marked as ready")
60+
case <-time.After(time.Second):
61+
t.Fatal("timeout waiting for server to start")
62+
}
63+
64+
proc.WrapUp()
65+
time.Sleep(time.Millisecond * 50)
66+
close(shutdownCalled)
67+
}
68+
69+
type mockProbe struct {
70+
ready syncx.AtomicBool
71+
}
72+
73+
func (m *mockProbe) MarkReady() {
74+
m.ready.Set(true)
75+
}
76+
77+
func (m *mockProbe) MarkNotReady() {
78+
m.ready.Set(false)
79+
}
80+
81+
func (m *mockProbe) IsReady() bool {
82+
return m.ready.True()
83+
}
84+
85+
func (m *mockProbe) Name() string { return "" }

‎rest/server.go

+15-6
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package rest
33
import (
44
"crypto/tls"
55
"errors"
6+
"fmt"
67
"net/http"
78
"path"
89
"time"
910

1011
"github.com/zeromicro/go-zero/core/logx"
12+
"github.com/zeromicro/go-zero/internal/health"
1113
"github.com/zeromicro/go-zero/rest/chain"
1214
"github.com/zeromicro/go-zero/rest/handler"
1315
"github.com/zeromicro/go-zero/rest/httpx"
@@ -17,6 +19,8 @@ import (
1719
"github.com/zeromicro/go-zero/rest/router"
1820
)
1921

22+
const probeNamePrefix = "rest"
23+
2024
type (
2125
// RunOption defines the method to customize a Server.
2226
RunOption func(*Server)
@@ -26,8 +30,9 @@ type (
2630

2731
// A Server is a http server.
2832
Server struct {
29-
ngin *engine
30-
router httpx.Router
33+
ngin *engine
34+
router httpx.Router
35+
healthManager health.Probe
3136
}
3237
)
3338

@@ -50,9 +55,13 @@ func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
5055
return nil, err
5156
}
5257

58+
healthManager := health.NewHealthManager(fmt.Sprintf("%s-%s:%d", probeNamePrefix, c.Host, c.Port))
59+
health.AddProbe(healthManager)
60+
5361
server := &Server{
54-
ngin: newEngine(c),
55-
router: router.NewRouter(),
62+
ngin: newEngine(c),
63+
router: router.NewRouter(),
64+
healthManager: healthManager,
5665
}
5766

5867
opts = append([]RunOption{WithNotFoundHandler(nil)}, opts...)
@@ -118,14 +127,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
118127
// Graceful shutdown is enabled by default.
119128
// Use proc.SetTimeToForceQuit to customize the graceful shutdown period.
120129
func (s *Server) Start() {
121-
handleError(s.ngin.start(s.router))
130+
handleError(s.ngin.start(s.router, s.healthManager))
122131
}
123132

124133
// StartWithOpts starts the Server.
125134
// Graceful shutdown is enabled by default.
126135
// Use proc.SetTimeToForceQuit to customize the graceful shutdown period.
127136
func (s *Server) StartWithOpts(opts ...StartOption) {
128-
handleError(s.ngin.start(s.router, opts...))
137+
handleError(s.ngin.start(s.router, s.healthManager, opts...))
129138
}
130139

131140
// Stop stops the Server.

‎rest/server_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/stretchr/testify/assert"
1818
"github.com/zeromicro/go-zero/core/conf"
1919
"github.com/zeromicro/go-zero/core/logx/logtest"
20+
"github.com/zeromicro/go-zero/core/service"
2021
"github.com/zeromicro/go-zero/rest/chain"
2122
"github.com/zeromicro/go-zero/rest/httpx"
2223
"github.com/zeromicro/go-zero/rest/internal/cors"
@@ -754,6 +755,33 @@ Port: 54321
754755
}
755756
}
756757

758+
func TestServerProbe(t *testing.T) {
759+
server := MustNewServer(RestConf{
760+
ServiceConf: service.ServiceConf{
761+
DevServer: service.DevServerConfig{
762+
Host: "localhost",
763+
Port: 6061,
764+
HealthPath: "/healthz",
765+
Enabled: true,
766+
},
767+
},
768+
Host: "localhost",
769+
Port: 8888,
770+
})
771+
assert.NotNil(t, server)
772+
assert.False(t, server.healthManager.IsReady())
773+
resp, err := http.Get("http://localhost:6061/healthz")
774+
assert.Nil(t, err)
775+
assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
776+
777+
go server.Start()
778+
defer server.Stop()
779+
780+
assert.Eventually(t, func() bool {
781+
return server.healthManager.IsReady()
782+
}, time.Millisecond*100, time.Millisecond*10)
783+
}
784+
757785
//go:embed testdata
758786
var content embed.FS
759787

‎zrpc/internal/rpcserver.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ func NewRpcServer(addr string, opts ...ServerOption) Server {
3434
opt(&options)
3535
}
3636

37+
healthManager := health.NewHealthManager(fmt.Sprintf("%s-%s", probeNamePrefix, addr))
38+
health.AddProbe(healthManager)
39+
3740
return &rpcServer{
3841
baseRpcServer: newBaseRpcServer(addr, &options),
39-
healthManager: health.NewHealthManager(fmt.Sprintf("%s-%s", probeNamePrefix, addr)),
42+
healthManager: healthManager,
4043
}
4144
}
4245

@@ -63,7 +66,6 @@ func (s *rpcServer) Start(register RegisterFn) error {
6366
s.health.Resume()
6467
}
6568
s.healthManager.MarkReady()
66-
health.AddProbe(s.healthManager)
6769

6870
// we need to make sure all others are wrapped up,
6971
// so we do graceful stop at shutdown phase instead of wrap up phase

‎zrpc/internal/rpcserver_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,18 @@ func TestRpcServer_WithBadAddress(t *testing.T) {
5252

5353
proc.WrapUp()
5454
}
55+
56+
func TestServerProbe(t *testing.T) {
57+
server := NewRpcServer("localhost:12345")
58+
assert.NotNil(t, server)
59+
svr, ok := server.(*rpcServer)
60+
assert.True(t, ok)
61+
assert.False(t, svr.healthManager.IsReady())
62+
go func() {
63+
err := svr.Start(func(server *grpc.Server) {})
64+
assert.Nil(t, err)
65+
}()
66+
assert.Eventually(t, func() bool {
67+
return svr.healthManager.IsReady()
68+
}, time.Millisecond*100, time.Millisecond*10)
69+
}

0 commit comments

Comments
 (0)
Please sign in to comment.