#define _GNU_SOURCE // memrchr, rawmemchr
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/prctl.h>
#include <signal.h>
#include <limits.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/statvfs.h>

#include <php.h>
#include <php_main.h>
#include <SAPI.h>
#include <php_output.h>
#include <zend_smart_str.h>
#include <standard/php_var.h>
#include <standard/info.h>
#include <php_network.h>
#include <sockets/php_sockets.h>
#include <zend_exceptions.h>
#include <spl/spl_functions.h>
#include <zend_builtin_functions.h>
#include <zend_signal.h>

#include "func.h"
#include "hash.h"

static sem_t rsem;
static pthread_mutex_t nlock, wlock;
static pthread_cond_t ncond;
static volatile unsigned int threads = 0;
static volatile unsigned int wthreads = 0;
static volatile zend_bool isRun = 1;
static pthread_t mthread;
static pthread_key_t pkey;

volatile unsigned int maxthreads = 256;
volatile unsigned int delay = 1;
volatile zend_bool isDebug = 0;
volatile zend_bool isReload = 0;
volatile int isPerf = 0;

static ts_rsrc_id php_threadtask_globals_id;
#define SINFO(v) ZEND_TSRMG(php_threadtask_globals_id, php_threadtask_globals_struct *, v)

static long le_threadtask_descriptor;
#define PHP_THREADTASK_DESCRIPTOR "threadtask"

static long le_ts_var_descriptor;
#define PHP_TS_VAR_DESCRIPTOR "ts_var_t"

static ts_hash_table_t ts_var;

typedef struct _timeout_t {
	pthread_t tid;
	double sec;
	struct _timeout_t *prev;
	struct _timeout_t *next;
} timeout_t;

static timeout_t *thead = NULL;
static pthread_mutex_t tlock;

typedef struct _php_threadtask_globals_struct {
	char strftime[20];
	zend_bool is_throw_exit;
	int timestamp;
	timeout_t *timeout;
} php_threadtask_globals_struct;

typedef struct _wait_t {
	pthread_t tid;
	sem_t sem;
	zend_bool isExit;
} wait_t;

typedef struct _task_t {
	pthread_t thread;
	char *name;
	int argc;
	char **argv;
	char *logfile;
	char *logmode;
	FILE *fp;
	wait_t *wait;
	struct _task_t *prev;
	struct _task_t *next;
} task_t;

static task_t *taskn = NULL;
static task_t *head_task = NULL;
static task_t *tail_task = NULL;

const char *gettimeofstr() {
	time_t t;
	struct tm *tmp;

	t = time(NULL);

	if(SINFO(timestamp) == t) return SINFO(strftime);

	SINFO(timestamp) = t;
	tmp = localtime(&t);
	if (tmp == NULL) {
		perror("localtime error");
		return "0000-00-00 00:00:00";
	}

	if (strftime(SINFO(strftime), sizeof(SINFO(strftime)), "%F %T", tmp) == 0) {
		perror("strftime error");
		return "";
	}

	return SINFO(strftime);
}

#define MICRO_IN_SEC 1000000.00

double microtime() {
	struct timeval tp = {0};

	if (gettimeofday(&tp, NULL)) {
		return 0;
	}   

	return (double)(tp.tv_sec + tp.tv_usec / MICRO_IN_SEC);
}

void free_task(task_t *task) {
	register int i;

	free(task->name);
	for(i=0; i<task->argc; i++) {
		free(task->argv[i]);
	}
	free(task->argv);

	if(task->logfile) free(task->logfile);
	if(task->logmode) free(task->logmode);
	if(task->fp) fclose(task->fp);
	
	free(task);
}

void thread_sigmask() {
	register int sig;
	sigset_t set;

	sigemptyset(&set);

	for(sig=1; sig<=NSIG; sig++) {
		sigaddset(&set, sig);
	}

	pthread_sigmask(SIG_SETMASK, &set, NULL);
}

void thread_init() {
	sem_init(&rsem, 0, 0);
	pthread_mutex_init(&nlock, NULL);
	pthread_mutex_init(&wlock, NULL);
	pthread_cond_init(&ncond, NULL);
	pthread_key_create(&pkey, NULL);
	pthread_setspecific(pkey, NULL);
#ifdef LOCK_TIMEOUT
	pthread_key_create(&tskey, ts_table_table_tid_destroy);
	pthread_setspecific(tskey, NULL);
#endif
	pthread_mutex_init(&tlock, NULL);

	ts_hash_table_init(&ts_var, 2);
	thread_sigmask();

	mthread = pthread_self();
}

#ifdef MYSQLI_USE_MYSQLND
zend_class_entry *mysqli_link_class_entry;
zend_class_entry *mysqli_stmt_class_entry;
#endif

void thread_running() {
	if(PG(error_log) == NULL) PG(display_errors) = 0;

#ifdef MYSQLI_USE_MYSQLND
	{
		zend_string *mysqli_str = zend_string_init(ZEND_STRL("mysqli"), 0);
		zend_string *stmt_str = zend_string_init(ZEND_STRL("mysqli_stmt"), 0);

		mysqli_link_class_entry = zend_lookup_class(mysqli_str);
		dprintf("mysqli_link_class_entry: %p\n", mysqli_link_class_entry);

		mysqli_stmt_class_entry = zend_lookup_class(stmt_str);
		dprintf("mysqli_stmt_class_entry: %p\n", mysqli_stmt_class_entry);

		zend_string_release_ex(mysqli_str, 0);
		zend_string_release_ex(stmt_str, 0);
	}
#endif

	dprintf("sizeof(Bucket) = %lu\n", sizeof(Bucket));
	dprintf("sizeof(HashTable) = %lu\n", sizeof(HashTable));
	dprintf("sizeof(zval) = %lu\n", sizeof(zval));

	dprintf("sizeof(type_t) = %lu\n", sizeof(type_t));
	dprintf("sizeof(value_t) = %lu\n", sizeof(value_t));
	dprintf("sizeof(bucket_t) = %lu\n", sizeof(bucket_t));
	dprintf("sizeof(hash_table_t) = %lu\n", sizeof(hash_table_t));
	dprintf("sizeof(ts_hash_table_t) = %lu\n", sizeof(ts_hash_table_t));
#ifdef LOCK_TIMEOUT
	dprintf("sizeof(tskey_hash_table_t) = %lu\n", sizeof(tskey_hash_table_t));
#endif
	dprintf("sizeof(sem_t) = %lu\n", sizeof(sem_t));
	dprintf("sizeof(pthread_t) = %lu\n", sizeof(pthread_t));
	dprintf("sizeof(pthread_mutex_t) = %lu\n", sizeof(pthread_mutex_t));
	dprintf("sizeof(pthread_rwlock_t) = %lu\n", sizeof(pthread_rwlock_t));
}

void thread_destroy() {
	if(taskn) {
		free_task(taskn);
		taskn = NULL;
	}
	
	ts_hash_table_destroy_ex(&ts_var, 0);

	pthread_mutex_destroy(&tlock);

#ifdef LOCK_TIMEOUT
	pthread_key_delete(tskey);
#endif
	pthread_key_delete(pkey);
	pthread_cond_destroy(&ncond);
	pthread_mutex_destroy(&nlock);
	pthread_mutex_destroy(&wlock);
	sem_destroy(&rsem);
}

size_t (*old_ub_write_handler)(const char *str, size_t str_length);
void (*old_flush_handler)(void *server_context);

size_t php_thread_ub_write_handler(const char *str, size_t str_length) {
	task_t *task = (task_t *) pthread_getspecific(pkey);

	if(task == NULL) {
		return old_ub_write_handler(str, str_length);
	}

	try:
	if(task->fp == NULL) {
		task->fp = fopen(task->logfile, task->logmode);
		if(task->fp == NULL) {
			dprintf("[%s] open file %s is failure, code is %d, error is %s\n", task->name, task->logfile, errno, strerror(errno));
			return FAILURE;
		}
	}

	if(fwrite(str, 1, str_length, task->fp) != str_length) {
		if(errno == EACCES) {
			dprintf("[%s] write file %s is failure, code is %d, error is %s\n", task->name, task->logfile, errno, strerror(errno));
			return old_ub_write_handler(str, str_length);
		} else {
			fclose(task->fp);
			task->fp = NULL;
			goto try;
		}
	}

	return str_length;
}

void php_thread_flush_handler(void *server_context) {
	task_t *task = (task_t *) pthread_getspecific(pkey);

	if(task == NULL) {
		old_flush_handler(server_context);
	} else if(task->fp) {
		if(fflush(task->fp) == EOF) {
			fclose(task->fp);
			task->fp = NULL;
		}
	}
}

void cli_register_file_handles(void);

void *thread_task(task_t *task) {
	zend_file_handle file_handle;
	sigset_t waitset;
	siginfo_t info;
	struct timespec timeout;
	char path[PATH_MAX];

	sigemptyset(&waitset);
	sigaddset(&waitset, SIGINT);
	sigaddset(&waitset, SIGTERM);

	thread_sigmask();

	task->thread = pthread_self();
	if(task->wait) {
		task->wait->tid = task->thread;
	}

	pthread_mutex_lock(&nlock);
	if(tail_task) {
		tail_task->next = task;
		task->prev = tail_task;
		tail_task = task;
	} else {
		head_task = tail_task = task;
	}
	pthread_mutex_unlock(&nlock);

	ts_resource(0);

#ifdef LOCK_TIMEOUT
	pthread_setspecific(tskey, NULL);
#endif

	dprintf("begin thread\n");

newtask:
	dprintf("[%s] newtask\n", task->name);

	prctl(PR_SET_NAME, (unsigned long) task->name);

	if(task->logfile && task->logmode) {
		pthread_setspecific(pkey, task);
	} else {
		pthread_setspecific(pkey, NULL);
	}
	
	SG(options) |= SAPI_OPTION_NO_CHDIR;
	SG(request_info).argc = task->argc;
	SG(request_info).argv = task->argv;
	SG(request_info).path_translated = task->argv[0];

	loop:
	if(php_request_startup() == FAILURE) {
		goto err;
	}

	thread_sigmask();

	SG(headers_sent) = 1;
	SG(request_info).no_headers = 1;
	if(PG(error_log) == NULL) PG(display_errors) = 0;

	zend_register_string_constant(ZEND_STRL("THREAD_TASK_NAME"), task->name, CONST_CS, PHP_USER_CONSTANT);

	cli_register_file_handles();

	dprintf("[%s] running\n", task->name);

#if PHP_VERSION_ID >= 70400
	CG(skip_shebang) = 1;
#endif

	zend_first_try {
		if(realpath(task->argv[0], path) == NULL) {
			dprintf("[%s] %d %s\n", task->name, errno, strerror(errno));
		} else {
			zend_stream_init_filename(&file_handle, path);
			php_execute_script(&file_handle);
			dprintf("[%s] oktask\n", task->name);
		}
	} zend_end_try();

	if(EG(exit_status)) {
		dprintf("[%s] exit_status = %d\n", task->name, EG(exit_status));
		php_request_shutdown(NULL);
		if(!task->wait) {
			timeout.tv_sec = delay;
			timeout.tv_nsec = 0;
			sigprocmask(SIG_BLOCK, &waitset, NULL);
			sigtimedwait(&waitset, &info, &timeout);
			if(isRun) goto loop;
		}
	} else {
		php_request_shutdown(NULL);
	}

	SG(request_info).argc = 0;
	SG(request_info).argv = NULL;
	
	if(task->wait) {
		sem_post(&task->wait->sem);
		if(task->wait->isExit) {
			goto err;
		} else {
			task->wait->isExit = 1;
		}
	}
	
	if(!isRun) goto err;
	
	dprintf("[%s] waittask\n", task->name);
	
	pthread_mutex_lock(&wlock);
	wthreads++;
	pthread_mutex_unlock(&wlock);

	if(!clock_gettime(CLOCK_REALTIME, &timeout)) {
		timeout.tv_sec += delay;
		timeout.tv_nsec = 0;
		sem_timedwait(&rsem, &timeout);
	} else sem_wait(&rsem);

	pthread_mutex_lock(&wlock);
	wthreads--;
	if(taskn) {
		pthread_mutex_lock(&nlock);
		taskn->thread = task->thread;
		taskn->prev = task->prev;
		taskn->next = task->next;
		if(taskn->prev) {
			taskn->prev->next = taskn;
		} else {
			head_task = taskn;
		}
		if(taskn->next) {
			taskn->next->prev = taskn;
		} else {
			tail_task = taskn;
		}
		pthread_mutex_unlock(&nlock);

		dprintf("[%s] endtask\n", task->name);
		
		free_task(task);
		task = taskn;
		taskn = NULL;
		pthread_mutex_unlock(&wlock);
		goto newtask;
	} else pthread_mutex_unlock(&wlock);

err:
	dprintf("[%s] endtask\n", task->name);
	dprintf("end thread\n");

	thread_sigmask();
	
	ts_free_thread();

	pthread_mutex_lock(&nlock);
	threads--;
	if(head_task == task) {
		head_task = head_task->next;
		if(head_task == NULL) {
			tail_task = NULL;
		} else {
			head_task->prev = NULL;
		}
	} else if(tail_task == task) {
		tail_task = tail_task->prev;
		tail_task->next = NULL;
	} else {
		task->prev->next = task->next;
		task->next->prev = task->prev;
	}

	free_task(task);
	
	pthread_cond_signal(&ncond);
	pthread_mutex_unlock(&nlock);

	pthread_exit(NULL);
}

