diff --git a/frankenphp.go b/frankenphp.go index 8b19dd2285..3f7bbdf582 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -46,6 +46,7 @@ var ( ErrInvalidRequest = errors.New("not a FrankenPHP request") ErrAlreadyStarted = errors.New("FrankenPHP is already started") ErrInvalidPHPVersion = errors.New("FrankenPHP is only compatible with PHP 8.2+") + ErrZendSignals = errors.New(`FrankenPHP is not compatible with Zend Signals, recompile PHP with the "--disable-zend-signals" configuration option`) ErrMainThreadCreation = errors.New("error creating the main thread") ErrScriptExecution = errors.New("error during PHP script execution") ErrNotRunning = errors.New("server is not registered, you must first call frankenphp.Init() with the WithServer() option") @@ -156,6 +157,21 @@ func Config() PHPConfig { } } +// checkPHPConfig rejects the PHP builds FrankenPHP cannot run on +func checkPHPConfig(config PHPConfig) error { + if config.Version.MajorVersion < 8 || (config.Version.MajorVersion == 8 && config.Version.MinorVersion < 2) { + return ErrInvalidPHPVersion + } + + // FrankenPHP never calls zend_signal_startup(), so in ZTS the ini entries + // of the signal globals overwrite the TSRM entry of each thread + if config.ZTS && config.ZendSignals { + return ErrZendSignals + } + + return nil +} + func calculateMaxThreads(opt *opt) (numWorkers int, _ error) { maxProcs := runtime.GOMAXPROCS(0) * 2 maxThreadsFromWorkers := 0 @@ -294,9 +310,10 @@ func Init(options ...Option) error { config := Config() - if config.Version.MajorVersion < 8 || (config.Version.MajorVersion == 8 && config.Version.MinorVersion < 2) { + if err := checkPHPConfig(config); err != nil { shutdown() - return ErrInvalidPHPVersion + + return err } if config.ZTS { diff --git a/phpconfig_test.go b/phpconfig_test.go new file mode 100644 index 0000000000..465c834a7a --- /dev/null +++ b/phpconfig_test.go @@ -0,0 +1,36 @@ +package frankenphp + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCheckPHPConfig(t *testing.T) { + supported := PHPConfig{Version: PHPVersion{MajorVersion: 8, MinorVersion: 2}, ZTS: true} + + t.Run("a supported build is accepted", func(t *testing.T) { + require.NoError(t, checkPHPConfig(supported)) + }) + + t.Run("PHP older than 8.2 is rejected", func(t *testing.T) { + old := supported + old.Version.MinorVersion = 1 + assert.ErrorIs(t, checkPHPConfig(old), ErrInvalidPHPVersion) + }) + + t.Run("Zend signals are rejected in ZTS", func(t *testing.T) { + signals := supported + signals.ZendSignals = true + assert.ErrorIs(t, checkPHPConfig(signals), ErrZendSignals) + }) + + t.Run("Zend signals are allowed without ZTS", func(t *testing.T) { + // without ZTS the ini entries address the globals directly + signals := supported + signals.ZTS = false + signals.ZendSignals = true + require.NoError(t, checkPHPConfig(signals)) + }) +}