Files
fasthttp/prefork
RW 481e579af9 feat(prefork): Enhance prefork management with WatchMaster, CommandProducer, and Windows support (#2180)
* feat(prefork): add WatchMaster and callback support for child process management

* feat(prefork): add CommandProducer for customizable child process commands

* refactor(prefork): improve comments and parameter order in ListenAndServeTLS

* refactor(prefork): enhance logging message and clarify OnChildRecover callback comment

* fix(prefork): add Windows support to watchMaster

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>

* refactor(prefork): extract listenAsChild to eliminate DRY violation

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>

* fix(prefork): restore upstream ListenAndServeTLS parameter order

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>

* fix(prefork): address lint errors and review feedback

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>

* refactor(prefork): address maintainer review feedback

- watchMaster: log errors from FindProcess/Wait instead of swallowing
- watchMaster: don't call OnMasterDeath if FindProcess fails
- OnChildRecover: change signature to func(pid int), drop unused error return
- OnChildSpawn: add comment clarifying deferred cleanup handles the child
- CommandProducer: improve docs describing contract and use cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(prefork): address erikdubbelboer review feedback

- 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>

* fix(prefork): avoid zombie processes and replace shallow tests

- 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>

* fix(prefork): ensure recovery default stays positive

* test(prefork): isolate lifecycle tests

* fix(prefork): tighten recovery callback flow

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 13:17:27 +02:00
..
2020-02-12 13:51:27 +01:00

Prefork

Server prefork implementation.

Preforks master process between several child processes increases performance, because Go doesn't have to share and manage memory between cores.

WARNING: using prefork prevents the use of any global state!. Things like in-memory caches won't work.

  • How it works:
import (
    "github.com/valyala/fasthttp"
    "github.com/valyala/fasthttp/prefork"
)

server := &fasthttp.Server{
    // Your configuration
}

// Wraps the server with prefork
preforkServer := prefork.New(server)

if err := preforkServer.ListenAndServe(":8080"); err != nil {
    panic(err)
}

Benchmarks

Environment:

  • Machine: MacBook Pro 13-inch, 2017
  • OS: MacOS 10.15.3
  • Go: go1.13.6 darwin/amd64

Handler code:

func requestHandler(ctx *fasthttp.RequestCtx) {
    // Simulates some hard work
    time.Sleep(100 * time.Millisecond)
}

Test command:

$ wrk -H 'Host: localhost' -H 'Accept: text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7' -H 'Connection: keep-alive' --latency -d 15 -c 512 --timeout 8 -t 4 http://localhost:8080

Results:

  • prefork
Running 15s test @ http://localhost:8080
  4 threads and 512 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     4.75ms    4.27ms 126.24ms   97.45%
    Req/Sec    26.46k     4.16k   71.18k    88.72%
  Latency Distribution
     50%    4.55ms
     75%    4.82ms
     90%    5.46ms
     99%   15.49ms
  1581916 requests in 15.09s, 140.30MB read
  Socket errors: connect 0, read 318, write 0, timeout 0
Requests/sec: 104861.58
Transfer/sec:      9.30MB
  • non-prefork
Running 15s test @ http://localhost:8080
  4 threads and 512 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.42ms   11.83ms 177.19ms   96.42%
    Req/Sec    24.96k     5.83k   56.83k    82.93%
  Latency Distribution
     50%    4.53ms
     75%    4.93ms
     90%    6.94ms
     99%   74.54ms
  1472441 requests in 15.09s, 130.59MB read
  Socket errors: connect 0, read 265, write 0, timeout 0
Requests/sec:  97553.34
Transfer/sec:      8.65MB