Skip to content

Commit f9c251b

Browse files
authored
Merge pull request #383 from openswoole/feat/fiber_api
Feat/fiber api
2 parents 34ad64c + 6e85207 commit f9c251b

34 files changed

+780
-204
lines changed

.github/workflows/ext.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ jobs:
4848
- name: Install dependencies
4949
run: |
5050
brew install autoconf
51-
brew install shivammathur/php/php@8.2
52-
brew link --overwrite --force shivammathur/php/php@8.2
51+
brew install shivammathur/php/php@8.3
52+
brew link --overwrite --force shivammathur/php/php@8.3
5353
brew install pcre2
5454
brew install c-ares
55-
ln -s /opt/homebrew/include/pcre2.h /opt/homebrew/opt/php@8.2/include/php/ext/pcre/pcre2.h
55+
ln -s /opt/homebrew/include/pcre2.h /opt/homebrew/opt/php@8.3/include/php/ext/pcre/pcre2.h
5656
- uses: actions/checkout@v4
5757
- name: phpize
5858
run: phpize
@@ -70,11 +70,9 @@ jobs:
7070
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
7171
strategy:
7272
matrix:
73-
php-version: [ '8.2.26']
73+
php-version: [ '8.3.15']
7474
alpine-version: [ '3.20' ]
7575
include:
76-
- php-version: '8.3.15'
77-
alpine-version: '3.20'
7876
- php-version: '8.4.2'
7977
alpine-version: '3.21'
8078
- php-version: '8.5.0'

.github/workflows/test-laravel.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
strategy:
1212
fail-fast: true
1313
matrix:
14-
php: [8.2, 8.3, 8.4, 8.5]
14+
php: [8.3, 8.4, 8.5]
1515
steps:
1616
- uses: actions/checkout@v4
1717
- name: Setup PHP

.github/workflows/test-linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
php: ['8.2', '8.3', '8.4', '8.5']
15+
php: ['8.3', '8.4', '8.5']
1616
steps:
1717
- uses: actions/checkout@v4
1818
- name: Show machine information

ci/docker-compile.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern 2>/dev/null || true
1515
#-----------compile------------
1616
#-------print error only-------
1717
export TERM=${TERM:-dumb}
18+
1819
cd "${__DIR__}" && cd ../ && \
1920
./clear.sh > /dev/null && \
2021
phpize --clean > /dev/null && \
@@ -35,3 +36,10 @@ docker-php-ext-enable --ini-name zzz-docker-php-ext-openswoole.ini openswoole >
3536
echo "zend.max_allowed_stack_size=-1" > /usr/local/etc/php/conf.d/zzz-stack-size.ini && \
3637
php --ri curl && \
3738
php --ri openswoole
39+
40+
#-----------install xdebug for fiber context tests (disabled by default)------------
41+
echo "" && echo "📦 Installing xdebug..." && echo ""
42+
pecl install xdebug > /dev/null 2>&1 && \
43+
docker-php-ext-enable xdebug > /dev/null 2>&1 && \
44+
echo "xdebug.mode=off" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
45+
echo "xdebug installed (mode=off)" || echo "⚠️ xdebug installation failed, fiber xdebug tests will be skipped"

ci/docker-route.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ __DIR__=$(cd "$(dirname "$0")";pwd)
55
# enter the dir
66
cd "${__DIR__}"
77

8+
# map SWOOLE_BRANCH (from docker-compose) to CI_BRANCH (used by run-tests.sh)
9+
[ -n "${SWOOLE_BRANCH}" ] && export CI_BRANCH="${SWOOLE_BRANCH}"
10+
811
# show system info
912
date && echo ""
1013
uname -a && echo ""

ci/route.sh

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ __DIR__=$(cd "$(dirname "$0")";pwd)
44

55
export DOCKER_COMPOSE_VERSION="5.0.2"
66