ZEND_BEGIN_ARG_INFO(arginfo_create_task, 3)
	ZEND_ARG_INFO(0, taskname)
	ZEND_ARG_INFO(0, filename)
	ZEND_ARG_ARRAY_INFO(0, params, 0)
	ZEND_ARG_INFO(0, logfile)
	ZEND_ARG_INFO(0, logmode)
	ZEND_ARG_INFO(1, res)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(create_task) {
	zval *params;
	char *taskname, *filename, *logfile = NULL, *logmode = "ab";
	size_t taskname_len, filename_len, logfile_len = 0, logmode_len = 2;
	task_t *task;
	HashTable *ht;
	zval *val, *res = NULL;

	pthread_t thread;
	pthread_attr_t attr;
	int ret;

	ZEND_PARSE_PARAMETERS_START(3, 6)
		Z_PARAM_STRING(taskname, taskname_len)
		Z_PARAM_STRING(filename, filename_len)
		Z_PARAM_ARRAY(params)
		Z_PARAM_OPTIONAL
		Z_PARAM_STRING(logfile, logfile_len)
		Z_PARAM_STRING(logmode, logmode_len)
		Z_PARAM_ZVAL_DEREF(res)
	ZEND_PARSE_PARAMETERS_END();

	if(access(filename, F_OK|R_OK) != 0) {
		fprintf(stderr, "Could not open input file: %s\n", filename);
		RETURN_FALSE;
	}

	ht = Z_ARRVAL_P(params);
	task = (task_t *) malloc(sizeof(task_t));
	memset(task, 0, sizeof(task_t));

	task->name = strndup(taskname, taskname_len);

	dprintf("CREATE_TASK: %s\n", task->name);

	task->argc = zend_hash_num_elements(ht) + 1;
	task->argv = (char**) malloc(sizeof(char*) * task->argc);
	task->argv[0] = strndup(filename, filename_len);

	if(logfile && logfile_len) {
		task->logfile = strndup(logfile, logfile_len);
	}
	if(logmode && logmode_len) {
		task->logmode = strndup(logmode, logmode_len);
	}
	
	if(res) {
		task->wait = (wait_t*) malloc(sizeof(wait_t));
		memset(task->wait, 0, sizeof(wait_t));
		sem_init(&task->wait->sem, 0, 0);
		
		zval_ptr_dtor(res);
		ZVAL_RES(res, zend_register_resource(task->wait, le_threadtask_descriptor));
		
		dprintf("RESOURCE %p register\n", task->wait);
	}

	ret = 0;
	ZEND_HASH_FOREACH_VAL(ht, val) {
		convert_to_string(val);
		task->argv[++ret] = strndup(Z_STRVAL_P(val), Z_STRLEN_P(val));
	} ZEND_HASH_FOREACH_END();

	dprintf("TASK0: %s\n", taskname);
	idle:
	pthread_mutex_lock(&wlock);
	if(wthreads) {
		if(taskn) {
			pthread_mutex_unlock(&wlock);
			usleep(500);
			goto idle;
		}
		taskn = task;
		pthread_mutex_unlock(&wlock);
		
		sem_post(&rsem);
		
		dprintf("TASK1: %s\n", taskname);
		
		RETURN_TRUE;
	} else {
		pthread_mutex_unlock(&wlock);
		
		pthread_mutex_lock(&nlock);
		if(threads >= maxthreads) {
			pthread_mutex_unlock(&nlock);
			usleep(500);
			goto idle;
		}
		threads++;
		pthread_mutex_unlock(&nlock);
	}
	dprintf("TASK1: %s\n", taskname);

	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	ret = pthread_create(&thread, &attr, (void*(*)(void*)) thread_task, task);
	if(ret) {
		errno = ret;
		perror("pthread_create() is error");
		errno = 0;
		free_task(task);
	} else if(task->wait) {
		task->wait->tid = thread;
	}
	pthread_attr_destroy(&attr);

	RETURN_BOOL(ret == 0);
}

ZEND_BEGIN_ARG_INFO(arginfo_task_is_run, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_is_run) {
	zval *res;
	wait_t *ptr;
	int v = 0;
	
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_RESOURCE(res)
	ZEND_PARSE_PARAMETERS_END();
	
	ptr = (wait_t*) zend_fetch_resource_ex(res, PHP_THREADTASK_DESCRIPTOR, le_threadtask_descriptor);
	if(ptr && !sem_getvalue(&ptr->sem, &v) && !v) {
		RETURN_TRUE;
	} else {
		RETURN_FALSE;
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_task_join, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_join) {
	zval *res;
	wait_t *ptr;
	
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_RESOURCE(res)
	ZEND_PARSE_PARAMETERS_END();
	
	ptr = (wait_t*) zend_fetch_resource_ex(res, PHP_THREADTASK_DESCRIPTOR, le_threadtask_descriptor);
	if(ptr) sem_wait(&ptr->sem);
	
	RETURN_BOOL(ptr);
}

ZEND_BEGIN_ARG_INFO(arginfo_task_kill, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_TYPE_INFO(0, sig, IS_LONG, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_kill) {
	zval *res;
	zend_long sig = SIGINT;
	wait_t *ptr;
	int ret;
	
	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_RESOURCE(res)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(sig)
	ZEND_PARSE_PARAMETERS_END();
	
	ptr = (wait_t*) zend_fetch_resource_ex(res, PHP_THREADTASK_DESCRIPTOR, le_threadtask_descriptor);
	if(ptr) {
		if(!ptr->isExit) {
			ptr->isExit = 1;

			dprintf("pthread_kill %d\n", pthread_tid_ex(ptr->tid));
			ret = pthread_kill(ptr->tid, (int) sig);
			if(ret) {
				errno = ret;
				perror("pthread_create() is error");
				errno = 0;
				ptr->isExit = 0;
			}
		}

		RETURN_BOOL(ptr->isExit);
	} else {
		RETURN_FALSE;
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_task_wait, 0)
ZEND_ARG_INFO(0, sig)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_wait) {
	task_t *task;
	pthread_t thread;
	zend_long sig;
	int i;

	if(mthread != pthread_self()) {
		php_printf("The task_wait function can only be executed in main thread\n");
		return;
	}

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_LONG(sig)
	ZEND_PARSE_PARAMETERS_END();
	
	if(sig == SIGUSR1 || sig == SIGUSR2) {
		sig = SIGINT;
		isReload = 1;
	}
	isRun = 0;
	dprintf("TASK_WAIT begin\n");

	pthread_mutex_lock(&nlock);
	for(i=0; i<threads; i++) sem_post(&rsem);

	task = head_task;
	while(task) {
		thread = task->thread;
		task = task->next;
		dprintf("pthread_kill %d\n", pthread_tid_ex(thread));
		pthread_kill(thread, (int) sig);
	}

	while(threads > 0) {
		pthread_cond_wait(&ncond, &nlock);
	}
	pthread_mutex_unlock(&nlock);
	
	dprintf("TASK_WAIT end\n");
}

ZEND_BEGIN_ARG_INFO(arginfo_task_set_delay, 0)
ZEND_ARG_INFO(0, delay)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_set_delay) {
	zend_long d;
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_LONG(d)
	ZEND_PARSE_PARAMETERS_END();
	RETVAL_LONG(delay);
	if(delay > 0) delay = d;
}

ZEND_BEGIN_ARG_INFO(arginfo_task_set_threads, 0)
ZEND_ARG_INFO(0, threads)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_set_threads) {
	zend_long d;
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_LONG(d)
	ZEND_PARSE_PARAMETERS_END();
	RETVAL_LONG(maxthreads);
	if(d > 1) maxthreads = d;
}

ZEND_BEGIN_ARG_INFO(arginfo_task_set_debug, 0)
ZEND_ARG_INFO(0, isDebug)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_set_debug) {
	zend_bool d;
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_BOOL(d)
	ZEND_PARSE_PARAMETERS_END();
	RETVAL_BOOL(isDebug);
	isDebug = d;
}

ZEND_BEGIN_ARG_INFO(arginfo_task_set_run, 0)
ZEND_ARG_INFO(0, isRun)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_set_run) {
	zend_bool d = 0;
	ZEND_PARSE_PARAMETERS_START(0, 1)
		Z_PARAM_OPTIONAL
		Z_PARAM_BOOL(d)
	ZEND_PARSE_PARAMETERS_END();
	RETVAL_BOOL(isRun);
	isRun = d;
}

ZEND_BEGIN_ARG_INFO(arginfo_task_get_num, 0)
ZEND_ARG_INFO(0, is_max)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_get_num) {
	zend_bool is_max = 0;
	ZEND_PARSE_PARAMETERS_START(0, 1)
		Z_PARAM_OPTIONAL
		Z_PARAM_BOOL(is_max)
	ZEND_PARSE_PARAMETERS_END();

	if(is_max) {
		RETVAL_LONG(maxthreads);
	} else {
		pthread_mutex_lock(&nlock);
		RETVAL_LONG(threads);
		pthread_mutex_unlock(&nlock);
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_task_get_run, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_get_run) {
	ZEND_PARSE_PARAMETERS_NONE();

	RETVAL_BOOL(isRun);
}

ZEND_BEGIN_ARG_INFO(arginfo_task_get_debug, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_get_debug) {
	ZEND_PARSE_PARAMETERS_NONE();

	RETVAL_BOOL(isDebug);
}

ZEND_BEGIN_ARG_INFO(arginfo_task_get_delay, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(task_get_delay) {
	ZEND_PARSE_PARAMETERS_NONE();

	RETVAL_LONG(delay);
}

ZEND_BEGIN_ARG_INFO(arginfo_is_main_task, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(is_main_task) {
	ZEND_PARSE_PARAMETERS_NONE();

	RETURN_BOOL(mthread == pthread_self());
}

// -----------------------------------------------------------------------------------------------------------
void debug_print_backtrace(const char *prefix, int skip_last, int options, int limit) {
	char name[64];
	
	prctl(PR_GET_NAME, (unsigned long) name);

#if PHP_VERSION_ID >= 80100
	zval backtrace;

	zend_fetch_debug_backtrace(&backtrace, skip_last, options, limit);
	ZEND_ASSERT(Z_TYPE(backtrace) == IS_ARRAY);

	zend_string *str = zend_trace_to_string(Z_ARRVAL(backtrace), /* include_main */ false);
	if(ZSTR_LEN(str)) {
		fprintf(stderr, "%s[%s] %*s", prefix, name, (int) ZSTR_LEN(str), ZSTR_VAL(str));
	}
	zend_string_release(str);

	zval_ptr_dtor(&backtrace);
#else
	zval func, retval, params[2];

	ZVAL_LONG(&params[0], options);
	ZVAL_LONG(&params[1], limit);

	ZVAL_STRING(&func, "debug_print_backtrace");

	zend_printf("%s[%s] ", prefix, name);
	call_user_function(NULL, NULL, &func, &retval, 2, params);

	zval_ptr_dtor(&func);
	zval_ptr_dtor(&retval);
	zval_ptr_dtor(&params[0]);
	zval_ptr_dtor(&params[1]);
#endif
}

#ifdef HAVE_STRUCT_SIGINFO_T
static void segv_signal_handler(int signo, siginfo_t *siginfo, void *context) {
#else
static void segv_signal_handler(int signo) {
#endif
	debug_print_backtrace("[SIGSEGV]", 0, 0, 0);
	
	zend_bailout();
}

static void segv_signal(int restart, int mask_all) {
	struct sigaction act,oact;

#ifdef HAVE_STRUCT_SIGINFO_T
	act.sa_sigaction = segv_signal_handler;
#else
	act.sa_handler = segv_signal_handler;
#endif
	if (mask_all) {
		sigfillset(&act.sa_mask);
	} else {
		sigemptyset(&act.sa_mask);
	}
	act.sa_flags = 0;
#ifdef HAVE_STRUCT_SIGINFO_T
	act.sa_flags |= SA_SIGINFO;
#endif
	if (!restart) {
#ifdef SA_INTERRUPT
		act.sa_flags |= SA_INTERRUPT; /* SunOS */
#endif
	} else {
#ifdef SA_RESTART
		act.sa_flags |= SA_RESTART; /* SVR4, 4.3+BSD */
#endif
	}

	zend_sigaction(SIGSEGV, &act, &oact);
}

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pthread_sigmask, 0, 2, _IS_BOOL, 0)
	ZEND_ARG_TYPE_INFO(0, mode, IS_LONG, 0)
	ZEND_ARG_TYPE_INFO(0, signals, IS_ARRAY, 0)
	ZEND_ARG_INFO(1, old_signals)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(pthread_sigmask) {
	zend_long how, signo;
	zval *user_set, *user_oldset = NULL, *user_signo;
	sigset_t set, oldset;

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "la|z/", &how, &user_set, &user_oldset) == FAILURE) {
		RETURN_FALSE;
	}

	if (sigemptyset(&set) != 0 || sigemptyset(&oldset) != 0) { 
		php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
		RETURN_FALSE;
	}

	ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(user_set), user_signo) {
		signo = zval_get_long(user_signo);
		if (sigaddset(&set, signo) != 0) { 
			php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
			RETURN_FALSE;
		}
	} ZEND_HASH_FOREACH_END();

	if (pthread_sigmask(how, &set, &oldset) != 0) { 
		php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
		RETURN_FALSE;
	}

	if (user_oldset != NULL) {
		if (Z_TYPE_P(user_oldset) != IS_ARRAY) {
			zval_ptr_dtor(user_oldset);
			array_init(user_oldset);
		} else {
			zend_hash_clean(Z_ARRVAL_P(user_oldset));
		}

		for (signo = 1; signo < NSIG; ++signo) {
			if (sigismember(&oldset, signo) != 1) { 
				continue;
			}
			add_next_index_long(user_oldset, signo);
		}
	}

	segv_signal(1, 1);

	RETURN_TRUE;
}

