@marcel91200 / caddy Mirror · read-only / commits / a10910f3
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

admin: Sync server variables (fix #4260) (#4274)

* Synchronize server assignment/references to avoid data race * only hold lock during var reassignment

Steven Angles committed Aug 16, 2021 at 17:04 UTC a10910f3981908424493c043d26dfcb4e5f8dc25
2 files changed +49 -20
admin.go
+13 -2
@@ -335,6 +335,7 @@ func replaceLocalAdminServer(cfg *Config) error {
335 return err
336 }
337
338 + serverMu.Lock()
339 localAdminServer = &http.Server{
340 Addr: addr.String(), // for logging purposes only
341 Handler: handler,
@@ -343,10 +344,14 @@ func replaceLocalAdminServer(cfg *Config) error {
344 IdleTimeout: 60 * time.Second,
345 MaxHeaderBytes: 1024 * 64,
346 }
347 + serverMu.Unlock()
348
349 adminLogger := Log().Named("admin")
350 go func() {
349 - if err := localAdminServer.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
351 + serverMu.Lock()
352 + server := localAdminServer
353 + serverMu.Unlock()
354 + if err := server.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
355 adminLogger.Error("admin server shutdown for unknown reason", zap.Error(err))
356 }
357 }()
@@ -474,6 +479,7 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
479 return err
480 }
481
482 + serverMu.Lock()
483 // create secure HTTP server
484 remoteAdminServer = &http.Server{
485 Addr: addr.String(), // for logging purposes only
@@ -485,6 +491,7 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
491 MaxHeaderBytes: 1024 * 64,
492 ErrorLog: serverLogger,
493 }
494 + serverMu.Unlock()
495
496 // start listener
497 ln, err := Listen(addr.Network, addr.JoinHostPort(0))
@@ -494,7 +501,10 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
501 ln = tls.NewListener(ln, tlsConfig)
502
503 go func() {
497 - if err := remoteAdminServer.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
504 + serverMu.Lock()
505 + server := remoteAdminServer
506 + serverMu.Unlock()
507 + if err := server.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
508 remoteLogger.Error("admin remote server shutdown for unknown reason", zap.Error(err))
509 }
510 }()
@@ -1229,6 +1239,7 @@ var bufPool = sync.Pool{
1239
1240 // keep a reference to admin endpoint singletons while they're active
1241 var (
1242 + serverMu sync.Mutex
1243 localAdminServer, remoteAdminServer *http.Server
1244 identityCertCache *certmagic.Cache
1245 )
admin_test.go
+36 -18
@@ -17,9 +17,28 @@ package caddy
17 import (
18 "encoding/json"
19 "reflect"
20 + "sync"
21 "testing"
22 )
23
24 +var testCfg = []byte(`{
25 + "apps": {
26 + "http": {
27 + "servers": {
28 + "myserver": {
29 + "listen": ["tcp/localhost:8080-8084"],
30 + "read_timeout": "30s"
31 + },
32 + "yourserver": {
33 + "listen": ["127.0.0.1:5000"],
34 + "read_header_timeout": "15s"
35 + }
36 + }
37 + }
38 + }
39 + }
40 + `)
41 +
42 func TestUnsyncedConfigAccess(t *testing.T) {
43 // each test is performed in sequence, so
44 // each change builds on the previous ones;
@@ -108,25 +127,24 @@ func TestUnsyncedConfigAccess(t *testing.T) {
127 }
128 }
129
130 +// TestLoadConcurrent exercises Load under concurrent conditions
131 +// and is most useful under test with `-race` enabled.
132 +func TestLoadConcurrent(t *testing.T) {
133 + var wg sync.WaitGroup
134 +
135 + for i := 0; i < 100; i++ {
136 + wg.Add(1)
137 + go func() {
138 + _ = Load(testCfg, true)
139 + wg.Done()
140 + }()
141 + }
142 +
143 + wg.Wait()
144 +}
145 +
146 func BenchmarkLoad(b *testing.B) {
147 for i := 0; i < b.N; i++ {
113 - cfg := []byte(`{
114 - "apps": {
115 - "http": {
116 - "servers": {
117 - "myserver": {
118 - "listen": ["tcp/localhost:8080-8084"],
119 - "read_timeout": "30s"
120 - },
121 - "yourserver": {
122 - "listen": ["127.0.0.1:5000"],
123 - "read_header_timeout": "15s"
124 - }
125 - }
126 - }
127 - }
128 - }
129 - `)
130 - Load(cfg, true)
148 + Load(testCfg, true)
149 }
150 }