352 lines
11 KiB
C++
Executable File
352 lines
11 KiB
C++
Executable File
/*
|
|
+----------------------------------------------------------------------+
|
|
| Swoole |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 2.0 of the Apache license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.apache.org/licenses/LICENSE-2.0.html |
|
|
| If you did not receive a copy of the Apache2.0 license and are unable|
|
|
| to obtain it through the world-wide-web, please send a note to |
|
|
| license@swoole.com so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Author: Xinyu Zhu <xyzhu1120@gmail.com> |
|
|
| shiguangqi <shiguangqi2008@gmail.com> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "php_swoole.h"
|
|
|
|
#ifdef SW_COROUTINE
|
|
#include "coroutine.h"
|
|
#include "swoole_coroutine.h"
|
|
#include "zend_vm.h"
|
|
#include "zend_closures.h"
|
|
|
|
/* PHP 7.3 compatibility macro {{{*/
|
|
#ifndef ZEND_CLOSURE_OBJECT
|
|
# define ZEND_CLOSURE_OBJECT(func) (zend_object*)func->op_array.prototype
|
|
#endif
|
|
#ifndef GC_ADDREF
|
|
# define GC_ADDREF(ref) ++GC_REFCOUNT(ref)
|
|
# define GC_DELREF(ref) --GC_REFCOUNT(ref)
|
|
#endif/*}}}*/
|
|
|
|
#define TASK_SLOT \
|
|
((int)((ZEND_MM_ALIGNED_SIZE(sizeof(coro_task)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval)) - 1) / ZEND_MM_ALIGNED_SIZE(sizeof(zval))))
|
|
#define SWCC(x) sw_current_context->x
|
|
|
|
coro_global COROG;
|
|
static coro_task* sw_get_current_task();
|
|
static void sw_coro_func(void *);
|
|
|
|
#if PHP_MAJOR_VERSION >= 7 && PHP_MINOR_VERSION >= 2
|
|
static inline void sw_vm_stack_init(void)
|
|
{
|
|
uint32_t size = COROG.stack_size;
|
|
zend_vm_stack page = (zend_vm_stack) emalloc(size);
|
|
|
|
page->top = ZEND_VM_STACK_ELEMENTS(page);
|
|
page->end = (zval*) ((char*) page + size);
|
|
page->prev = NULL;
|
|
|
|
EG(vm_stack) = page;
|
|
EG(vm_stack)->top++;
|
|
EG(vm_stack_top) = EG(vm_stack)->top;
|
|
EG(vm_stack_end) = EG(vm_stack)->end;
|
|
}
|
|
#else
|
|
#define sw_vm_stack_init zend_vm_stack_init
|
|
#endif
|
|
|
|
int coro_init(TSRMLS_D)
|
|
{
|
|
if (zend_get_module_started("xdebug") == SUCCESS)
|
|
{
|
|
swoole_php_fatal_error(E_ERROR,
|
|
"can not use xdebug in swoole coroutine, please remove xdebug in php.ini and retry.");
|
|
return 0;
|
|
}
|
|
COROG.origin_vm_stack = EG(vm_stack);
|
|
COROG.origin_vm_stack_top = EG(vm_stack_top);
|
|
COROG.origin_vm_stack_end = EG(vm_stack_end);
|
|
|
|
COROG.coro_num = 0;
|
|
if (COROG.max_coro_num <= 0)
|
|
{
|
|
COROG.max_coro_num = DEFAULT_MAX_CORO_NUM;
|
|
}
|
|
if (COROG.stack_size <= 0)
|
|
{
|
|
COROG.stack_size = DEFAULT_STACK_SIZE;
|
|
}
|
|
|
|
COROG.active = 1;
|
|
SwooleWG.coro_timeout_list = swLinkedList_new(1, NULL);
|
|
coroutine_set_close(sw_coro_close);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void coro_check(TSRMLS_D)
|
|
{
|
|
if (sw_get_current_cid() == -1)
|
|
{
|
|
swoole_php_fatal_error(E_ERROR, "must be called in the coroutine.");
|
|
}
|
|
}
|
|
|
|
void coro_destroy(TSRMLS_D)
|
|
{
|
|
if (COROG.chan_pipe)
|
|
{
|
|
COROG.chan_pipe->close(COROG.chan_pipe);
|
|
efree(COROG.chan_pipe);
|
|
COROG.chan_pipe = NULL;
|
|
}
|
|
}
|
|
|
|
static void sw_coro_func(void *arg)
|
|
{
|
|
php_args *php_arg = (php_args *) arg;
|
|
zend_fcall_info_cache *fci_cache = php_arg->fci_cache;
|
|
zval **argv = php_arg->argv;
|
|
int argc = php_arg->argc;
|
|
zval *retval = php_arg->retval;
|
|
void *post_callback = php_arg->post_callback;
|
|
void* params = php_arg->params;
|
|
int cid = coroutine_get_cid();
|
|
|
|
zend_function *func;
|
|
uint32_t i;
|
|
coro_task *task;
|
|
|
|
zend_vm_stack origin_vm_stack = EG(vm_stack);
|
|
zval *origin_vm_stack_top = EG(vm_stack_top);
|
|
zval *origin_vm_stack_end = EG(vm_stack_end);
|
|
|
|
func = fci_cache->function_handler;
|
|
sw_vm_stack_init();
|
|
zend_execute_data *call = (zend_execute_data *) (EG(vm_stack_top));
|
|
|
|
task = (coro_task *) EG(vm_stack_top);
|
|
EG(vm_stack_top) = (zval *) ((char *) call + TASK_SLOT * sizeof(zval));
|
|
|
|
call = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_FUNCTION | ZEND_CALL_ALLOCATED, func, argc,
|
|
fci_cache->called_scope, fci_cache->object);
|
|
|
|
#if PHP_MINOR_VERSION < 1
|
|
EG(scope) = func->common.scope;
|
|
#endif
|
|
|
|
for (i = 0; i < argc; ++i)
|
|
{
|
|
zval *target;
|
|
target = ZEND_CALL_ARG(call, i + 1);
|
|
ZVAL_COPY(target, argv[i]);
|
|
}
|
|
call->symbol_table = NULL;
|
|
|
|
EG(current_execute_data) = NULL;
|
|
if (UNEXPECTED(func->op_array.fn_flags & ZEND_ACC_CLOSURE))
|
|
{
|
|
uint32_t call_info;
|
|
GC_ADDREF(ZEND_CLOSURE_OBJECT(func));
|
|
call_info = ZEND_CALL_CLOSURE;
|
|
ZEND_ADD_CALL_FLAG(call, call_info);
|
|
}
|
|
zend_init_execute_data(call, &func->op_array, retval);
|
|
|
|
task->cid = cid;
|
|
task->execute_data = call;
|
|
task->stack = EG(vm_stack);
|
|
task->vm_stack_top = EG(vm_stack_top);
|
|
task->vm_stack_end = EG(vm_stack_end);
|
|
task->origin_stack = origin_vm_stack;
|
|
task->origin_vm_stack_top = origin_vm_stack_top;
|
|
task->origin_vm_stack_end = origin_vm_stack_end;
|
|
task->start_time = time(NULL);
|
|
task->function = NULL;
|
|
task->is_yield = 0;
|
|
task->state = SW_CORO_RUNNING;
|
|
task->co = coroutine_get_by_id(cid);
|
|
|
|
COROG.call_stack[COROG.call_stack_size++] = task;
|
|
COROG.current_coro = task;
|
|
swTraceLog(SW_TRACE_COROUTINE, "Create coro id: %d, coro total count: %d, heap size: %zu", cid, COROG.coro_num, zend_memory_usage(0));
|
|
|
|
EG(current_execute_data) = task->execute_data;
|
|
EG(vm_stack) = task->stack;
|
|
EG(vm_stack_top) = task->vm_stack_top;
|
|
EG(vm_stack_end) = task->vm_stack_end;
|
|
zend_execute_ex(EG(current_execute_data) TSRMLS_CC);
|
|
}
|
|
|
|
int sw_coro_create(zend_fcall_info_cache *fci_cache, zval **argv, int argc, zval *retval, void *post_callback,
|
|
void *params)
|
|
{
|
|
if (unlikely(COROG.coro_num >= COROG.max_coro_num) )
|
|
{
|
|
COROG.error = 1;
|
|
swWarn("exceed max number of coro_num %d, max_coro_num:%d", COROG.coro_num, COROG.max_coro_num);
|
|
return CORO_LIMIT;
|
|
}
|
|
|
|
php_args php_args;
|
|
php_args.fci_cache = fci_cache;
|
|
php_args.argv = argv;
|
|
php_args.argc = argc;
|
|
php_args.retval = retval;
|
|
php_args.post_callback = post_callback;
|
|
php_args.params = params;
|
|
|
|
COROG.error = 0;
|
|
COROG.coro_num++;
|
|
|
|
return coroutine_create(sw_coro_func, (void*) &php_args);
|
|
}
|
|
|
|
void sw_coro_save(zval *return_value, php_context *sw_current_context)
|
|
{
|
|
SWCC(current_coro_return_value_ptr) = return_value;
|
|
SWCC(current_execute_data) = EG(current_execute_data);
|
|
SWCC(current_vm_stack) = EG(vm_stack);
|
|
SWCC(current_vm_stack_top) = EG(vm_stack_top);
|
|
SWCC(current_vm_stack_end) = EG(vm_stack_end);
|
|
SWCC(current_task) = (coro_task *) sw_get_current_task();;
|
|
}
|
|
|
|
int sw_coro_resume(php_context *sw_current_context, zval *retval, zval *coro_retval)
|
|
{
|
|
coro_task *task = SWCC(current_task);
|
|
COROG.call_stack[COROG.call_stack_size++] = task;
|
|
COROG.current_coro = task;
|
|
swTraceLog(SW_TRACE_COROUTINE,"sw_coro_resume coro id %d", COROG.current_coro->cid);
|
|
task->state = SW_CORO_RUNNING;
|
|
EG(current_execute_data) = SWCC(current_execute_data);
|
|
EG(vm_stack) = SWCC(current_vm_stack);
|
|
EG(vm_stack_top) = SWCC(current_vm_stack_top);
|
|
EG(vm_stack_end) = SWCC(current_vm_stack_end);
|
|
if (EG(current_execute_data)->prev_execute_data->opline->result_type != IS_UNUSED && retval)
|
|
{
|
|
ZVAL_COPY(SWCC(current_coro_return_value_ptr), retval);
|
|
}
|
|
swDebug("cid=%d", task->cid);
|
|
coroutine_resume(task->co);
|
|
if (unlikely(EG(exception)))
|
|
{
|
|
if (retval)
|
|
{
|
|
zval_ptr_dtor(retval);
|
|
}
|
|
zend_exception_error(EG(exception), E_ERROR TSRMLS_CC);
|
|
}
|
|
return CORO_END;
|
|
}
|
|
|
|
void sw_coro_yield()
|
|
{
|
|
coro_task *task = (coro_task *) sw_get_current_task();
|
|
COROG.call_stack_size--;
|
|
swTraceLog(SW_TRACE_COROUTINE,"coro_yield coro id %d", task->cid);
|
|
task->state = SW_CORO_YIELD;
|
|
task->is_yield = 1;
|
|
EG(vm_stack) = task->origin_stack;
|
|
EG(vm_stack_top) = task->origin_vm_stack_top;
|
|
EG(vm_stack_end) = task->origin_vm_stack_end;
|
|
coroutine_yield(task->co);
|
|
}
|
|
|
|
void sw_coro_close()
|
|
{
|
|
coro_task *task = (coro_task *) sw_get_current_task();
|
|
swTraceLog(SW_TRACE_COROUTINE,"coro_close coro id %d", task->cid);
|
|
if (!task->is_yield)
|
|
{
|
|
EG(vm_stack) = task->origin_stack;
|
|
EG(vm_stack_top) = task->origin_vm_stack_top;
|
|
EG(vm_stack_end) = task->origin_vm_stack_end;
|
|
}
|
|
else
|
|
{
|
|
EG(vm_stack) = COROG.origin_vm_stack;
|
|
EG(vm_stack_top) = COROG.origin_vm_stack_top;
|
|
EG(vm_stack_end) = COROG.origin_vm_stack_end;
|
|
}
|
|
COROG.call_stack_size--;
|
|
efree(task->stack);
|
|
COROG.coro_num--;
|
|
COROG.current_coro = NULL;
|
|
swTraceLog(SW_TRACE_COROUTINE, "close coro and %d remained. usage size: %zu. malloc size: %zu", COROG.coro_num, zend_memory_usage(0), zend_memory_usage(1));
|
|
}
|
|
|
|
int sw_get_current_cid()
|
|
{
|
|
if (unlikely(COROG.active == 0))
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
coro_task* task = sw_get_current_task();
|
|
if (task)
|
|
{
|
|
return task->cid;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static coro_task* sw_get_current_task()
|
|
{
|
|
return (COROG.call_stack_size > 0) ? COROG.call_stack[COROG.call_stack_size - 1] : NULL;
|
|
}
|
|
|
|
void coro_handle_timeout()
|
|
{
|
|
swLinkedList *timeout_list = SwooleWG.coro_timeout_list;
|
|
swTimer_node *tnode = NULL;
|
|
if (timeout_list != NULL && timeout_list->num > 0)
|
|
{
|
|
php_context *cxt = (php_context *) swLinkedList_shift(timeout_list);
|
|
while (cxt != NULL)
|
|
{
|
|
cxt->onTimeout(cxt);
|
|
cxt = (php_context *) swLinkedList_shift(timeout_list);
|
|
}
|
|
}
|
|
|
|
timeout_list = SwooleWG.delayed_coro_timeout_list;
|
|
if (likely(timeout_list != NULL))
|
|
{
|
|
swTimer_coro_callback *scc = (swTimer_coro_callback *) swLinkedList_shift(timeout_list);
|
|
while (scc != NULL)
|
|
{
|
|
php_context *context = (php_context *) scc->data;
|
|
if (unlikely(context->state == SW_CORO_CONTEXT_TERM))
|
|
{
|
|
efree(context);
|
|
efree(scc);
|
|
}
|
|
else
|
|
{
|
|
context->state = SW_CORO_CONTEXT_RUNNING;
|
|
tnode = SwooleG.timer.add(&SwooleG.timer, scc->ms, 0, scc, php_swoole_onTimeout);
|
|
|
|
if (tnode == NULL)
|
|
{
|
|
efree(scc);
|
|
swWarn("Addtimer coro failed.");
|
|
}
|
|
else
|
|
{
|
|
tnode->type = SW_TIMER_TYPE_CORO;
|
|
*scc->timeout_id = tnode->id;
|
|
}
|
|
}
|
|
scc = (swTimer_coro_callback *) swLinkedList_shift(timeout_list);
|
|
}
|
|
}
|
|
}
|
|
#endif
|