ZEND_BEGIN_ARG_INFO(arginfo_pthread_yield, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(pthread_yield) {
	ZEND_PARSE_PARAMETERS_NONE();

	RETURN_BOOL(sched_yield() == 0);
}

// ===========================================================================================================

static zend_class_entry *spl_ce_GoExitException;

static int go_exit_handler(zend_execute_data *execute_data) {
	if(SINFO(is_throw_exit)) {
		const zend_op *opline = EX(opline);
		zval ex;
		zend_object *obj;
		zval _exit_status;
		zval *exit_status = NULL;

		if (opline->op1_type != IS_UNUSED) {
			if (opline->op1_type == IS_CONST) {
				// see: https://github.com/php/php-src/commit/e70618aff6f447a298605d07648f2ce9e5a284f5
#ifdef EX_CONSTANT
				exit_status = EX_CONSTANT(opline->op1);
#else
				exit_status = RT_CONSTANT(opline, opline->op1);
#endif
			} else {
				exit_status = EX_VAR(opline->op1.var);
			}
			if (Z_ISREF_P(exit_status)) {
				exit_status = Z_REFVAL_P(exit_status);
			}
			ZVAL_DUP(&_exit_status, exit_status);
			exit_status = &_exit_status;
		} else {
			exit_status = &_exit_status;
			ZVAL_NULL(exit_status);
		}
		obj = zend_throw_exception(spl_ce_GoExitException, "In go function is run exit()", 0);
		ZVAL_OBJ(&ex, obj);
		Z_TRY_ADDREF_P(exit_status);
		zend_update_property(spl_ce_GoExitException, Z_OBJ_PROP(&ex), ZEND_STRL("status"), exit_status);
	}

	return ZEND_USER_OPCODE_DISPATCH;
}

ZEND_BEGIN_ARG_INFO(arginfo_spl_ce_GoExitException_getStatus, 0)
ZEND_END_ARG_INFO()

static PHP_METHOD(spl_ce_GoExitException, getStatus) {
	zval *prop, rv;
	zend_string *status;

	ZEND_PARSE_PARAMETERS_NONE();

	status = zend_string_init(ZEND_STRL("status"), 0);
	prop = zend_read_property_ex(Z_OBJCE_P(ZEND_THIS), Z_OBJ_PROP(ZEND_THIS), status, 0, &rv);
	zend_string_release_ex(status, 0);

	RETURN_ZVAL(prop, 1, 0);
}

ZEND_BEGIN_ARG_INFO_EX(arginfo_go, 0, 0, 1)
ZEND_ARG_INFO(0, function_name)
ZEND_ARG_VARIADIC_INFO(0, parameters)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(go) {
	zend_fcall_info fci; 
	zend_fcall_info_cache fci_cache;

	ZEND_PARSE_PARAMETERS_START(1, -1)
		Z_PARAM_FUNC(fci, fci_cache)
	#if PHP_VERSION_ID >= 80000
		Z_PARAM_VARIADIC_WITH_NAMED(fci.params, fci.param_count, fci.named_params)
	#else
		Z_PARAM_VARIADIC('*', fci.params, fci.param_count)
	#endif
	ZEND_PARSE_PARAMETERS_END();

	fci.retval = return_value;
	
	zend_bool b = SINFO(is_throw_exit);
	SINFO(is_throw_exit) = 1;

	zend_try {
		if(zend_call_function(&fci, &fci_cache) == FAILURE) {
			RETVAL_FALSE;
		}
	} zend_catch {
		EG(exit_status) = 0;
	} zend_end_try();

	SINFO(is_throw_exit) = b;
}

ZEND_BEGIN_ARG_INFO(arginfo_call_and_free_shutdown, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(call_and_free_shutdown) {
	ZEND_PARSE_PARAMETERS_NONE();

	zend_try {
		php_call_shutdown_functions();
	} zend_catch {
		EG(exit_status) = 0;
	} zend_end_try();

	php_free_shutdown_functions();
}

// ===========================================================================================================

ZEND_BEGIN_ARG_INFO(arginfo_set_timeout, 0)
ZEND_ARG_INFO(0, seconds)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(set_timeout) {
	zend_long sec = 1;
	ZEND_PARSE_PARAMETERS_START(0, 1)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(sec)
	ZEND_PARSE_PARAMETERS_END();

	if(SINFO(timeout) || sec <= 0) {
		RETURN_FALSE;
	}
	
	timeout_t *t = (timeout_t*) malloc(sizeof(timeout_t));
	t->tid = pthread_self();
	t->sec = microtime() + sec;
	SINFO(timeout) = t;

	pthread_mutex_lock(&tlock);
	if(thead) {
		t->next = thead->next;
		thead->next = t;
		t->next->prev = t;
		t->prev = thead;
	} else {
		t->prev = t;
		t->next = t;
		thead = t;
	}
	pthread_mutex_unlock(&tlock);

	RETURN_TRUE;
}

ZEND_BEGIN_ARG_INFO(arginfo_clear_timeout, 0)
ZEND_ARG_INFO(0, seconds)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(clear_timeout) {
	ZEND_PARSE_PARAMETERS_NONE();

	if(SINFO(timeout) == NULL) {
		RETURN_FALSE;
	} else {
		timeout_t *t = SINFO(timeout);

		pthread_mutex_lock(&tlock);
		if(thead == t) {
			if(t->next == t) {
				thead = NULL;
			} else {
				thead = t->next;
				thead->prev = t->prev;
				thead->prev->next = thead;
			}
		} else {
			t->prev->next = t->next;
			t->next->prev = t->prev;
		}
		pthread_mutex_unlock(&tlock);
		
		free(t);

		SINFO(timeout) = NULL;

		RETURN_TRUE;
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_trigger_timeout, 0)
ZEND_ARG_INFO(0, sig)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(trigger_timeout) {
	zend_long sig = SIGALRM;
	ZEND_PARSE_PARAMETERS_START(0, 1)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(sig)
	ZEND_PARSE_PARAMETERS_END();

	timeout_t *t;
	double sec = microtime();

	pthread_mutex_lock(&tlock);
	t = thead;
	while(t) {
		if(t->sec <= sec) {
			pthread_kill(t->tid, sig);
		}
		if(t->next == thead) break;
		t = t->next;
	}
	pthread_mutex_unlock(&tlock);

	RETURN_TRUE;
}

// ===========================================================================================================

static hash_table_t *share_var_ht = NULL;
static pthread_mutex_t share_var_rlock;
static pthread_mutex_t share_var_wlock;
static volatile int share_var_locks = 0;

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_init, 0, 0, 0)
ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_exists, 0, 0, 1)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_get, 0, 0, 0)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_get_and_del, 0, 0, 0)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_put, 0, 0, 1)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_inc, 0, 0, 2)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_set, 0, 0, 2)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_set_ex, 0, 0, 3)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_ARG_INFO(0, value)
ZEND_ARG_TYPE_INFO(0, expire, IS_LONG, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_del, 0, 0, 1)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_clean, 0, 0, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_clean_ex, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, expire, IS_LONG, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_count, 0, 0, 0)
ZEND_ARG_VARIADIC_INFO(0, keys)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_share_var_destory, 0, 0, 0)
ZEND_END_ARG_INFO()

#define SHARE_VAR_RLOCK() \
	pthread_mutex_lock(&share_var_rlock); \
	if ((++(share_var_locks)) == 1) { \
		pthread_mutex_lock(&share_var_wlock); \
	} \
	pthread_mutex_unlock(&share_var_rlock)

#define SHARE_VAR_RUNLOCK() \
	pthread_mutex_lock(&share_var_rlock); \
	if ((--(share_var_locks)) == 0) { \
		pthread_mutex_unlock(&share_var_wlock); \
	} \
	pthread_mutex_unlock(&share_var_rlock)

#define SHARE_VAR_WLOCK() pthread_mutex_lock(&share_var_wlock)
#define SHARE_VAR_WUNLOCK() pthread_mutex_unlock(&share_var_wlock)

//---------------------------------------------------------------------------------------

#define __NULL (void)0

#define SERIALIZE(z,ok) SERIALIZE_EX(z,__NULL,ok,__NULL,__NULL)
#define SERIALIZE_EX(z,r1,ok,r2,r3) \
	do { \
		php_serialize_data_t var_hash; \
		smart_str buf = {0}; \
		PHP_VAR_SERIALIZE_INIT(var_hash); \
		php_var_serialize(&buf, z, &var_hash); \
		PHP_VAR_SERIALIZE_DESTROY(var_hash); \
		if (EG(exception)) { \
			smart_str_free(&buf); \
			r1; \
		} else if (buf.s) { \
			ok; \
			smart_str_free(&buf); \
			r2; \
		} else { \
			r3; \
		} \
	} while(0)

#define UNSERIALIZE(s,l,ok) UNSERIALIZE_EX(s,l,__NULL,ok,__NULL)
#define UNSERIALIZE_EX(s,l,r,ok,ok2) \
	do { \
		php_unserialize_data_t var_hash; \
		char *__buf = s; \
		const unsigned char *__p = (const unsigned char *) __buf; \
		size_t __buflen = l; \
		PHP_VAR_UNSERIALIZE_INIT(var_hash); \
		zval *retval = var_tmp_var(&var_hash); \
		if(!php_var_unserialize(retval, &__p, __p + __buflen, &var_hash)) { \
			if (!EG(exception)) { \
				php_error_docref(NULL, E_NOTICE, "Error at offset " ZEND_LONG_FMT " of %zd bytes", (zend_long)((char*)__p - __buf), __buflen); \
			} \
			r; \
		} else { \
			ok;ok2; \
		} \
		PHP_VAR_UNSERIALIZE_DESTROY(var_hash); \
	} while(0)

static PHP_FUNCTION(share_var_init)
{
	if(mthread != pthread_self()) {
		php_printf("The share_var_destory function can only be executed in main thread\n");
		return;
	}
	
	if(share_var_ht) return;

	zend_long size = 128;
	if(zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &size) == FAILURE) return;

	pthread_mutex_init(&share_var_rlock, NULL);
	pthread_mutex_init(&share_var_wlock, NULL);
	share_var_ht = (hash_table_t*) malloc(sizeof(hash_table_t));

	RETVAL_BOOL(hash_table_init(share_var_ht, size) == SUCCESS);
}

static PHP_FUNCTION(share_var_exists)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;
	if(arg_num <= 0) return;
	if(!share_var_ht) return;

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	SHARE_VAR_RLOCK();
	value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2 = {.type=NULL_T,.expire=0};
	RETVAL_FALSE;
	for(i=0; i<arg_num && v1.type == HT_T; i++) {
		if(i+1 == arg_num) {
			if(Z_TYPE(arguments[i]) == IS_LONG) {
				RETVAL_BOOL(hash_table_index_exists((hash_table_t*) v1.ptr, Z_LVAL(arguments[i])));
			} else {
				convert_to_string(&arguments[i]);
				RETVAL_BOOL(hash_table_exists((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i])));
			}
		} else if(Z_TYPE(arguments[i]) == IS_LONG) {
			if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) break;
		} else {
			convert_to_string(&arguments[i]);
			if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) break;
		}
		v1 = v2;
	}
	SHARE_VAR_RUNLOCK();

	end:
	efree(arguments);
}

static int hash_table_to_zval(bucket_t *p, zval *a) {
	if(p->nKeyLength == 0) {
		switch(p->value.type) {
			case NULL_T:
				add_index_null(a, p->h);
				break;
			case BOOL_T:
				add_index_bool(a, p->h, p->value.b);
				break;
			case LONG_T:
				add_index_long(a, p->h, p->value.l);
				break;
			case DOUBLE_T:
				add_index_double(a, p->h, p->value.d);
				break;
			case STR_T:
				add_index_stringl(a, p->h, p->value.str->str, p->value.str->len);
				break;
			case HT_T: {
				zval z;
				array_init_size(&z, hash_table_num_elements(p->value.ptr));
				hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_to_zval, &z);
				add_index_zval(a, p->h, &z);
				break;
			}
			case SERI_T: {
				zval rv;
				UNSERIALIZE_EX(p->value.str->str, p->value.str->len, __NULL, ZVAL_COPY(&rv, retval), add_index_zval(a, p->h, &rv));
				break;
			}
			case TS_HT_T: {
				zval z;
				array_init_size(&z, hash_table_num_elements(p->value.ptr));
				ts_hash_table_rd_lock(p->value.ptr);
				hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_to_zval, &z);
				ts_hash_table_rd_unlock(p->value.ptr);
				add_index_zval(a, p->h, &z);
				break;
			}
		}
	} else {
		switch(p->value.type) {
			case NULL_T:
				add_assoc_null_ex(a, p->arKey, p->nKeyLength);
				break;
			case BOOL_T:
				add_assoc_bool_ex(a, p->arKey, p->nKeyLength, p->value.b);
				break;
			case LONG_T:
				add_assoc_long_ex(a, p->arKey, p->nKeyLength, p->value.l);
				break;
			case DOUBLE_T:
				add_assoc_double_ex(a, p->arKey, p->nKeyLength, p->value.d);
				break;
			case STR_T:
				add_assoc_stringl_ex(a, p->arKey, p->nKeyLength, p->value.str->str, p->value.str->len);
				break;
			case HT_T: {
				zval z;
				array_init_size(&z, hash_table_num_elements(p->value.ptr));
				hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_to_zval, &z);
				add_assoc_zval_ex(a, p->arKey, p->nKeyLength, &z);
				break;
			}
			case SERI_T: {
				zval rv;
				UNSERIALIZE_EX(p->value.str->str, p->value.str->len, __NULL, ZVAL_COPY(&rv, retval), add_assoc_zval_ex(a, p->arKey, p->nKeyLength, &rv));
				break;
			}
			case TS_HT_T: {
				zval z;
				array_init_size(&z, hash_table_num_elements(p->value.ptr));
				ts_hash_table_rd_lock(p->value.ptr);
				hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_to_zval, &z);
				ts_hash_table_rd_unlock(p->value.ptr);
				add_assoc_zval_ex(a, p->arKey, p->nKeyLength, &z);
				break;
			}
		}
	}
	
	return HASH_TABLE_APPLY_KEEP;
}

void value_to_zval(value_t *v, zval *return_value) {
	switch(v->type) {
		case BOOL_T:
			RETVAL_BOOL(v->b);
			break;
		case LONG_T:
			RETVAL_LONG(v->l);
			break;
		case DOUBLE_T:
			RETVAL_DOUBLE(v->d);
			break;
		case STR_T:
			RETVAL_STRINGL(v->str->str, v->str->len);
			break;
		case HT_T:
			array_init_size(return_value, hash_table_num_elements(v->ptr));
			hash_table_apply_with_argument(v->ptr, (hash_apply_func_arg_t) hash_table_to_zval, return_value);
			break;
		case SERI_T: {
			UNSERIALIZE(v->str->str, v->str->len, ZVAL_COPY(return_value, retval));
			break;
		}
		case TS_HT_T:
			array_init_size(return_value, hash_table_num_elements(v->ptr));
			ts_hash_table_rd_lock(v->ptr);
			hash_table_apply_with_argument(v->ptr, (hash_apply_func_arg_t) hash_table_to_zval, return_value);
			ts_hash_table_rd_unlock(v->ptr);
			break;
		default:
			RETVAL_NULL();
			break;
	}
}

static PHP_FUNCTION(share_var_get)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;

	if(!share_var_ht) return;

	if(arg_num <= 0) {
		SHARE_VAR_RLOCK();
		array_init_size(return_value, hash_table_num_elements(share_var_ht));
		hash_table_apply_with_argument(share_var_ht, (hash_apply_func_arg_t) hash_table_to_zval, return_value);
		SHARE_VAR_RUNLOCK();
		return;
	}

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	SHARE_VAR_RLOCK();
	value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2 = {.type=NULL_T,.expire=0};
	for(i=0; i<arg_num && v1.type == HT_T; i++) {
		if(Z_TYPE(arguments[i]) == IS_LONG) {
			if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) break;
		} else {
			convert_to_string(&arguments[i]);
			if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) break;
		}
		if(i == arg_num - 1) value_to_zval(&v2, return_value);
		else v1 = v2;
	}
	SHARE_VAR_RUNLOCK();

	end:
	efree(arguments);
}

static PHP_FUNCTION(share_var_get_and_del)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;

	if(!share_var_ht) return;

	if(arg_num <= 0) {
		SHARE_VAR_WLOCK();
		array_init_size(return_value, hash_table_num_elements(share_var_ht));
		hash_table_apply_with_argument(share_var_ht, (hash_apply_func_arg_t) hash_table_to_zval, return_value);
		hash_table_clean(share_var_ht);
		SHARE_VAR_WUNLOCK();
		return;
	}

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	SHARE_VAR_WLOCK();
	value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2 = {.type=NULL_T,.expire=0};
	for(i=0; i<arg_num && v1.type == HT_T; i++) {
		if(Z_TYPE(arguments[i]) == IS_LONG) {
			if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) break;

			if(i == arg_num - 1) {
				value_to_zval(&v2, return_value);
				hash_table_index_del((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]));
			}
		} else {
			convert_to_string(&arguments[i]);
			if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) break;
			
			if(i == arg_num - 1) {
				value_to_zval(&v2, return_value);
				hash_table_del((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]));
			}
		}

		v1 = v2;
	}
	SHARE_VAR_WUNLOCK(); // if(i!=arg_num) {printf("TYPE: %d,%d,%d\n", v1.type, v2.type, i);php_var_dump(&arguments[0], 0);php_var_dump(&arguments[i], 0);}

	end:
	efree(arguments);
}

#if PHP_VERSION_ID < 70300
#define GC_RECURSIVE_BEGIN(z) do { \
	if(!ZEND_HASH_APPLY_PROTECTION(z) || (z)->u.v.nApplyCount++ <= 1) {
#define GC_RECURSIVE_END(z) \
			if(ZEND_HASH_APPLY_PROTECTION(z)) (z)->u.v.nApplyCount--; \
		} else { \
			const char *space; \
			const char *class_name = get_active_class_name(&space); \
			zend_error(E_WARNING, "%s%s%s() does not handle circular references", class_name, space, get_active_function_name()); \
		} \
	} while(0)
#else
#define GC_RECURSIVE_BEGIN(z) \
	do { \
		if(!(GC_FLAGS(z) & GC_IMMUTABLE) && GC_IS_RECURSIVE(z)) { \
			const char *space; \
			const char *class_name = get_active_class_name(&space); \
			zend_error(E_WARNING, "%s%s%s() does not handle circular references", class_name, space, get_active_function_name()); \
		} else { \
			GC_TRY_PROTECT_RECURSION(z)
#define GC_RECURSIVE_END(z) \
			GC_TRY_UNPROTECT_RECURSION(z); \
		} \
	} while(0)
