package frankenphp import ( "context" "fmt" "log/slog" "time" ) // defaultMaxConsecutiveFailures is the default maximum number of consecutive failures before panicking const defaultMaxConsecutiveFailures = 6 // Option instances allow to configure FrankenPHP. type Option func(h *opt) error // WorkerOption instances allow configuring FrankenPHP worker. type WorkerOption func(*workerOpt) error // opt contains the available options. // // If you change this, also update the Caddy module and the documentation. type opt struct { hotReloadOpt ctx context.Context numThreads int maxThreads int workers []workerOpt logger *slog.Logger metrics Metrics phpIni map[string]string maxWaitTime time.Duration maxIdleTime time.Duration } type workerOpt struct { mercureContext name string fileName string num int maxThreads int env PreparedEnv requestOptions []RequestOption watch []string maxConsecutiveFailures int extensionWorkers *extensionWorkers onThreadReady func(int) onThreadShutdown func(int) onServerStartup func() onServerShutdown func() } // WithContext sets the main context to use. func WithContext(ctx context.Context) Option { return func(h *opt) error { h.ctx = ctx return nil } } // WithNumThreads configures the number of PHP threads to start. func WithNumThreads(numThreads int) Option { return func(o *opt) error { o.numThreads = numThreads return nil } } func WithMaxThreads(maxThreads int) Option { return func(o *opt) error { o.maxThreads = maxThreads return nil } } func WithMetrics(m Metrics) Option { return func(o *opt) error { o.metrics = m return nil } } // WithWorkers configures the PHP workers to start func WithWorkers(name, fileName string, num int, options ...WorkerOption) Option { return func(o *opt) error { worker := workerOpt{ name: name, fileName: fileName, num: num, env: PrepareEnv(nil), watch: []string{}, maxConsecutiveFailures: defaultMaxConsecutiveFailures, } for _, option := range options { if err := option(&worker); err != nil { return err } } o.workers = append(o.workers, worker) return nil } } // EXPERIMENTAL: WithExtensionWorkers allow extensions to create workers. // // A worker script with the provided name, fileName and thread count will be registered, along with additional // configuration through WorkerOptions. // // Workers are designed to run indefinitely and will be gracefully shut down when FrankenPHP shuts down. // // Extension workers receive the lowest priority when determining thread allocations. If the requested number of threads // cannot be allocated, then FrankenPHP will panic and provide this information to the user (who will need to allocate // more total threads). Don't be greedy. func WithExtensionWorkers(name, fileName string, numThreads int, options ...WorkerOption) (Workers, Option) { w := &extensionWorkers{ name: name, fileName: fileName, num: numThreads, } w.options = append(options, withExtensionWorkers(w)) return w, WithWorkers(w.name, w.fileName, w.num, w.options...) } // WithLogger configures the global logger to use. func WithLogger(l *slog.Logger) Option { return func(o *opt) error { o.logger = l return nil } } // WithPhpIni configures user defined PHP ini settings. func WithPhpIni(overrides map[string]string) Option { return func(o *opt) error { o.phpIni = overrides return nil } } // WithMaxWaitTime configures the max time a request may be stalled waiting for a thread. func WithMaxWaitTime(maxWaitTime time.Duration) Option { return func(o *opt) error { o.maxWaitTime = maxWaitTime return nil } } // WithMaxIdleTime configures the max time an autoscaled thread may be idle before being deactivated. func WithMaxIdleTime(maxIdleTime time.Duration) Option { return func(o *opt) error { o.maxIdleTime = maxIdleTime return nil } } // WithWorkerEnv sets environment variables for the worker func WithWorkerEnv(env map[string]string) WorkerOption { return func(w *workerOpt) error { w.env = PrepareEnv(env) return nil } } // WithWorkerRequestOptions sets options for the main dummy request created for the worker func WithWorkerRequestOptions(options ...RequestOption) WorkerOption { return func(w *workerOpt) error { w.requestOptions = append(w.requestOptions, options...) return nil } } // WithWorkerMaxThreads sets the max number of threads for this specific worker func WithWorkerMaxThreads(num int) WorkerOption { return func(w *workerOpt) error { w.maxThreads = num return nil } } // WithWorkerWatchMode sets directories to watch for file changes func WithWorkerWatchMode(watch []string) WorkerOption { return func(w *workerOpt) error { w.watch = watch return nil } } // WithWorkerMaxFailures sets the maximum number of consecutive failures before panicking func WithWorkerMaxFailures(maxFailures int) WorkerOption { return func(w *workerOpt) error { if maxFailures < -1 { return fmt.Errorf("max consecutive failures must be >= -1, got %d", maxFailures) } w.maxConsecutiveFailures = maxFailures return nil } } func WithWorkerOnReady(f func(int)) WorkerOption { return func(w *workerOpt) error { w.onThreadReady = f return nil } } func WithWorkerOnShutdown(f func(int)) WorkerOption { return func(w *workerOpt) error { w.onThreadShutdown = f return nil } } // WithWorkerOnServerStartup adds a function to be called right after server startup. Useful for extensions. func WithWorkerOnServerStartup(f func()) WorkerOption { return func(w *workerOpt) error { w.onServerStartup = f return nil } } // WithWorkerOnServerShutdown adds a function to be called right before server shutdown. Useful for extensions. func WithWorkerOnServerShutdown(f func()) WorkerOption { return func(w *workerOpt) error { w.onServerShutdown = f return nil } } func withExtensionWorkers(w *extensionWorkers) WorkerOption { return func(wo *workerOpt) error { wo.extensionWorkers = w return nil } }