Skip to content

Commit 30b1caa

Browse files
authored
Merge pull request #4210 from gofiber/prefork_optimization
2 parents 5a8c2fd + c1ed72f commit 30b1caa

11 files changed

Lines changed: 237 additions & 210 deletions

File tree

client/client.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"encoding/json"
1313
"encoding/xml"
1414
"errors"
15+
"fmt"
1516
"io"
1617
"os"
1718
"path/filepath"
@@ -25,6 +26,7 @@ import (
2526

2627
"github.com/valyala/fasthttp"
2728
"github.com/valyala/fasthttp/fasthttpproxy"
29+
"golang.org/x/net/http/httpproxy"
2830
)
2931

3032
var ErrFailedToAppendCert = errors.New("failed to append certificate")
@@ -311,7 +313,16 @@ func (c *Client) SetProxyURL(proxyURL string) error {
311313
c.mu.Lock()
312314
defer c.mu.Unlock()
313315

314-
c.applyDial(fasthttpproxy.FasthttpHTTPDialer(proxyURL))
316+
// Build the fasthttp proxy dialer directly so invalid proxy URLs are returned
317+
// to callers instead of being swallowed by FasthttpHTTPDialer.
318+
dialer := fasthttpproxy.Dialer{
319+
Config: httpproxy.Config{HTTPProxy: proxyURL, HTTPSProxy: proxyURL},
320+
}
321+
dialFunc, err := dialer.GetDialFunc(false)
322+
if err != nil {
323+
return fmt.Errorf("client: invalid proxy URL: %w", err)
324+
}
325+
c.applyDial(dialFunc)
315326
return nil
316327
}
317328

client/client_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,10 +2235,7 @@ func Test_Client_SetProxyURL(t *testing.T) {
22352235
t.Parallel()
22362236
client := New()
22372237

2238-
err := client.SetProxyURL(":this is not a proxy")
2239-
require.NoError(t, err)
2240-
2241-
_, err = client.Get("http://localhost:3000")
2238+
err := client.SetProxyURL("ftp://127.0.0.1:8080")
22422239
require.Error(t, err)
22432240
})
22442241
}

constants.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,3 +336,8 @@ const (
336336
ConstraintDatetime = "datetime"
337337
ConstraintRegex = "regex"
338338
)
339+
340+
// OS identifiers
341+
const (
342+
windowsOS = "windows"
343+
)

docs/api/fiber.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ app.Listen(":8080", fiber.ListenConfig{
119119
| <Reference id="ShutdownTimeout">ShutdownTimeout</Reference> | `time.Duration` | Specifies the maximum duration to wait for the server to gracefully shutdown. When the timeout is reached, the graceful shutdown process is interrupted and forcibly terminated, and the `context.DeadlineExceeded` error is passed to the `OnPostShutdown` callback. Set to 0 to disable the timeout and wait indefinitely. | `10 * time.Second` |
120120
| <Reference id="listeneraddrfunc">ListenerAddrFunc</Reference> | `func(addr net.Addr)` | Allows accessing and customizing `net.Listener`. | `nil` |
121121
| <Reference id="listenernetwork">ListenerNetwork</Reference> | `string` | Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "unix" (Unix Domain Sockets). WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen. | `tcp4` |
122+
| <Reference id="preforkrecoverthreshold">PreforkRecoverThreshold</Reference> | `int` | Defines the maximum number of child process restarts after crashes before the prefork master exits with an error. Only applies when prefork is enabled. | `max(1, runtime.GOMAXPROCS(0) / 2)` |
123+
| <Reference id="preforklogger">PreforkLogger</Reference> | `PreforkLogger` | Sets a custom logger for the prefork process manager. Only applies when prefork is enabled. | Fiber logger |
122124
| <Reference id="unixsocketfilemode">UnixSocketFileMode</Reference> | `os.FileMode` | FileMode to set for Unix Domain Socket (ListenerNetwork must be "unix") | `0770` |
123125
| <Reference id="tlsconfigfunc">TLSConfigFunc</Reference> | `func(tlsConfig *tls.Config)` | Allows customizing `tls.Config` as you want. Ignored when `TLSConfig` is set. | `nil` |
124126
| <Reference id="tlsconfig">TLSConfig</Reference> | `*tls.Config` | Recommended base TLS configuration (cloned). Use for external certificate providers via `GetCertificate`. When set, other TLS fields are ignored. | `nil` |

hooks_test.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
1313
"github.com/valyala/bytebufferpool"
14+
"github.com/valyala/fasthttp/prefork"
1415

1516
"github.com/gofiber/fiber/v3/log"
1617
)
@@ -519,7 +520,8 @@ func Test_ListenData_Hook_HelperFunctions(t *testing.T) {
519520
}
520521

521522
func Test_Hook_OnListenPrefork(t *testing.T) {
522-
t.Parallel()
523+
enableTestPreforkMaster(t)
524+
523525
app := New()
524526

525527
buf := bytebufferpool.Get()
@@ -532,33 +534,32 @@ func Test_Hook_OnListenPrefork(t *testing.T) {
532534
return nil
533535
})
534536

535-
go func() {
536-
time.Sleep(1000 * time.Millisecond)
537-
assert.NoError(t, app.Shutdown())
538-
}()
539-
540-
require.NoError(t, app.Listen(":0", ListenConfig{DisableStartupMessage: true, EnablePrefork: true}))
537+
err := app.Listen(":0", ListenConfig{
538+
DisableStartupMessage: true,
539+
EnablePrefork: true,
540+
PreforkRecoverThreshold: 1,
541+
})
542+
require.ErrorIs(t, err, prefork.ErrOverRecovery)
541543
require.Equal(t, "ready", buf.String())
542544
}
543545

