From 9ab0a5592316eb87fe14a6a0df45cc3236fdb02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 28 Jun 2024 15:04:49 +0200 Subject: [PATCH 01/25] perf: improve PHP thread management --- C-Thread-Pool/LICENSE | 21 -- C-Thread-Pool/README.md | 70 ----- C-Thread-Pool/thpool.c | 571 ---------------------------------------- C-Thread-Pool/thpool.h | 187 ------------- frankenphp.c | 35 ++- frankenphp.go | 55 ++-- 6 files changed, 46 insertions(+), 893 deletions(-) delete mode 100644 C-Thread-Pool/LICENSE delete mode 100644 C-Thread-Pool/README.md delete mode 100644 C-Thread-Pool/thpool.c delete mode 100644 C-Thread-Pool/thpool.h diff --git a/C-Thread-Pool/LICENSE b/C-Thread-Pool/LICENSE deleted file mode 100644 index bf4494c15b..0000000000 --- a/C-Thread-Pool/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Johan Hanssen Seferidis - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/C-Thread-Pool/README.md b/C-Thread-Pool/README.md deleted file mode 100644 index dc710b7e0b..0000000000 --- a/C-Thread-Pool/README.md +++ /dev/null @@ -1,70 +0,0 @@ -[![GitHub Actions](https://github.com/Pithikos/C-Thread-Pool/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/Pithikos/C-Thread-Pool/actions?query=workflow%3Atests+branch%3Amaster) - - -# C Thread Pool - -This is a minimal but advanced threadpool implementation. - - * ANCI C and POSIX compliant - * Pause/resume/wait as you like - * Simple easy-to-digest API - * Well tested - -The threadpool is under MIT license. Notice that this project took a considerable amount of work and sacrifice of my free time and the reason I give it for free (even for commercial use) is so when you become rich and wealthy you don't forget about us open-source creatures of the night. Cheers! - -If this project reduced your development time feel free to buy me a coffee. - -[![Donate](https://www.paypal.com/en_US/i/btn/x-click-but21.gif)](https://www.paypal.me/seferidis) - - -## Run an example - -The library is not precompiled so you have to compile it with your project. The thread pool -uses POSIX threads so if you compile with gcc on Linux you have to use the flag `-pthread` like this: - - gcc example.c thpool.c -D THPOOL_DEBUG -pthread -o example - - -Then run the executable like this: - - ./example - - -## Basic usage - -1. Include the header in your source file: `#include "thpool.h"` -2. Create a thread pool with number of threads you want: `threadpool thpool = thpool_init(4);` -3. Add work to the pool: `thpool_add_work(thpool, (void*)function_p, (void*)arg_p);` - -The workers(threads) will start their work automatically as fast as there is new work -in the pool. If you want to wait for all added work to be finished before continuing -you can use `thpool_wait(thpool);`. If you want to destroy the pool you can use -`thpool_destroy(thpool);`. - - -## API - -For a deeper look into the documentation check in the [thpool.h](https://github.com/Pithikos/C-Thread-Pool/blob/master/thpool.h) file. Below is a fast practical overview. - -| Function example | Description | -|---------------------------------|---------------------------------------------------------------------| -| ***thpool_init(4)*** | Will return a new threadpool with `4` threads. | -| ***thpool_add_work(thpool, (void*)function_p, (void*)arg_p)*** | Will add new work to the pool. Work is simply a function. You can pass a single argument to the function if you wish. If not, `NULL` should be passed. | -| ***thpool_wait(thpool)*** | Will wait for all jobs (both in queue and currently running) to finish. | -| ***thpool_destroy(thpool)*** | This will destroy the threadpool. If jobs are currently being executed, then it will wait for them to finish. | -| ***thpool_pause(thpool)*** | All threads in the threadpool will pause no matter if they are idle or executing work. | -| ***thpool_resume(thpool)*** | If the threadpool is paused, then all threads will resume from where they were. | -| ***thpool_num_threads_working(thpool)*** | Will return the number of currently working threads. | - - -## Contribution - -You are very welcome to contribute. If you have a new feature in mind, you can always open an issue on github describing it so you don't end up doing a lot of work that might not be eventually merged. Generally we are very open to contributions as long as they follow the below keypoints. - -* Try to keep the API as minimal as possible. That means if a feature or fix can be implemented without affecting the existing API but requires more development time, then we will opt to sacrifice development time. -* Solutions need to be POSIX compliant. The thread-pool is advertised as such so it makes sense that it actually is. -* For coding style simply try to stick to the conventions you find in the existing codebase. -* Tests: A new fix or feature should be covered by tests. If the existing tests are not sufficient, we expect an according test to follow with the pull request. -* Documentation: for a new feature please add documentation. For an API change the documentation has to be thorough and super easy to understand. - -If you wish to **get access as a collaborator** feel free to mention it in the issue https://github.com/Pithikos/C-Thread-Pool/issues/78 diff --git a/C-Thread-Pool/thpool.c b/C-Thread-Pool/thpool.c deleted file mode 100644 index 83885c3777..0000000000 --- a/C-Thread-Pool/thpool.c +++ /dev/null @@ -1,571 +0,0 @@ -/* ******************************** - * Author: Johan Hanssen Seferidis - * License: MIT - * Description: Library providing a threading pool where you can add - * work. For usage, check the thpool.h file or README.md - * - *//** @file thpool.h *//* - * - ********************************/ - -#if defined(__APPLE__) -#include -#else -#ifndef _POSIX_C_SOURCE -#define _POSIX_C_SOURCE 200809L -#endif -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 500 -#endif -#endif -#include -#include -#include -#include -#include -#include -#include -#if defined(__linux__) -#include -#endif -#if defined(__FreeBSD__) || defined(__OpenBSD__) -#include -#endif - -#include "thpool.h" - -#ifdef THPOOL_DEBUG -#define THPOOL_DEBUG 1 -#else -#define THPOOL_DEBUG 0 -#endif - -#if !defined(DISABLE_PRINT) || defined(THPOOL_DEBUG) -#define err(str) fprintf(stderr, str) -#else -#define err(str) -#endif - -#ifndef THPOOL_THREAD_NAME -#define THPOOL_THREAD_NAME thpool -#endif - -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) - -static volatile int threads_keepalive; -static volatile int threads_on_hold; - - - -/* ========================== STRUCTURES ============================ */ - - -/* Binary semaphore */ -typedef struct bsem { - pthread_mutex_t mutex; - pthread_cond_t cond; - int v; -} bsem; - - -/* Job */ -typedef struct job{ - struct job* prev; /* pointer to previous job */ - void (*function)(void* arg); /* function pointer */ - void* arg; /* function's argument */ -} job; - - -/* Job queue */ -typedef struct jobqueue{ - pthread_mutex_t rwmutex; /* used for queue r/w access */ - job *front; /* pointer to front of queue */ - job *rear; /* pointer to rear of queue */ - bsem *has_jobs; /* flag as binary semaphore */ - int len; /* number of jobs in queue */ -} jobqueue; - - -/* Thread */ -typedef struct thread{ - int id; /* friendly id */ - pthread_t pthread; /* pointer to actual thread */ - struct thpool_* thpool_p; /* access to thpool */ -} thread; - - -/* Threadpool */ -typedef struct thpool_{ - thread** threads; /* pointer to threads */ - volatile int num_threads_alive; /* threads currently alive */ - volatile int num_threads_working; /* threads currently working */ - pthread_mutex_t thcount_lock; /* used for thread count etc */ - pthread_cond_t threads_all_idle; /* signal to thpool_wait */ - jobqueue jobqueue; /* job queue */ -} thpool_; - - - - - -/* ========================== PROTOTYPES ============================ */ - - -static int thread_init(thpool_* thpool_p, struct thread** thread_p, int id); -static void* thread_do(struct thread* thread_p); -static void thread_hold(int sig_id); -static void thread_destroy(struct thread* thread_p); - -static int jobqueue_init(jobqueue* jobqueue_p); -static void jobqueue_clear(jobqueue* jobqueue_p); -static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob_p); -static struct job* jobqueue_pull(jobqueue* jobqueue_p); -static void jobqueue_destroy(jobqueue* jobqueue_p); - -static void bsem_init(struct bsem *bsem_p, int value); -static void bsem_reset(struct bsem *bsem_p); -static void bsem_post(struct bsem *bsem_p); -static void bsem_post_all(struct bsem *bsem_p); -static void bsem_wait(struct bsem *bsem_p); - - - - - -/* ========================== THREADPOOL ============================ */ - - -/* Initialise thread pool */ -struct thpool_* thpool_init(int num_threads){ - - threads_on_hold = 0; - threads_keepalive = 1; - - if (num_threads < 0){ - num_threads = 0; - } - - /* Make new thread pool */ - thpool_* thpool_p; - thpool_p = (struct thpool_*)malloc(sizeof(struct thpool_)); - if (thpool_p == NULL){ - err("thpool_init(): Could not allocate memory for thread pool\n"); - return NULL; - } - thpool_p->num_threads_alive = 0; - thpool_p->num_threads_working = 0; - - /* Initialise the job queue */ - if (jobqueue_init(&thpool_p->jobqueue) == -1){ - err("thpool_init(): Could not allocate memory for job queue\n"); - free(thpool_p); - return NULL; - } - - /* Make threads in pool */ - thpool_p->threads = (struct thread**)malloc(num_threads * sizeof(struct thread *)); - if (thpool_p->threads == NULL){ - err("thpool_init(): Could not allocate memory for threads\n"); - jobqueue_destroy(&thpool_p->jobqueue); - free(thpool_p); - return NULL; - } - - pthread_mutex_init(&(thpool_p->thcount_lock), NULL); - pthread_cond_init(&thpool_p->threads_all_idle, NULL); - - /* Thread init */ - int n; - for (n=0; nthreads[n], n); -#if THPOOL_DEBUG - printf("THPOOL_DEBUG: Created thread %d in pool \n", n); -#endif - } - - /* Wait for threads to initialize */ - while (thpool_p->num_threads_alive != num_threads) {} - - return thpool_p; -} - - -/* Add work to the thread pool */ -int thpool_add_work(thpool_* thpool_p, void (*function_p)(void*), void* arg_p){ - job* newjob; - - newjob=(struct job*)malloc(sizeof(struct job)); - if (newjob==NULL){ - err("thpool_add_work(): Could not allocate memory for new job\n"); - return -1; - } - - /* add function and argument */ - newjob->function=function_p; - newjob->arg=arg_p; - - /* add job to queue */ - jobqueue_push(&thpool_p->jobqueue, newjob); - - return 0; -} - - -/* Wait until all jobs have finished */ -void thpool_wait(thpool_* thpool_p){ - pthread_mutex_lock(&thpool_p->thcount_lock); - while (thpool_p->jobqueue.len || thpool_p->num_threads_working) { - pthread_cond_wait(&thpool_p->threads_all_idle, &thpool_p->thcount_lock); - } - pthread_mutex_unlock(&thpool_p->thcount_lock); -} - - -/* Destroy the threadpool */ -void thpool_destroy(thpool_* thpool_p){ - /* No need to destroy if it's NULL */ - if (thpool_p == NULL) return ; - - volatile int threads_total = thpool_p->num_threads_alive; - - /* End each thread 's infinite loop */ - threads_keepalive = 0; - - /* Give one second to kill idle threads */ - double TIMEOUT = 1.0; - time_t start, end; - double tpassed = 0.0; - time (&start); - while (tpassed < TIMEOUT && thpool_p->num_threads_alive){ - bsem_post_all(thpool_p->jobqueue.has_jobs); - time (&end); - tpassed = difftime(end,start); - } - - /* Poll remaining threads */ - while (thpool_p->num_threads_alive){ - bsem_post_all(thpool_p->jobqueue.has_jobs); - sleep(1); - } - - /* Job queue cleanup */ - jobqueue_destroy(&thpool_p->jobqueue); - /* Deallocs */ - int n; - for (n=0; n < threads_total; n++){ - thread_destroy(thpool_p->threads[n]); - } - free(thpool_p->threads); - free(thpool_p); -} - - -/* Pause all threads in threadpool */ -void thpool_pause(thpool_* thpool_p) { - int n; - for (n=0; n < thpool_p->num_threads_alive; n++){ - pthread_kill(thpool_p->threads[n]->pthread, SIGUSR1); - } -} - - -/* Resume all threads in threadpool */ -void thpool_resume(thpool_* thpool_p) { - // resuming a single threadpool hasn't been - // implemented yet, meanwhile this suppresses - // the warnings - (void)thpool_p; - - threads_on_hold = 0; -} - - -int thpool_num_threads_working(thpool_* thpool_p){ - return thpool_p->num_threads_working; -} - - - - - -/* ============================ THREAD ============================== */ - - -/* Initialize a thread in the thread pool - * - * @param thread address to the pointer of the thread to be created - * @param id id to be given to the thread - * @return 0 on success, -1 otherwise. - */ -static int thread_init (thpool_* thpool_p, struct thread** thread_p, int id){ - - *thread_p = (struct thread*)malloc(sizeof(struct thread)); - if (*thread_p == NULL){ - err("thread_init(): Could not allocate memory for thread\n"); - return -1; - } - - (*thread_p)->thpool_p = thpool_p; - (*thread_p)->id = id; - - pthread_create(&(*thread_p)->pthread, NULL, (void * (*)(void *)) thread_do, (*thread_p)); - pthread_detach((*thread_p)->pthread); - return 0; -} - - -/* Sets the calling thread on hold */ -static void thread_hold(int sig_id) { - (void)sig_id; - threads_on_hold = 1; - while (threads_on_hold){ - sleep(1); - } -} - - -/* What each thread is doing -* -* In principle this is an endless loop. The only time this loop gets interrupted is once -* thpool_destroy() is invoked or the program exits. -* -* @param thread thread that will run this function -* @return nothing -*/ -static void* thread_do(struct thread* thread_p){ - - /* Set thread name for profiling and debugging */ - char thread_name[16] = {0}; - - snprintf(thread_name, 16, TOSTRING(THPOOL_THREAD_NAME) "-%d", thread_p->id); - -#if defined(__linux__) - /* Use prctl instead to prevent using _GNU_SOURCE flag and implicit declaration */ - prctl(PR_SET_NAME, thread_name); -#elif defined(__APPLE__) && defined(__MACH__) - pthread_setname_np(thread_name); -#elif defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(thread_p->pthread, thread_name); -#else - err("thread_do(): pthread_setname_np is not supported on this system"); -#endif - - /* Assure all threads have been created before starting serving */ - thpool_* thpool_p = thread_p->thpool_p; - - /* Register signal handler */ - struct sigaction act; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_ONSTACK; - act.sa_handler = thread_hold; - if (sigaction(SIGUSR1, &act, NULL) == -1) { - err("thread_do(): cannot handle SIGUSR1"); - } - - /* Mark thread as alive (initialized) */ - pthread_mutex_lock(&thpool_p->thcount_lock); - thpool_p->num_threads_alive += 1; - pthread_mutex_unlock(&thpool_p->thcount_lock); - - while(threads_keepalive){ - - bsem_wait(thpool_p->jobqueue.has_jobs); - - if (threads_keepalive){ - - pthread_mutex_lock(&thpool_p->thcount_lock); - thpool_p->num_threads_working++; - pthread_mutex_unlock(&thpool_p->thcount_lock); - - /* Read job from queue and execute it */ - void (*func_buff)(void*); - void* arg_buff; - job* job_p = jobqueue_pull(&thpool_p->jobqueue); - if (job_p) { - func_buff = job_p->function; - arg_buff = job_p->arg; - func_buff(arg_buff); - free(job_p); - } - - pthread_mutex_lock(&thpool_p->thcount_lock); - thpool_p->num_threads_working--; - if (!thpool_p->num_threads_working) { - pthread_cond_signal(&thpool_p->threads_all_idle); - } - pthread_mutex_unlock(&thpool_p->thcount_lock); - - } - } - pthread_mutex_lock(&thpool_p->thcount_lock); - thpool_p->num_threads_alive --; - pthread_mutex_unlock(&thpool_p->thcount_lock); - - return NULL; -} - - -/* Frees a thread */ -static void thread_destroy (thread* thread_p){ - free(thread_p); -} - - - - - -/* ============================ JOB QUEUE =========================== */ - - -/* Initialize queue */ -static int jobqueue_init(jobqueue* jobqueue_p){ - jobqueue_p->len = 0; - jobqueue_p->front = NULL; - jobqueue_p->rear = NULL; - - jobqueue_p->has_jobs = (struct bsem*)malloc(sizeof(struct bsem)); - if (jobqueue_p->has_jobs == NULL){ - return -1; - } - - pthread_mutex_init(&(jobqueue_p->rwmutex), NULL); - bsem_init(jobqueue_p->has_jobs, 0); - - return 0; -} - - -/* Clear the queue */ -static void jobqueue_clear(jobqueue* jobqueue_p){ - - while(jobqueue_p->len){ - free(jobqueue_pull(jobqueue_p)); - } - - jobqueue_p->front = NULL; - jobqueue_p->rear = NULL; - bsem_reset(jobqueue_p->has_jobs); - jobqueue_p->len = 0; - -} - - -/* Add (allocated) job to queue - */ -static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob){ - - pthread_mutex_lock(&jobqueue_p->rwmutex); - newjob->prev = NULL; - - switch(jobqueue_p->len){ - - case 0: /* if no jobs in queue */ - jobqueue_p->front = newjob; - jobqueue_p->rear = newjob; - break; - - default: /* if jobs in queue */ - jobqueue_p->rear->prev = newjob; - jobqueue_p->rear = newjob; - - } - jobqueue_p->len++; - - bsem_post(jobqueue_p->has_jobs); - pthread_mutex_unlock(&jobqueue_p->rwmutex); -} - - -/* Get first job from queue(removes it from queue) - * Notice: Caller MUST hold a mutex - */ -static struct job* jobqueue_pull(jobqueue* jobqueue_p){ - - pthread_mutex_lock(&jobqueue_p->rwmutex); - job* job_p = jobqueue_p->front; - - switch(jobqueue_p->len){ - - case 0: /* if no jobs in queue */ - break; - - case 1: /* if one job in queue */ - jobqueue_p->front = NULL; - jobqueue_p->rear = NULL; - jobqueue_p->len = 0; - break; - - default: /* if >1 jobs in queue */ - jobqueue_p->front = job_p->prev; - jobqueue_p->len--; - /* more than one job in queue -> post it */ - bsem_post(jobqueue_p->has_jobs); - - } - - pthread_mutex_unlock(&jobqueue_p->rwmutex); - return job_p; -} - - -/* Free all queue resources back to the system */ -static void jobqueue_destroy(jobqueue* jobqueue_p){ - jobqueue_clear(jobqueue_p); - free(jobqueue_p->has_jobs); -} - - - - - -/* ======================== SYNCHRONISATION ========================= */ - - -/* Init semaphore to 1 or 0 */ -static void bsem_init(bsem *bsem_p, int value) { - if (value < 0 || value > 1) { - err("bsem_init(): Binary semaphore can take only values 1 or 0"); - exit(1); - } - pthread_mutex_init(&(bsem_p->mutex), NULL); - pthread_cond_init(&(bsem_p->cond), NULL); - bsem_p->v = value; -} - - -/* Reset semaphore to 0 */ -static void bsem_reset(bsem *bsem_p) { - pthread_mutex_destroy(&(bsem_p->mutex)); - pthread_cond_destroy(&(bsem_p->cond)); - bsem_init(bsem_p, 0); -} - - -/* Post to at least one thread */ -static void bsem_post(bsem *bsem_p) { - pthread_mutex_lock(&bsem_p->mutex); - bsem_p->v = 1; - pthread_cond_signal(&bsem_p->cond); - pthread_mutex_unlock(&bsem_p->mutex); -} - - -/* Post to all threads */ -static void bsem_post_all(bsem *bsem_p) { - pthread_mutex_lock(&bsem_p->mutex); - bsem_p->v = 1; - pthread_cond_broadcast(&bsem_p->cond); - pthread_mutex_unlock(&bsem_p->mutex); -} - - -/* Wait on semaphore until semaphore has value 0 */ -static void bsem_wait(bsem* bsem_p) { - pthread_mutex_lock(&bsem_p->mutex); - while (bsem_p->v != 1) { - pthread_cond_wait(&bsem_p->cond, &bsem_p->mutex); - } - bsem_p->v = 0; - pthread_mutex_unlock(&bsem_p->mutex); -} diff --git a/C-Thread-Pool/thpool.h b/C-Thread-Pool/thpool.h deleted file mode 100644 index af3e68d165..0000000000 --- a/C-Thread-Pool/thpool.h +++ /dev/null @@ -1,187 +0,0 @@ -/********************************** - * @author Johan Hanssen Seferidis - * License: MIT - * - **********************************/ - -#ifndef _THPOOL_ -#define _THPOOL_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* =================================== API ======================================= */ - - -typedef struct thpool_* threadpool; - - -/** - * @brief Initialize threadpool - * - * Initializes a threadpool. This function will not return until all - * threads have initialized successfully. - * - * @example - * - * .. - * threadpool thpool; //First we declare a threadpool - * thpool = thpool_init(4); //then we initialize it to 4 threads - * .. - * - * @param num_threads number of threads to be created in the threadpool - * @return threadpool created threadpool on success, - * NULL on error - */ -threadpool thpool_init(int num_threads); - - -/** - * @brief Add work to the job queue - * - * Takes an action and its argument and adds it to the threadpool's job queue. - * If you want to add to work a function with more than one arguments then - * a way to implement this is by passing a pointer to a structure. - * - * NOTICE: You have to cast both the function and argument to not get warnings. - * - * @example - * - * void print_num(int num){ - * printf("%d\n", num); - * } - * - * int main() { - * .. - * int a = 10; - * thpool_add_work(thpool, (void*)print_num, (void*)a); - * .. - * } - * - * @param threadpool threadpool to which the work will be added - * @param function_p pointer to function to add as work - * @param arg_p pointer to an argument - * @return 0 on success, -1 otherwise. - */ -int thpool_add_work(threadpool, void (*function_p)(void*), void* arg_p); - - -/** - * @brief Wait for all queued jobs to finish - * - * Will wait for all jobs - both queued and currently running to finish. - * Once the queue is empty and all work has completed, the calling thread - * (probably the main program) will continue. - * - * Smart polling is used in wait. The polling is initially 0 - meaning that - * there is virtually no polling at all. If after 1 seconds the threads - * haven't finished, the polling interval starts growing exponentially - * until it reaches max_secs seconds. Then it jumps down to a maximum polling - * interval assuming that heavy processing is being used in the threadpool. - * - * @example - * - * .. - * threadpool thpool = thpool_init(4); - * .. - * // Add a bunch of work - * .. - * thpool_wait(thpool); - * puts("All added work has finished"); - * .. - * - * @param threadpool the threadpool to wait for - * @return nothing - */ -void thpool_wait(threadpool); - - -/** - * @brief Pauses all threads immediately - * - * The threads will be paused no matter if they are idle or working. - * The threads return to their previous states once thpool_resume - * is called. - * - * While the thread is being paused, new work can be added. - * - * @example - * - * threadpool thpool = thpool_init(4); - * thpool_pause(thpool); - * .. - * // Add a bunch of work - * .. - * thpool_resume(thpool); // Let the threads start their magic - * - * @param threadpool the threadpool where the threads should be paused - * @return nothing - */ -void thpool_pause(threadpool); - - -/** - * @brief Unpauses all threads if they are paused - * - * @example - * .. - * thpool_pause(thpool); - * sleep(10); // Delay execution 10 seconds - * thpool_resume(thpool); - * .. - * - * @param threadpool the threadpool where the threads should be unpaused - * @return nothing - */ -void thpool_resume(threadpool); - - -/** - * @brief Destroy the threadpool - * - * This will wait for the currently active threads to finish and then 'kill' - * the whole threadpool to free up memory. - * - * @example - * int main() { - * threadpool thpool1 = thpool_init(2); - * threadpool thpool2 = thpool_init(2); - * .. - * thpool_destroy(thpool1); - * .. - * return 0; - * } - * - * @param threadpool the threadpool to destroy - * @return nothing - */ -void thpool_destroy(threadpool); - - -/** - * @brief Show currently working threads - * - * Working threads are the threads that are performing work (not idle). - * - * @example - * int main() { - * threadpool thpool1 = thpool_init(2); - * threadpool thpool2 = thpool_init(2); - * .. - * printf("Working threads: %d\n", thpool_num_threads_working(thpool1)); - * .. - * return 0; - * } - * - * @param threadpool the threadpool of interest - * @return integer number of threads working - */ -int thpool_num_threads_working(threadpool); - - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/frankenphp.c b/frankenphp.c index 088f6997a1..182c78f8bb 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -18,9 +18,6 @@ #include #include -#include "C-Thread-Pool/thpool.c" -#include "C-Thread-Pool/thpool.h" - #include "_cgo_export.h" #include "frankenphp_arginfo.h" @@ -719,7 +716,14 @@ sapi_module_struct frankenphp_sapi_module = { STANDARD_SAPI_MODULE_PROPERTIES}; -static void *manager_thread(void *arg) { +static void *php_thread(void *arg) { + while (go_handle_request()) { + } + + return NULL; +} + +static void *php_init(void *arg) { /* * SIGPIPE must be masked in non-Go threads: * https://pkg.go.dev/os/signal#hdr-Go_programs_that_use_cgo_or_SWIG @@ -761,17 +765,22 @@ static void *manager_thread(void *arg) { frankenphp_sapi_module.startup(&frankenphp_sapi_module); - threadpool thpool = thpool_init(num_threads); + pthread_t *threads = malloc(num_threads * sizeof(pthread_t)); + for (int i = 0; i < num_threads; i++) { + if (pthread_create(&(*(threads + i)), NULL, &php_thread, NULL) != 0) { + perror("failed to create PHP thead"); + exit(EXIT_FAILURE); + } + } - uintptr_t rh; - while ((rh = go_fetch_request())) { - thpool_add_work(thpool, go_execute_script, (void *)rh); + for (int i = 0; i < num_threads; i++) { + if (pthread_join((*(threads + i)), NULL) != 0) { + perror("failed to join PHP thead"); + exit(EXIT_FAILURE); + } } /* channel closed, shutdown gracefully */ - thpool_wait(thpool); - thpool_destroy(thpool); - frankenphp_sapi_module.shutdown(&frankenphp_sapi_module); sapi_shutdown(); @@ -794,8 +803,8 @@ static void *manager_thread(void *arg) { int frankenphp_init(int num_threads) { pthread_t thread; - if (pthread_create(&thread, NULL, *manager_thread, - (void *)(intptr_t)num_threads) != 0) { + if (pthread_create(&thread, NULL, &php_init, (void *)(intptr_t)num_threads) != + 0) { go_shutdown(); return -1; diff --git a/frankenphp.go b/frankenphp.go index 76a0469de8..dadeda0a5f 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -468,16 +468,36 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error return nil } -//export go_fetch_request -func go_fetch_request() C.uintptr_t { +//export go_handle_request +func go_handle_request() bool { select { case <-done: - return 0 + return false case r := <-requestChan: h := cgo.NewHandle(r) r.Context().Value(handleKey).(*handleList).AddHandle(h) - return C.uintptr_t(h) + + fc, ok := FromContext(r.Context()) + if !ok { + panic(InvalidRequestError) + } + defer func() { + maybeCloseContext(fc) + r.Context().Value(handleKey).(*handleList).FreeAll() + }() + + if err := updateServerContext(r, true, 0); err != nil { + panic(err) + } + + // scriptFilename is freed in frankenphp_execute_script() + fc.exitStatus = C.frankenphp_execute_script(C.CString(fc.scriptFilename)) + if fc.exitStatus < 0 { + panic(ScriptExecutionError) + } + + return true } } @@ -487,33 +507,6 @@ func maybeCloseContext(fc *FrankenPHPContext) { }) } -// go_execute_script Note: only called in cgi-mode -// -//export go_execute_script -func go_execute_script(rh unsafe.Pointer) { - handle := cgo.Handle(rh) - - request := handle.Value().(*http.Request) - fc, ok := FromContext(request.Context()) - if !ok { - panic(InvalidRequestError) - } - defer func() { - maybeCloseContext(fc) - request.Context().Value(handleKey).(*handleList).FreeAll() - }() - - if err := updateServerContext(request, true, 0); err != nil { - panic(err) - } - - // scriptFilename is freed in frankenphp_execute_script() - fc.exitStatus = C.frankenphp_execute_script(C.CString(fc.scriptFilename)) - if fc.exitStatus < 0 { - panic(ScriptExecutionError) - } -} - //export go_ub_write func go_ub_write(rh C.uintptr_t, cBuf *C.char, length C.int) (C.size_t, C.bool) { r := cgo.Handle(rh).Value().(*http.Request) From df95da520cc3e777336f9ef328f7f7e020d2d31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 3 Jul 2024 15:12:49 +0200 Subject: [PATCH 02/25] cleanup --- .github/workflows/lint.yaml | 1 - CONTRIBUTING.md | 1 - Dockerfile | 1 - alpine.Dockerfile | 1 - docs/cn/CONTRIBUTING.md | 1 - docs/fr/CONTRIBUTING.md | 1 - docs/tr/CONTRIBUTING.md | 1 - frankenphp.go | 4 ---- static-builder.Dockerfile | 1 - 9 files changed, 12 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 37cd22c8a4..e75623fce4 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -32,7 +32,6 @@ jobs: DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} LINTER_RULES_PATH: / - FILTER_REGEX_EXCLUDE: '.*C-Thread-Pool/.*' MARKDOWN_CONFIG_FILE: .markdown-lint.yaml VALIDATE_CPP: false VALIDATE_JSCPD: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3a43bdcf9..850229dd4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,6 @@ If docker version is lower than 23.0, build is failed by dockerignore [pattern i !testdata/*.php !testdata/*.txt +!caddy -+!C-Thread-Pool +!internal ``` diff --git a/Dockerfile b/Dockerfile index 6a0ae174a9..a4b48ddee6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -80,7 +80,6 @@ RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get WORKDIR /go/src/app COPY --link *.* ./ COPY --link caddy caddy -COPY --link C-Thread-Pool C-Thread-Pool COPY --link internal internal COPY --link testdata testdata diff --git a/alpine.Dockerfile b/alpine.Dockerfile index 33e99a0373..114d8ed1c8 100644 --- a/alpine.Dockerfile +++ b/alpine.Dockerfile @@ -78,7 +78,6 @@ RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get WORKDIR /go/src/app COPY --link *.* ./ COPY --link caddy caddy -COPY --link C-Thread-Pool C-Thread-Pool COPY --link internal internal COPY --link testdata testdata diff --git a/docs/cn/CONTRIBUTING.md b/docs/cn/CONTRIBUTING.md index 9fecf9593b..a7887983b4 100644 --- a/docs/cn/CONTRIBUTING.md +++ b/docs/cn/CONTRIBUTING.md @@ -19,7 +19,6 @@ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 - !testdata/*.php !testdata/*.txt +!caddy -+!C-Thread-Pool +!internal ``` diff --git a/docs/fr/CONTRIBUTING.md b/docs/fr/CONTRIBUTING.md index ce478456e1..7f9973969b 100644 --- a/docs/fr/CONTRIBUTING.md +++ b/docs/fr/CONTRIBUTING.md @@ -19,7 +19,6 @@ Si la version de Docker est inférieure à 23.0, la construction échoue à caus !testdata/*.php !testdata/*.txt +!caddy -+!C-Thread-Pool +!internal ``` diff --git a/docs/tr/CONTRIBUTING.md b/docs/tr/CONTRIBUTING.md index 2149de4159..a7b0937658 100644 --- a/docs/tr/CONTRIBUTING.md +++ b/docs/tr/CONTRIBUTING.md @@ -19,7 +19,6 @@ Docker sürümü 23.0'dan düşükse, derleme dockerignore [pattern issue](https !testdata/*.php !testdata/*.txt +!caddy -+!C-Thread-Pool +!internal ``` diff --git a/frankenphp.go b/frankenphp.go index dadeda0a5f..709018cea8 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -5,10 +5,6 @@ // [FrankenPHP app server]: https://frankenphp.dev package frankenphp -//go:generate rm -Rf C-Thread-Pool/ -//go:generate git clone --depth=1 git@github.com:Pithikos/C-Thread-Pool.git -//go:generate rm -Rf C-Thread-Pool/.git C-Thread-Pool/.github C-Thread-Pool/docs C-Thread-Pool/tests C-Thread-Pool/example.c - // Use PHP includes corresponding to your PHP installation by running: // // export CGO_CFLAGS=$(php-config --includes) diff --git a/static-builder.Dockerfile b/static-builder.Dockerfile index 97812ae8f0..ad4949a067 100644 --- a/static-builder.Dockerfile +++ b/static-builder.Dockerfile @@ -83,7 +83,6 @@ RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get WORKDIR /go/src/app COPY *.* ./ COPY caddy caddy -COPY C-Thread-Pool C-Thread-Pool RUN --mount=type=secret,id=github-token GITHUB_TOKEN=$(cat /run/secrets/github-token) ./build-static.sh && \ rm -Rf dist/static-php-cli/source/* From e329e5fe3cbf85ca9925b8a26b20f861a09c727b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 3 Jul 2024 16:40:39 +0200 Subject: [PATCH 03/25] free --- frankenphp.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frankenphp.c b/frankenphp.c index 182c78f8bb..3581bb6614 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -758,6 +758,10 @@ static void *php_init(void *arg) { frankenphp_sapi_module.ini_entries = HARDCODED_INI; #else frankenphp_sapi_module.ini_entries = malloc(sizeof(HARDCODED_INI)); + if (frankenphp_sapi_module.ini_entries == NULL) { + perror("malloc failed"); + exit(EXIT_FAILURE); + } memcpy(frankenphp_sapi_module.ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI)); #endif @@ -765,10 +769,16 @@ static void *php_init(void *arg) { frankenphp_sapi_module.startup(&frankenphp_sapi_module); - pthread_t *threads = malloc(num_threads * sizeof(pthread_t)); + pthread_t *threads = calloc(num_threads, sizeof(pthread_t)); + if (threads == NULL) { + perror("calloc failed"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < num_threads; i++) { if (pthread_create(&(*(threads + i)), NULL, &php_thread, NULL) != 0) { perror("failed to create PHP thead"); + free(threads); exit(EXIT_FAILURE); } } @@ -776,9 +786,11 @@ static void *php_init(void *arg) { for (int i = 0; i < num_threads; i++) { if (pthread_join((*(threads + i)), NULL) != 0) { perror("failed to join PHP thead"); + free(threads); exit(EXIT_FAILURE); } } + free(threads); /* channel closed, shutdown gracefully */ frankenphp_sapi_module.shutdown(&frankenphp_sapi_module); From d88f33ea5b847b52dc85a72fd44402c2b70e29b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 3 Jul 2024 16:48:11 +0200 Subject: [PATCH 04/25] malloc instead of calloc --- frankenphp.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index 3581bb6614..4749681ae9 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -450,8 +450,7 @@ int frankenphp_update_server_context( #endif /* todo: use a pool */ - ctx = (frankenphp_server_context *)calloc( - 1, sizeof(frankenphp_server_context)); + ctx = (frankenphp_server_context *)malloc(sizeof(frankenphp_server_context)); if (ctx == NULL) { return FAILURE; } @@ -769,9 +768,9 @@ static void *php_init(void *arg) { frankenphp_sapi_module.startup(&frankenphp_sapi_module); - pthread_t *threads = calloc(num_threads, sizeof(pthread_t)); + pthread_t *threads = malloc(num_threads * sizeof(pthread_t)); if (threads == NULL) { - perror("calloc failed"); + perror("malloc failed"); exit(EXIT_FAILURE); } From fc1c7b15a9d0749a18d9fada603c2908ba660d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 4 Jul 2024 10:03:06 +0200 Subject: [PATCH 05/25] cs --- frankenphp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frankenphp.c b/frankenphp.c index 4749681ae9..dc44f1cc99 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -450,7 +450,8 @@ int frankenphp_update_server_context( #endif /* todo: use a pool */ - ctx = (frankenphp_server_context *)malloc(sizeof(frankenphp_server_context)); + ctx = + (frankenphp_server_context *)malloc(sizeof(frankenphp_server_context)); if (ctx == NULL) { return FAILURE; } From 9343850236aaa9780282a8fdebff709b1430888b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 4 Jul 2024 15:04:52 +0200 Subject: [PATCH 06/25] feat: set thread name --- frankenphp.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index dc44f1cc99..0ea70e180b 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "_cgo_export.h" #include "frankenphp_arginfo.h" @@ -716,9 +717,29 @@ sapi_module_struct frankenphp_sapi_module = { STANDARD_SAPI_MODULE_PROPERTIES}; +/* Sets thread name for profiling and debugging. + * + * Adapted from https://github.com/Pithikos/C-Thread-Pool + * Copyright: Johan Hanssen Seferidis + * License: MIT +*/ +static void set_thread_name(char *thread_name) { +#if defined(__linux__) + /* Use prctl instead to prevent using _GNU_SOURCE flag and implicit declaration */ + prctl(PR_SET_NAME, thread_name); +#elif defined(__APPLE__) && defined(__MACH__) + pthread_setname_np(thread_name); +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + pthread_set_name_np(thread_p->pthread, thread_name); +#endif +} + static void *php_thread(void *arg) { - while (go_handle_request()) { - } + char thread_name[16] = {0}; + snprintf(thread_name, 16, "php-%" PRIxPTR, (uintptr_t)arg); + set_thread_name(thread_name); + + while (go_handle_request()) {} return NULL; } @@ -739,6 +760,8 @@ static void *php_init(void *arg) { intptr_t num_threads = (intptr_t)arg; + set_thread_name("php-init"); + #ifdef ZTS #if (PHP_VERSION_ID >= 80300) php_tsrm_startup_ex(num_threads); @@ -775,8 +798,8 @@ static void *php_init(void *arg) { exit(EXIT_FAILURE); } - for (int i = 0; i < num_threads; i++) { - if (pthread_create(&(*(threads + i)), NULL, &php_thread, NULL) != 0) { + for (uintptr_t i = 0; i < num_threads; i++) { + if (pthread_create(&(*(threads + i)), NULL, &php_thread, (void *)i) != 0) { perror("failed to create PHP thead"); free(threads); exit(EXIT_FAILURE); From 698a54282055c7c2810cf3a7c75345bd97824b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 4 Jul 2024 21:31:49 +0200 Subject: [PATCH 07/25] thread name --- frankenphp.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index 0ea70e180b..a49c572f42 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -18,6 +18,11 @@ #include #include #include +#if defined(__linux__) +#include +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#endif #include "_cgo_export.h" #include "frankenphp_arginfo.h" @@ -730,7 +735,7 @@ static void set_thread_name(char *thread_name) { #elif defined(__APPLE__) && defined(__MACH__) pthread_setname_np(thread_name); #elif defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(thread_p->pthread, thread_name); + pthread_set_name_np(pthread_self(), thread_name); #endif } @@ -744,7 +749,7 @@ static void *php_thread(void *arg) { return NULL; } -static void *php_init(void *arg) { +static void *php_main(void *arg) { /* * SIGPIPE must be masked in non-Go threads: * https://pkg.go.dev/os/signal#hdr-Go_programs_that_use_cgo_or_SWIG @@ -760,7 +765,7 @@ static void *php_init(void *arg) { intptr_t num_threads = (intptr_t)arg; - set_thread_name("php-init"); + set_thread_name("php-main"); #ifdef ZTS #if (PHP_VERSION_ID >= 80300) @@ -838,7 +843,7 @@ static void *php_init(void *arg) { int frankenphp_init(int num_threads) { pthread_t thread; - if (pthread_create(&thread, NULL, &php_init, (void *)(intptr_t)num_threads) != + if (pthread_create(&thread, NULL, &php_main, (void *)(intptr_t)num_threads) != 0) { go_shutdown(); From af1f2036df7c9346aef6399597bbae051477df0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 5 Jul 2024 13:25:39 +0200 Subject: [PATCH 08/25] cs --- frankenphp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index a49c572f42..c8b6752209 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -17,7 +18,6 @@ #include #include #include -#include #if defined(__linux__) #include #elif defined(__FreeBSD__) || defined(__OpenBSD__) @@ -727,10 +727,11 @@ sapi_module_struct frankenphp_sapi_module = { * Adapted from https://github.com/Pithikos/C-Thread-Pool * Copyright: Johan Hanssen Seferidis * License: MIT -*/ + */ static void set_thread_name(char *thread_name) { #if defined(__linux__) - /* Use prctl instead to prevent using _GNU_SOURCE flag and implicit declaration */ + /* Use prctl instead to prevent using _GNU_SOURCE flag and implicit + * declaration */ prctl(PR_SET_NAME, thread_name); #elif defined(__APPLE__) && defined(__MACH__) pthread_setname_np(thread_name); @@ -744,7 +745,8 @@ static void *php_thread(void *arg) { snprintf(thread_name, 16, "php-%" PRIxPTR, (uintptr_t)arg); set_thread_name(thread_name); - while (go_handle_request()) {} + while (go_handle_request()) { + } return NULL; } From e325b25cf5266671bd8bd6c81f5bf619ec2ceb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 6 Jul 2024 22:09:49 +0200 Subject: [PATCH 09/25] buffer --- frankenphp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frankenphp.go b/frankenphp.go index 709018cea8..9a9eae6bd0 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -311,7 +311,7 @@ func Init(options ...Option) error { shutdownWG.Add(1) done = make(chan struct{}) - requestChan = make(chan *http.Request) + requestChan = make(chan *http.Request, 16) if C.frankenphp_init(C.int(opt.numThreads)) != 0 { return MainThreadCreationError From e8d8868444e95801b460029dc2829652123a83da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 6 Jul 2024 22:16:50 +0200 Subject: [PATCH 10/25] more buffer --- worker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker.go b/worker.go index 9d1222f0f2..258db9e4e5 100644 --- a/worker.go +++ b/worker.go @@ -40,7 +40,7 @@ func startWorkers(fileName string, nbWorkers int, env PreparedEnv) error { return fmt.Errorf("workers %q: already started", absFileName) } - workersRequestChans.Store(absFileName, make(chan *http.Request)) + workersRequestChans.Store(absFileName, make(chan *http.Request, 16)) shutdownWG.Add(nbWorkers) workersReadyWG.Add(nbWorkers) From 584c1318c67cc717c4104eff0e50cc43c51d3ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 6 Jul 2024 22:32:54 +0200 Subject: [PATCH 11/25] debug, wip --- frankenphp.go | 4 +++- worker.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frankenphp.go b/frankenphp.go index 9a9eae6bd0..2142f5cdfb 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -41,6 +41,7 @@ import ( "strconv" "strings" "sync" + "time" "unsafe" "github.com/maypok86/otter" @@ -311,11 +312,12 @@ func Init(options ...Option) error { shutdownWG.Add(1) done = make(chan struct{}) - requestChan = make(chan *http.Request, 16) + requestChan = make(chan *http.Request) if C.frankenphp_init(C.int(opt.numThreads)) != 0 { return MainThreadCreationError } + time.Sleep(1 * time.Second) if err := initWorkers(opt.workers); err != nil { return err diff --git a/worker.go b/worker.go index 258db9e4e5..9d1222f0f2 100644 --- a/worker.go +++ b/worker.go @@ -40,7 +40,7 @@ func startWorkers(fileName string, nbWorkers int, env PreparedEnv) error { return fmt.Errorf("workers %q: already started", absFileName) } - workersRequestChans.Store(absFileName, make(chan *http.Request, 16)) + workersRequestChans.Store(absFileName, make(chan *http.Request)) shutdownWG.Add(nbWorkers) workersReadyWG.Add(nbWorkers) From 7235fc7cb5c7a8983240a027e8a5f8b884e55855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 7 Jul 2024 09:57:34 +0200 Subject: [PATCH 12/25] debug --- frankenphp.c | 2 ++ frankenphp.go | 2 -- testdata/_executor.php | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index c8b6752209..73468e3ba0 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -748,6 +748,8 @@ static void *php_thread(void *arg) { while (go_handle_request()) { } + fprintf(stderr, "PHP thread finished\n"); + return NULL; } diff --git a/frankenphp.go b/frankenphp.go index 2142f5cdfb..709018cea8 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -41,7 +41,6 @@ import ( "strconv" "strings" "sync" - "time" "unsafe" "github.com/maypok86/otter" @@ -317,7 +316,6 @@ func Init(options ...Option) error { if C.frankenphp_init(C.int(opt.numThreads)) != 0 { return MainThreadCreationError } - time.Sleep(1 * time.Second) if err := initWorkers(opt.workers); err != nil { return err diff --git a/testdata/_executor.php b/testdata/_executor.php index 61a5319f11..6ba3f3d08a 100644 --- a/testdata/_executor.php +++ b/testdata/_executor.php @@ -1,5 +1,7 @@ Date: Sun, 7 Jul 2024 10:57:41 +0200 Subject: [PATCH 13/25] push --- worker.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/worker.go b/worker.go index 9d1222f0f2..00ceb4de05 100644 --- a/worker.go +++ b/worker.go @@ -6,6 +6,7 @@ import "C" import ( "errors" "fmt" + "log" "net/http" "path/filepath" "runtime/cgo" @@ -144,6 +145,7 @@ func go_frankenphp_worker_handle_request_start(mrh C.uintptr_t) C.uintptr_t { l := getLogger() + log.Printf("rc: %#v", rc) l.Debug("waiting for request", zap.String("worker", fc.scriptFilename)) var r *http.Request @@ -155,6 +157,8 @@ func go_frankenphp_worker_handle_request_start(mrh C.uintptr_t) C.uintptr_t { case r = <-rc: } + l.Debug("request received") + fc.currentWorkerRequest = cgo.NewHandle(r) r.Context().Value(handleKey).(*handleList).AddHandle(fc.currentWorkerRequest) From 0136657d2330aab0fa7810ae4d220daa4ac1bf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 7 Jul 2024 11:33:04 +0200 Subject: [PATCH 14/25] debug --- frankenphp.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frankenphp.go b/frankenphp.go index 709018cea8..e2bb765f93 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -34,6 +34,7 @@ import ( "errors" "fmt" "io" + "log" "net/http" "os" "runtime" @@ -452,9 +453,16 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error if nil != fc.responseWriter { if v, ok := workersRequestChans.Load(fc.scriptFilename); ok { rc = v.(chan *http.Request) + log.Printf("serve with worker: %q", fc.scriptFilename) + } else { + log.Printf("worker not found?!") } + } else { + log.Printf("serve without worker") } + log.Printf("rc: %#v", rc) + select { case <-done: case rc <- request: From 58febaf3e4609d9a873d1c8ed73ed8e1606832c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 7 Jul 2024 11:49:20 +0200 Subject: [PATCH 15/25] debug --- frankenphp.go | 7 ++++--- worker.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frankenphp.go b/frankenphp.go index e2bb765f93..6f97336e12 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -438,6 +438,7 @@ func updateServerContext(request *http.Request, create bool, mrh C.uintptr_t) er // ServeHTTP executes a PHP script according to the given context. func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error { + log.Printf("begin ServeHTTP") shutdownWG.Add(1) defer shutdownWG.Done() @@ -455,13 +456,13 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error rc = v.(chan *http.Request) log.Printf("serve with worker: %q", fc.scriptFilename) } else { - log.Printf("worker not found?!") + log.Printf("serve without worker") } } else { - log.Printf("serve without worker") + log.Printf("main worker script") } - log.Printf("rc: %#v", rc) + log.Printf("rc in ServeHTTP: %#v", rc) select { case <-done: diff --git a/worker.go b/worker.go index 00ceb4de05..d6d84326ad 100644 --- a/worker.go +++ b/worker.go @@ -145,7 +145,7 @@ func go_frankenphp_worker_handle_request_start(mrh C.uintptr_t) C.uintptr_t { l := getLogger() - log.Printf("rc: %#v", rc) + log.Printf("rc in go_frankenphp_worker_handle_request_start: %#v", rc) l.Debug("waiting for request", zap.String("worker", fc.scriptFilename)) var r *http.Request From eafa2f6b874648e4950f40b67e7e2494cba00119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 7 Jul 2024 14:22:45 +0200 Subject: [PATCH 16/25] test --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b3f071df05..e9fc9a1f82 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -20,6 +20,7 @@ jobs: php-versions: ['8.2', '8.3'] env: GOEXPERIMENT: cgocheck2 + GOMAXPROCS: 10 steps: - uses: actions/checkout@v4 From c47bfbe3b947aafede59a949e2948e7a901889e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 7 Jul 2024 14:43:31 +0200 Subject: [PATCH 17/25] test --- frankenphp_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frankenphp_test.go b/frankenphp_test.go index 2c1e5c6815..3df0d81d67 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -63,7 +63,9 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), * } initOpts = append(initOpts, opts.initOpts...) + t.Log("before test init") err := frankenphp.Init(initOpts...) + t.Log("after test init") require.Nil(t, err) defer frankenphp.Shutdown() @@ -85,6 +87,7 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), * wg.Add(opts.nbParrallelRequests) for i := 0; i < opts.nbParrallelRequests; i++ { go func(i int) { + t.Logf("Send internal test request %d", i) test(handler, ts, i) wg.Done() }(i) From 4b15e1ed60f444bd5688605e4ed6209d99a11dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 7 Jul 2024 18:05:34 +0200 Subject: [PATCH 18/25] test --- frankenphp.go | 4 ++++ worker.go | 1 + 2 files changed, 5 insertions(+) diff --git a/frankenphp.go b/frankenphp.go index 6f97336e12..4b0b67c912 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -314,13 +314,17 @@ func Init(options ...Option) error { done = make(chan struct{}) requestChan = make(chan *http.Request) + logger.Debug("before C.frankenphp_init") if C.frankenphp_init(C.int(opt.numThreads)) != 0 { return MainThreadCreationError } + logger.Debug("after C.frankenphp_init") + logger.Debug("before initWorkers") if err := initWorkers(opt.workers); err != nil { return err } + logger.Debug("after") logger.Info("FrankenPHP started 🐘", zap.String("php_version", Version().Version), zap.Int("num_threads", opt.numThreads)) if EmbeddedAppPath != "" { diff --git a/worker.go b/worker.go index d6d84326ad..614f876219 100644 --- a/worker.go +++ b/worker.go @@ -79,6 +79,7 @@ func startWorkers(fileName string, nbWorkers int, env PreparedEnv) error { if err := ServeHTTP(nil, r); err != nil { panic(err) } + l.Debug("worker started") fc := r.Context().Value(contextKey).(*FrankenPHPContext) if fc.currentWorkerRequest != 0 { From 4fe529e4b2ef7faf52a531e4cba85d5d292eb94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 7 Jul 2024 18:06:33 +0200 Subject: [PATCH 19/25] test --- frankenphp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/frankenphp.go b/frankenphp.go index 4b0b67c912..2b9c3c2bd1 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -471,6 +471,7 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error select { case <-done: case rc <- request: + log.Printf("request sent") <-fc.done } From 284ee23df82b294fcebc81f699954b4d3932755b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 8 Jul 2024 15:44:39 +0200 Subject: [PATCH 20/25] debug --- frankenphp.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frankenphp.go b/frankenphp.go index 2b9c3c2bd1..65cb6fefa9 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -42,6 +42,7 @@ import ( "strconv" "strings" "sync" + "time" "unsafe" "github.com/maypok86/otter" @@ -321,10 +322,11 @@ func Init(options ...Option) error { logger.Debug("after C.frankenphp_init") logger.Debug("before initWorkers") + time.Sleep(2 * time.Second) if err := initWorkers(opt.workers); err != nil { return err } - logger.Debug("after") + logger.Debug("after initWorkers") logger.Info("FrankenPHP started 🐘", zap.String("php_version", Version().Version), zap.Int("num_threads", opt.numThreads)) if EmbeddedAppPath != "" { From 062e0f87b6e12646ecde0c51742f361de7da4c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 8 Jul 2024 17:20:00 +0200 Subject: [PATCH 21/25] debug --- frankenphp.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frankenphp.go b/frankenphp.go index 65cb6fefa9..aca11d3a52 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -444,7 +444,6 @@ func updateServerContext(request *http.Request, create bool, mrh C.uintptr_t) er // ServeHTTP executes a PHP script according to the given context. func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error { - log.Printf("begin ServeHTTP") shutdownWG.Add(1) defer shutdownWG.Done() @@ -455,12 +454,15 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error fc.responseWriter = responseWriter + worker := false + rc := requestChan // Detect if a worker is available to handle this request if nil != fc.responseWriter { if v, ok := workersRequestChans.Load(fc.scriptFilename); ok { rc = v.(chan *http.Request) log.Printf("serve with worker: %q", fc.scriptFilename) + worker = true } else { log.Printf("serve without worker") } @@ -468,12 +470,10 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error log.Printf("main worker script") } - log.Printf("rc in ServeHTTP: %#v", rc) - select { case <-done: case rc <- request: - log.Printf("request sent") + log.Printf("request sent (worker %#v)", worker) <-fc.done } @@ -495,6 +495,7 @@ func go_handle_request() bool { panic(InvalidRequestError) } defer func() { + time.Sleep(1 * time.Second) maybeCloseContext(fc) r.Context().Value(handleKey).(*handleList).FreeAll() }() From d477a68f0e1d9aebe4d4e5b85b09d73a0d6e22bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 8 Jul 2024 17:43:55 +0200 Subject: [PATCH 22/25] debug --- frankenphp.go | 2 +- worker.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frankenphp.go b/frankenphp.go index aca11d3a52..3ec73ef13b 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -495,7 +495,7 @@ func go_handle_request() bool { panic(InvalidRequestError) } defer func() { - time.Sleep(1 * time.Second) + log.Print("maybeCloseContext in go_handle_request") maybeCloseContext(fc) r.Context().Value(handleKey).(*handleList).FreeAll() }() diff --git a/worker.go b/worker.go index 614f876219..6ad9e77431 100644 --- a/worker.go +++ b/worker.go @@ -107,7 +107,11 @@ func startWorkers(fileName string, nbWorkers int, env PreparedEnv) error { }() } + log.Printf("after go routine in startWorkers") + workersReadyWG.Wait() + log.Printf("workersReadyWG ready") + m.Lock() defer m.Unlock() From 76ce476f07288afe2264f1d3b1c7ef4b662a4457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 8 Jul 2024 19:32:54 +0200 Subject: [PATCH 23/25] debug --- frankenphp.c | 1 + frankenphp.go | 2 +- worker.go | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frankenphp.c b/frankenphp.c index 73468e3ba0..86a942ca29 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -332,6 +332,7 @@ PHP_FUNCTION(frankenphp_handle_request) { RETURN_THROWS(); } + fprintf(stderr, "before go_frankenphp_worker_readyé"); if (!ctx->worker_ready) { /* Clean the first dummy request created to initialize the worker */ frankenphp_worker_request_shutdown(); diff --git a/frankenphp.go b/frankenphp.go index 3ec73ef13b..e543fa2ab5 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -462,12 +462,12 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error if v, ok := workersRequestChans.Load(fc.scriptFilename); ok { rc = v.(chan *http.Request) log.Printf("serve with worker: %q", fc.scriptFilename) - worker = true } else { log.Printf("serve without worker") } } else { log.Printf("main worker script") + worker = true } select { diff --git a/worker.go b/worker.go index 6ad9e77431..1729e67c14 100644 --- a/worker.go +++ b/worker.go @@ -132,6 +132,7 @@ func stopWorkers() { //export go_frankenphp_worker_ready func go_frankenphp_worker_ready() { + log.Print("go_frankenphp_worker_ready") workersReadyWG.Done() } From 71f3e0ed1bde37225e8022d64adf72516bb978cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 8 Jul 2024 23:41:22 +0200 Subject: [PATCH 24/25] debug again --- frankenphp.c | 2 +- frankenphp.go | 17 ----------------- testdata/_executor.php | 2 -- worker.go | 3 +-- 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index 86a942ca29..ed30f84e39 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -332,7 +332,7 @@ PHP_FUNCTION(frankenphp_handle_request) { RETURN_THROWS(); } - fprintf(stderr, "before go_frankenphp_worker_readyé"); + fprintf(stderr, "before go_frankenphp_worker_ready (ready? %d)\n", ctx->worker_ready); if (!ctx->worker_ready) { /* Clean the first dummy request created to initialize the worker */ frankenphp_worker_request_shutdown(); diff --git a/frankenphp.go b/frankenphp.go index e543fa2ab5..709018cea8 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -34,7 +34,6 @@ import ( "errors" "fmt" "io" - "log" "net/http" "os" "runtime" @@ -42,7 +41,6 @@ import ( "strconv" "strings" "sync" - "time" "unsafe" "github.com/maypok86/otter" @@ -315,18 +313,13 @@ func Init(options ...Option) error { done = make(chan struct{}) requestChan = make(chan *http.Request) - logger.Debug("before C.frankenphp_init") if C.frankenphp_init(C.int(opt.numThreads)) != 0 { return MainThreadCreationError } - logger.Debug("after C.frankenphp_init") - logger.Debug("before initWorkers") - time.Sleep(2 * time.Second) if err := initWorkers(opt.workers); err != nil { return err } - logger.Debug("after initWorkers") logger.Info("FrankenPHP started 🐘", zap.String("php_version", Version().Version), zap.Int("num_threads", opt.numThreads)) if EmbeddedAppPath != "" { @@ -454,26 +447,17 @@ func ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) error fc.responseWriter = responseWriter - worker := false - rc := requestChan // Detect if a worker is available to handle this request if nil != fc.responseWriter { if v, ok := workersRequestChans.Load(fc.scriptFilename); ok { rc = v.(chan *http.Request) - log.Printf("serve with worker: %q", fc.scriptFilename) - } else { - log.Printf("serve without worker") } - } else { - log.Printf("main worker script") - worker = true } select { case <-done: case rc <- request: - log.Printf("request sent (worker %#v)", worker) <-fc.done } @@ -495,7 +479,6 @@ func go_handle_request() bool { panic(InvalidRequestError) } defer func() { - log.Print("maybeCloseContext in go_handle_request") maybeCloseContext(fc) r.Context().Value(handleKey).(*handleList).FreeAll() }() diff --git a/testdata/_executor.php b/testdata/_executor.php index 6ba3f3d08a..61a5319f11 100644 --- a/testdata/_executor.php +++ b/testdata/_executor.php @@ -1,7 +1,5 @@ Date: Tue, 9 Jul 2024 00:21:19 +0200 Subject: [PATCH 25/25] fix and clean --- frankenphp.c | 8 +++----- frankenphp_test.go | 3 --- worker.go | 9 --------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index ed30f84e39..3ccd64c442 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -332,7 +332,6 @@ PHP_FUNCTION(frankenphp_handle_request) { RETURN_THROWS(); } - fprintf(stderr, "before go_frankenphp_worker_ready (ready? %d)\n", ctx->worker_ready); if (!ctx->worker_ready) { /* Clean the first dummy request created to initialize the worker */ frankenphp_worker_request_shutdown(); @@ -463,6 +462,7 @@ int frankenphp_update_server_context( return FAILURE; } + ctx->worker_ready = false; ctx->cookie_data = NULL; ctx->finished = false; @@ -749,8 +749,6 @@ static void *php_thread(void *arg) { while (go_handle_request()) { } - fprintf(stderr, "PHP thread finished\n"); - return NULL; } @@ -810,7 +808,7 @@ static void *php_main(void *arg) { for (uintptr_t i = 0; i < num_threads; i++) { if (pthread_create(&(*(threads + i)), NULL, &php_thread, (void *)i) != 0) { - perror("failed to create PHP thead"); + perror("failed to create PHP thread"); free(threads); exit(EXIT_FAILURE); } @@ -818,7 +816,7 @@ static void *php_main(void *arg) { for (int i = 0; i < num_threads; i++) { if (pthread_join((*(threads + i)), NULL) != 0) { - perror("failed to join PHP thead"); + perror("failed to join PHP thread"); free(threads); exit(EXIT_FAILURE); } diff --git a/frankenphp_test.go b/frankenphp_test.go index 3df0d81d67..2c1e5c6815 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -63,9 +63,7 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), * } initOpts = append(initOpts, opts.initOpts...) - t.Log("before test init") err := frankenphp.Init(initOpts...) - t.Log("after test init") require.Nil(t, err) defer frankenphp.Shutdown() @@ -87,7 +85,6 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), * wg.Add(opts.nbParrallelRequests) for i := 0; i < opts.nbParrallelRequests; i++ { go func(i int) { - t.Logf("Send internal test request %d", i) test(handler, ts, i) wg.Done() }(i) diff --git a/worker.go b/worker.go index dbb7618a83..9d1222f0f2 100644 --- a/worker.go +++ b/worker.go @@ -6,7 +6,6 @@ import "C" import ( "errors" "fmt" - "log" "net/http" "path/filepath" "runtime/cgo" @@ -79,7 +78,6 @@ func startWorkers(fileName string, nbWorkers int, env PreparedEnv) error { if err := ServeHTTP(nil, r); err != nil { panic(err) } - l.Debug("worker started") fc := r.Context().Value(contextKey).(*FrankenPHPContext) if fc.currentWorkerRequest != 0 { @@ -107,11 +105,7 @@ func startWorkers(fileName string, nbWorkers int, env PreparedEnv) error { }() } - log.Printf("after go routine in startWorkers") - workersReadyWG.Wait() - log.Printf("workersReadyWG ready") - m.Lock() defer m.Unlock() @@ -132,7 +126,6 @@ func stopWorkers() { //export go_frankenphp_worker_ready func go_frankenphp_worker_ready() { - log.Print("in go_frankenphp_worker_ready") workersReadyWG.Done() } @@ -162,8 +155,6 @@ func go_frankenphp_worker_handle_request_start(mrh C.uintptr_t) C.uintptr_t { case r = <-rc: } - l.Debug("request received") - fc.currentWorkerRequest = cgo.NewHandle(r) r.Context().Value(handleKey).(*handleList).AddHandle(fc.currentWorkerRequest)