-
-
Notifications
You must be signed in to change notification settings - Fork 2k
feat: enhance prefork functionality with new configuration options and improved child process recovery #4032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
df85d48
24e742c
8122431
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,7 +111,10 @@ app.Listen(":8080", fiber.ListenConfig{ | |
| | <Reference id="certfile">CertFile</Reference> | `string` | Path of the certificate file. If you want to use TLS, you must enter this field. | `""` | | ||
| | <Reference id="certkeyfile">CertKeyFile</Reference> | `string` | Path of the certificate's private key. If you want to use TLS, you must enter this field. | `""` | | ||
| | <Reference id="disablestartupmessage">DisableStartupMessage</Reference> | `bool` | When set to true, it will not print out the «Fiber» ASCII art and listening address. | `false` | | ||
| | <Reference id="enableprefork">EnablePrefork</Reference> | `bool` | When set to true, this will spawn multiple Go processes listening on the same port. | `false` | | ||
| | <Reference id="enableprefork">EnablePrefork</Reference> | `bool` | When set to true, this will spawn multiple Go processes listening on the same port. See [Prefork](#prefork) for more details. | `false` | | ||
| | <Reference id="disablereuseportfallback">DisableReuseportFallback</Reference> | `bool` | When set to true, prefork will fail if SO_REUSEPORT is not supported instead of falling back to file descriptor sharing. Only relevant when `EnablePrefork` is true. | `false` | | ||
| | <Reference id="disablechildrecovery">DisableChildRecovery</Reference> | `bool` | When set to true, disables automatic recovery of crashed child processes in prefork mode. Only relevant when `EnablePrefork` is true. | `false` | | ||
| | <Reference id="maxchildrecoveries">MaxChildRecoveries</Reference> | `int` | Maximum number of times a child process can be recovered before giving up. Set to 0 for unlimited recoveries. Only relevant when `EnablePrefork` is true and `DisableChildRecovery` is false. | `0` (unlimited) | | ||
|
Comment on lines
+115
to
+117
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| | <Reference id="enableprintroutes">EnablePrintRoutes</Reference> | `bool` | If set to true, will print all routes with their method, path, and handler. | `false` | | ||
| | <Reference id="gracefulcontext">GracefulContext</Reference> | `context.Context` | Field to shutdown Fiber by given context gracefully. | `nil` | | ||
| | <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` | | ||
|
|
@@ -152,6 +155,54 @@ app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true}) | |
|
|
||
| This distributes the incoming connections between the spawned processes and allows more requests to be handled simultaneously. | ||
|
|
||
| **How it works:** | ||
|
|
||
| - On systems with **SO_REUSEPORT support** (Linux, macOS, FreeBSD): Each child process creates its own listener using SO_REUSEPORT, allowing the kernel to load-balance connections efficiently. | ||
| - On systems **without SO_REUSEPORT** (older systems, AIX, Solaris): Fiber automatically falls back to **file descriptor sharing**, where the master process creates a single listener and shares it with all children. Prefork remains active! | ||
| - You can control this behavior with `DisableReuseportFallback` (see below). | ||
|
Comment on lines
+158
to
+162
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| **Advanced Configuration:** | ||
|
|
||
| ```go title="Prefork with child recovery" | ||
| app.Listen(":8080", fiber.ListenConfig{ | ||
| EnablePrefork: true, | ||
| // Automatically restart crashed child processes (default: enabled) | ||
| DisableChildRecovery: false, | ||
| // Maximum recovery attempts per child before giving up (0 = unlimited) | ||
| MaxChildRecoveries: 10, | ||
| }) | ||
|
Comment on lines
+164
to
+173
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ``` | ||
|
|
||
| ```go title="Disable fallback to file descriptor sharing" | ||
| app.Listen(":8080", fiber.ListenConfig{ | ||
| EnablePrefork: true, | ||
| // Fail if SO_REUSEPORT is not supported (no fallback) | ||
| DisableReuseportFallback: true, | ||
| }) | ||
|
Comment on lines
+176
to
+181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ``` | ||
|
|
||
| **Child Process Recovery:** | ||
|
|
||
| By default, if a child process crashes, it will be automatically restarted to maintain the desired number of worker processes. You can: | ||
|
|
||
| - Disable recovery entirely with `DisableChildRecovery: true` | ||
| - Limit recovery attempts with `MaxChildRecoveries: N` | ||
|
ReneWerner87 marked this conversation as resolved.
|
||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| **Prefork with Custom Listener:** | ||
|
|
||
| You can also use prefork with a custom listener created via `reuseport.Listen`: | ||
|
|
||
| ```go title="Prefork with custom listener" | ||
| import "github.com/valyala/fasthttp/reuseport" | ||
|
|
||
| ln, err := reuseport.Listen("tcp4", ":8080") | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| app.Listener(ln, fiber.ListenConfig{EnablePrefork: true}) | ||
| ``` | ||
|
Comment on lines
+191
to
+204
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| #### TLS | ||
|
|
||
| Prefer `TLSConfig` for TLS configuration so you can fully control certificates and settings. When `TLSConfig` is set, Fiber ignores `CertFile`, `CertKeyFile`, `CertClientFile`, `TLSMinVersion`, `AutoCertManager`, and `TLSConfigFunc`. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,7 @@ package fiber | |||||
|
|
||||||
| import ( | ||||||
| "bytes" | ||||||
| "context" | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
| "errors" | ||||||
| "os" | ||||||
| "runtime" | ||||||
|
|
@@ -532,12 +533,24 @@ func Test_Hook_OnListenPrefork(t *testing.T) { | |||||
| return nil | ||||||
| }) | ||||||
|
|
||||||
| ctx, cancel := context.WithCancel(context.Background()) | ||||||
| defer cancel() | ||||||
|
ReneWerner87 marked this conversation as resolved.
|
||||||
|
|
||||||
| go func() { | ||||||
| time.Sleep(1000 * time.Millisecond) | ||||||
| assert.NoError(t, app.Shutdown()) | ||||||
| cancel() | ||||||
|
ReneWerner87 marked this conversation as resolved.
|
||||||
| }() | ||||||
|
|
||||||
| require.NoError(t, app.Listen(":0", ListenConfig{DisableStartupMessage: true, EnablePrefork: true})) | ||||||
| err := app.Listen(":0", ListenConfig{ | ||||||
| DisableStartupMessage: true, | ||||||
| EnablePrefork: true, | ||||||
| GracefulContext: ctx, | ||||||
| }) | ||||||
| // Either graceful shutdown or empty error is acceptable | ||||||
| if err != nil && err.Error() != "" { | ||||||
|
||||||
| if err != nil && err.Error() != "" { | |
| if err != nil { |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -130,6 +130,24 @@ type ListenConfig struct { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Default: false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| EnablePrintRoutes bool `json:"enable_print_routes"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // When set to true, prefork will fail if SO_REUSEPORT is not supported | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // instead of falling back to standard listener. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Default: false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DisableReuseportFallback bool `json:"disable_reuseport_fallback"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ReneWerner87 marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // When set to true, disables automatic recovery of crashed child processes in prefork mode. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // By default, if a child process crashes, it will be automatically restarted. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Default: false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DisableChildRecovery bool `json:"disable_child_recovery"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ReneWerner87 marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Maximum number of times a child process can be recovered before giving up. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Set to 0 for unlimited recoveries. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Default: 0 (unlimited) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MaxChildRecoveries int `json:"max_child_recoveries"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
ReneWerner87 marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // listenConfigDefault is a function to set default values of ListenConfig. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -295,9 +313,32 @@ func (app *App) Listener(ln net.Listener, config ...ListenConfig) error { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Prefork is not supported for custom listeners | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Prefork support for custom listeners | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if cfg.EnablePrefork { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.Warn("Prefork isn't supported for custom listeners.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Extract address and network from listener | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addr := ln.Addr().String() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| network := ln.Addr().Network() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if this is a TLS listener | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tlsConfig := getTLSConfig(ln) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Validate that the listener network is compatible with prefork | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if network != "tcp" && network != "tcp4" && network != "tcp6" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.Warnf("[prefork] Prefork only supports tcp, tcp4, and tcp6 networks. Current network: %s. Ignoring prefork flag.", network) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return app.server.Serve(ln) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Close the provided listener since prefork will create its own listeners | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := ln.Close(); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.Warnf("[prefork] failed to close provided listener: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use prefork mode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // NOTE: This assumes the provided listener was created with reuseport.Listen or similar | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If the system doesn't support SO_REUSEPORT, prefork will automatically fall back | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // to standard listening mode (see prefork.go for fallback logic) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // to standard listening mode (see prefork.go for fallback logic) | |
| // to file descriptor sharing mode (FD sharing fallback, see prefork.go for fallback logic) |
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on line 337 incorrectly states "This assumes the provided listener was created with reuseport.Listen or similar". However, when a custom listener is provided to app.Listener(), the code closes it (line 332) and then calls prefork() which will either use SO_REUSEPORT or fall back to FD sharing. The assumption about how the listener was created is not relevant since it's closed before prefork starts. This comment could be misleading and should be updated or removed.
| // NOTE: This assumes the provided listener was created with reuseport.Listen or similar | |
| // If the system doesn't support SO_REUSEPORT, prefork will automatically fall back | |
| // to standard listening mode (see prefork.go for fallback logic) | |
| // Prefork will create and manage its own listeners for the given address. | |
| // It will attempt to use SO_REUSEPORT where supported and will automatically | |
| // fall back to its alternative listening strategy (see prefork.go for details). |
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When prefork is enabled with a custom listener, the provided listener is closed (line 332) before calling prefork(). However, if prefork() fails (e.g., due to invalid address format or other errors), the caller has lost their listener and cannot recover. This breaks the principle of "do no harm on failure". Consider deferring the close until after prefork() succeeds, or at minimum document this behavior so users are aware that the listener will be closed even if prefork fails.
| // Close the provided listener since prefork will create its own listeners | |
| if err := ln.Close(); err != nil { | |
| log.Warnf("[prefork] failed to close provided listener: %v", err) | |
| } | |
| // Use prefork mode | |
| // NOTE: This assumes the provided listener was created with reuseport.Listen or similar | |
| // If the system doesn't support SO_REUSEPORT, prefork will automatically fall back | |
| // to standard listening mode (see prefork.go for fallback logic) | |
| log.Info("[prefork] Starting prefork mode with custom listener address") | |
| return app.prefork(addr, tlsConfig, &cfg) | |
| // Use prefork mode | |
| // NOTE: This assumes the provided listener was created with reuseport.Listen or similar | |
| // If the system doesn't support SO_REUSEPORT, prefork will automatically fall back | |
| // to standard listening mode (see prefork.go for fallback logic) | |
| log.Info("[prefork] Starting prefork mode with custom listener address") | |
| // Call prefork first; only close the original listener if prefork succeeds. | |
| if err := app.prefork(addr, tlsConfig, &cfg); err != nil { | |
| // Do not close the provided listener on failure so the caller can decide how to recover. | |
| return err | |
| } | |
| // Prefork completed successfully; the original listener is no longer needed. | |
| if err := ln.Close(); err != nil { | |
| log.Warnf("[prefork] failed to close provided listener after prefork completed: %v", err) | |
| } | |
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The update to the
EnablePreforkdescription, adding a link to the detailed Prefork section, is a good improvement for user navigation and understanding.