#endif

static int zval_array_to_hash_table(zval *pDest, int num_args, va_list args, zend_hash_key *hash_key);
static void zval_to_value(zval *z, value_t *v) {
	v->expire = 0;
	while(Z_ISREF_P(z)) z = Z_REFVAL_P(z);
	switch(Z_TYPE_P(z)) {
		case IS_FALSE:
		case IS_TRUE:
			v->type = BOOL_T;
			v->b = Z_TYPE_P(z) == IS_TRUE;
			break;
		case IS_LONG:
			v->type = LONG_T;
			v->l = Z_LVAL_P(z);
			break;
		case IS_DOUBLE:
			v->type = DOUBLE_T;
			v->d = Z_DVAL_P(z);
			break;
		case IS_STRING:
			v->type = STR_T;
			v->str = (string_t*) malloc(sizeof(string_t)+Z_STRLEN_P(z));
			memcpy(v->str->str, Z_STRVAL_P(z), Z_STRLEN_P(z));
			v->str->str[Z_STRLEN_P(z)] = '\0';
			v->str->len = Z_STRLEN_P(z);
			break;
		case IS_ARRAY:
			GC_RECURSIVE_BEGIN(Z_ARR_P(z));
			v->type = HT_T;
			v->ptr = malloc(sizeof(hash_table_t));
			hash_table_init((hash_table_t*) v->ptr, 2);
			zend_hash_apply_with_arguments(Z_ARR_P(z), zval_array_to_hash_table, 1, v->ptr);
			GC_RECURSIVE_END(Z_ARR_P(z));
			break;
		case IS_OBJECT:
			#define __SERI_OK2 \
				v->type = SERI_T; \
				v->str = (string_t*) malloc(sizeof(string_t)+ZSTR_LEN(buf.s)); \
				memcpy(v->str->str, ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)); \
				v->str->str[ZSTR_LEN(buf.s)] = '\0'; \
				v->str->len = ZSTR_LEN(buf.s)
			SERIALIZE(z, __SERI_OK2);
			#undef __SERI_OK2
			break;
		default:
			v->type = NULL_T;
			break;
	}
}

static int zval_array_to_hash_table(zval *pDest, int num_args, va_list args, zend_hash_key *hash_key) {
	value_t v={.type=NULL_T,.expire=0};
	hash_table_t *ht = va_arg(args, hash_table_t*);
	
	while(Z_ISREF_P(pDest)) pDest = Z_REFVAL_P(pDest);

	if(hash_key->key) {
		if(Z_TYPE_P(pDest) == IS_ARRAY) {
			if(hash_table_find(ht, ZSTR_VAL(hash_key->key), ZSTR_LEN(hash_key->key), &v) == FAILURE || v.type != HT_T) {
				zval_to_value(pDest, &v);
				hash_table_update(ht, ZSTR_VAL(hash_key->key), ZSTR_LEN(hash_key->key), &v, NULL);
			} else {
				GC_RECURSIVE_BEGIN(Z_ARR_P(pDest));
				zend_hash_apply_with_arguments(Z_ARR_P(pDest), zval_array_to_hash_table, 1, v.ptr);
				GC_RECURSIVE_END(Z_ARR_P(pDest));
			}
		} else {
			zval_to_value(pDest, &v);
			hash_table_update(ht, ZSTR_VAL(hash_key->key), ZSTR_LEN(hash_key->key), &v, NULL);
		}
	} else {
		if(Z_TYPE_P(pDest) == IS_ARRAY) {
			if(hash_table_index_find(ht, hash_key->h, &v) == FAILURE || v.type != HT_T) {
				zval_to_value(pDest, &v);
				hash_table_index_update(ht, hash_key->h, &v, NULL);
			} else {
				GC_RECURSIVE_BEGIN(Z_ARR_P(pDest));
				zend_hash_apply_with_arguments(Z_ARR_P(pDest), zval_array_to_hash_table, 1, v.ptr);
				GC_RECURSIVE_END(Z_ARR_P(pDest));
			}
		} else {
			zval_to_value(pDest, &v);
			hash_table_index_update(ht, hash_key->h, &v, NULL);
		}
	}

	return ZEND_HASH_APPLY_KEEP;
}

static PHP_FUNCTION(share_var_put)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;
	if(arg_num <= 0) return;

	if(!share_var_ht) return;

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	SHARE_VAR_WLOCK();
	if(arg_num == 1) {
		if(Z_TYPE(arguments[0]) == IS_ARRAY) {
			GC_RECURSIVE_BEGIN(Z_ARR(arguments[0]));
			zend_hash_apply_with_arguments(Z_ARR(arguments[0]), zval_array_to_hash_table, 1, share_var_ht);
			GC_RECURSIVE_END(Z_ARR(arguments[0]));
			RETVAL_TRUE;
		} else {
			value_t v3;
			zval_to_value(&arguments[0], &v3);
			RETVAL_BOOL(hash_table_next_index_insert(share_var_ht, &v3, NULL) == SUCCESS);
		}
	} else {
		value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2;
		RETVAL_FALSE;
		for(i=0; i<arg_num; i++) {
			v2.type = NULL_T;
			if(i+2 == arg_num) {
				if(Z_TYPE(arguments[i+1]) == IS_ARRAY) {
					if(Z_TYPE(arguments[i]) == IS_LONG) {
						if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE || v2.type != HT_T) {
							zval_to_value(&arguments[i+1], &v2);
							RETVAL_BOOL(hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL) == SUCCESS);
						} else {
							GC_RECURSIVE_BEGIN(Z_ARR(arguments[i+1]));
							zend_hash_apply_with_arguments(Z_ARR(arguments[i+1]), zval_array_to_hash_table, 1, v2.ptr);
							GC_RECURSIVE_END(Z_ARR(arguments[i+1]));
							RETVAL_TRUE;
						}
					} else {
						convert_to_string(&arguments[i]);
						if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE || v2.type != HT_T) {
							zval_to_value(&arguments[i+1], &v2);
							RETVAL_BOOL(hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL) == SUCCESS);
						} else {
							GC_RECURSIVE_BEGIN(Z_ARR(arguments[i+1]));
							zend_hash_apply_with_arguments(Z_ARR(arguments[i+1]), zval_array_to_hash_table, 1, v2.ptr);
							GC_RECURSIVE_END(Z_ARR(arguments[i+1]));
							RETVAL_TRUE;
						}
					}
				} else {
					zval_to_value(&arguments[i+1], &v2);
					if(Z_TYPE(arguments[i]) == IS_LONG) {
						RETVAL_BOOL(hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL) == SUCCESS);
					} else {
						convert_to_string(&arguments[i]);
						RETVAL_BOOL(hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL) == SUCCESS);
					}
				}
				break;
			} else if(Z_TYPE(arguments[i]) == IS_LONG) {
				if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) {
					v2.type = HT_T;
					v2.ptr = malloc(sizeof(hash_table_t));
					hash_table_init(v2.ptr, 2);
					hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL);
				} else {
					if(v2.type != HT_T) {
						v2.type = HT_T;
						v2.ptr = malloc(sizeof(hash_table_t));
						hash_table_init(v2.ptr, 2);
						hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL);
					}
				}
			} else {
				convert_to_string(&arguments[i]);
				if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) {
					v2.type = HT_T;
					v2.ptr = malloc(sizeof(hash_table_t));
					hash_table_init(v2.ptr, 2);
					hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL);
				} else {
					if(v2.type != HT_T) {
						v2.type = HT_T;
						v2.ptr = malloc(sizeof(hash_table_t));
						hash_table_init(v2.ptr, 2);
						hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL);
					}
				}
			}
			v1 = v2;
		}
	}
	SHARE_VAR_WUNLOCK();

	end:
	efree(arguments);
}

#define VALUE_ADD(k,v,t) \
	switch(dst->type) { \
		case BOOL_T: \
			dst->v = dst->b + src->k;\
			dst->type = t;\
			break; \
		case LONG_T: \
			dst->l = dst->l + src->k;\
			break; \
		case DOUBLE_T: \
			dst->d = dst->d + src->k;\
			break; \
		default: \
			dst->v = src->k; \
			dst->type = t; \
			break; \
	}

static void value_add(value_t *dst, value_t *src) {
	if(dst->type == HT_T) {
		hash_table_next_index_insert(dst->ptr, src, NULL);
	} else {
		switch(src->type) {
			case NULL_T:
				break;
			case BOOL_T:
				VALUE_ADD(b,l,LONG_T);
				break;
			case LONG_T:
				VALUE_ADD(l,l,LONG_T);
				break;
			case DOUBLE_T:
				VALUE_ADD(d,d,DOUBLE_T);
				break;
			case STR_T:
				if(dst->type == STR_T) {
					string_t *s = (string_t*) malloc(sizeof(string_t)+dst->str->len+src->str->len);
					s->len = dst->str->len+src->str->len;
					memcpy(s->str, dst->str->str, dst->str->len);
					memcpy(s->str + dst->str->len, src->str->str, src->str->len);
					s->str[s->len] = '\0';
					//free(dst->str);
					dst->str = s;
					hash_table_value_free(src);
				} else {
					dst->type = STR_T;
					dst->str = src->str;
				}
				break;
			default:
				hash_table_value_free(src);
				break;
		}
	}
}

static PHP_FUNCTION(share_var_inc)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;
	if(arg_num <= 1) return;

	if(!share_var_ht) return;

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	RETVAL_FALSE;

	SHARE_VAR_WLOCK();
	{
		value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2, v3 = {.type=NULL_T,.expire=0};
		ulong h;
		for(i=0; i<arg_num; i++) {
			v2.type = NULL_T;
			if(i+2 == arg_num) {
				zval_to_value(&arguments[i+1], &v2);
				if(Z_TYPE(arguments[i]) == IS_LONG) {
					if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v3) == FAILURE) {
						if(hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL) == SUCCESS) {
							value_to_zval(&v2, return_value);
						}
					} else {
						value_add(&v3, &v2);
						if(v3.type != HT_T) {
							if(hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v3, NULL) == SUCCESS) {
								value_to_zval(&v3, return_value);
							}
						} else RETVAL_LONG(hash_table_num_elements(v3.ptr));
					}
				} else {
					h = zend_get_hash_value(Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]));
					if(hash_table_quick_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), h, &v3) == FAILURE) {
						if(hash_table_quick_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), h, &v2, NULL) == SUCCESS) {
							value_to_zval(&v2, return_value);
						}
					} else {
						value_add(&v3, &v2);
						if(v3.type != HT_T) {
							if(hash_table_quick_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), h, &v3, NULL) == SUCCESS) {
								value_to_zval(&v3, return_value);
							}
						} else RETVAL_LONG(hash_table_num_elements(v3.ptr));
					}
				}
				break;
			} else if(Z_TYPE(arguments[i]) == IS_LONG) {
				if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) {
					v2.type = HT_T;
					v2.ptr = malloc(sizeof(hash_table_t));
					hash_table_init(v2.ptr, 2);
					hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL);
				} else {
					if(v2.type != HT_T) {
						v2.type = HT_T;
						v2.ptr = malloc(sizeof(hash_table_t));
						hash_table_init(v2.ptr, 2);
						hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL);
					}
				}
			} else {
				convert_to_string(&arguments[i]);
				if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) {
					v2.type = HT_T;
					v2.ptr = malloc(sizeof(hash_table_t));
					hash_table_init(v2.ptr, 2);
					hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL);
				} else {
					if(v2.type != HT_T) {
						v2.type = HT_T;
						v2.ptr = malloc(sizeof(hash_table_t));
						hash_table_init(v2.ptr, 2);
						hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL);
					}
				}
			}
			v1 = v2;
		}
	}
	SHARE_VAR_WUNLOCK();

	end:
	efree(arguments);
}

static PHP_FUNCTION(share_var_set)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;
	if(arg_num <= 1) return;

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	SHARE_VAR_WLOCK();
	value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2 = {.type=NULL_T,.expire=0};
	RETVAL_FALSE;
	for(i=0; i<arg_num && v1.type == HT_T; i++) {
		if(i+2 == arg_num) {
			zval_to_value(&arguments[i+1], &v2);
			if(Z_TYPE(arguments[i]) == IS_LONG) {
				RETVAL_BOOL(hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL) == SUCCESS);
			} else {
				convert_to_string(&arguments[i]);
				RETVAL_BOOL(hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL) == SUCCESS);
			}
			break;
		} else if(Z_TYPE(arguments[i]) == IS_LONG) {
			if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) {
				v2.type = HT_T;
				v2.ptr = malloc(sizeof(hash_table_t));
				hash_table_init(v2.ptr, 2);
				hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL);
			} else {
				if(v2.type != HT_T) {
					v2.type = HT_T;
					v2.ptr = malloc(sizeof(hash_table_t));
					hash_table_init(v2.ptr, 2);
					hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL);
				}
			}
		} else {
			convert_to_string(&arguments[i]);
			if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) {
				v2.type = HT_T;
				v2.ptr = malloc(sizeof(hash_table_t));
				hash_table_init(v2.ptr, 2);
				hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL);
			} else {
				if(v2.type != HT_T) {
					v2.type = HT_T;
					v2.ptr = malloc(sizeof(hash_table_t));
					hash_table_init(v2.ptr, 2);
					hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL);
				}
			}
		}
		v1 = v2;
	}
	SHARE_VAR_WUNLOCK();

	end:
	efree(arguments);
}

static PHP_FUNCTION(share_var_set_ex)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;
	if(arg_num <= 2) return;

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	SHARE_VAR_WLOCK();
	value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2 = {.type=NULL_T,.expire=0};
	RETVAL_FALSE;
	for(i=0; i<arg_num && v1.type == HT_T; i++) {
		if(i+3 == arg_num) {
			zval_to_value(&arguments[i+1], &v2);
			v2.expire = (int) Z_LVAL(arguments[i+2]);
			if(Z_TYPE(arguments[i]) == IS_LONG) {
				RETVAL_BOOL(hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL) == SUCCESS);
			} else {
				convert_to_string(&arguments[i]);
				RETVAL_BOOL(hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL) == SUCCESS);
			}
			break;
		} else if(Z_TYPE(arguments[i]) == IS_LONG) {
			if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) {
				v2.type = HT_T;
				v2.ptr = malloc(sizeof(hash_table_t));
				hash_table_init(v2.ptr, 2);
				hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL);
			} else {
				if(v2.type != HT_T) {
					v2.type = HT_T;
					v2.ptr = malloc(sizeof(hash_table_t));
					hash_table_init(v2.ptr, 2);
					hash_table_index_update((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2, NULL);
				}
			}
		} else {
			convert_to_string(&arguments[i]);
			if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) {
				v2.type = HT_T;
				v2.ptr = malloc(sizeof(hash_table_t));
				hash_table_init(v2.ptr, 2);
				hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL);
			} else {
				if(v2.type != HT_T) {
					v2.type = HT_T;
					v2.ptr = malloc(sizeof(hash_table_t));
					hash_table_init(v2.ptr, 2);
					hash_table_update((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2, NULL);
				}
			}
		}
		v1 = v2;
	}
	SHARE_VAR_WUNLOCK();

	end:
	efree(arguments);
}

