diff --git a/context.go b/context.go index e18c16a0fd..3fbfdea399 100644 --- a/context.go +++ b/context.go @@ -67,7 +67,7 @@ func newContextFromRequest(request *http.Request, responseWriter http.ResponseWr startedAt: time.Now(), server: s, splitPath: s.splitPath, - logger: s.logger, + logger: s.logger.Load(), request: request, documentRoot: s.root, responseWriter: responseWriter, diff --git a/frankenphp.go b/frankenphp.go index 6ccc71ede9..8df62feea1 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -57,7 +57,7 @@ var ( contextKey = contextKeyStruct{} serverHeader = []string{"FrankenPHP"} - isRunning bool + isRunning atomic.Bool onServerShutdown []func() // Set default values to make Shutdown() idempotent @@ -240,10 +240,9 @@ func calculateMaxThreads(opt *opt) (numWorkers int, _ error) { // Init starts the PHP runtime and the configured workers. func Init(options ...Option) error { - if isRunning { + if !isRunning.CompareAndSwap(false, true) { return ErrAlreadyStarted } - isRunning = true // Ignore all SIGPIPE signals to prevent weird issues with systemd: https://github.com/php/frankenphp/issues/1020 // Docker/Moby has a similar hack: https://github.com/moby/moby/blob/d828b032a87606ae34267e349bf7f7ccb1f6495a/cmd/dockerd/docker.go#L87-L90 @@ -367,7 +366,7 @@ func Init(options ...Option) error { // Shutdown stops the workers and the PHP runtime. func Shutdown() { - if !isRunning { + if !isRunning.Load() { return } @@ -387,7 +386,7 @@ func Shutdown() { _ = os.RemoveAll(EmbeddedAppPath) } - isRunning = false + isRunning.Store(false) if globalLogger.Enabled(globalCtx, slog.LevelDebug) { globalLogger.LogAttrs(globalCtx, slog.LevelDebug, "FrankenPHP shut down") } diff --git a/server.go b/server.go index 55733d0adf..16ea5742e0 100644 --- a/server.go +++ b/server.go @@ -4,6 +4,7 @@ import ( "fmt" "log/slog" "net/http" + "sync/atomic" "github.com/dunglas/frankenphp/internal/fastabs" ) @@ -15,12 +16,18 @@ type Server struct { root string splitPath []string env PreparedEnv - logger *slog.Logger workers []*worker workersByPath map[string]*worker workersWithRequestMatcher []*worker workerOpts []workerOpt - isRegistered bool + + // registered while FrankenPHP runs with this server; read by concurrent + // ServeHTTP calls while Init()/Shutdown() flip it, hence atomic + isRegistered atomic.Bool + + // atomic for the same reason: the fallback server's logger is replaced + // at registration time while in-flight requests may read it + logger atomic.Pointer[slog.Logger] } var ( @@ -34,18 +41,18 @@ var ( func registerServers(newServers []*Server) { servers = newServers - fallbackServer.isRegistered = true - fallbackServer.logger = globalLogger + fallbackServer.logger.Store(globalLogger) + fallbackServer.isRegistered.Store(true) for i, s := range servers { - s.isRegistered = true + s.isRegistered.Store(true) s.idx = i } } func unregisterServers() { - fallbackServer.isRegistered = false + fallbackServer.isRegistered.Store(false) for _, server := range servers { - server.isRegistered = false + server.isRegistered.Store(false) } } @@ -63,11 +70,15 @@ func NewServer(root string, splitPath []string, env map[string]string, logger *s root: root, splitPath: splitPath, env: PrepareEnv(env), - logger: logger, workersByPath: make(map[string]*worker), workerOpts: make([]workerOpt, 0), } + if logger == nil { + logger = globalLogger + } + s.logger.Store(logger) + if len(s.splitPath) == 0 { s.splitPath = []string{".php"} } @@ -76,10 +87,6 @@ func NewServer(root string, splitPath []string, env map[string]string, logger *s s.env = PrepareEnv(nil) } - if s.logger == nil { - s.logger = globalLogger - } - return s, nil } @@ -102,7 +109,7 @@ func (s *Server) addWorker(w *worker) error { // The request will be scoped to the server instance that was registered via WithServer(). // Otherwise, it is equivalent to calling ServeHTTP. func (s *Server) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request, opts ...RequestOption) error { - if !s.isRegistered { + if !s.isRegistered.Load() { return ErrNotRunning }