├── .gitignore ├── CREDITS ├── EXPERIMENTAL ├── README.md ├── config.m4 ├── coroutine.c ├── coroutine.class.def.php ├── helper └── coroutine.class.def.php ├── php_coroutine.h ├── t.php └── tests └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | CodeIgniter-3.0.6 3 | .deps 4 | *.lo 5 | *.la 6 | .libs 7 | acinclude.m4 8 | aclocal.m4 9 | autom4te.cache 10 | build 11 | config.guess 12 | config.h 13 | config.h.in 14 | config.log 15 | config.nice 16 | config.status 17 | config.sub 18 | configure 19 | configure.in 20 | include 21 | install-sh 22 | libtool 23 | ltmain.sh 24 | Makefile 25 | Makefile.fragments 26 | Makefile.global 27 | Makefile.objects 28 | missing 29 | mkinstalldirs 30 | modules 31 | run-tests.php 32 | tests/*/*.diff 33 | tests/*/*.out 34 | tests/*/*.php 35 | tests/*/*.exp 36 | tests/*/*.log 37 | tests/*/*.sh 38 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | coroutine -------------------------------------------------------------------------------- /EXPERIMENTAL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxiaofa/php-coroutine/2967bd2784508833ae62eefdbb8be4082c992189/EXPERIMENTAL -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-coroutine (Beta PHP7 Only) 2 | php的coroutine(协程\用户态线程)扩展 支持多层函数调用中yield 3 | 4 | 5 | ##使用方法 6 | ```php 7 | 8 | function a() 9 | { 10 | echo 1; 11 | Coroutine::yield(); 12 | echo 3; 13 | } 14 | 15 | $co = new Coroutine(function(){ 16 | a(); 17 | }); 18 | $co->resume(); 19 | echo 2; 20 | $co->resume(); 21 | 22 | result:123 23 | ``` 24 | ##Methods 25 | ###Coroutine::__construct($callable) 26 | 27 | 1. 传入函数名:$co = new Coroutine("run"); 28 | 2. 传入类方法:$co = new Coroutine([$object,"methodName"]); 29 | 3. 传入闭包(Closure):$co = new Coroutine(function(){}); 30 | 31 | ###Coroutine::resume() 32 | 33 | 开始\恢复 Coroutine运行; 34 | 35 | ###Coroutine::yield() 36 | 37 | 中断Coroutine,程序返回到调用 resume的点 38 | 39 | ###Coroutine::running() 40 | 41 | 返回当前协程实例 42 | 43 | ###Coroutine::reset(); 44 | 45 | 当协程执行结束后,调用reset可以使协程回到刚创建时候的状态 46 | 47 | ##Properties 48 | ###$status 49 | 50 | 当前协程状态 51 | Access:Public 52 | 53 | ###Constants 54 | * STATUS_SUSPEND = 0; 55 | * STATUS_RUNNING = 1; 56 | * STATUS_DEAD = 2; 57 | 58 | #注意 59 | 1. 当前版本不支持在协程中对另一个协程resume操作 60 | 2. 协程中会对register_shutdown_function的函数进行拦截,并在协程结束后调用 61 | 3. 不能在内部函数回调后 Coroutine->yield,必须等内部函数全部执行完后才能Coroutine->yield 62 | 63 | #to be continued 64 | * 第二版正在开发中,将会解决无法从call_user_func中yield的问题 65 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl $Id$ 2 | dnl config.m4 for extension coroutine 3 | 4 | dnl Comments in this file start with the string 'dnl'. 5 | dnl Remove where necessary. This file will not work 6 | dnl without editing. 7 | 8 | dnl If your extension references something external, use with: 9 | 10 | PHP_ARG_WITH(coroutine, for coroutine support, 11 | Make sure that the comment is aligned: 12 | [ --with-coroutine Include coroutine support]) 13 | 14 | dnl Otherwise use enable: 15 | 16 | dnl PHP_ARG_ENABLE(coroutine, whether to enable coroutine support, 17 | dnl Make sure that the comment is aligned: 18 | dnl [ --enable-coroutine Enable coroutine support]) 19 | 20 | if test "$PHP_COROUTINE" != "no"; then 21 | dnl Write more examples of tests here... 22 | 23 | dnl # --with-coroutine -> check with-path 24 | dnl SEARCH_PATH="/usr/local /usr" # you might want to change this 25 | dnl SEARCH_FOR="/include/coroutine.h" # you most likely want to change this 26 | dnl if test -r $PHP_COROUTINE/$SEARCH_FOR; then # path given as parameter 27 | dnl COROUTINE_DIR=$PHP_COROUTINE 28 | dnl else # search default path list 29 | dnl AC_MSG_CHECKING([for coroutine files in default path]) 30 | dnl for i in $SEARCH_PATH ; do 31 | dnl if test -r $i/$SEARCH_FOR; then 32 | dnl COROUTINE_DIR=$i 33 | dnl AC_MSG_RESULT(found in $i) 34 | dnl fi 35 | dnl done 36 | dnl fi 37 | dnl 38 | dnl if test -z "$COROUTINE_DIR"; then 39 | dnl AC_MSG_RESULT([not found]) 40 | dnl AC_MSG_ERROR([Please reinstall the coroutine distribution]) 41 | dnl fi 42 | 43 | dnl # --with-coroutine -> add include path 44 | dnl PHP_ADD_INCLUDE($COROUTINE_DIR/include) 45 | 46 | dnl # --with-coroutine -> check for lib and symbol presence 47 | dnl LIBNAME=coroutine # you may want to change this 48 | dnl LIBSYMBOL=coroutine # you most likely want to change this 49 | 50 | dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, 51 | dnl [ 52 | dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $COROUTINE_DIR/$PHP_LIBDIR, COROUTINE_SHARED_LIBADD) 53 | dnl AC_DEFINE(HAVE_COROUTINELIB,1,[ ]) 54 | dnl ],[ 55 | dnl AC_MSG_ERROR([wrong coroutine lib version or lib not found]) 56 | dnl ],[ 57 | dnl -L$COROUTINE_DIR/$PHP_LIBDIR -lm 58 | dnl ]) 59 | dnl 60 | dnl PHP_SUBST(COROUTINE_SHARED_LIBADD) 61 | 62 | PHP_NEW_EXTENSION(coroutine, coroutine.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) 63 | fi 64 | -------------------------------------------------------------------------------- /coroutine.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxiaofa/php-coroutine/2967bd2784508833ae62eefdbb8be4082c992189/coroutine.c -------------------------------------------------------------------------------- /coroutine.class.def.php: -------------------------------------------------------------------------------- 1 | = 4 34 | # define PHP_COROUTINE_API __attribute__ ((visibility("default"))) 35 | #else 36 | # define PHP_COROUTINE_API 37 | #endif 38 | 39 | #ifdef ZTS 40 | #include "TSRM.h" 41 | #endif 42 | 43 | #define Z_COROUTINE_CONTEXT_P(z) ((coroutine_context *)(zend_read_property(coroutine_ce,(z),"context",7,1,NULL)->value.lval)) 44 | #define COROUTINE_SYMBOL_TABLE(ctx) (ctx)->symbol_table 45 | #define COROUTINE_SYMBOL_TABLE_P(ctx) &(COROUTINE_SYMBOL_TABLE(ctx)) 46 | #define CO_G(v) coroutine_globals.v 47 | #define CURRCO(v) CO_G(context)->v 48 | 49 | static zend_always_inline void i_init_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value) /* {{{ */ 50 | { 51 | 52 | 53 | EX(opline) = op_array->opcodes; 54 | EX(call) = NULL; 55 | EX(return_value) = return_value; 56 | 57 | if (UNEXPECTED(EX(symbol_table) != NULL)) { 58 | if (UNEXPECTED(op_array->this_var != (uint32_t)-1) && EXPECTED(Z_OBJ(EX(This)))) { 59 | GC_REFCOUNT(Z_OBJ(EX(This)))++; 60 | if (!zend_hash_str_add(EX(symbol_table), "this", sizeof("this")-1, &EX(This))) { 61 | GC_REFCOUNT(Z_OBJ(EX(This)))--; 62 | } 63 | } 64 | 65 | zend_attach_symbol_table(execute_data); 66 | } else { 67 | uint32_t first_extra_arg, num_args; 68 | 69 | /* Handle arguments */ 70 | first_extra_arg = op_array->num_args; 71 | num_args = EX_NUM_ARGS(); 72 | if (UNEXPECTED(num_args > first_extra_arg)) { 73 | if (EXPECTED(!(op_array->fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))) { 74 | zval *end, *src, *dst; 75 | uint32_t type_flags = 0; 76 | 77 | if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0)) { 78 | /* Skip useless ZEND_RECV and ZEND_RECV_INIT opcodes */ 79 | EX(opline) += first_extra_arg; 80 | } 81 | 82 | /* move extra args into separate array after all CV and TMP vars */ 83 | end = EX_VAR_NUM(first_extra_arg - 1); 84 | src = end + (num_args - first_extra_arg); 85 | dst = src + (op_array->last_var + op_array->T - first_extra_arg); 86 | if (EXPECTED(src != dst)) { 87 | do { 88 | type_flags |= Z_TYPE_INFO_P(src); 89 | ZVAL_COPY_VALUE(dst, src); 90 | ZVAL_UNDEF(src); 91 | src--; 92 | dst--; 93 | } while (src != end); 94 | } else { 95 | do { 96 | type_flags |= Z_TYPE_INFO_P(src); 97 | src--; 98 | } while (src != end); 99 | } 100 | ZEND_ADD_CALL_FLAG(execute_data, ((type_flags >> Z_TYPE_FLAGS_SHIFT) & IS_TYPE_REFCOUNTED)); 101 | } 102 | } else if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0)) { 103 | /* Skip useless ZEND_RECV and ZEND_RECV_INIT opcodes */ 104 | EX(opline) += num_args; 105 | } 106 | 107 | /* Initialize CV variables (skip arguments) */ 108 | if (EXPECTED((int)num_args < op_array->last_var)) { 109 | zval *var = EX_VAR_NUM(num_args); 110 | zval *end = EX_VAR_NUM(op_array->last_var); 111 | 112 | do { 113 | ZVAL_UNDEF(var); 114 | var++; 115 | } while (var != end); 116 | } 117 | 118 | if (op_array->this_var != (uint32_t)-1 && EXPECTED(Z_OBJ(EX(This)))) { 119 | ZVAL_OBJ(EX_VAR(op_array->this_var), Z_OBJ(EX(This))); 120 | GC_REFCOUNT(Z_OBJ(EX(This)))++; 121 | } 122 | } 123 | 124 | if (!op_array->run_time_cache) { 125 | if (op_array->function_name) { 126 | op_array->run_time_cache = zend_arena_alloc(&CG(arena), op_array->cache_size); 127 | } else { 128 | op_array->run_time_cache = emalloc(op_array->cache_size); 129 | } 130 | memset(op_array->run_time_cache, 0, op_array->cache_size); 131 | } 132 | EX_LOAD_RUN_TIME_CACHE(op_array); 133 | EX_LOAD_LITERALS(op_array); 134 | 135 | EG(current_execute_data) = execute_data; 136 | //ZEND_VM_INTERRUPT_CHECK(); 137 | } 138 | /* }}} */ 139 | 140 | struct _coroutine_function_hook{ 141 | void* raw_ptr; 142 | zend_function *target_function; 143 | zend_fcall_info fci; 144 | zend_fcall_info_cache fci_cache; 145 | }; 146 | 147 | typedef struct _coroutine_function_hook coroutine_function_hook; 148 | 149 | 150 | struct _coroutine_context{ 151 | /* CoThread instance */ 152 | zend_object* this_obj; 153 | 154 | /* The suspended execution context. */ 155 | zend_execute_data *execute_data; 156 | 157 | zend_execute_data *top_execute_data; 158 | /* The separate stack used by coroutine */ 159 | zend_vm_stack stack; 160 | 161 | zend_fcall_info_cache *fci_cache; 162 | 163 | 164 | char status; 165 | 166 | HashTable *shutdown_function_names; 167 | HashTable *function_hook; 168 | 169 | }; 170 | 171 | typedef struct _coroutine_context coroutine_context; 172 | 173 | struct _coroutine_globals{ 174 | coroutine_context *context; 175 | zend_execute_data *main_execute_data; 176 | zend_class_entry *main_scope; 177 | zend_vm_stack main_stack; 178 | void *cache_handler; 179 | HashTable *shutdown_function_names; 180 | 181 | } coroutine_globals; 182 | 183 | 184 | 185 | /* 186 | Declare any global variables you may need between the BEGIN 187 | and END macros here: 188 | 189 | ZEND_BEGIN_MODULE_GLOBALS(coroutine) 190 | zend_long global_value; 191 | char *global_string; 192 | ZEND_END_MODULE_GLOBALS(coroutine) 193 | */ 194 | 195 | /* Always refer to the globals in your function as COROUTINE_G(variable). 196 | You are encouraged to rename these macros something shorter, see 197 | examples in any other php module directory. 198 | */ 199 | #define COROUTINE_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(coroutine, v) 200 | 201 | #if defined(ZTS) && defined(COMPILE_DL_COROUTINE) 202 | ZEND_TSRMLS_CACHE_EXTERN() 203 | #endif 204 | 205 | 206 | 207 | 208 | #ifdef HAVE_GCC_GLOBAL_REGS 209 | # if defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(i386) 210 | # define ZEND_VM_FP_GLOBAL_REG "%esi" 211 | # define ZEND_VM_IP_GLOBAL_REG "%edi" 212 | # elif defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(__x86_64__) 213 | # define ZEND_VM_FP_GLOBAL_REG "%r14" 214 | # define ZEND_VM_IP_GLOBAL_REG "%r15" 215 | # elif defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(__powerpc64__) 216 | # define ZEND_VM_FP_GLOBAL_REG "r28" 217 | # define ZEND_VM_IP_GLOBAL_REG "r29" 218 | # elif defined(__IBMC__) && ZEND_GCC_VERSION >= 4002 && defined(__powerpc64__) 219 | # define ZEND_VM_FP_GLOBAL_REG "r28" 220 | # define ZEND_VM_IP_GLOBAL_REG "r29" 221 | # endif 222 | #endif 223 | 224 | #ifdef ZEND_VM_IP_GLOBAL_REG 225 | #pragma GCC diagnostic ignored "-Wvolatile-register-var" 226 | register const zend_op* volatile opline __asm__(ZEND_VM_IP_GLOBAL_REG); 227 | #pragma GCC diagnostic warning "-Wvolatile-register-var" 228 | #endif 229 | 230 | #ifdef ZEND_VM_FP_GLOBAL_REG 231 | #pragma GCC diagnostic ignored "-Wvolatile-register-var" 232 | register zend_execute_data* volatile execute_data __asm__(ZEND_VM_FP_GLOBAL_REG); 233 | #pragma GCC diagnostic warning "-Wvolatile-register-var" 234 | #endif 235 | 236 | 237 | #ifdef ZEND_VM_IP_GLOBAL_REG 238 | # define OPLINE opline 239 | # define USE_OPLINE 240 | # define LOAD_OPLINE() opline = EX(opline) 241 | # define LOAD_NEXT_OPLINE() opline = EX(opline) + 1 242 | # define SAVE_OPLINE() EX(opline) = opline 243 | #else 244 | # define OPLINE EX(opline) 245 | # define USE_OPLINE const zend_op *opline = EX(opline); 246 | # define LOAD_OPLINE() 247 | # define LOAD_NEXT_OPLINE() ZEND_VM_INC_OPCODE() 248 | # define SAVE_OPLINE() 249 | #endif 250 | 251 | 252 | 253 | 254 | #endif /* PHP_COROUTINE_H */ 255 | 256 | 257 | /* 258 | * Local variables: 259 | * tab-width: 4 260 | * c-basic-offset: 4 261 | * End: 262 | * vim600: noet sw=4 ts=4 fdm=marker 263 | * vim<600: noet sw=4 ts=4 264 | */ 265 | -------------------------------------------------------------------------------- /t.php: -------------------------------------------------------------------------------- 1 | hook('setcookie',function(){ 16 | print_r(func_get_args()); 17 | }); 18 | $a->resume(); -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | CodeIgniter-3.0.6 3 | .deps 4 | *.lo 5 | *.la 6 | .libs 7 | acinclude.m4 8 | aclocal.m4 9 | autom4te.cache 10 | build 11 | config.guess 12 | config.h 13 | config.h.in 14 | config.log 15 | config.nice 16 | config.status 17 | config.sub 18 | configure 19 | configure.in 20 | include 21 | install-sh 22 | libtool 23 | ltmain.sh 24 | Makefile 25 | Makefile.fragments 26 | Makefile.global 27 | Makefile.objects 28 | missing 29 | mkinstalldirs 30 | modules 31 | run-tests.php 32 | tests/*/*.diff 33 | tests/*/*.out 34 | tests/*/*.php 35 | tests/*/*.exp 36 | tests/*/*.log 37 | tests/*/*.sh 38 | --------------------------------------------------------------------------------