static PHP_FUNCTION(share_var_del)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;
	if(arg_num <= 0) return;
	if(!share_var_ht) return;

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	SHARE_VAR_WLOCK();
	value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2 = {.type=NULL_T,.expire=0};
	RETVAL_FALSE;
	for(i=0; i<arg_num && v1.type == HT_T; i++) {
		if(i+1 == arg_num) {
			if(Z_TYPE(arguments[i]) == IS_LONG) {
				RETVAL_BOOL(hash_table_index_del((hash_table_t*) v1.ptr, Z_LVAL(arguments[i])) == SUCCESS);
			} else {
				convert_to_string(&arguments[i]);
				RETVAL_BOOL(hash_table_del((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i])) == SUCCESS);
			}
		} else if(Z_TYPE(arguments[i]) == IS_LONG) {
			if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) break;
		} else {
			convert_to_string(&arguments[i]);
			if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) break;
		}
		v1 = v2;
	}
	SHARE_VAR_WUNLOCK();

	end:
	efree(arguments);
}

static PHP_FUNCTION(share_var_clean)
{
	if(!share_var_ht) return;

	int n;
	SHARE_VAR_WLOCK();
	n = hash_table_num_elements(share_var_ht);
	hash_table_clean(share_var_ht);
	SHARE_VAR_WUNLOCK();

	RETVAL_LONG(n);
}

static int hash_table_clean_ex(bucket_t *p, int *ex) {
	if(p->value.expire && p->value.expire <= *ex) {
		return HASH_TABLE_APPLY_REMOVE;
	} else if(p->value.type == HT_T) {
		hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_clean_ex, ex);
	} else if(p->value.type == TS_HT_T) {
		ts_hash_table_t *ptr = (ts_hash_table_t*) p->value.ptr;
		if(ptr->expire && ptr->expire < *ex) return HASH_TABLE_APPLY_REMOVE;
		ts_hash_table_wr_lock(ptr);
		hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_clean_ex, ex);
		ts_hash_table_wr_unlock(ptr);
	}
	
	return HASH_TABLE_APPLY_KEEP;
}

static PHP_FUNCTION(share_var_clean_ex)
{
	zend_long d;
	int n;
	
	if(!share_var_ht) return;
	
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_LONG(d)
	ZEND_PARSE_PARAMETERS_END();
	
	if(d <= 0) return;
	
	n = (int) d;

	SHARE_VAR_WLOCK();
	hash_table_apply_with_argument(share_var_ht, (hash_apply_func_arg_t) hash_table_clean_ex, &n);
	n = hash_table_num_elements(share_var_ht);
	SHARE_VAR_WUNLOCK();

	RETVAL_LONG(n);
}

static PHP_FUNCTION(share_var_count)
{
	zval *arguments;
	int arg_num = ZEND_NUM_ARGS(), i;

	if(!share_var_ht) return;

	if(arg_num <= 0) {
		SHARE_VAR_RLOCK();
		RETVAL_LONG(hash_table_num_elements(share_var_ht));
		SHARE_VAR_RUNLOCK();
		return;
	}

	arguments = (zval *) safe_emalloc(sizeof(zval), arg_num, 0);
	if(zend_get_parameters_array_ex(arg_num, arguments) == FAILURE) goto end;

	SHARE_VAR_RLOCK();
	value_t v1 = {.type=HT_T,.ptr=share_var_ht,.expire=0}, v2 = {.type=NULL_T,.expire=0};
	for(i=0; i<arg_num && v1.type == HT_T; i++) {
		if(Z_TYPE(arguments[i]) == IS_LONG) {
			if(hash_table_index_find((hash_table_t*) v1.ptr, Z_LVAL(arguments[i]), &v2) == FAILURE) break;
		} else {
			convert_to_string(&arguments[i]);
			if(hash_table_find((hash_table_t*) v1.ptr, Z_STRVAL(arguments[i]), Z_STRLEN(arguments[i]), &v2) == FAILURE) break;
		}
		if(i == arg_num - 1) {
			switch(v2.type) {
				case STR_T:
					RETVAL_LONG(- (zend_long) v2.str->len);
					break;
				case SERI_T:
					RETVAL_TRUE;
					break;
				case HT_T:
					RETVAL_LONG(hash_table_num_elements(v2.ptr));
					break;
				default:
					RETVAL_FALSE;
					break;
			}
		} else v1 = v2;
	}
	SHARE_VAR_RUNLOCK();

	end:
	efree(arguments);
}

static PHP_FUNCTION(share_var_destory)
{
	if(mthread != pthread_self()) {
		php_printf("The share_var_destory function can only be executed in main thread\n");
		return;
	}

	if(!share_var_ht) return;

	int n = hash_table_num_elements(share_var_ht);
	pthread_mutex_destroy(&share_var_rlock);
	pthread_mutex_destroy(&share_var_wlock);
	hash_table_destroy(share_var_ht);
	
	free(share_var_ht);
	share_var_ht = NULL;

	RETVAL_LONG(n);
}

// -----------------------------------------------------------------------------------------------------------

static int hash_table_to_zval_wr(bucket_t *p, zval *a) {
	if(p->nKeyLength == 0) {
		switch(p->value.type) {
			case NULL_T:
				add_index_null(a, p->h);
				break;
			case BOOL_T:
				add_index_bool(a, p->h, p->value.b);
				break;
			case LONG_T:
				add_index_long(a, p->h, p->value.l);
				break;
			case DOUBLE_T:
				add_index_double(a, p->h, p->value.d);
				break;
			case STR_T:
				add_index_stringl(a, p->h, p->value.str->str, p->value.str->len);
				break;
			case HT_T: {
				zval z;
				array_init_size(&z, hash_table_num_elements(p->value.ptr));
				hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_to_zval, &z);
				add_index_zval(a, p->h, &z);
				break;
			}
			case SERI_T: {
				zval rv;
				UNSERIALIZE_EX(p->value.str->str, p->value.str->len, __NULL, ZVAL_COPY(&rv, retval), add_index_zval(a, p->h, &rv));
				break;
			}
			case TS_HT_T: {
				zval z;
				array_init_size(&z, hash_table_num_elements(p->value.ptr));
				ts_hash_table_wr_lock(p->value.ptr);
				hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_to_zval_wr, &z);
				ts_hash_table_wr_unlock(p->value.ptr);
				add_index_zval(a, p->h, &z);
				break;
			}
		}
	} else {
		switch(p->value.type) {
			case NULL_T:
				add_assoc_null_ex(a, p->arKey, p->nKeyLength);
				break;
			case BOOL_T:
				add_assoc_bool_ex(a, p->arKey, p->nKeyLength, p->value.b);
				break;
			case LONG_T:
				add_assoc_long_ex(a, p->arKey, p->nKeyLength, p->value.l);
				break;
			case DOUBLE_T:
				add_assoc_double_ex(a, p->arKey, p->nKeyLength, p->value.d);
				break;
			case STR_T:
				add_assoc_stringl_ex(a, p->arKey, p->nKeyLength, p->value.str->str, p->value.str->len);
				break;
			case HT_T: {
				zval z;
				array_init_size(&z, hash_table_num_elements(p->value.ptr));
				hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_to_zval, &z);
				add_assoc_zval_ex(a, p->arKey, p->nKeyLength, &z);
				break;
			}
			case SERI_T: {
				zval rv;
				UNSERIALIZE_EX(p->value.str->str, p->value.str->len, __NULL, ZVAL_COPY(&rv, retval), add_assoc_zval_ex(a, p->arKey, p->nKeyLength, &rv));
				break;
			}
			case TS_HT_T: {
				zval z;
				array_init_size(&z, hash_table_num_elements(p->value.ptr));
				ts_hash_table_wr_lock(p->value.ptr);
				hash_table_apply_with_argument(p->value.ptr, (hash_apply_func_arg_t) hash_table_to_zval_wr, &z);
				ts_hash_table_wr_unlock(p->value.ptr);
				add_assoc_zval_ex(a, p->arKey, p->nKeyLength, &z);
				break;
			}
		}
	}
	
	return HASH_TABLE_APPLY_KEEP;
}