7-
export PHP_VERSION=${PHP_VERSION:-${1:-"8.2"}}
7+
export PHP_VERSION=${PHP_VERSION:-${1:-"8.3"}}
88
export CI_BRANCH=${CI_BRANCH:-${2:-"master"}}
99

1010
[ -z "${CI_BRANCH}" ] && export CI_BRANCH="master"
@@ -31,6 +31,19 @@ check_docker_dependency(){
3131
fi
3232
}
3333

34+
wait_for_mysql(){
35+
echo "⏳ Waiting for MySQL to be ready..."
36+
for i in $(seq 1 30); do
37+
if docker exec mysql mysqladmin ping -h 127.0.0.1 -u root -proot --silent > /dev/null 2>&1; then
38+
echo "✅ MySQL is ready"
39+
return 0
40+
fi
41+
sleep 2
42+
done
43+
echo "❌ MySQL failed to start within 60 seconds!"
44+
return 1
45+
}
46+
3447
start_docker_containers(){
3548
remove_docker_containers
3649
cd ${__DIR__} && \
@@ -40,12 +53,12 @@ start_docker_containers(){
4053
echo "\n❌ Create containers failed!"
4154
exit 1
4255
fi
56+
wait_for_mysql
4357
}
4458

4559
remove_docker_containers(){
4660
cd ${__DIR__} && \
47-
docker compose kill > /dev/null 2>&1 && \
48-
docker compose rm -f > /dev/null 2>&1
61+
docker compose down --remove-orphans -v > /dev/null 2>&1
4962
}
5063

5164
run_tests_in_docker(){

config.m4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ if test "$PHP_SWOOLE" != "no"; then
641641
src/coroutine/base.cc \
642642
src/coroutine/channel.cc \
643643
src/coroutine/context.cc \
644+
src/coroutine/fiber_context.cc \
644645
src/coroutine/file_lock.cc \
645646
src/coroutine/hook.cc \
646647
src/coroutine/selector.cc \

ext-src/php_swoole.cc

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ STD_ZEND_INI_BOOLEAN("openswoole.enable_coroutine", "On", PHP_INI_ALL, OnUpdateB
140140
* enable swoole coroutine epreemptive scheduler
141141
*/
142142
STD_ZEND_INI_BOOLEAN("openswoole.enable_preemptive_scheduler", "Off", PHP_INI_ALL, OnUpdateBool, enable_preemptive_scheduler, zend_openswoole_globals, openswoole_globals)
143+
/**
144+
* use PHP fiber context for coroutines (enables xdebug tracing)
145+
*/
146+
STD_ZEND_INI_BOOLEAN("openswoole.use_fiber_context", "Off", PHP_INI_ALL, OnUpdateBool, use_fiber_context, zend_openswoole_globals, openswoole_globals)
143147
/**
144148
* display error
145149
*/
@@ -154,6 +158,7 @@ PHP_INI_END()
154158
static void php_swoole_init_globals(zend_openswoole_globals *openswoole_globals) {
155159
openswoole_globals->enable_coroutine = 1;
156160
openswoole_globals->enable_preemptive_scheduler = 0;
161+
openswoole_globals->use_fiber_context = 0;
157162
openswoole_globals->socket_buffer_size = SW_SOCKET_BUFFER_SIZE;
158163
openswoole_globals->display_errors = 1;
159164
}
@@ -1226,10 +1231,19 @@ PHP_MINFO_FUNCTION(openswoole) {
12261231
php_info_print_table_row(2, "Built", buf);
12271232
#if defined(SW_USE_THREAD_CONTEXT)
12281233
php_info_print_table_row(2, "coroutine", "enabled with thread context");
1229-
#elif defined(SW_USE_ASM_CONTEXT)
1230-
php_info_print_table_row(2, "coroutine", "enabled with boost asm context");
12311234
#else
1232-
php_info_print_table_row(2, "coroutine", "enabled with ucontext");
1235+
if (swoole::Coroutine::use_fiber_context) {
1236+
php_info_print_table_row(2, "coroutine", "enabled with fiber context");
1237+
}
1238+
#if defined(SW_USE_ASM_CONTEXT)
1239+
else {
1240+
php_info_print_table_row(2, "coroutine", "enabled with boost asm context");
1241+
}
1242+
#else
1243+
else {
1244+
php_info_print_table_row(2, "coroutine", "enabled with ucontext");
1245+
}
1246+
#endif
12331247
#endif
12341248
#ifdef SW_DEBUG
12351249
php_info_print_table_row(2, "debug", "enabled");

ext-src/php_swoole_private.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,11 +376,7 @@ zend_bool php_swoole_signal_isset_handler(int signo);
376376
#endif
377377
/*}}}*/
378378

379-
/* PHP 8 compatibility macro {{{*/
380-
#define sw_zend7_object zend_object
381-
#define SW_Z7_OBJ_P(object) object
382379
#define SW_Z8_OBJ_P(zobj) Z_OBJ_P(zobj)
383-
/*}}}*/
384380

385381
typedef ssize_t php_stream_size_t;
386382

ext-src/swoole_coroutine.cc

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ bool PHPCoroutine::interrupt_thread_running = false;
6262

6363
// extern void php_swoole_load_library();
6464

65-
#if PHP_VERSION_ID >= 80500
65+
#if PHP_VERSION_ID >= 80400
6666
static zif_handler ori_exit_handler = nullptr;
6767
#else
6868
static user_opcode_handler_t ori_exit_handler = nullptr;
@@ -203,12 +203,7 @@ static int coro_exit_handler(zend_execute_data *execute_data) {
203203

204204
if (opline->op1_type != IS_UNUSED) {
205205
if (opline->op1_type == IS_CONST) {
206-
// see: https://github.com/php/php-src/commit/e70618aff6f447a298605d07648f2ce9e5a284f5
207-
#ifdef EX_CONSTANT
208-
exit_status = EX_CONSTANT(opline->op1);
209-
#else
210206
exit_status = RT_CONSTANT(opline, opline->op1);
211-
#endif
212207
} else {
213208
exit_status = EX_VAR(opline->op1.var);
214209
}
@@ -231,8 +226,8 @@ static int coro_exit_handler(zend_execute_data *execute_data) {
231226
return ZEND_USER_OPCODE_DISPATCH;
232227
}
233228
#endif
234-
#if PHP_VERSION_ID >= 80500
235-
/* PHP 8.5+: exit() is a regular function, intercept via function handler replacement */
229+
#if PHP_VERSION_ID >= 80400
230+
/* PHP 8.4+: exit() is a regular function, intercept via function handler replacement */
236231
PHP_FUNCTION(swoole_exit) {
237232
zend_long flags = 0;
238233
if (Coroutine::get_current()) {
@@ -313,10 +308,9 @@ void PHPCoroutine::activate() {
313308
return;
314309
}
315310

316-
if (zend_hash_str_find_ptr(&module_registry, ZEND_STRL("xdebug"))) {
317-
php_swoole_fatal_error(
318-
E_WARNING,
319-
"Using Xdebug in coroutines is extremely dangerous, please notice that it may lead to coredump!");
311+
/* Apply INI setting for fiber context if not already set via Co::set() */
312+
if (SWOOLE_G(use_fiber_context)) {
313+
Coroutine::set_use_fiber_context(true);
320314
}
321315

322316
/* init reactor and register event wait */
@@ -488,32 +482,40 @@ inline void PHPCoroutine::save_vm_stack(PHPContext *task) {
488482
task->error_handling = EG(error_handling);
489483
task->exception_class = EG(exception_class);
490484
task->exception = EG(exception);
491-
if (UNEXPECTED(task->in_silence)) {
492-
task->tmp_error_reporting = EG(error_reporting);
493-
EG(error_reporting) = task->ori_error_reporting;
485+
if (!Coroutine::use_fiber_context) {
486+
// With fiber context, zend_fiber_switch_context manages EG(error_reporting).
487+
// Do not modify it here to avoid conflicts.
488+
if (UNEXPECTED(task->in_silence)) {
489+
task->tmp_error_reporting = EG(error_reporting);
490+
EG(error_reporting) = task->ori_error_reporting;
491+
}
494492
}
495493
}
496494

497495
inline void PHPCoroutine::restore_vm_stack(PHPContext *task) {
496+
if (!Coroutine::use_fiber_context) {
497+
// With fiber context, zend_fiber_switch_context() saves/restores these fields
498+
// automatically. Restoring them here would conflict and corrupt VM state.
498499
#ifdef SW_CORO_SWAP_BAILOUT
499-
EG(bailout) = task->bailout;
500+
EG(bailout) = task->bailout;
500501
#endif
501-
EG(vm_stack_top) = task->vm_stack_top;
502-
EG(vm_stack_end) = task->vm_stack_end;
503-
EG(vm_stack) = task->vm_stack;
504-
EG(vm_stack_page_size) = task->vm_stack_page_size;
505-
EG(current_execute_data) = task->execute_data;
506-
EG(jit_trace_num) = task->jit_trace_num;
502+
EG(vm_stack_top) = task->vm_stack_top;
503+
EG(vm_stack_end) = task->vm_stack_end;
504+
EG(vm_stack) = task->vm_stack;
505+
EG(vm_stack_page_size) = task->vm_stack_page_size;
506+
EG(current_execute_data) = task->execute_data;
507+
EG(jit_trace_num) = task->jit_trace_num;
507508
#ifdef ZEND_CHECK_STACK_LIMIT
508-
EG(stack_base) = task->stack_base;
509-
EG(stack_limit) = task->stack_limit;
509+
EG(stack_base) = task->stack_base;
510+
EG(stack_limit) = task->stack_limit;
510511
#endif
512+
if (UNEXPECTED(task->in_silence)) {
513+
EG(error_reporting) = task->tmp_error_reporting;
514+
}
515+
}
511516
EG(error_handling) = task->error_handling;
512517
EG(exception_class) = task->exception_class;
513518
EG(exception) = task->exception;
514-
if (UNEXPECTED(task->in_silence)) {
515-
EG(error_reporting) = task->tmp_error_reporting;
516-
}
517519
}
518520

519521
inline void PHPCoroutine::save_og(PHPContext *task) {
@@ -608,7 +610,18 @@ void PHPCoroutine::on_close(void *arg) {
608610
if (SwooleG.max_concurrency > 0 && task->pcid == -1) {
609611
SwooleWG.worker_concurrency--;
610612
}
611-
vm_stack_destroy();
613+
if (Coroutine::use_fiber_context) {
614+
// With fiber context, EG(vm_stack) is the caller's (restored by zend_fiber_switch_context).
615+
// Destroy the coroutine's VM stack using the pointer saved in the task struct.
616+
zend_vm_stack stack = task->vm_stack;
617+
while (stack != nullptr) {
618+
zend_vm_stack p = stack->prev;
619+
efree(stack);
620+
stack = p;
621+
}
622+
} else {
623+
vm_stack_destroy();
624+
}
612625
restore_task(origin_task);
613626

614627
swoole_trace_log(SW_TRACE_COROUTINE,
@@ -696,16 +709,20 @@ void PHPCoroutine::main_func(void *arg) {
696709
task->enable_scheduler = true;
697710

698711
#ifdef ZEND_CHECK_STACK_LIMIT
699-
if(task->co) {
700-
EG(stack_base) = (void*)((uintptr_t)task->co->get_context().get_stack() + task->co->get_context().get_stack_size());
701-
zend_ulong reserve = EG(reserved_stack_size);
702-
#ifdef __APPLE__
703-
reserve = reserve * 2;
704-
#endif
705-
EG(stack_limit) = (int8_t*)task->co->get_context().get_stack() + reserve;
706-
} else {
707-
EG(stack_base) = nullptr;
708-
EG(stack_limit) = nullptr;
712+
if (!Coroutine::use_fiber_context) {
713+
// Fiber context: zend_fiber_switch_context() sets EG(stack_base/stack_limit) automatically.
714+
// Other backends: we must set them manually from the coroutine stack.
715+
if(task->co) {
716+
EG(stack_base) = (void*)((uintptr_t)task->co->get_context().get_stack() + task->co->get_context().get_stack_size());
717+
zend_ulong reserve = EG(reserved_stack_size);
718+
#ifdef __APPLE__
719+
reserve = reserve * 2;
720+
#endif
721+
EG(stack_limit) = (int8_t*)task->co->get_context().get_stack() + reserve;
722+
} else {
723+
EG(stack_base) = nullptr;
724+
EG(stack_limit) = nullptr;
725+
}
709726
}
710727
#endif
711728
save_vm_stack(task);
@@ -788,6 +805,14 @@ void PHPCoroutine::main_func(void *arg) {
788805
}
789806
zval_ptr_dtor(retval);
790807

808+
if (Coroutine::use_fiber_context) {
809+
// Save the coroutine's final VM stack state so on_close can properly
810+
// destroy it. After main_func returns, fiber_func returns to the PHP
811+
// fiber trampoline which does the final context switch. The coroutine's
812+
// EG(vm_stack) is only accessible via this saved pointer in on_close.
813+
save_vm_stack(task);
814+
}
815+
791816
if (UNEXPECTED(EG(exception))) {
792817
zend_exception_error(EG(exception), E_ERROR);
793818
// TODO: php8 don't exit on exceptions, but no reason to continue, fix this in the future
@@ -895,8 +920,8 @@ void php_swoole_coroutine_rinit() {
895920
ori_exit_handler = zend_get_user_opcode_handler(ZEND_EXIT);
896921
zend_set_user_opcode_handler(ZEND_EXIT, coro_exit_handler);
897922
#endif
898-
#if PHP_VERSION_ID >= 80500
899-
/* PHP 8.5+: exit() is a regular function, replace its handler */
923+
#if PHP_VERSION_ID >= 80400
924+
/* PHP 8.4+: exit() is a regular function, replace its handler */
900925
zend_function *exit_fn = (zend_function *) zend_hash_str_find_ptr(EG(function_table), ZEND_STRL("exit"));
901926
if (exit_fn) {
902927
ori_exit_handler = exit_fn->internal_function.handler;
@@ -912,7 +937,7 @@ void php_swoole_coroutine_rinit() {
912937
}
913938

914939
void php_swoole_coroutine_rshutdown() {
915-
#if PHP_VERSION_ID >= 80500
940+
#if PHP_VERSION_ID >= 80400
916941
/* Restore original exit() handler */
917942
if (ori_exit_handler) {
918943
zend_function *exit_fn = (zend_function *) zend_hash_str_find_ptr(EG(function_table), ZEND_STRL("exit"));
@@ -993,6 +1018,7 @@ static PHP_METHOD(swoole_coroutine, stats) {
9931018
add_assoc_long_ex(return_value, ZEND_STRL("coroutine_num"), Coroutine::count());
9941019
add_assoc_long_ex(return_value, ZEND_STRL("coroutine_peak_num"), Coroutine::get_peak_num());
9951020
add_assoc_long_ex(return_value, ZEND_STRL("coroutine_last_cid"), Coroutine::get_last_cid());
1021+
add_assoc_bool_ex(return_value, ZEND_STRL("use_fiber_context"), Coroutine::use_fiber_context);
9961022
}
9971023

9981024
static PHP_METHOD(swoole_coroutine, run) {

0 commit comments

Comments
 (0)