diff --git a/Dockerfile b/Dockerfile index 634d2d6196..83ce475344 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,88 @@ -FROM php:8.2-zts-bullseye AS builder +FROM php:8.2-zts-bullseye AS php-base + +# Note that this image is based on the official PHP image, once https://github.com/php/php-src/pull/10141 is merged, this stage can be removed + +RUN rm -Rf /usr/local/include/php/ /usr/local/lib/libphp.* /usr/local/lib/php/ /usr/local/php/ + +ENV PHPIZE_DEPS \ + autoconf \ + dpkg-dev \ + file \ + g++ \ + gcc \ + libc-dev \ + make \ + pkg-config \ + re2c + +RUN apt-get update && \ + apt-get -y --no-install-recommends install \ + $PHPIZE_DEPS \ + libargon2-dev \ + libcurl4-openssl-dev \ + libonig-dev \ + libreadline-dev \ + libsodium-dev \ + libsqlite3-dev \ + libssl-dev \ + libxml2-dev \ + zlib1g-dev \ + bison \ + git \ + && \ + apt-get clean + +RUN git clone --depth=1 --single-branch --branch=PHP-8.2 https://github.com/php/php-src.git && \ + cd php-src && \ + # --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly + ./buildconf && \ + ./configure \ + --enable-embed \ + --enable-zts \ + --disable-zend-signals \ + --enable-zend-max-execution-timers \ + # --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) + --enable-mysqlnd \ + # make sure invalid --configure-flags are fatal errors instead of just warnings + --enable-option-checking=fatal \ + # https://github.com/docker-library/php/issues/439 + --with-mhash \ + # https://github.com/docker-library/php/issues/822 + --with-pic \ + # --enable-ftp is included here because ftp_ssl_connect() needs ftp to be compiled statically (see https://github.com/docker-library/php/issues/236) + --enable-ftp \ + # --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) + --enable-mbstring \ + # https://wiki.php.net/rfc/argon2_password_hash + --with-password-argon2 \ + # https://wiki.php.net/rfc/libsodium + --with-sodium=shared \ + # always build against system sqlite3 (https://github.com/php/php-src/commit/6083a387a81dbbd66d6316a3a12a63f06d5f7109) + --with-pdo-sqlite=/usr \ + --with-sqlite3=/usr \ + --with-curl \ + --with-iconv \ + --with-openssl \ + --with-readline \ + --with-zlib \ + # https://github.com/bwoebi/phpdbg-docs/issues/1#issuecomment-163872806 ("phpdbg is primarily a CLI debugger, and is not suitable for debugging an fpm stack.") + --disable-phpdbg \ + --with-config-file-path="$PHP_INI_DIR" \ + --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" && \ + make -j$(nproc) && \ + make install && \ + rm -Rf php-src/ && \ + echo "Creating src archive for building extensions\n" && \ + tar -c -f /usr/src/php.tar.xz -J /php-src/ && \ + ldconfig && \ + php --version + +FROM php-base AS builder COPY --from=golang:1.19-bullseye /usr/local/go/bin/go /usr/local/bin/go COPY --from=golang:1.19-bullseye /usr/local/go /usr/local/go -# This is required to link the frankenPHP binary to the PHP binary +# This is required to link the FrankenPHP binary to the PHP binary RUN apt-get update && \ apt-get -y --no-install-recommends install \ libargon2-dev \ @@ -35,7 +114,7 @@ COPY internal internal COPY testdata testdata # todo: automate this? -# see https://github.com/docker-library/php/blob/master/8.2-rc/bullseye/zts/Dockerfile#L57-L59 for php values +# see https://github.com/docker-library/php/blob/master/8.2/bullseye/zts/Dockerfile#L57-L59 for PHP values ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS" CGO_CFLAGS=$PHP_CFLAGS CGO_CPPFLAGS=$PHP_CPPFLAGS RUN cd caddy/frankenphp && \ @@ -57,6 +136,13 @@ RUN echo ' /app/public/index.php COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp COPY --from=builder /etc/Caddyfile /etc/Caddyfile +COPY --from=php-base /usr/local/include/php/ /usr/local/include/php +COPY --from=php-base /usr/local/lib/libphp.* /usr/local/lib +COPY --from=php-base /usr/local/lib/php/ /usr/local/lib/php +COPY --from=php-base /usr/local/php/ /usr/local/php +COPY --from=php-base /usr/local/bin/ /usr/local/bin +COPY --from=php-base /usr/src /usr/src + RUN sed -i 's/php/frankenphp run/g' /usr/local/bin/docker-php-entrypoint CMD [ "--config", "/etc/Caddyfile" ] diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 542989b694..f9334f17f7 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,4 +1,84 @@ -FROM php:8.2-zts-alpine3.17 AS builder +FROM php:8.2-zts-alpine3.17 AS php-base + +# Note that this image is based on the official PHP image, once https://github.com/php/php-src/pull/10141 is merged, this stage can be removed + +RUN rm -Rf /usr/local/include/php/ /usr/local/lib/libphp.* /usr/local/lib/php/ /usr/local/php/ + +ENV PHPIZE_DEPS \ + autoconf \ + dpkg-dev dpkg \ + file \ + g++ \ + gcc \ + libc-dev \ + make \ + pkgconf \ + re2c + +RUN apk add --no-cache --virtual .build-deps \ + $PHPIZE_DEPS \ + argon2-dev \ + coreutils \ + curl-dev \ + readline-dev \ + libsodium-dev \ + sqlite-dev \ + openssl-dev \ + libxml2-dev \ + gnu-libiconv-dev \ + linux-headers \ + oniguruma-dev \ + bison \ + git + +RUN git clone --depth=1 --single-branch --branch=PHP-8.2 https://github.com/php/php-src.git + +WORKDIR /php-src/ + +# --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly +RUN ./buildconf +RUN ./configure \ + --enable-embed \ + --enable-zts \ + --disable-zend-signals \ + --enable-zend-max-execution-timers \ + # --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) + --enable-mysqlnd \ + # make sure invalid --configure-flags are fatal errors instead of just warnings + --enable-option-checking=fatal \ + # https://github.com/docker-library/php/issues/439 + --with-mhash \ + # https://github.com/docker-library/php/issues/822 + --with-pic \ + # --enable-ftp is included here because ftp_ssl_connect() needs ftp to be compiled statically (see https://github.com/docker-library/php/issues/236) + --enable-ftp \ + # --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) + --enable-mbstring \ + # https://wiki.php.net/rfc/argon2_password_hash + --with-password-argon2 \ + # https://wiki.php.net/rfc/libsodium + --with-sodium=shared \ + # always build against system sqlite3 (https://github.com/php/php-src/commit/6083a387a81dbbd66d6316a3a12a63f06d5f7109) + --with-pdo-sqlite=/usr \ + --with-sqlite3=/usr \ + --with-curl \ + --with-iconv \ + --with-openssl \ + --with-readline \ + --with-zlib \ + # https://github.com/bwoebi/phpdbg-docs/issues/1#issuecomment-163872806 ("phpdbg is primarily a CLI debugger, and is not suitable for debugging an fpm stack.") + --disable-phpdbg \ + --with-config-file-path="$PHP_INI_DIR" \ + --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" +RUN make -j$(nproc) +RUN make install +RUN rm -Rf /php-src/ +RUN echo "Creating src archive for building extensions\n" +RUN tar -c -f /usr/src/php.tar.xz -J /php-src/ +#RUN ldconfig +RUN php --version + +FROM php-base AS builder COPY --from=golang:1.19-alpine3.17 /usr/local/go/bin/go /usr/local/bin/go COPY --from=golang:1.19-alpine3.17 /usr/local/go /usr/local/go @@ -56,6 +136,13 @@ RUN echo ' /app/public/index.php COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp COPY --from=builder /etc/Caddyfile /etc/Caddyfile +COPY --from=php-base /usr/local/include/php/ /usr/local/include/php +COPY --from=php-base /usr/local/lib/libphp.* /usr/local/lib +COPY --from=php-base /usr/local/lib/php/ /usr/local/lib/php +COPY --from=php-base /usr/local/php/ /usr/local/php +COPY --from=php-base /usr/local/bin/ /usr/local/bin +COPY --from=php-base /usr/src /usr/src + RUN sed -i 's/php/frankenphp run/g' /usr/local/bin/docker-php-entrypoint CMD [ "--config", "/etc/Caddyfile" ] diff --git a/Dockerfile.dev b/Dockerfile.dev index a36a9ff895..2551a34adb 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -30,7 +30,8 @@ RUN apt-get update && \ gdb \ valgrind \ neovim \ - zsh && \ + zsh \ + libtool-bin && \ echo 'set auto-load safe-path /' > /root/.gdbinit && \ echo '* soft core unlimited' >> /etc/security/limits.conf \ && \ @@ -44,6 +45,7 @@ RUN git clone --branch=PHP-8.2 https://github.com/php/php-src.git && \ --enable-embed \ --enable-zts \ --disable-zend-signals \ + --enable-zend-max-execution-timers \ --enable-debug && \ make -j$(nproc) && \ make install && \ diff --git a/docs/compile.md b/docs/compile.md index 54da0b2d16..82b64cc4b9 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -20,7 +20,8 @@ Then, configure PHP for your platform: ./configure \ --enable-embed \ --enable-zts \ - --disable-zend-signals + --disable-zend-signals \ + --enable-zend-max-execution-timers ``` ### Mac diff --git a/frankenphp.c b/frankenphp.c index 9a2a5904a2..e12457a591 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -2,16 +2,18 @@ #include #include #include +#include #include -#include -#include #include #include #include +#include #include #include #include #include +#include +#include #include "C-Thread-Pool/thpool.h" #include "C-Thread-Pool/thpool.c" @@ -23,10 +25,12 @@ ZEND_TSRMLS_CACHE_DEFINE() #endif -/* Timeouts are currently fundamentally broken with ZTS: https://bugs.php.net/bug.php?id=79464 */ +/* Timeouts are currently fundamentally broken with ZTS except on Linux: https://bugs.php.net/bug.php?id=79464 */ +#ifndef ZEND_MAX_EXECUTION_TIMERS static const char HARDCODED_INI[] = "max_execution_time=0\n" "max_input_time=-1\n\0"; +#endif static const char *MODULES_TO_RELOAD[] = { "filter", @@ -34,8 +38,8 @@ static const char *MODULES_TO_RELOAD[] = { NULL }; -frankenphp_php_version frankenphp_version() { - return (frankenphp_php_version){ +frankenphp_version frankenphp_get_version() { + return (frankenphp_version){ PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION, @@ -45,26 +49,31 @@ frankenphp_php_version frankenphp_version() { }; } -int frankenphp_check_version() { -#ifndef ZTS - return -1; +frankenphp_config frankenphp_get_config() { + return (frankenphp_config){ + frankenphp_get_version(), +#ifdef ZTS + true, +#else + false, #endif - - if (PHP_VERSION_ID < 80200) { - return -2; - } - #ifdef ZEND_SIGNALS - return -3; + true, +#else + false, #endif - - return SUCCESS; +#ifdef ZEND_MAX_EXECUTION_TIMERS + true, +#else + false, +#endif + }; } typedef struct frankenphp_server_context { - bool worker; uintptr_t current_request; - uintptr_t main_request; /* Only available during worker initialization */ + uintptr_t main_request; + bool worker_ready; char *cookie_data; bool finished; } frankenphp_server_context; @@ -82,17 +91,12 @@ static void frankenphp_request_reset() { } /* Adapted from php_request_shutdown */ -static void frankenphp_worker_request_shutdown(uintptr_t current_request) { +static void frankenphp_worker_request_shutdown() { /* Flush all output buffers */ zend_try { php_output_end_all(); } zend_end_try(); - /* Reset max_execution_time (no longer executing php code after response sent) */ - /*zend_try { - zend_unset_timeout(); - } zend_end_try();*/ - // TODO: store the list of modules to reload in a global module variable const char **module_name; zend_module_entry *module; @@ -116,10 +120,7 @@ static void frankenphp_worker_request_shutdown(uintptr_t current_request) { sapi_deactivate(); } zend_end_try(); - if (current_request != 0) go_frankenphp_worker_handle_request_end(current_request, true); - zend_set_memory_limit(PG(memory_limit)); - } /* Adapted from php_request_startup() */ @@ -189,23 +190,25 @@ static int frankenphp_worker_request_startup() { PHP_FUNCTION(frankenphp_finish_request) { /* {{{ */ if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); + RETURN_THROWS(); } frankenphp_server_context *ctx = SG(server_context); - if(!ctx->finished) { - php_output_end_all(); - php_header(); + if (ctx->finished) { + RETURN_FALSE; + } - go_frankenphp_worker_handle_request_end(ctx->current_request, false); - ctx->finished = true; + php_output_end_all(); + php_header(); - RETURN_TRUE; - } + if (ctx->current_request != 0) { + go_frankenphp_finish_request(ctx->main_request, ctx->current_request, false); + } - RETURN_FALSE; + ctx->finished = true; + RETURN_TRUE; } /* }}} */ PHP_FUNCTION(frankenphp_handle_request) { @@ -218,24 +221,40 @@ PHP_FUNCTION(frankenphp_handle_request) { frankenphp_server_context *ctx = SG(server_context); - uintptr_t previous_request = ctx->current_request; - if (ctx->main_request) { + if (ctx->main_request == 0) { + // not a worker, throw an error + zend_throw_exception(spl_ce_RuntimeException, "frankenphp_handle_request() called while not in worker mode", 0); + RETURN_THROWS(); + } + + if (!ctx->worker_ready) { /* Clean the first dummy request created to initialize the worker */ - frankenphp_worker_request_shutdown(0); + frankenphp_worker_request_shutdown(); - previous_request = ctx->main_request; + ctx->worker_ready = true; /* Mark the worker as ready to handle requests */ go_frankenphp_worker_ready(); } - uintptr_t next_request = go_frankenphp_worker_handle_request_start(previous_request); +#ifdef ZEND_MAX_EXECUTION_TIMERS + // Disable timeouts while waiting for a request to handle + zend_unset_timeout(); +#endif + + uintptr_t request = go_frankenphp_worker_handle_request_start(ctx->main_request); if ( frankenphp_worker_request_startup() == FAILURE /* Shutting down */ - || !next_request + || !request ) RETURN_FALSE; +#ifdef ZEND_MAX_EXECUTION_TIMERS + // Reset default timeout + // TODO: add support for max_input_time + zend_set_timeout(INI_INT("max_execution_time"), 0); +#endif + /* Call the PHP func */ zval retval = {0}; fci.size = sizeof fci; @@ -245,10 +264,12 @@ PHP_FUNCTION(frankenphp_handle_request) { } /* If an exception occured, print the message to the client before closing the connection */ - if (EG(exception)) + if (EG(exception)) { zend_exception_error(EG(exception), E_ERROR); + } - frankenphp_worker_request_shutdown(next_request); + frankenphp_worker_request_shutdown(); + go_frankenphp_finish_request(ctx->main_request, request, true); RETURN_TRUE; } @@ -319,7 +340,7 @@ uintptr_t frankenphp_request_shutdown() { frankenphp_server_context *ctx = SG(server_context); - if (ctx->worker && ctx->current_request) { + if (ctx->main_request && ctx->current_request) { frankenphp_request_reset(); } @@ -358,32 +379,28 @@ int frankenphp_update_server_context( if (create) { #ifdef ZTS - /* initial resource fetch */ - (void)ts_resource(0); + /* initial resource fetch */ + (void)ts_resource(0); # ifdef PHP_WIN32 - ZEND_TSRMLS_CACHE_UPDATE(); + ZEND_TSRMLS_CACHE_UPDATE(); # endif #endif - /* todo: use a pool */ - ctx = (frankenphp_server_context *) calloc(1, sizeof(frankenphp_server_context)); - if (ctx == NULL) return FAILURE; + /* todo: use a pool */ + ctx = (frankenphp_server_context *) calloc(1, sizeof(frankenphp_server_context)); + if (ctx == NULL) return FAILURE; - ctx->worker = false; - ctx->current_request = 0; - ctx->main_request = 0; - ctx->cookie_data = NULL; - ctx->finished = false; + ctx->cookie_data = NULL; + ctx->finished = false; - SG(server_context) = ctx; - } else + SG(server_context) = ctx; + } else { ctx = (frankenphp_server_context *) SG(server_context); + } ctx->main_request = main_request; ctx->current_request = current_request; - if (ctx->main_request) ctx->worker = true; - SG(request_info).auth_password = auth_password; SG(request_info).auth_user = auth_user; SG(request_info).request_method = request_method; @@ -541,6 +558,7 @@ sapi_module_struct frankenphp_sapi_module = { static void *manager_thread(void *arg) { #ifdef ZTS + // TODO: use tsrm_startup() directly as we now the number of expected threads php_tsrm_startup(); /*tsrm_error_set(TSRM_ERROR_LEVEL_INFO, NULL);*/ # ifdef PHP_WIN32 @@ -550,8 +568,10 @@ static void *manager_thread(void *arg) { sapi_startup(&frankenphp_sapi_module); +#ifndef ZEND_MAX_EXECUTION_TIMERS frankenphp_sapi_module.ini_entries = malloc(sizeof(HARDCODED_INI)); memcpy(frankenphp_sapi_module.ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI)); +#endif frankenphp_sapi_module.startup(&frankenphp_sapi_module); diff --git a/frankenphp.go b/frankenphp.go index c2c8eb39ee..8c3eb4afa5 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -130,8 +130,9 @@ type FrankenPHPContext struct { // Whether the request is already closed by us closed sync.Once - responseWriter http.ResponseWriter - done chan interface{} + responseWriter http.ResponseWriter + done chan interface{} + currentWorkerRequest cgo.Handle } func clientHasClosed(r *http.Request) bool { @@ -174,9 +175,16 @@ type PHPVersion struct { VersionID int } +type PHPConfig struct { + Version PHPVersion + ZTS bool + ZendSignals bool + ZendMaxExecutionTimers bool +} + // Version returns infos about the PHP version. func Version() PHPVersion { - cVersion := C.frankenphp_version() + cVersion := C.frankenphp_get_version() return PHPVersion{ int(cVersion.major_version), @@ -188,6 +196,17 @@ func Version() PHPVersion { } } +func Config() PHPConfig { + cConfig := C.frankenphp_get_config() + + return PHPConfig{ + Version: Version(), + ZTS: bool(cConfig.zts), + ZendSignals: bool(cConfig.zend_signals), + ZendMaxExecutionTimers: bool(cConfig.zend_max_execution_timers), + } +} + // Init starts the PHP runtime and the configured workers. func Init(options ...Option) error { if requestChan != nil { @@ -238,18 +257,19 @@ func Init(options ...Option) error { return NotEnoughThreads } - switch C.frankenphp_check_version() { - case -1: - if opt.numThreads != 1 { - opt.numThreads = 1 - logger.Warn(`ZTS is not enabled, only 1 thread will be available, recompile PHP using the "--enable-zts" configuration option or performance will be degraded`) - } + config := Config() - case -2: + if config.Version.MajorVersion < 8 || config.Version.MinorVersion < 2 { return InvalidPHPVersionError + } - case -3: - return ZendSignalsError + if config.ZTS { + if !config.ZendMaxExecutionTimers && runtime.GOOS == "linux" { + logger.Warn(`Zend Timer is not enabled, "--enable-zend-timer" configuration option or timeouts (e.g. "max_execution_time") will not work as expected`) + } + } else { + opt.numThreads = 1 + logger.Warn(`ZTS is not enabled, only 1 thread will be available, recompile PHP using the "--enable-zts" configuration option or performance will be degraded`) } shutdownWG.Add(1) @@ -293,7 +313,7 @@ func getLogger() *zap.Logger { return logger } -func updateServerContext(request *http.Request, create bool) error { +func updateServerContext(request *http.Request, create bool, mrh C.uintptr_t) error { fc, ok := FromContext(request.Context()) if !ok { return InvalidRequestError @@ -333,9 +353,9 @@ func updateServerContext(request *http.Request, create bool) error { cRequestUri := C.CString(request.URL.RequestURI()) - var rh, mwrh cgo.Handle + var rh cgo.Handle if fc.responseWriter == nil { - mwrh = cgo.NewHandle(request) + mrh = C.uintptr_t(cgo.NewHandle(request)) } else { rh = cgo.NewHandle(request) } @@ -343,7 +363,7 @@ func updateServerContext(request *http.Request, create bool) error { ret := C.frankenphp_update_server_context( C.bool(create), C.uintptr_t(rh), - C.uintptr_t(mwrh), + mrh, cMethod, cQueryString, @@ -425,7 +445,7 @@ func go_execute_script(rh unsafe.Pointer) { } defer maybeCloseContext(fc) - if err := updateServerContext(request, true); err != nil { + if err := updateServerContext(request, true, 0); err != nil { panic(err) } @@ -519,6 +539,7 @@ func go_write_header(rh C.uintptr_t, status C.int) { return } + // FIXME: http: superfluous response.WriteHeader call from github.com/dunglas/frankenphp.go_write_header fc.responseWriter.WriteHeader(int(status)) if status >= 100 && status < 200 { diff --git a/frankenphp.h b/frankenphp.h index 67a5ff8d56..9c7d4d0085 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -2,19 +2,27 @@ #define _FRANKENPPHP_H #include +#include #include -typedef struct frankenphp_php_version { - int major_version; - int minor_version; - int release_version; +typedef struct frankenphp_version { + unsigned char major_version; + unsigned char minor_version; + unsigned char release_version; const char *extra_version; const char *version; - int version_id; -} frankenphp_php_version; + unsigned long version_id; +} frankenphp_version; +frankenphp_version frankenphp_get_version(); + +typedef struct frankenphp_config { + frankenphp_version version; + bool zts; + bool zend_signals; + bool zend_max_execution_timers; +} frankenphp_config; +frankenphp_config frankenphp_get_config(); -frankenphp_php_version frankenphp_version(); -int frankenphp_check_version(); int frankenphp_init(int num_threads); int frankenphp_update_server_context( diff --git a/frankenphp_test.go b/frankenphp_test.go index f07d506bba..cf41c4da50 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -109,7 +109,9 @@ func BenchmarkHelloWorld(b *testing.B) { } func TestHelloWorld_module(t *testing.T) { testHelloWorld(t, nil) } -func TestHelloWorld_worker(t *testing.T) { testHelloWorld(t, &testOptions{workerScript: "index.php"}) } +func TestHelloWorld_worker(t *testing.T) { + testHelloWorld(t, &testOptions{workerScript: "index.php"}) +} func testHelloWorld(t *testing.T, opts *testOptions) { runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/index.php?i=%d", i), nil) @@ -591,6 +593,28 @@ func testFlush(t *testing.T, opts *testOptions) { }, opts) } +func TestTimeout_module(t *testing.T) { testTimeout(t, &testOptions{}) } +func TestTimeout_worker(t *testing.T) { + testTimeout(t, &testOptions{workerScript: "timeout.php"}) +} +func testTimeout(t *testing.T, opts *testOptions) { + config := frankenphp.Config() + if !config.ZendMaxExecutionTimers { + t.Skip("Zend Timer is not enabled") + } + + runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { + req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/timeout.php?i=%d", i), nil) + w := httptest.NewRecorder() + handler(w, req) + + resp := w.Result() + body, _ := io.ReadAll(resp.Body) + + assert.Contains(t, string(body), fmt.Sprintf("request: %d\n
\nFatal error: Maximum execution time of 1 second exceeded in", i)) + }, opts) +} + func TestVersion(t *testing.T) { v := frankenphp.Version() diff --git a/testdata/Caddyfile b/testdata/Caddyfile index 9bbf4bc20d..c43aa90009 100644 --- a/testdata/Caddyfile +++ b/testdata/Caddyfile @@ -1,7 +1,7 @@ { debug frankenphp { - worker ./phpinfo.php + worker ./timeout.php } } diff --git a/testdata/non-worker.php b/testdata/non-worker.php new file mode 100644 index 0000000000..9c3dc7b7ad --- /dev/null +++ b/testdata/non-worker.php @@ -0,0 +1,3 @@ +Fatal error: Uncaught RuntimeException: frankenphp_handle_request() called while not in worker mode") + }, nil) +} + func ExampleServeHTTP_workers() { if err := frankenphp.Init( frankenphp.WithWorkers("worker1.php", 4),