void value_to_zval_wr(value_t *v, zval *return_value) {
	switch(v->type) {
		case BOOL_T:
			RETVAL_BOOL(v->b);
			break;
		case LONG_T:
			RETVAL_LONG(v->l);
			break;
		case DOUBLE_T:
			RETVAL_DOUBLE(v->d);
			break;
		case STR_T:
			RETVAL_STRINGL(v->str->str, v->str->len);
			break;
		case HT_T:
			array_init_size(return_value, hash_table_num_elements(v->ptr));
			hash_table_apply_with_argument(v->ptr, (hash_apply_func_arg_t) hash_table_to_zval, return_value);
			break;
		case SERI_T: {
			UNSERIALIZE(v->str->str, v->str->len, ZVAL_COPY(return_value, retval));
			break;
		}
		case TS_HT_T:
			array_init_size(return_value, hash_table_num_elements(v->ptr));
			ts_hash_table_wr_lock(v->ptr);
			hash_table_apply_with_argument(v->ptr, (hash_apply_func_arg_t) hash_table_to_zval_wr, return_value);
			ts_hash_table_wr_unlock(v->ptr);
			break;
		default:
			RETVAL_NULL();
			break;
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_declare, 1)
ZEND_ARG_INFO(0, key)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 1)
ZEND_ARG_TYPE_INFO(0, is_fd, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_declare) {
	zend_string *key = NULL;
	zend_long index = 0;
	zend_bool is_null = 0;
	zval *zv = NULL;
	zend_bool is_fd = 0;

	ts_hash_table_t *ts_ht;
	value_t v = {.expire=0};

	ZEND_PARSE_PARAMETERS_START(1, 3)
		Z_PARAM_STR_OR_LONG_OR_NULL(key, index, is_null);
		Z_PARAM_OPTIONAL
		Z_PARAM_RESOURCE_OR_NULL(zv)
		Z_PARAM_BOOL(is_fd)
	ZEND_PARSE_PARAMETERS_END();
	
	if(zv) {
		if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
			RETURN_FALSE;
		}
	} else {
		ts_ht = &ts_var;
	}

	if(is_null) {
		ts_hash_table_rd_lock(ts_ht);
		ts_hash_table_ref(ts_ht);
		ts_hash_table_rd_unlock(ts_ht);
	} else {
		if(key) {
			zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
			
			ts_hash_table_rd_lock(ts_ht);
			if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v) == SUCCESS && v.type == TS_HT_T) {
				ts_hash_table_ref(v.ptr);
				ts_hash_table_rd_unlock(ts_ht);
			} else {
				ts_hash_table_rd_unlock(ts_ht);
				ts_hash_table_wr_lock(ts_ht);
				if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v) == FAILURE || v.type != TS_HT_T) {
					v.type = TS_HT_T;
					v.ptr = (ts_hash_table_t *) malloc(sizeof(ts_hash_table_t));
					ts_hash_table_init(v.ptr, 2);
					hash_table_quick_update(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v, NULL);
				}
				ts_hash_table_ref(v.ptr);
				ts_hash_table_wr_unlock(ts_ht);
			}
		} else {
			ts_hash_table_rd_lock(ts_ht);
			if(hash_table_index_find(&ts_ht->ht, index, &v) == SUCCESS && v.type == TS_HT_T) {
				ts_hash_table_ref(v.ptr);
				ts_hash_table_rd_unlock(ts_ht);
			} else {
				ts_hash_table_rd_unlock(ts_ht);
				ts_hash_table_wr_lock(ts_ht);
				if(hash_table_index_find(&ts_ht->ht, index, &v) == FAILURE || v.type != TS_HT_T) {
					v.type = TS_HT_T;
					v.ptr = (ts_hash_table_t *) malloc(sizeof(ts_hash_table_t));
					ts_hash_table_init(v.ptr, 2);
					hash_table_index_update(&ts_ht->ht, index, &v, NULL);
				}
				ts_hash_table_ref(v.ptr);
				ts_hash_table_wr_unlock(ts_ht);
			}
		}
		
		ts_ht = (ts_hash_table_t*) v.ptr;
	}

	if(is_fd) {
		ts_hash_table_lock(ts_ht);
		if(!ts_ht->fds[0] && !ts_ht->fds[1] && socketpair(AF_UNIX, SOCK_STREAM, 0, ts_ht->fds) != 0) {
			ts_ht->fds[0] = 0;
			ts_ht->fds[1] = 0;
		}
		ts_hash_table_unlock(ts_ht);
	}

	RETURN_RES(zend_register_resource(ts_ht, le_ts_var_descriptor));
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_fd, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_TYPE_INFO(0, is_write, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_fd) {
	zval *zv;
	zend_bool is_write = 0;
	
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_OPTIONAL
		Z_PARAM_BOOL(is_write)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	if(is_write) {
		socket_import_fd(ts_ht->fds[1], return_value);
	} else {
		socket_import_fd(ts_ht->fds[0], return_value);
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_expire, 2)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_TYPE_INFO(0, expire, IS_LONG, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_expire) {
	zval *zv;
	zend_long expire = 0;
	
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(2, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_LONG(expire)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	ts_hash_table_wr_lock(ts_ht);
	ts_ht->expire = expire;
	ts_hash_table_wr_unlock(ts_ht);
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_exists, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_exists) {
	zval *zv;
	zend_string *key = NULL;
	zend_long index = 0;
	
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(2, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_STR_OR_LONG(key, index);
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	if(key) {
		zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
		
		ts_hash_table_rd_lock(ts_ht);
		RETVAL_BOOL(hash_table_quick_exists(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h));
		ts_hash_table_rd_unlock(ts_ht);
	} else {
		ts_hash_table_rd_lock(ts_ht);
		RETVAL_BOOL(hash_table_index_exists(&ts_ht->ht, index));
		ts_hash_table_rd_unlock(ts_ht);
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_set, 3)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, val)
ZEND_ARG_TYPE_INFO(0, expire, IS_LONG, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_set) {
	zval *zv;
	zend_string *key = NULL;
	zend_long index = 0;
	zend_bool is_null = 0;
	zval *val;
	zend_long expire = 0;
	
	ts_hash_table_t *ts_ht;
	value_t v;

	ZEND_PARSE_PARAMETERS_START(3, 4)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_STR_OR_LONG_OR_NULL(key, index, is_null)
		Z_PARAM_ZVAL(val)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(expire);
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}
	
	zval_to_value(val, &v);
	v.expire = expire;

	if(is_null) {
		ts_hash_table_wr_lock(ts_ht);
		RETVAL_BOOL(hash_table_next_index_insert(&ts_ht->ht, &v, NULL) == SUCCESS);
		ts_hash_table_wr_unlock(ts_ht);
	} else if(key) {
		zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
		
		ts_hash_table_wr_lock(ts_ht);
		RETVAL_BOOL(hash_table_quick_update(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v, NULL) == SUCCESS);
		ts_hash_table_wr_unlock(ts_ht);
	} else {
		ts_hash_table_wr_lock(ts_ht);
		RETVAL_BOOL(hash_table_index_update(&ts_ht->ht, index, &v, NULL) == SUCCESS);
		ts_hash_table_wr_unlock(ts_ht);
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_push, 2)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, val)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_push) {
	zval *zv;
	zval *args;
	int i, argc, n = 0, ret;
	
	ts_hash_table_t *ts_ht;
	value_t v;

	ZEND_PARSE_PARAMETERS_START(2, -1)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_VARIADIC('+', args, argc)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}
	
	for(i=0; i<argc; i++) {
		zval_to_value(&args[i], &v);
		
		ts_hash_table_wr_lock(ts_ht);
		ret = hash_table_next_index_insert(&ts_ht->ht, &v, NULL);
		ts_hash_table_wr_unlock(ts_ht);
		
		if(ret == SUCCESS) n++;
		else hash_table_value_free(&v);
	}

	RETURN_LONG(n);
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_pop, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(1, key)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_pop) {
	zval *zv;
	zval *key = NULL;
	
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_OPTIONAL
		Z_PARAM_ZVAL_DEREF(key)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}
	
	ts_hash_table_wr_lock(ts_ht);
	if(ts_ht->ht.pListTail) {
		value_to_zval_wr(&ts_ht->ht.pListTail->value, return_value);
		if(key) {
			zval_ptr_dtor(key);
			if(ts_ht->ht.pListTail->nKeyLength == 0) {
				ZVAL_LONG(key, ts_ht->ht.pListTail->h);
			} else {
				ZVAL_STRINGL(key, ts_ht->ht.pListTail->arKey, ts_ht->ht.pListTail->nKeyLength);
			}
		}
		hash_table_bucket_delete(&ts_ht->ht, ts_ht->ht.pListTail);
	}
	ts_hash_table_wr_unlock(ts_ht);
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_shift, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(1, key)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_shift) {
	zval *zv;
	zval *key = NULL;
	
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_OPTIONAL
		Z_PARAM_ZVAL_DEREF(key)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}
	
	ts_hash_table_wr_lock(ts_ht);
	if(ts_ht->ht.pListHead) {
		value_to_zval_wr(&ts_ht->ht.pListHead->value, return_value);
		if(key) {
			zval_ptr_dtor(key);
			if(ts_ht->ht.pListHead->nKeyLength == 0) {
				ZVAL_LONG(key, ts_ht->ht.pListHead->h);
			} else {
				ZVAL_STRINGL(key, ts_ht->ht.pListHead->arKey, ts_ht->ht.pListHead->nKeyLength);
			}
		}
		hash_table_bucket_delete(&ts_ht->ht, ts_ht->ht.pListHead);
	}
	ts_hash_table_wr_unlock(ts_ht);
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_minmax, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_TYPE_INFO(0, is_max, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, is_key, _IS_BOOL, 0)
ZEND_ARG_INFO(1, key)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_minmax) {
	zval *zv;
	zval *key = NULL;
	zend_bool is_max = 0, is_key = 0;
	
	ts_hash_table_t *ts_ht;
	bucket_t *p = NULL;

	ZEND_PARSE_PARAMETERS_START(1, 4)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_OPTIONAL
		Z_PARAM_BOOL(is_max)
		Z_PARAM_BOOL(is_key)
		Z_PARAM_ZVAL_DEREF(key)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}
	
	ts_hash_table_rd_lock(ts_ht);
	if(hash_table_minmax(&ts_ht->ht, is_key ? compare_key : compare_value, is_max, &p) == SUCCESS) {
		value_to_zval(&p->value, return_value);
		if(key) {
			zval_ptr_dtor(key);
			if(p->nKeyLength == 0) {
				ZVAL_LONG(key, p->h);
			} else {
				ZVAL_STRINGL(key, p->arKey, p->nKeyLength);
			}
		}
	} else {
		RETVAL_FALSE;
	}
	ts_hash_table_rd_unlock(ts_ht);
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_get, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, key)
ZEND_ARG_TYPE_INFO(0, is_del, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_get) {
	zval *zv;
	zend_string *key = NULL;
	zend_long index = 0;
	zend_bool is_null = 1;
	zend_bool is_del = 0;
	
	ts_hash_table_t *ts_ht;
	value_t v;

	ZEND_PARSE_PARAMETERS_START(1, 3)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_OPTIONAL
		Z_PARAM_STR_OR_LONG_OR_NULL(key, index, is_null)
		Z_PARAM_BOOL(is_del)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	if(is_null) {
		ts_hash_table_rd_lock(ts_ht);
		array_init_size(return_value, hash_table_num_elements(&ts_ht->ht));
		hash_table_apply_with_argument(&ts_ht->ht, (hash_apply_func_arg_t) hash_table_to_zval, return_value);
		ts_hash_table_rd_unlock(ts_ht);
	} else if(is_del) {
		if(key) {
			zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
			
			ts_hash_table_wr_lock(ts_ht);
			if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v) == SUCCESS) {
				value_to_zval_wr(&v, return_value);
				hash_table_quick_del(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h);
			}
			ts_hash_table_wr_unlock(ts_ht);
		} else {
			ts_hash_table_wr_lock(ts_ht);
			if(hash_table_index_find(&ts_ht->ht, index, &v) == SUCCESS) {
				value_to_zval_wr(&v, return_value);
				hash_table_index_del(&ts_ht->ht, index);
			}
			ts_hash_table_wr_unlock(ts_ht);
		}
	} else {
		if(key) {
			zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
			
			ts_hash_table_rd_lock(ts_ht);
			if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v) == SUCCESS) {
				value_to_zval(&v, return_value);
			}
			ts_hash_table_rd_unlock(ts_ht);
		} else {
			ts_hash_table_rd_lock(ts_ht);
			if(hash_table_index_find(&ts_ht->ht, index, &v) == SUCCESS) {
				value_to_zval(&v, return_value);
			}
			ts_hash_table_rd_unlock(ts_ht);
		}
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_get_or_set, 3)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, callback)
ZEND_ARG_TYPE_INFO(0, expire, IS_LONG, 0)
ZEND_ARG_VARIADIC_INFO(0, parameters)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_get_or_set) {
	zval *zv;
	zend_string *key = NULL;
	zend_long index = 0;

	zend_fcall_info fci;
	zend_fcall_info_cache fci_cache;

	zend_long expire = 0;
	
	ts_hash_table_t *ts_ht;
	value_t v;

	ZEND_PARSE_PARAMETERS_START(3, -1)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_STR_OR_LONG(key, index)
		Z_PARAM_FUNC(fci, fci_cache)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(expire);
	#if PHP_VERSION_ID >= 80000
		Z_PARAM_VARIADIC_WITH_NAMED(fci.params, fci.param_count, fci.named_params)
	#else
		Z_PARAM_VARIADIC('*', fci.params, fci.param_count)
	#endif
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}
	
	fci.retval = return_value;

	if(key) {
		zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
		
		ts_hash_table_rd_lock(ts_ht);
		if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v) == SUCCESS) {
			value_to_zval(&v, return_value);
			ts_hash_table_rd_unlock(ts_ht);
		} else {
			ts_hash_table_rd_unlock(ts_ht);
			
			ts_hash_table_wr_lock(ts_ht);
			if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v) == SUCCESS) {
				value_to_zval(&v, return_value);
			} else {
				zend_try {
					if (zend_call_function(&fci, &fci_cache) == SUCCESS) {
						zval_to_value(return_value, &v);
						v.expire = expire;
						hash_table_quick_update(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v, NULL);
					}
				} zend_catch {
					EG(exit_status) = 0;
				} zend_end_try();
			}
			ts_hash_table_wr_unlock(ts_ht);
		}
	} else {
		ts_hash_table_rd_lock(ts_ht);
		if(hash_table_index_find(&ts_ht->ht, index, &v) == SUCCESS) {
			value_to_zval(&v, return_value);
			ts_hash_table_rd_unlock(ts_ht);
		} else {
			ts_hash_table_rd_unlock(ts_ht);
			
			ts_hash_table_wr_lock(ts_ht);
			if(hash_table_index_find(&ts_ht->ht, index, &v) == SUCCESS) {
				value_to_zval(&v, return_value);
			} else {
				zend_try {
					if (zend_call_function(&fci, &fci_cache) == SUCCESS) {
						zval_to_value(return_value, &v);
						v.expire = expire;
						hash_table_index_update(&ts_ht->ht, index, &v, NULL);
					}
				} zend_catch {
					EG(exit_status) = 0;
				} zend_end_try();
			}
			ts_hash_table_wr_unlock(ts_ht);
		}
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_del, 2)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_del) {
	zval *zv;
	zend_string *key = NULL;
	zend_long index = 0;
	
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(2, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_STR_OR_LONG(key, index)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	if(key) {
		zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
		
		ts_hash_table_wr_lock(ts_ht);
		RETVAL_BOOL(hash_table_quick_del(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h) == SUCCESS);
		ts_hash_table_wr_unlock(ts_ht);
	} else {
		ts_hash_table_wr_lock(ts_ht);
		RETVAL_BOOL(hash_table_index_del(&ts_ht->ht, index) == SUCCESS);
		ts_hash_table_wr_unlock(ts_ht);
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_inc, 3)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, val)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_inc) {
	zval *zv;
	zend_string *key = NULL;
	zend_long index = 0;
	zval *val;
	zend_bool is_null = 0;
	
	ts_hash_table_t *ts_ht;
	value_t v1,v2;

	ZEND_PARSE_PARAMETERS_START(3, 3)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_STR_OR_LONG_OR_NULL(key, index, is_null)
		Z_PARAM_ZVAL(val)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	zval_to_value(val, &v1);

	if(is_null) {
		ts_hash_table_wr_lock(ts_ht);
		RETVAL_BOOL(hash_table_next_index_insert(&ts_ht->ht, &v1, NULL) == SUCCESS);
		ts_hash_table_wr_unlock(ts_ht);
	} else if(key) {
		zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
		
		ts_hash_table_wr_lock(ts_ht);
		if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v2) == FAILURE) {
			if(hash_table_quick_update(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v1, NULL) == SUCCESS) {
				value_to_zval_wr(&v1, return_value);
			}
		} else {
			value_add(&v2, &v1);
			if(v2.type != HT_T) {
				if(hash_table_quick_update(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v2, NULL) == SUCCESS) {
					value_to_zval_wr(&v2, return_value);
				}
			} else RETVAL_LONG(hash_table_num_elements(v2.ptr));
		}
		ts_hash_table_wr_unlock(ts_ht);
	} else {
		ts_hash_table_wr_lock(ts_ht);
		if(hash_table_index_find(&ts_ht->ht, index, &v2) == FAILURE) {
			if(hash_table_index_update(&ts_ht->ht, index, &v1, NULL) == SUCCESS) {
				value_to_zval_wr(&v1, return_value);
			}
		} else {
			value_add(&v2, &v1);
			if(v2.type != HT_T) {
				if(hash_table_index_update(&ts_ht->ht, index, &v2, NULL) == SUCCESS) {
					value_to_zval_wr(&v2, return_value);
				}
			} else RETVAL_LONG(hash_table_num_elements(v2.ptr));
		}
		ts_hash_table_wr_unlock(ts_ht);
	}
}

typedef struct {
	int write;
	int read;
} lock_t;

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_lock, 4)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, key)
ZEND_ARG_TYPE_INFO(0, expire, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, is_write, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_lock) {
	zval *zv;
	zend_string *key = NULL;
	zend_long index = 0;
	zend_long expire = 0;
	zend_bool is_write = 0;
	
	ts_hash_table_t *ts_ht;
	value_t v = {.type = LONG_T, .expire = 0, .l = 0};
	lock_t *l = (lock_t*) &v.l;
	int t;

	ZEND_PARSE_PARAMETERS_START(4, 4)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_STR_OR_LONG(key, index)
		Z_PARAM_LONG(expire)
		Z_PARAM_BOOL(is_write)
	ZEND_PARSE_PARAMETERS_END();
	
	RETVAL_FALSE;
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		return;
	}

	if(key) {
		zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
		
		ts_hash_table_wr_lock(ts_ht);
		t = (int) time(NULL);
		if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v) == FAILURE || (v.expire > 0 && v.expire <= t)) {
			v.type = LONG_T;
			v.expire = (expire > 0 ? t + expire : 0);
			l->write = 1;
			l->read = 0;
			if(!is_write) l->read = 1;
			
			if(hash_table_quick_update(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v, NULL) == SUCCESS) {
				RETVAL_TRUE;
			}
		} else if(!is_write) {
			l->read ++;
			if(hash_table_quick_update(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v, NULL) == SUCCESS) {
				RETVAL_TRUE;
			}
		}
		ts_hash_table_wr_unlock(ts_ht);
	} else {
		ts_hash_table_wr_lock(ts_ht);
		t = (int) time(NULL);
		if(hash_table_index_find(&ts_ht->ht, index, &v) == FAILURE || (v.expire > 0 && v.expire <= t)) {
			v.expire = (expire > 0 ? t + expire : 0);
			l->write = 1;
			if(!is_write) l->read = 1;
			
			if(hash_table_index_update(&ts_ht->ht, index, &v, NULL) == SUCCESS) {
				RETVAL_TRUE;
			}
		} else if(!is_write) {
			l->read ++;
			if(hash_table_index_update(&ts_ht->ht, index, &v, NULL) == SUCCESS) {
				RETVAL_TRUE;
			}
		}
		ts_hash_table_wr_unlock(ts_ht);
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_unlock, 3)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_INFO(0, key)
ZEND_ARG_TYPE_INFO(0, is_write, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_unlock) {
	zval *zv;
	zend_string *key = NULL;
	zend_long index = 0;
	zend_bool is_write = 0;
	
	ts_hash_table_t *ts_ht;
	value_t v;
	lock_t *l = (lock_t*) &v.l;

	ZEND_PARSE_PARAMETERS_START(3, 3)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_STR_OR_LONG(key, index)
		Z_PARAM_BOOL(is_write)
	ZEND_PARSE_PARAMETERS_END();
	
	RETVAL_FALSE;
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		return;
	}

	if(key) {
		zend_long h = zend_get_hash_value(ZSTR_VAL(key), ZSTR_LEN(key));
		
		ts_hash_table_wr_lock(ts_ht);
		if(hash_table_quick_find(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v) == SUCCESS) {
			if(is_write) {
				if(!l->read) {
					hash_table_quick_del(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h);
					RETVAL_TRUE;
				}
			} else if(l->read > 0) {
				l->read --;
				
				if(l->read) {
					if(hash_table_quick_update(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h, &v, NULL) == SUCCESS) {
						RETVAL_TRUE;
					}
				} else {
					hash_table_quick_del(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h);
					RETVAL_TRUE;
				}
			} else {
				hash_table_quick_del(&ts_ht->ht, ZSTR_VAL(key), ZSTR_LEN(key), h);
			}
		}
		ts_hash_table_wr_unlock(ts_ht);
	} else {
		ts_hash_table_wr_lock(ts_ht);
		if(hash_table_index_find(&ts_ht->ht, index, &v) == SUCCESS) {
			if(is_write) {
				if(!l->read) {
					hash_table_index_del(&ts_ht->ht, index);
					RETVAL_TRUE;
				}
			} else if(l->read > 0) {
				l->read --;
				
				if(l->read) {
					if(hash_table_index_update(&ts_ht->ht, index, &v, NULL) == SUCCESS) {
						RETVAL_TRUE;
					}
				} else {
					hash_table_index_del(&ts_ht->ht, index);
					RETVAL_TRUE;
				}
			} else {
				hash_table_index_del(&ts_ht->ht, index);
			}
		}
		ts_hash_table_wr_unlock(ts_ht);
	}
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_count, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_count) {
	zval *zv;
	
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_RESOURCE(zv)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	ts_hash_table_rd_lock(ts_ht);
	RETVAL_LONG(hash_table_num_elements(&ts_ht->ht));
	ts_hash_table_rd_unlock(ts_ht);
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_clean, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_TYPE_INFO(0, expire, IS_LONG, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_clean) {
	zval *zv;
	zend_long expire = 0;
	
	ts_hash_table_t *ts_ht;
	int n;

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(expire)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}
	
	n = (int) expire;

	ts_hash_table_wr_lock(ts_ht);
	if(expire) hash_table_apply_with_argument(&ts_ht->ht, (hash_apply_func_arg_t) hash_table_clean_ex, &n);
	RETVAL_LONG(hash_table_num_elements(&ts_ht->ht));
	if(expire == 0) hash_table_clean(&ts_ht->ht);
	ts_hash_table_wr_unlock(ts_ht);
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_reindex, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_ARG_TYPE_INFO(0, only_integer_keys, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_reindex) {
	zval *zv;
	zend_bool only_integer_keys = 0;
	
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_OPTIONAL
		Z_PARAM_BOOL(only_integer_keys)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}
	
	ts_hash_table_wr_lock(ts_ht);
	hash_table_reindex(&ts_ht->ht, only_integer_keys);
	ts_hash_table_wr_unlock(ts_ht);

	RETURN_TRUE;
}