544546
func Test_Hook_OnHook(t *testing.T) {
545547
app := New()
546548

547-
// Reset test var
548-
testPreforkMaster = true
549-
testOnPrefork = true
550-
551-
go func() {
552-
time.Sleep(1000 * time.Millisecond)
553-
assert.NoError(t, app.Shutdown())
554-
}()
549+
enableTestPreforkMaster(t)
550+
enableTestOnPrefork(t)
555551

556552
app.Hooks().OnFork(func(pid int) error {
557553
require.Equal(t, 1, pid)
558554
return nil
559555
})
560556

561-
require.NoError(t, app.prefork(":0", nil, &ListenConfig{DisableStartupMessage: true, EnablePrefork: true}))
557+
err := app.prefork(":0", nil, &ListenConfig{
558+
DisableStartupMessage: true,
559+
EnablePrefork: true,
560+
PreforkRecoverThreshold: 1,
561+
})
562+
require.ErrorIs(t, err, prefork.ErrOverRecovery)
562563
}
563564

564565
func Test_Hook_OnMount(t *testing.T) {

listen.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ type ListenConfig struct {
4747
// Default: nil
4848
GracefulContext context.Context `json:"graceful_context"` //nolint:containedctx // It's needed to set context inside Listen.
4949

50+
// PreforkLogger sets a custom logger for the prefork process manager.
51+
// This only applies when EnablePrefork is true.
52+
//
53+
// Default: Fiber's built-in logger (log.Infof)
54+
PreforkLogger PreforkLogger `json:"prefork_logger"`
55+
5056
// TLSConfigFunc allows customizing tls.Config as you want.
5157
//
5258
// Default: nil
@@ -126,6 +132,13 @@ type ListenConfig struct {
126132
// Default: false
127133
EnablePrefork bool `json:"enable_prefork"`
128134

135+
// PreforkRecoverThreshold defines the maximum number of times a child process
136+
// can be restarted after crashing before the master process exits with an error.
137+
// This only applies when EnablePrefork is true.
138+
//
139+
// Default: max(1, runtime.GOMAXPROCS(0) / 2)
140+
PreforkRecoverThreshold int `json:"prefork_recover_threshold"`
141+
129142
// If set to true, will print all routes with their method, path and handler.
130143
//
131144
// Default: false

listen_test.go

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/stretchr/testify/require"
2121
"github.com/valyala/fasthttp"
2222
"github.com/valyala/fasthttp/fasthttputil"
23+
"github.com/valyala/fasthttp/prefork"
2324
"golang.org/x/crypto/acme/autocert"
2425
)
2526

@@ -165,16 +166,21 @@ func testGracefulShutdown(t *testing.T, shutdownTimeout time.Duration) {
165166

166167
// go test -run Test_Listen_Prefork
167168
func Test_Listen_Prefork(t *testing.T) {
168-
testPreforkMaster = true
169+
enableTestPreforkMaster(t)
169170

170171
app := New()
171172

172-
require.NoError(t, app.Listen(":0", ListenConfig{DisableStartupMessage: true, EnablePrefork: true}))
173+
err := app.Listen(":0", ListenConfig{
174+
DisableStartupMessage: true,
175+
EnablePrefork: true,
176+
PreforkRecoverThreshold: 1,
177+
})
178+
require.ErrorIs(t, err, prefork.ErrOverRecovery)
173179
}
174180

175181
// go test -run Test_Listen_TLSMinVersion
176182
func Test_Listen_TLSMinVersion(t *testing.T) {
177-
testPreforkMaster = true
183+
enableTestPreforkMaster(t)
178184

179185
app := New()
180186

@@ -202,11 +208,13 @@ func Test_Listen_TLSMinVersion(t *testing.T) {
202208
require.NoError(t, app.Listen(":0", ListenConfig{TLSMinVersion: tls.VersionTLS13}))
203209

204210
// Valid TLSMinVersion with Prefork
205-
go func() {
206-
time.Sleep(1000 * time.Millisecond)
207-
assert.NoError(t, app.Shutdown())
208-
}()
209-
require.NoError(t, app.Listen(":0", ListenConfig{DisableStartupMessage: true, EnablePrefork: true, TLSMinVersion: tls.VersionTLS13}))
211+
err := app.Listen(":0", ListenConfig{
212+
DisableStartupMessage: true,
213+
EnablePrefork: true,
214+
TLSMinVersion: tls.VersionTLS13,
215+
PreforkRecoverThreshold: 1,
216+
})
217+
require.ErrorIs(t, err, prefork.ErrOverRecovery)
210218
}
211219

212220
// go test -run Test_Listen_TLS
@@ -232,7 +240,7 @@ func Test_Listen_TLS(t *testing.T) {
232240

233241
// go test -run Test_Listen_TLS_Prefork
234242
func Test_Listen_TLS_Prefork(t *testing.T) {
235-
testPreforkMaster = true
243+
enableTestPreforkMaster(t)
236244

237245
app := New()
238246

@@ -244,17 +252,14 @@ func Test_Listen_TLS_Prefork(t *testing.T) {
244252
CertKeyFile: "./.github/testdata/template.tmpl",
245253
}))
246254

247-
go func() {
248-
time.Sleep(1000 * time.Millisecond)
249-
assert.NoError(t, app.Shutdown())
250-
}()
251-
252-
require.NoError(t, app.Listen(":0", ListenConfig{
253-
DisableStartupMessage: true,
254-
EnablePrefork: true,
255-
CertFile: "./.github/testdata/ssl.pem",
256-
CertKeyFile: "./.github/testdata/ssl.key",
257-
}))
255+
tlsErr := app.Listen(":0", ListenConfig{
256+
DisableStartupMessage: true,
257+
EnablePrefork: true,
258+
CertFile: "./.github/testdata/ssl.pem",
259+
CertKeyFile: "./.github/testdata/ssl.key",
260+
PreforkRecoverThreshold: 1,
261+
})
262+
require.ErrorIs(t, tlsErr, prefork.ErrOverRecovery)
258263
}
259264

260265
// go test -run Test_Listen_MutualTLS
@@ -282,7 +287,7 @@ func Test_Listen_MutualTLS(t *testing.T) {
282287

283288
// go test -run Test_Listen_MutualTLS_Prefork
284289
func Test_Listen_MutualTLS_Prefork(t *testing.T) {
285-
testPreforkMaster = true
290+
enableTestPreforkMaster(t)
286291

287292
app := New()
288293

@@ -295,18 +300,15 @@ func Test_Listen_MutualTLS_Prefork(t *testing.T) {
295300
CertClientFile: "./.github/testdata/ca-chain.cert.pem",
296301
}))
297302

298-
go func() {
299-
time.Sleep(1000 * time.Millisecond)
300-
assert.NoError(t, app.Shutdown())
301-
}()
302-
303-
require.NoError(t, app.Listen(":0", ListenConfig{
304-
DisableStartupMessage: true,
305-
EnablePrefork: true,
306-
CertFile: "./.github/testdata/ssl.pem",
307-
CertKeyFile: "./.github/testdata/ssl.key",
308-
CertClientFile: "./.github/testdata/ca-chain.cert.pem",
309-
}))
303+
mtlsErr := app.Listen(":0", ListenConfig{
304+
DisableStartupMessage: true,
305+
EnablePrefork: true,
306+
CertFile: "./.github/testdata/ssl.pem",
307+
CertKeyFile: "./.github/testdata/ssl.key",
308+
CertClientFile: "./.github/testdata/ca-chain.cert.pem",
309+
PreforkRecoverThreshold: 1,
310+
})
311+
require.ErrorIs(t, mtlsErr, prefork.ErrOverRecovery)
310312
}
311313

312314
// go test -run Test_Listener

middleware/logger/logger_test.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -665,28 +665,31 @@ func Test_Logger_WithLatency(t *testing.T) {
665665
defer bytebufferpool.Put(buff)
666666
app := fiber.New()
667667

668+
var latencyDuration time.Duration
669+
fixedStart := time.Unix(0, 0)
668670
logger := New(Config{
669671
Stream: buff,
670672
Format: "${latency}",
673+
LoggerFunc: func(c fiber.Ctx, data *Data, cfg *Config) error {
674+
data.Start = fixedStart
675+
data.Stop = fixedStart.Add(latencyDuration)
676+
return defaultLoggerInstance(c, data, cfg)
677+
},
671678
})
672679
app.Use(logger)
673680

674681
// Define a list of time units to test
675682
timeUnits := getLatencyTimeUnits()
676683

677-
// Initialize a new time unit
678-
sleepDuration := 1 * time.Nanosecond
679-
680-
// Define a test route that sleeps
684+
// Define a test route
681685
app.Get("/test", func(c fiber.Ctx) error {
682-
time.Sleep(sleepDuration)
683686
return c.SendStatus(fiber.StatusOK)
684687
})
685688

686689
// Loop through each time unit and assert that the log output contains the expected latency value
687690
for _, tu := range timeUnits {
688-
// Update the sleep duration for the next iteration
689-
sleepDuration = 1 * tu.div
691+
// Update the latency duration for the next iteration
692+
latencyDuration = tu.div
690693

691694
// Create a new HTTP request to the test route
692695
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", http.NoBody), fiber.TestConfig{
@@ -710,27 +713,30 @@ func Test_Logger_WithLatency_DefaultFormat(t *testing.T) {
710713
defer bytebufferpool.Put(buff)
711714
app := fiber.New()
712715

716+
var latencyDuration time.Duration
717+
fixedStart := time.Unix(0, 0)
713718
logger := New(Config{
714719
Stream: buff,
720+
LoggerFunc: func(c fiber.Ctx, data *Data, cfg *Config) error {
721+
data.Start = fixedStart
722+
data.Stop = fixedStart.Add(latencyDuration)
723+
return defaultLoggerInstance(c, data, cfg)
724+
},
715725
})
716726
app.Use(logger)
717727

718728
// Define a list of time units to test
719729
timeUnits := getLatencyTimeUnits()
720730

721-
// Initialize a new time unit
722-
sleepDuration := 1 * time.Nanosecond
723-
724-
// Define a test route that sleeps
731+
// Define a test route
725732
app.Get("/test", func(c fiber.Ctx) error {
726-
time.Sleep(sleepDuration)
727733
return c.SendStatus(fiber.StatusOK)
728734
})
729735

730736
// Loop through each time unit and assert that the log output contains the expected latency value
731737
for _, tu := range timeUnits {
732-
// Update the sleep duration for the next iteration
733-
sleepDuration = 1 * tu.div
738+
// Update the latency duration for the next iteration
739+
latencyDuration = tu.div
734740

735741
// Create a new HTTP request to the test route
736742
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", http.NoBody), fiber.TestConfig{

0 commit comments

Comments
 (0)