Addresses outstanding review concerns and several adjacent issues
surfaced during a follow-up review pass.
Lifecycle / supervision
- Track every per-child Wait goroutine via sync.WaitGroup and unblock
pending sigCh sends through a context.Cancel so early-return paths
(OnChildSpawn / OnMasterReady error, recovery doCommand error,
ErrOverRecovery) can no longer leak goroutines or stall children.
- Install signal.Notify(SIGTERM, SIGINT) in the master so deploy/
rolling-restart signals enter the shutdown path instead of killing
the master without graceful teardown.
- Replace the unconditional SIGKILL defer with a SIGTERM-then-SIGKILL
sequence gated by a configurable ShutdownGracePeriod (defaults to 5s,
Windows path stays SIGKILL since Signal(SIGTERM) is unsupported).
API
- OnChildRecover now returns error so callers can implement recovery
policies (circuit-breaker etc.); panic in any hook is recovered and
surfaced as the returned error, with diagnostic logging.
- Add RecoverInterval (optional crash-loop backoff) and
ShutdownGracePeriod fields with safe zero-value defaults.
- Export ErrCommandProducerNilCmd and ErrCommandProducerNotStarted
sentinel errors so callers can errors.Is them.
- Rename oldPid/newPid to oldPID/newPID per Go initialism convention.
- Logger interface now declares an explicit compile-time compatibility
check with fasthttp.Logger.
Resource hygiene
- Master closes both the original tcpListener and the duped fd in
p.files when prefork() returns; previously the duped fd leaked once
per call.
- doCommand wraps every error path with %w + fmt.Errorf so caller-side
diagnostics keep stage context.
- Strip pre-existing FASTHTTP_PREFORK_CHILD entries before appending so
child env never carries duplicate keys.
- Extract magic numbers as package constants
(inheritedListenerFD, masterPollInterval, defaultShutdownGracePeriod,
preforkChildEnvValue).
- Rename the inherited listener fd via os.NewFile so net.FileListener
errors are diagnosable.
Tests
- Migrate to t.Setenv (drop the global setUp/tearDown helpers) — fixes
the env-mutation-vs-parallel race.
- Replace rand.Intn port helper with `:0` + Listener.Addr() to remove
port-collision flakes under -count and parallel runs.
- Collapse the three near-identical Test_ListenAndServe* tests into a
single table-driven subtest that actually asserts the args forwarded
to ServeFunc/ServeTLSFunc/ServeTLSEmbedFunc.
- Add coverage for the previously untested branches:
CommandProducer returning err / nil cmd / unstarted cmd,
initial OnChildSpawn error, OnMasterReady error,
hook panic surfacing, RecoverInterval enforcement.
- noopChildProducer helper kills + waits any spawned child binaries
during cleanup so failed tests no longer leave subprocesses around.
- Move Wait() goroutine before OnChildSpawn so Kill()+Wait() works
correctly if a callback fails and the deferred cleanup runs
- Add Wait() call in deferred cleanup after Kill() to reap children
- Same fix in recovery loop
- Remove shallow callback tests that only tested Go compiler
- Add Test_Prefork_Lifecycle: runs full prefork with CommandProducer,
verifies callbacks fire in correct order with correct arguments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OnChildRecover: signature changed to func(oldPid, newPid int) so
callers can track which process was replaced
- OnChildSpawn: also called for recovered children (a recovered child
is still a spawned child)
- watchMaster: call OnMasterDeath when FindProcess fails (process is
most likely gone)
- CommandProducer: document that FASTHTTP_PREFORK_CHILD=1 must be set
in the child env, and what the default does when nil
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On new connections with ReduceMemoryUsage enabled, serveConn could reach
acquireByteReader before installing a read deadline. That left the first
blocking read outside ReadTimeout and allowed silent clients to keep the
connection open until some external timeout closed it.
Apply ReadTimeout before the first read on a new connection, while keeping
the existing idle-timeout behavior for keep-alive requests. Add a regression
test that verifies the server closes a silent ReduceMemoryUsage connection
after the first-byte timeout.
Prevent request and response first-line setters from serializing
embedded CR or LF bytes into the start line.
Route SetMethod, SetRequestURI, SetProtocol, and SetStatusMessage
through the existing newline sanitization used by other header-value
setters. This preserves behavior for valid inputs while preventing
header injection through malformed first-line values.
Thanks to @vnykmshr for reporting this issue.
Lint fixes:
- Remove unused Reuseport field write in test (govet/unusedwrite)
- Replace fmt.Errorf with errors.New for static errors (perfsprint)
Review feedback (Copilot):
- Validate CommandProducer returns a started command (nil/Process check)
- Clarify ListenAndServeTLS doc: parameter order and internal forwarding
- Use hermetic test binary re-exec instead of external 'go' binary
- Rename misleading test to reflect what it actually asserts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep upstream's (addr, certKey, certFile) signature to avoid breaking
callers. Fix the doc comment to match the actual parameter order instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The three ListenAndServe* methods had identical child setup code
(listen, set ln, watch master). Extract to listenAsChild() for
cleaner code. Also add comment for the magic file descriptor number 3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On Windows, os.Getppid() returns a static PID that doesn't change when
the parent exits (no reparenting). Use FindProcess+Wait instead, which
correctly detects parent exit. Also document why masterPID comparison
works for Docker containers (master PID 1 case).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrate upstream's OnMasterDeath callback (replaces WatchMaster bool),
os.Executable() for child command, and watchMaster as method on Prefork.
Keep our OnChildSpawn, OnMasterReady, OnChildRecover callbacks and
CommandProducer. Update tests accordingly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reject dial target addresses containing CR or LF before building the
HTTP CONNECT request in httpProxyDial.
This prevents header injection through HTTP proxies when callers pass
unsanitized target addresses via low-level dial paths such as
HostClient.Addr or direct proxy dialer usage.
Reported by https://github.com/OLU-DEVX
Rewritten FS paths were only checked for the "/../" substring, which
allowed leading "../" values to bypass the traversal guard.
Reject any rewritten path containing a ".." path segment before joining
it with FS.Root. This closes the PathRewrite/NewPathPrefixStripper escape
in the default OS-backed handler and keeps rewritten paths within the
intended static root.
This vulnerability was discovered and reported by bugbunny.ai
Keep headerScanner strict so malformed MIME header lines are still rejected.
Move trimming before ':' into the HTTP header handling paths that
intentionally normalize header names, and add a fuzz seed for the
regression case.
* fix: detect master process death in prefork children
Prefork child processes had no mechanism to detect if the master process
died unexpectedly. Children would become orphans, get reparented to
PID 1, and keep running silently with no supervision.
Add a watchMaster goroutine that stores the original parent PID at
startup and exits when the parent PID changes, matching the approach
used in gofiber/fiber.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add integration test for watchMaster orphan detection
Verifies that prefork children exit when the master process is killed,
using a two-level subprocess chain (test → master → child) with pipe-based
synchronization to ensure the child has recorded its PPID before the
master is killed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: pass masterPID to watchMaster and clean up tests
Capture PPID before launching the goroutine to eliminate a race between
the PPID snapshot and the ready signal. Align test style with the rest
of the project (t.Parallel, naming, ASCII-only comments).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make prefork orphan detection configurable via OnMasterDeath callback
Address review feedback: make watchMaster opt-in via an OnMasterDeath
callback field (nil/off by default for backwards compatibility). Users
can set DefaultOnMasterDeath for os.Exit(1) or provide custom cleanup.
Also fixes ticker leak in watchMaster.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* address review feedback: remove DefaultOnMasterDeath, delete tests, fix log message
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: enhance performance
* fix: improve request URI parsing condition
* feat: validate HTTP date parsing and optimize status code length calculation
* Address parsing and lint issues
* chore: update Go version to 1.24.x in CI configuration
* feat: enhance HTTP date parsing and request URI handling
* refactor: optimize month and day name parsing using bitwise operations
* refactor: replace cookie token comparison with case insensitive function and streamline request URI parsing
* refactor: streamline request body handling and simplify request URI assignment
* chore: update Go version to 1.25.x in CI configuration
* feat: add fuzz testing for HTTP date parsing to improve robustness
* refactor: avoid unused return values in HTTP date parsing benchmarks
* refactor: update HTTP date parsing to use http.TimeFormat for consistency
ServeFile and ServeFS interpret the path as a URI, so percent-encoded
sequences are decoded and characters like '?' and '#' act as URI
delimiters. This makes it impossible to serve files whose names
contain those characters.
Changing this behavior would be backwards incompatible. So instead the
new ServeFileLiteral, ServeFSLiteral and SendFileLiteral are added.
The new Literal variants percent-encode the path before setting it as
the request URI, preserving every byte of the original filesystem path.
Thanks to @thesmartshadow for reporting this issue.
Prevents `header.Set("Key", "value\r\nEvil-Header: injected")` from
producing extra header lines in the HTTP response/request.
Thanks to @instantraaamen for reporting this issue.
- Apply `fs.Root` in non-`os.FS` path resolution.
- Normalize `fs.FS` roots (`./`, trailing slash, leading slash, separators).
- Handle `PathRewrite` outputs without a leading slash.
- Add tests for `MapFS` and `DirFS` root enforcement.
* Add WithLimit methods for uncompression
The current uncompress methods don't enforce a memory limit and are
susceptible to things like zip bombs. This pull introduces new methods
so retain backwards compatibility. The old methods might be deprecated
in the future.
* Fix suggestion