static int hash_table_keys_to_zval(bucket_t *p, zval *a) {
	if(p->nKeyLength == 0) {
		add_next_index_long(a, p->h);
	} else {
		add_next_index_stringl(a, p->arKey, p->nKeyLength);
	}
	
	return HASH_TABLE_APPLY_KEEP;
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_keys, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_keys) {
	zval *zv;
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_RESOURCE(zv)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	ts_hash_table_rd_lock(ts_ht);
	array_init_size(return_value, hash_table_num_elements(&ts_ht->ht));
	hash_table_apply_with_argument(&ts_ht->ht, (hash_apply_func_arg_t) hash_table_keys_to_zval, return_value);
	ts_hash_table_rd_unlock(ts_ht);
}

static int hash_table_expires_to_zval(bucket_t *p, zval *a) {
	if(p->nKeyLength == 0) {
		add_index_long(a, p->h, p->value.expire);
	} else {
		add_assoc_long_ex(a, p->arKey, p->nKeyLength, p->value.expire);
	}
	
	return HASH_TABLE_APPLY_KEEP;
}

ZEND_BEGIN_ARG_INFO(arginfo_ts_var_expires, 1)
ZEND_ARG_TYPE_INFO(0, res, IS_RESOURCE, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(ts_var_expires) {
	zval *zv;
	ts_hash_table_t *ts_ht;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_RESOURCE(zv)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((ts_ht = (ts_hash_table_t *) zend_fetch_resource_ex(zv, PHP_TS_VAR_DESCRIPTOR, le_ts_var_descriptor)) == NULL) {
		RETURN_FALSE;
	}

	ts_hash_table_rd_lock(ts_ht);
	array_init_size(return_value, hash_table_num_elements(&ts_ht->ht));
	hash_table_apply_with_argument(&ts_ht->ht, (hash_apply_func_arg_t) hash_table_expires_to_zval, return_value);
	ts_hash_table_rd_unlock(ts_ht);
}

// ===========================================================================================================

#if PHP_VERSION_ID < 80000
php_socket *socket_import_file_descriptor(PHP_SOCKET socket) {
#ifdef SO_DOMAIN
	int						type;
	socklen_t				type_len = sizeof(type);
#endif
	php_socket 				*retsock;
	php_sockaddr_storage	addr;
	socklen_t				addr_len = sizeof(addr);
#ifndef PHP_WIN32
	int					 t;
#endif

	retsock = php_create_socket();
	retsock->bsd_socket = socket;

	/* determine family */
#ifdef SO_DOMAIN
	if (getsockopt(socket, SOL_SOCKET, SO_DOMAIN, &type, &type_len) == 0) {
		retsock->type = type;
	} else
#endif
	if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) {
		retsock->type = addr.ss_family;
	} else {
		goto error;
	}

	/* determine blocking mode */
#ifndef PHP_WIN32
	t = fcntl(socket, F_GETFL);
	if (t == -1) {
		goto error;
	} else {
		retsock->blocking = !(t & O_NONBLOCK);
	}
#endif

	return retsock;

error:
	efree(retsock);
	return NULL;
}
#endif

#if PHP_VERSION_ID < 80000
ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_export_fd, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, socket, IS_RESOURCE, 0)
ZEND_ARG_TYPE_INFO(0, is_close, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(socket_export_fd) {
	zval *zv;
	php_socket *sock;
	zend_bool is_close = 0;

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_RESOURCE(zv)
		Z_PARAM_OPTIONAL
		Z_PARAM_BOOL(is_close)
	ZEND_PARSE_PARAMETERS_END();
	
	if ((sock = (php_socket *) zend_fetch_resource_ex(zv, php_sockets_le_socket_name, php_sockets_le_socket())) == NULL) {
		RETURN_FALSE;
	}
	
	RETVAL_LONG(sock->bsd_socket);
	
	if(is_close) {
		sock->bsd_socket = -1;
	}
}
#else
ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_export_fd, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, socket, Socket, 0)
ZEND_ARG_TYPE_INFO(0, is_close, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(socket_export_fd) {
	zval *zsocket;
	php_socket *socket;
	zend_bool is_close = 0;
	
	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &zsocket, socket_ce, &is_close) == FAILURE) {
		RETURN_THROWS();
	}

	socket = Z_SOCKET_P(zsocket);
	ENSURE_SOCKET_VALID(socket);
	
	RETVAL_LONG(socket->bsd_socket);
	
	if(is_close) {
		socket->bsd_socket = -1;
	}
}
#endif

void socket_import_fd(int fd, zval *return_value) {
	php_socket *sock;
	
	if(fd <= 0) RETURN_FALSE;

#if PHP_VERSION_ID >= 80000	
	object_init_ex(return_value, socket_ce);
	sock = Z_SOCKET_P(return_value);
	if (!socket_import_file_descriptor(fd, sock)) {
		zval_ptr_dtor(return_value);
		RETURN_FALSE;
	}
#else
	sock = socket_import_file_descriptor(fd);
	if(sock) {
		RETURN_RES(zend_register_resource(sock, php_sockets_le_socket()));
	} else RETURN_FALSE;
#endif
}

ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_import_fd, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, sockfd, IS_LONG, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(socket_import_fd) {
	zend_long fd = -1;
	
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_LONG(fd)
	ZEND_PARSE_PARAMETERS_END();
	
	socket_import_fd((int) fd, return_value);
}

ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_accept_ex, 0, 0, 3)
ZEND_ARG_TYPE_INFO(0, sockfd, IS_LONG, 0)
ZEND_ARG_INFO(1, addr)
ZEND_ARG_INFO(1, port)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(socket_accept_ex) {
	zend_long sockfd = -1;
	zval *addr, *port;
	int fd;
	php_sockaddr_storage sa_storage;
	socklen_t salen = sizeof(php_sockaddr_storage);
	struct sockaddr *sa;
	struct sockaddr_in *sin;
#if HAVE_IPV6
	struct sockaddr_in6 *sin6;
	char addr6[INET6_ADDRSTRLEN+1];
#endif
	struct sockaddr_un *s_un;
	char *addr_string;

	ZEND_PARSE_PARAMETERS_START(3, 3)
		Z_PARAM_LONG(sockfd)
		Z_PARAM_ZVAL_DEREF(addr)
		Z_PARAM_ZVAL_DEREF(port)
	ZEND_PARSE_PARAMETERS_END();
	
	sa = (struct sockaddr *) &sa_storage;
	
	fd = accept((int) sockfd, sa, &salen);

	zval_ptr_dtor(addr);
	zval_ptr_dtor(port);

	if(fd == -1) {
		RETURN_FALSE;
	}
	
	switch (sa->sa_family) {
#if HAVE_IPV6
		case AF_INET6:
			sin6 = (struct sockaddr_in6 *) sa;

			inet_ntop(AF_INET6, &sin6->sin6_addr, addr6, INET6_ADDRSTRLEN);

			ZVAL_STRING(addr, addr6);
			ZVAL_LONG(port, htons(sin6->sin6_port));
			break;
#endif
		case AF_INET:
			sin = (struct sockaddr_in *) sa;
			addr_string = inet_ntoa(sin->sin_addr);

			ZVAL_STRING(addr, addr_string);
			ZVAL_LONG(port, htons(sin->sin_port));
			break;

		case AF_UNIX:
			s_un = (struct sockaddr_un *) sa;

			ZVAL_STRING(addr, s_un->sun_path);
			ZVAL_LONG(port, 0);
			break;

		default:
			RETURN_FALSE;
	}

	socket_import_fd(fd, return_value);
}

// ===========================================================================================================

#if defined(MYSQLI_USE_MYSQLND) || PHP_VERSION_ID >= 80200
#include <mysqli/php_mysqli_structs.h>

ZEND_BEGIN_ARG_INFO_EX(arginfo_mysqli_export_fd, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, mysql, mysqli, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(mysqli_export_fd) {
	MY_MYSQL *mysql;
	zval *mysql_link;
	php_stream *stream = NULL;
	php_socket_t this_fd;

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) {
		#if PHP_VERSION_ID < 80000
		RETURN_FALSE;
		#else
		RETURN_THROWS();
		#endif
	}
	
	MYSQLI_FETCH_RESOURCE_CONN(mysql, mysql_link, MYSQLI_STATUS_VALID);
	
	stream = mysql->mysql->data->vio->data->m.get_stream(mysql->mysql->data->vio);
	if(stream != NULL && SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void*)&this_fd, 1) && ZEND_VALID_SOCKET(this_fd)) {
		RETURN_LONG(this_fd);
	} else {
		RETURN_FALSE;
	}
}

ZEND_BEGIN_ARG_INFO_EX(arginfo_mysqli_stmt_async_execute, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, statement, mysqli_stmt, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(mysqli_stmt_async_execute) {
	MY_STMT *stmt;
	zval *mysql_stmt;
	
	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) {
		#if PHP_VERSION_ID < 80000
		RETURN_FALSE;
		#else
		RETURN_THROWS();
		#endif
	}
	
	MYSQLI_FETCH_RESOURCE_STMT(stmt, mysql_stmt, MYSQLI_STATUS_VALID);
	
	if(FAIL == stmt->stmt->m->send_execute(stmt->stmt, MYSQLND_SEND_EXECUTE_IMPLICIT, NULL, NULL)) {
		RETURN_FALSE;
	} else {
		RETURN_TRUE;
	}
}

ZEND_BEGIN_ARG_INFO_EX(arginfo_mysqli_stmt_reap_async_query, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, statement, mysqli_stmt, 0)
ZEND_END_ARG_INFO()

static PHP_FUNCTION(mysqli_stmt_reap_async_query) {
	MY_STMT *stmt;
	zval *mysql_stmt;
	
	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) {
		#if PHP_VERSION_ID < 80000
		RETURN_FALSE;
		#else
		RETURN_THROWS();
		#endif
	}
	
	MYSQLI_FETCH_RESOURCE_STMT(stmt, mysql_stmt, MYSQLI_STATUS_VALID);
	
	if(FAIL == stmt->stmt->m->parse_execute_response(stmt->stmt, MYSQLND_PARSE_EXEC_RESPONSE_IMPLICIT)) {
		RETURN_FALSE;
	} else {
		RETURN_TRUE;
	}
}

#endif

// ===========================================================================================================

#ifndef GC_PROTECT_RECURSION
#define GC_PROTECT_RECURSION(ht) (ht)->u.v.nApplyCount++
#endif
#ifndef GC_UNPROTECT_RECURSION
#define GC_UNPROTECT_RECURSION(ht) (ht)->u.v.nApplyCount--
#endif
#ifndef Z_IS_RECURSIVE_P
#define Z_IS_RECURSIVE_P(val) Z_ARRVAL_P(val)->u.v.nApplyCount > 0
#endif
#ifndef ZEND_CONSTANT_SET_FLAGS
#define ZEND_CONSTANT_SET_FLAGS(c,f,fx) do {\
	(c)->flags = f;\
	(c)->module_number = fx;\
} while(0)
#endif

static int validate_constant_array(HashTable *ht) /* {{{ */
{
	int ret = 1;
	zval *val;

	GC_PROTECT_RECURSION(ht);
	ZEND_HASH_FOREACH_VAL_IND(ht, val) {
		ZVAL_DEREF(val);
		if (Z_REFCOUNTED_P(val)) {
			if (Z_TYPE_P(val) == IS_ARRAY) {
				if (Z_REFCOUNTED_P(val)) {
					if (Z_IS_RECURSIVE_P(val)) {
						zend_error(E_WARNING, "Constants cannot be recursive arrays");
						ret = 0;
						break;
					} else if (!validate_constant_array(Z_ARRVAL_P(val))) {
						ret = 0;
						break;
					}
				}
			} else if (Z_TYPE_P(val) != IS_STRING && Z_TYPE_P(val) != IS_RESOURCE) {
				zend_error(E_WARNING, "Constants may only evaluate to scalar values, arrays or resources");
				ret = 0;
				break;
			}
		}
	} ZEND_HASH_FOREACH_END();
	GC_UNPROTECT_RECURSION(ht);
	return ret;
}
/* }}} */

static void copy_constant_array(zval *dst, zval *src) /* {{{ */
{
	zend_string *key;
	zend_ulong idx;
	zval *new_val, *val;

	array_init_size(dst, zend_hash_num_elements(Z_ARRVAL_P(src)));
	ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(src), idx, key, val) {
		/* constant arrays can't contain references */
		ZVAL_DEREF(val);
		if (key) {
			new_val = zend_hash_add_new(Z_ARRVAL_P(dst), key, val);
		} else {
			new_val = zend_hash_index_add_new(Z_ARRVAL_P(dst), idx, val);
		}
		if (Z_TYPE_P(val) == IS_ARRAY) {
			if (Z_REFCOUNTED_P(val)) {
				copy_constant_array(new_val, val);
			}
		} else {
			Z_TRY_ADDREF_P(val);
		}
	} ZEND_HASH_FOREACH_END();
}
/* }}} */

ZEND_BEGIN_ARG_INFO_EX(arginfo_redefine, 0, 0, 2)
	ZEND_ARG_INFO(0, constant_name)
	ZEND_ARG_INFO(0, value)
	ZEND_ARG_INFO(0, case_insensitive)
ZEND_END_ARG_INFO()

PHP_FUNCTION(redefine) /* {{{ */
{
	zend_string *name;
	zval *val, val_free;
	zend_bool non_cs = 0;
	int case_sensitive = CONST_CS;
	zend_constant c;

	ZEND_PARSE_PARAMETERS_START(2, 3)
		Z_PARAM_STR(name)
		Z_PARAM_ZVAL(val)
		Z_PARAM_OPTIONAL
		Z_PARAM_BOOL(non_cs)
	ZEND_PARSE_PARAMETERS_END();

	if (non_cs) {
		case_sensitive = 0;
	}

	if (zend_memnstr(ZSTR_VAL(name), "::", sizeof("::") - 1, ZSTR_VAL(name) + ZSTR_LEN(name))) {
		zend_error(E_WARNING, "Class constants cannot be defined or redefined");
		RETURN_FALSE;
	}

	ZVAL_UNDEF(&val_free);

#if PHP_VERSION_ID < 80000
repeat:
#endif
	switch (Z_TYPE_P(val)) {
		case IS_LONG:
		case IS_DOUBLE:
		case IS_STRING:
		case IS_FALSE:
		case IS_TRUE:
		case IS_NULL:
		case IS_RESOURCE:
			break;
		case IS_ARRAY:
			if (Z_REFCOUNTED_P(val)) {
				if (!validate_constant_array(Z_ARRVAL_P(val))) {
					RETURN_FALSE;
				} else {
					copy_constant_array(&c.value, val);
					goto register_constant;
				}
			}
			break;
		case IS_OBJECT:
		#if PHP_VERSION_ID >= 80000
			if (Z_OBJ_HT_P(val)->cast_object(Z_OBJ_P(val), &val_free, IS_STRING) == SUCCESS) {
				val = &val_free;
				break;
			}
		#else
			if (Z_TYPE(val_free) == IS_UNDEF) {
				if (Z_OBJ_HT_P(val)->get) {
					val = Z_OBJ_HT_P(val)->get(val, &val_free);
					goto repeat;
				} else if (Z_OBJ_HT_P(val)->cast_object) {
					if (Z_OBJ_HT_P(val)->cast_object(val, &val_free, IS_STRING) == SUCCESS) {
						val = &val_free;
						break;
					}
				}
			}
		#endif
			/* no break */
		default:
			zend_error(E_WARNING, "Constants may only evaluate to scalar values, arrays or resources");
			zval_ptr_dtor(&val_free);
			RETURN_FALSE;
	}

	ZVAL_COPY(&c.value, val);
	zval_ptr_dtor(&val_free);

register_constant:
	if (non_cs) {
		zend_error(E_DEPRECATED,
			"define(): Declaration of case-insensitive constants is deprecated");
	}

	zval *zv = zend_get_constant(name);
	if(zv) {
		ZVAL_COPY(zv, val);
		RETURN_TRUE;
	}

	/* non persistent */
	ZEND_CONSTANT_SET_FLAGS(&c, case_sensitive, PHP_USER_CONSTANT);
	c.name = zend_string_copy(name);
	if (zend_register_constant(&c) == SUCCESS) {
		RETURN_TRUE;
	} else {
		RETURN_FALSE;
	}
}

// ===========================================================================================================

#if PHP_VERSION_ID < 70300
ZEND_BEGIN_ARG_INFO(arginfo_array_key_first, 1)
	ZEND_ARG_INFO(0, arg) /* ARRAY_INFO(0, arg, 0) */
ZEND_END_ARG_INFO()

PHP_FUNCTION(array_key_first)
{
	zval *stack;    /* Input stack */
	HashPosition pos;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ARRAY(stack)
	ZEND_PARSE_PARAMETERS_END();

	HashTable *target_hash = Z_ARRVAL_P (stack);
	zend_hash_internal_pointer_reset_ex(target_hash, &pos);
	zend_hash_get_current_key_zval_ex(target_hash, return_value, &pos);
}

ZEND_BEGIN_ARG_INFO(arginfo_array_key_last, 1)
	ZEND_ARG_INFO(0, arg) /* ARRAY_INFO(0, arg, 0) */
ZEND_END_ARG_INFO()

PHP_FUNCTION(array_key_last)
{
	zval *stack;    /* Input stack */
	HashPosition pos;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ARRAY(stack)
	ZEND_PARSE_PARAMETERS_END();

	HashTable *target_hash = Z_ARRVAL_P (stack);
	zend_hash_internal_pointer_end_ex(target_hash, &pos);
	zend_hash_get_current_key_zval_ex(target_hash, return_value, &pos);
}
#endif

#if PHP_VERSION_ID < 80100
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_is_list, 0, 1, _IS_BOOL, 0)
	ZEND_ARG_INFO(0, arg) /* ARRAY_INFO(0, arg, 0) */
ZEND_END_ARG_INFO()

PHP_FUNCTION(array_is_list)
{
	zval *stack;    /* Input stack */
	zend_long expected_idx = 0;
	zend_long num_idx;
	zend_string* str_idx;
	
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ARRAY(stack)
	ZEND_PARSE_PARAMETERS_END();
	
	HashTable *array = Z_ARRVAL_P(stack);
	
	ZEND_HASH_FOREACH_KEY(array, num_idx, str_idx) {
		if (str_idx != NULL || num_idx != expected_idx++) {
			RETURN_FALSE;
		}
	} ZEND_HASH_FOREACH_END();
	
	RETURN_TRUE;
}
#endif

ZEND_BEGIN_ARG_INFO(arginfo_statfs, 1)
	ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0)
ZEND_END_ARG_INFO()

PHP_FUNCTION(statfs)
{
	char *path = NULL;
	size_t pathlen = 0;

	struct statvfs vfs;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_STRING(path, pathlen)
	ZEND_PARSE_PARAMETERS_END();

	if(statvfs(path, &vfs)) {
		RETURN_FALSE;
	}
	
	array_init_size(return_value, 7);
	
	add_assoc_long_ex(return_value, ZEND_STRL("total"), vfs.f_blocks * vfs.f_bsize);
	add_assoc_long_ex(return_value, ZEND_STRL("avail"), vfs.f_bavail * vfs.f_bsize);
	add_assoc_long_ex(return_value, ZEND_STRL("free"), vfs.f_bfree * vfs.f_bsize);
	
	add_assoc_long_ex(return_value, ZEND_STRL("ftotal"), vfs.f_files);
	add_assoc_long_ex(return_value, ZEND_STRL("favail"), vfs.f_favail);
	add_assoc_long_ex(return_value, ZEND_STRL("ffree"), vfs.f_ffree);
	
	add_assoc_long_ex(return_value, ZEND_STRL("namemax"), vfs.f_namemax);
	// add_assoc_stringl_ex(return_value, ZEND_STRL("status"), p->value.str->str, p->value.str->len);
}

// -----------------------------------------------------------------------------------------------------------

static const zend_function_entry ext_functions[] = {
#if PHP_VERSION_ID < 70300
	ZEND_FE(array_key_first, arginfo_array_key_first)
	ZEND_FE(array_key_last, arginfo_array_key_last)
#endif
#if PHP_VERSION_ID < 80100
	ZEND_FE(array_is_list, arginfo_array_is_list)
#endif
	ZEND_FE(statfs, arginfo_statfs)
	PHP_FALIAS(statvfs, statfs, arginfo_statfs)

	ZEND_FE(is_main_task, arginfo_is_main_task)
	PHP_FALIAS(is_main_thread, is_main_task, arginfo_is_main_task)
	ZEND_FE(create_task, arginfo_create_task)
	ZEND_FE(task_kill, arginfo_task_kill)
	ZEND_FE(task_join, arginfo_task_join)
	ZEND_FE(task_is_run, arginfo_task_is_run)
	ZEND_FE(task_wait, arginfo_task_wait)
	ZEND_FE(task_get_delay, arginfo_task_get_delay)
	ZEND_FE(task_set_delay, arginfo_task_set_delay)
	ZEND_FE(task_get_num, arginfo_task_get_num)
	PHP_FALIAS(task_get_threads, task_get_num, arginfo_task_get_num)
	ZEND_FE(task_set_threads, arginfo_task_set_threads)
	ZEND_FE(task_get_debug, arginfo_task_get_debug)
	ZEND_FE(task_set_debug, arginfo_task_set_debug)
	ZEND_FE(task_get_run, arginfo_task_get_run)
	ZEND_FE(task_set_run, arginfo_task_set_run)

	ZEND_FE(pthread_sigmask, arginfo_pthread_sigmask)
	ZEND_FE(pthread_yield, arginfo_pthread_yield)

	ZEND_FE(go, arginfo_go)
	ZEND_FE(call_and_free_shutdown, arginfo_call_and_free_shutdown)
	ZEND_FE(redefine, arginfo_redefine)

	ZEND_FE(set_timeout, arginfo_set_timeout)
	ZEND_FE(clear_timeout, arginfo_clear_timeout)
	ZEND_FE(trigger_timeout, arginfo_trigger_timeout)

	PHP_FE(share_var_init, arginfo_share_var_init)
	PHP_FE(share_var_exists, arginfo_share_var_exists)
	PHP_FE(share_var_get, arginfo_share_var_get)
	PHP_FE(share_var_get_and_del, arginfo_share_var_get_and_del)
	PHP_FE(share_var_put, arginfo_share_var_put)
	PHP_FE(share_var_inc, arginfo_share_var_inc)
	PHP_FE(share_var_set, arginfo_share_var_set)
	PHP_FE(share_var_set_ex, arginfo_share_var_set_ex)
	PHP_FE(share_var_del, arginfo_share_var_del)
	PHP_FE(share_var_clean, arginfo_share_var_clean)
	PHP_FE(share_var_clean_ex, arginfo_share_var_clean_ex)
	PHP_FE(share_var_count, arginfo_share_var_count)
	PHP_FE(share_var_destory, arginfo_share_var_destory)

	PHP_FE(ts_var_declare, arginfo_ts_var_declare)
	PHP_FE(ts_var_fd, arginfo_ts_var_fd)
	PHP_FE(ts_var_expire, arginfo_ts_var_expire)
	PHP_FE(ts_var_exists, arginfo_ts_var_exists)
	PHP_FE(ts_var_set, arginfo_ts_var_set)
	PHP_FALIAS(ts_var_put, ts_var_set, arginfo_ts_var_set)
	PHP_FE(ts_var_push, arginfo_ts_var_push)
	PHP_FE(ts_var_pop, arginfo_ts_var_pop)
	PHP_FE(ts_var_shift, arginfo_ts_var_shift)
	PHP_FE(ts_var_minmax, arginfo_ts_var_minmax)
	PHP_FE(ts_var_get, arginfo_ts_var_get)
	PHP_FE(ts_var_get_or_set, arginfo_ts_var_get_or_set)
	PHP_FE(ts_var_lock, arginfo_ts_var_lock)
	PHP_FE(ts_var_unlock, arginfo_ts_var_unlock)
	PHP_FE(ts_var_del, arginfo_ts_var_del)
	PHP_FE(ts_var_inc, arginfo_ts_var_inc)
	PHP_FE(ts_var_count, arginfo_ts_var_count)
	PHP_FE(ts_var_clean, arginfo_ts_var_clean)
	PHP_FE(ts_var_reindex, arginfo_ts_var_reindex)
	PHP_FE(ts_var_keys, arginfo_ts_var_keys)
	PHP_FE(ts_var_expires, arginfo_ts_var_expires)
	
	PHP_FE(socket_export_fd, arginfo_socket_export_fd)
	PHP_FE(socket_import_fd, arginfo_socket_import_fd)
	PHP_FE(socket_accept_ex, arginfo_socket_accept_ex)

#if defined(MYSQLI_USE_MYSQLND) || PHP_VERSION_ID >= 80200
	PHP_FE(mysqli_export_fd, arginfo_mysqli_export_fd)
	PHP_FE(mysqli_stmt_async_execute, arginfo_mysqli_stmt_async_execute)
	PHP_FE(mysqli_stmt_reap_async_query, arginfo_mysqli_stmt_reap_async_query)
#endif
	
	{NULL, NULL, NULL}
};

static const zend_function_entry spl_ce_GoExitException_methods[] = {
	PHP_ME(spl_ce_GoExitException, getStatus, arginfo_spl_ce_GoExitException_getStatus, ZEND_ACC_PUBLIC)
	PHP_FE_END
};

// -----------------------------------------------------------------------------------------------------------

static void php_threadtask_globals_ctor(php_threadtask_globals_struct *php_threadtask_globals) {
	php_threadtask_globals->timestamp = 0;
	php_threadtask_globals->timeout = NULL;
	php_threadtask_globals->is_throw_exit = 0;
}

static void php_threadtask_globals_dtor(php_threadtask_globals_struct *php_threadtask_globals) {
	if(php_threadtask_globals->timeout) {
		timeout_t *t = php_threadtask_globals->timeout;

		pthread_mutex_lock(&tlock);
		if(thead == t) {
			if(t->next == t) {
				thead = NULL;
			} else {
				thead = t->next;
				thead->prev = thead->prev->prev;
				thead->prev->next = thead;
			}
		} else {
			t->prev = t;
			t->next = t;
			thead = t;
		}
		pthread_mutex_unlock(&tlock);
		
		free(t);

		php_threadtask_globals->timeout = NULL;
	}
}

static void php_destroy_threadtask(zend_resource *rsrc) {
	wait_t *ptr = (wait_t *) rsrc->ptr;
	
	sem_destroy(&ptr->sem);
	
	free(ptr);
	
	dprintf("RESOURCE %p destroy(task status)\n", ptr);
}

static void php_destroy_ts_var(zend_resource *rsrc) {
	ts_hash_table_destroy(rsrc->ptr);
	
	dprintf("RESOURCE %p destroy(ts var)\n", rsrc->ptr);
}

#define spl_ce_Exception zend_ce_exception

#if PHP_VERSION_ID >= 80100

#define REGISTER_SPL_SUB_CLASS_EX(class_name, parent_class_name, obj_ctor, funcs) \
	spl_register_sub_class(&spl_ce_ ## class_name, spl_ce_ ## parent_class_name, # class_name, obj_ctor, funcs);

void spl_register_sub_class(zend_class_entry ** ppce, zend_class_entry * parent_ce, char * class_name, void *obj_ctor, const zend_function_entry * function_list) {
	zend_class_entry ce;

	INIT_CLASS_ENTRY_EX(ce, class_name, strlen(class_name), function_list);
	*ppce = zend_register_internal_class_ex(&ce, parent_ce);

	/* entries changed by initialize */
	if (obj_ctor) {
		(*ppce)->create_object = obj_ctor;
	} else {
		(*ppce)->create_object = parent_ce->create_object;
	}
}
#endif

static PHP_MINIT_FUNCTION(threadtask) {
	ts_allocate_id(&php_threadtask_globals_id, sizeof(php_threadtask_globals_struct), (ts_allocate_ctor) php_threadtask_globals_ctor, (ts_allocate_dtor) php_threadtask_globals_dtor);
	le_threadtask_descriptor = zend_register_list_destructors_ex(php_destroy_threadtask, NULL, PHP_THREADTASK_DESCRIPTOR, module_number);
	le_ts_var_descriptor = zend_register_list_destructors_ex(php_destroy_ts_var, NULL, PHP_TS_VAR_DESCRIPTOR, module_number);
	
	REGISTER_SPL_SUB_CLASS_EX(GoExitException, Exception, NULL, spl_ce_GoExitException_methods);
	zend_declare_property_long(spl_ce_GoExitException, ZEND_STRL("status"), 0, ZEND_ACC_PRIVATE);
	zend_set_user_opcode_handler(ZEND_EXIT, go_exit_handler);

	return SUCCESS;
}

static PHP_MSHUTDOWN_FUNCTION(threadtask) {
	return SUCCESS;
}

static PHP_MINFO_FUNCTION(threadtask) {
	php_info_print_table_start();
	php_info_print_table_row(2, "threadtask", "active");
	php_info_print_table_end();

	DISPLAY_INI_ENTRIES();
}

zend_module_entry threadtask_module_entry = {
	STANDARD_MODULE_HEADER,
	"threadtask",
	ext_functions,
	PHP_MINIT(threadtask),
	PHP_MSHUTDOWN(threadtask),
	NULL,
	NULL,
	PHP_MINFO(threadtask),
	PHP_VERSION,
	STANDARD_MODULE_PROPERTIES
};