diff options
Diffstat (limited to 'ext/mysqlnd/mysqlnd.c')
-rw-r--r-- | ext/mysqlnd/mysqlnd.c | 368 |
1 files changed, 347 insertions, 21 deletions
diff --git a/ext/mysqlnd/mysqlnd.c b/ext/mysqlnd/mysqlnd.c index 5e32d7fda7..6e7441dcea 100644 --- a/ext/mysqlnd/mysqlnd.c +++ b/ext/mysqlnd/mysqlnd.c @@ -66,12 +66,205 @@ const char * mysqlnd_out_of_sync = "Commands out of sync; you can't run this com MYSQLND_STATS *mysqlnd_global_stats = NULL; static zend_bool mysqlnd_library_initted = FALSE; +MYSQLND_MEMORY_POOL mysqlnd_memory_pool; static enum_func_status mysqlnd_send_close(MYSQLND * conn TSRMLS_DC); +#define MYSQLND_SILENT 1 + +#ifdef MYSQLND_THREADED +/* {{{ _mysqlnd_fetch_thread */ +void * _mysqlnd_fetch_thread(void *arg) +{ + MYSQLND *conn = (MYSQLND *) arg; + MYSQLND_RES * result = NULL; + void ***tsrm_ls = conn->tsrm_ls; +#ifndef MYSQLND_SILENT + printf("conn=%p tsrm_ls=%p\n", conn, conn->tsrm_ls); +#endif + do { + pthread_mutex_lock(&conn->LOCK_work); + while (conn->thread_killed == FALSE /* && there is work */) { +#ifndef MYSQLND_SILENT + printf("Waiting for work in %s\n", __FUNCTION__); +#endif + pthread_cond_wait(&conn->COND_work, &conn->LOCK_work); + } + if (conn->thread_killed == TRUE) { +#ifndef MYSQLND_SILENT + printf("Thread killed in %s\n", __FUNCTION__); +#endif + pthread_cond_signal(&conn->COND_thread_ended); + pthread_mutex_unlock(&conn->LOCK_work); + break; + } +#ifndef MYSQLND_SILENT + printf("Got work in %s\n", __FUNCTION__); +#endif + CONN_SET_STATE(conn, CONN_FETCHING_DATA); + result = conn->current_result; + conn->current_result = NULL; + pthread_mutex_unlock(&conn->LOCK_work); + + mysqlnd_background_store_result_fetch_data(result TSRMLS_CC); + + /* do fetch the data from the wire */ + + pthread_mutex_lock(&conn->LOCK_work); + CONN_SET_STATE(conn, CONN_READY); + pthread_cond_signal(&conn->COND_work_done); +#ifndef MYSQLND_SILENT + printf("Signaling work done in %s\n", __FUNCTION__); +#endif + pthread_mutex_unlock(&conn->LOCK_work); + } while (1); + +#ifndef MYSQLND_SILENT + printf("Exiting worker thread in %s\n", __FUNCTION__); +#endif + return NULL; +} +/* }}} */ +#endif /* MYSQLND_THREADED */ + +/************************************************************************************************/ +/* Let's don't use pool allocation for now */ +/* {{{ mysqlnd_mempool_free_chunk */ +static +void mysqlnd_mempool_free_contents(MYSQLND_MEMORY_POOL * pool TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_mempool_dtor"); + uint i; + for (i = 0; i < pool->free_chunk_list_elements; i++) { + MYSQLND_MEMORY_POOL_CHUNK * chunk = pool->free_chunk_list[i]; + chunk->free_chunk(chunk, FALSE TSRMLS_CC); + } + + DBG_VOID_RETURN; +} +/* }}} */ + +/* Let's don't use pool allocation for now */ +/* {{{ mysqlnd_mempool_free_chunk */ +static +void mysqlnd_mempool_free_chunk(MYSQLND_MEMORY_POOL_CHUNK * chunk, zend_bool cache_it TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_mempool_free_chunk"); + MYSQLND_MEMORY_POOL * pool = chunk->pool; + if (chunk->from_pool) { + /* Try to back-off and guess if this is the last block allocated */ + if (chunk->ptr == (pool->arena + (pool->arena_size - pool->free_size - chunk->size))) { + /* + This was the last allocation. Lucky us, we can free + a bit of memory from the pool. Next time we will return from the same ptr. + */ + pool->free_size += chunk->size; + } + pool->refcount--; + } else { + mnd_free(chunk->ptr); + } + if (cache_it && pool->free_chunk_list_elements < MYSQLND_MEMORY_POOL_CHUNK_LIST_SIZE) { + chunk->ptr = NULL; + pool->free_chunk_list[pool->free_chunk_list_elements++] = chunk; + } else { + /* We did not cache it -> free it */ + mnd_free(chunk); + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_mempool_resize_chunk */ +static void +mysqlnd_mempool_resize_chunk(MYSQLND_MEMORY_POOL_CHUNK * chunk, uint size TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_mempool_resize_chunk"); + if (chunk->from_pool) { + MYSQLND_MEMORY_POOL * pool = chunk->pool; + /* Try to back-off and guess if this is the last block allocated */ + if (chunk->ptr == (pool->arena + (pool->arena_size - pool->free_size - chunk->size))) { + /* + This was the last allocation. Lucky us, we can free + a bit of memory from the pool. Next time we will return from the same ptr. + */ + if ((chunk->size + pool->free_size) < size) { + zend_uchar *new_ptr; + new_ptr = mnd_malloc(size); + memcpy(new_ptr, chunk->ptr, chunk->size); + chunk->ptr = new_ptr; + pool->free_size += chunk->size; + chunk->size = size; + chunk->pool = NULL; /* now we have no pool memory */ + pool->refcount--; + } else { + /* If the chunk is > than asked size then free_memory increases, otherwise decreases*/ + pool->free_size += (chunk->size - size); + } + } else { + /* Not last chunk, if the user asks for less, give it to him */ + if (chunk->size >= size) { + ; /* nop */ + } else { + zend_uchar *new_ptr; + new_ptr = mnd_malloc(size); + memcpy(new_ptr, chunk->ptr, chunk->size); + chunk->ptr = new_ptr; + chunk->size = size; + chunk->pool = NULL; /* now we have no pool memory */ + pool->refcount--; + } + } + } else { + chunk->ptr = mnd_realloc(chunk->ptr, size); + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_mempool_get_chunk */ +static +MYSQLND_MEMORY_POOL_CHUNK * mysqlnd_mempool_get_chunk(MYSQLND_MEMORY_POOL * pool, uint size TSRMLS_DC) +{ + MYSQLND_MEMORY_POOL_CHUNK *chunk = NULL; + DBG_ENTER("mysqlnd_mempool_get_chunk"); + + if (pool->free_chunk_list_elements) { + chunk = pool->free_chunk_list[--pool->free_chunk_list_elements]; + } else { + chunk = mnd_malloc(sizeof(MYSQLND_MEMORY_POOL_CHUNK)); + } + + chunk->free_chunk = mysqlnd_mempool_free_chunk; + chunk->resize_chunk = mysqlnd_mempool_resize_chunk; + chunk->size = size; + /* + Should not go over MYSQLND_MAX_PACKET_SIZE, since we + expect non-arena memory in mysqlnd_wireprotocol.c . We + realloc the non-arena memory. + */ + chunk->pool = pool; + if (size > pool->free_size) { + chunk->ptr = mnd_malloc(size); + chunk->from_pool = FALSE; + } else { + chunk->from_pool = TRUE; + ++pool->refcount; + chunk->ptr = pool->arena + (pool->arena_size - pool->free_size); + /* Last step, update free_size */ + pool->free_size -= size; + } + DBG_RETURN(chunk); +} +/* }}} */ +/************************************************************************************************/ + + /* {{{ mysqlnd_library_init */ static -void mysqlnd_library_init() +void mysqlnd_library_init(TSRMLS_D) { if (mysqlnd_library_initted == FALSE) { mysqlnd_library_initted = TRUE; @@ -81,6 +274,13 @@ void mysqlnd_library_init() #ifdef ZTS mysqlnd_global_stats->LOCK_access = tsrm_mutex_alloc(); #endif + mysqlnd_memory_pool.arena_size = 16000; + mysqlnd_memory_pool.free_size = mysqlnd_memory_pool.arena_size; + mysqlnd_memory_pool.refcount = 0; + /* OOM ? */ + mysqlnd_memory_pool.arena = mnd_malloc(mysqlnd_memory_pool.arena_size); + mysqlnd_memory_pool.get_chunk = mysqlnd_mempool_get_chunk; + mysqlnd_memory_pool.free_contents = mysqlnd_mempool_free_contents; } } /* }}} */ @@ -88,9 +288,12 @@ void mysqlnd_library_init() /* {{{ mysqlnd_library_end */ static -void mysqlnd_library_end() +void mysqlnd_library_end(TSRMLS_D) { if (mysqlnd_library_initted == TRUE) { + /* mnd_free will reference LOCK_access and won't crash...*/ + mysqlnd_memory_pool.free_contents(&mysqlnd_memory_pool TSRMLS_CC); + free(mysqlnd_memory_pool.arena); #ifdef ZTS tsrm_mutex_free(mysqlnd_global_stats->LOCK_access); #endif @@ -229,6 +432,7 @@ MYSQLND_METHOD(mysqlnd_conn, free_contents)(MYSQLND *conn TSRMLS_DC) mnd_pefree(conn->net.cmd_buffer.buffer, pers); conn->net.cmd_buffer.buffer = NULL; } + conn->charset = NULL; conn->greet_charset = NULL; @@ -246,6 +450,22 @@ MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor)(MYSQLND *conn TSRMLS_DC) conn->m->free_contents(conn TSRMLS_CC); +#ifdef MYSQLND_THREADED + if (conn->thread_is_running) { + pthread_mutex_lock(&conn->LOCK_work); + conn->thread_killed = TRUE; + pthread_cond_signal(&conn->COND_work); + pthread_cond_wait(&conn->COND_thread_ended, &conn->LOCK_work); + pthread_mutex_unlock(&conn->LOCK_work); + } + + tsrm_mutex_free(conn->LOCK_state); + + pthread_cond_destroy(&conn->COND_work); + pthread_cond_destroy(&conn->COND_work_done); + pthread_mutex_destroy(&conn->LOCK_work); +#endif + mnd_pefree(conn, conn->persistent); DBG_VOID_RETURN; @@ -363,7 +583,7 @@ mysqlnd_simple_command(MYSQLND *conn, enum php_mysqlnd_server_command command, DBG_ENTER("mysqlnd_simple_command"); DBG_INF_FMT("command=%s ok_packet=%d silent=%d", mysqlnd_command_to_text[command], ok_packet, silent); - switch (conn->state) { + switch (CONN_GET_STATE(conn)) { case CONN_READY: break; case CONN_QUIT_SENT: @@ -481,13 +701,13 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn, DBG_ENTER("mysqlnd_connect"); DBG_INF_FMT("host=%s user=%s db=%s port=%d flags=%d persistent=%d state=%d", host?host:"", user?user:"", db?db:"", port, mysql_flags, - conn? conn->persistent:0, conn? conn->state:-1); + conn? conn->persistent:0, conn? CONN_GET_STATE(conn):-1); - DBG_INF_FMT("state=%d", conn->state); - if (conn && conn->state > CONN_ALLOCED && conn->state ) { + DBG_INF_FMT("state=%d", CONN_GET_STATE(conn)); + if (conn && CONN_GET_STATE(conn) > CONN_ALLOCED && CONN_GET_STATE(conn) ) { DBG_INF("Connecting on a connected handle."); - if (conn->state < CONN_QUIT_SENT) { + if (CONN_GET_STATE(conn) < CONN_QUIT_SENT) { MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CLOSE_IMPLICIT); reconnect = TRUE; mysqlnd_send_close(conn TSRMLS_CC); @@ -551,7 +771,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn, self_alloced = TRUE; } - conn->state = CONN_ALLOCED; + CONN_SET_STATE(conn, CONN_ALLOCED); conn->net.packet_no = 0; if (conn->options.timeout_connect) { @@ -663,7 +883,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn, conn->scramble = auth_packet->server_scramble_buf = mnd_pemalloc(SCRAMBLE_LENGTH, conn->persistent); memcpy(auth_packet->server_scramble_buf, greet_packet.scramble_buf, SCRAMBLE_LENGTH); if (!PACKET_WRITE(auth_packet, conn)) { - conn->state = CONN_QUIT_SENT; + CONN_SET_STATE(conn, CONN_QUIT_SENT); SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); goto err; } @@ -687,7 +907,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn, } } } else { - conn->state = CONN_READY; + CONN_SET_STATE(conn, CONN_READY); conn->user = pestrdup(user, conn->persistent); conn->passwd = pestrndup(passwd, passwd_len, conn->persistent); @@ -759,6 +979,23 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn, DBG_INF("unicode set"); } #endif +#ifdef MYSQLND_THREADED + { + pthread_t th; + pthread_attr_t connection_attrib; + conn->tsrm_ls = tsrm_ls; + + pthread_attr_init(&connection_attrib); + pthread_attr_setdetachstate(&connection_attrib, PTHREAD_CREATE_DETACHED); + + conn->thread_is_running = TRUE; + if (pthread_create(&th, &connection_attrib, _mysqlnd_fetch_thread, (void*)conn)) { + conn->thread_is_running = FALSE; + } + } +#endif + + DBG_RETURN(conn); } err: @@ -1081,7 +1318,7 @@ MYSQLND_METHOD(mysqlnd_conn, kill)(MYSQLND *conn, unsigned int pid TSRMLS_DC) SET_ERROR_AFF_ROWS(conn); } else if (PASS == (ret = mysqlnd_simple_command(conn, COM_PROCESS_KILL, buff, 4, PROT_LAST, FALSE TSRMLS_CC))) { - conn->state = CONN_QUIT_SENT; + CONN_SET_STATE(conn, CONN_QUIT_SENT); } DBG_RETURN(ret); } @@ -1154,7 +1391,7 @@ MYSQLND_METHOD(mysqlnd_conn, shutdown)(MYSQLND * const conn, unsigned long level /* {{{ mysqlnd_send_close */ -enum_func_status +static enum_func_status mysqlnd_send_close(MYSQLND * conn TSRMLS_DC) { enum_func_status ret = PASS; @@ -1163,7 +1400,7 @@ mysqlnd_send_close(MYSQLND * conn TSRMLS_DC) DBG_INF_FMT("conn=%llu conn->net.stream->abstract=%p", conn->thread_id, conn->net.stream? conn->net.stream->abstract:NULL); - switch (conn->state) { + switch (CONN_GET_STATE(conn)) { case CONN_READY: DBG_INF("Connection clean, sending COM_QUIT"); ret = mysqlnd_simple_command(conn, COM_QUIT, NULL, 0, PROT_LAST, @@ -1199,7 +1436,7 @@ mysqlnd_send_close(MYSQLND * conn TSRMLS_DC) We hold one reference, and every other object which needs the connection does increase it by 1. */ - conn->state = CONN_QUIT_SENT; + CONN_SET_STATE(conn, CONN_QUIT_SENT); DBG_RETURN(ret); } @@ -1236,7 +1473,6 @@ MYSQLND_METHOD(mysqlnd_conn, close)(MYSQLND * conn, enum_connection_close_type c ret = conn->m->free_reference(conn TSRMLS_CC); - DBG_RETURN(ret); } /* }}} */ @@ -1273,6 +1509,46 @@ MYSQLND_METHOD_PRIVATE(mysqlnd_conn, free_reference)(MYSQLND * const conn TSRMLS /* }}} */ +/* {{{ mysqlnd_conn::get_state */ +#ifdef MYSQLND_THREADED +static enum mysqlnd_connection_state +MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_state)(MYSQLND * const conn TSRMLS_DC) +{ + enum mysqlnd_connection_state state; + DBG_ENTER("mysqlnd_conn::get_state"); + tsrm_mutex_lock(conn->LOCK_state); + state = conn->state; + tsrm_mutex_unlock(conn->LOCK_state); + DBG_RETURN(state); +} +#else +static enum mysqlnd_connection_state +MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_state)(MYSQLND * const conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn::get_state"); + DBG_RETURN(conn->state); +} +#endif +/* }}} */ + + +/* {{{ mysqlnd_conn::set_state */ +static void +MYSQLND_METHOD_PRIVATE(mysqlnd_conn, set_state)(MYSQLND * const conn, enum mysqlnd_connection_state new_state TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn::set_state"); +#ifdef MYSQLND_THREADED + tsrm_mutex_lock(conn->LOCK_state); +#endif + conn->state = new_state; +#ifdef MYSQLND_THREADED + tsrm_mutex_unlock(conn->LOCK_state); +#endif + DBG_VOID_RETURN; +} +/* }}} */ + + /* {{{ mysqlnd_conn::field_count */ static unsigned int MYSQLND_METHOD(mysqlnd_conn, field_count)(const MYSQLND * const conn) @@ -1420,7 +1696,7 @@ MYSQLND_METHOD(mysqlnd_conn, next_result)(MYSQLND * const conn TSRMLS_DC) DBG_ENTER("mysqlnd_conn::next_result"); DBG_INF_FMT("conn=%llu", conn->thread_id); - if (conn->state != CONN_NEXT_RESULT_PENDING) { + if (CONN_GET_STATE(conn) != CONN_NEXT_RESULT_PENDING) { DBG_RETURN(FAIL); } @@ -1433,7 +1709,7 @@ MYSQLND_METHOD(mysqlnd_conn, next_result)(MYSQLND * const conn TSRMLS_DC) if (FAIL == (ret = mysqlnd_query_read_result_set_header(conn, NULL TSRMLS_CC))) { DBG_ERR_FMT("Serious error. %s::%d", __FILE__, __LINE__); php_error_docref(NULL TSRMLS_CC, E_WARNING, "Serious error. PID=%d", getpid()); - conn->state = CONN_QUIT_SENT; + CONN_SET_STATE(conn, CONN_QUIT_SENT); } DBG_RETURN(ret); @@ -1710,7 +1986,7 @@ MYSQLND_METHOD(mysqlnd_conn, use_result)(MYSQLND * const conn TSRMLS_DC) } /* Nothing to store for UPSERT/LOAD DATA */ - if (conn->last_query_type != QUERY_SELECT || conn->state != CONN_FETCHING_DATA) { + if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != CONN_FETCHING_DATA) { SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); DBG_ERR("Command out of sync"); @@ -1743,7 +2019,7 @@ MYSQLND_METHOD(mysqlnd_conn, store_result)(MYSQLND * const conn TSRMLS_DC) } /* Nothing to store for UPSERT/LOAD DATA*/ - if (conn->last_query_type != QUERY_SELECT || conn->state != CONN_FETCHING_DATA) { + if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != CONN_FETCHING_DATA) { SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); DBG_ERR("Command out of sync"); @@ -1761,6 +2037,44 @@ MYSQLND_METHOD(mysqlnd_conn, store_result)(MYSQLND * const conn TSRMLS_DC) /* }}} */ +/* {{{ mysqlnd_conn::background_store_result */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn, background_store_result)(MYSQLND * const conn TSRMLS_DC) +{ + MYSQLND_RES *result; + + DBG_ENTER("mysqlnd_conn::store_result"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (!conn->current_result) { + DBG_RETURN(NULL); + } + + /* Nothing to store for UPSERT/LOAD DATA*/ + if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != CONN_FETCHING_DATA) { + SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, + mysqlnd_out_of_sync); + DBG_ERR("Command out of sync"); + DBG_RETURN(NULL); + } + + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_BUFFERED_SETS); + + result = conn->current_result; + + result = result->m.background_store_result(result, conn, FALSE TSRMLS_CC); + + /* + Should be here, because current_result is used by the fetching thread to get data info + The thread is contacted in mysqlnd_res::background_store_result(). + */ + conn->current_result = NULL; + + DBG_RETURN(result); +} +/* }}} */ + + /* {{{ mysqlnd_conn::get_connection_stats */ static void MYSQLND_METHOD(mysqlnd_conn, get_connection_stats)(const MYSQLND * const conn, @@ -1784,6 +2098,7 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_conn) MYSQLND_METHOD(mysqlnd_conn, query), MYSQLND_METHOD(mysqlnd_conn, use_result), MYSQLND_METHOD(mysqlnd_conn, store_result), + MYSQLND_METHOD(mysqlnd_conn, background_store_result), MYSQLND_METHOD(mysqlnd_conn, next_result), MYSQLND_METHOD(mysqlnd_conn, more_results), @@ -1829,6 +2144,8 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_conn) MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_reference), MYSQLND_METHOD_PRIVATE(mysqlnd_conn, free_reference), + MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_state), + MYSQLND_METHOD_PRIVATE(mysqlnd_conn, set_state), MYSQLND_CLASS_METHODS_END; @@ -1846,6 +2163,15 @@ PHPAPI MYSQLND *_mysqlnd_init(zend_bool persistent TSRMLS_DC) ret->m = & mysqlnd_mysqlnd_conn_methods; ret->m->get_reference(ret); +#ifdef MYSQLND_THREADED + ret->LOCK_state = tsrm_mutex_alloc(); + + pthread_mutex_init(&ret->LOCK_work, NULL); + pthread_cond_init(&ret->COND_work, NULL); + pthread_cond_init(&ret->COND_work_done, NULL); + pthread_cond_init(&ret->COND_thread_ended, NULL); +#endif + DBG_RETURN(ret); } /* }}} */ @@ -1985,7 +2311,7 @@ static PHP_MINIT_FUNCTION(mysqlnd) { REGISTER_INI_ENTRIES(); - mysqlnd_library_init(); + mysqlnd_library_init(TSRMLS_C); return SUCCESS; } /* }}} */ @@ -1995,7 +2321,7 @@ static PHP_MINIT_FUNCTION(mysqlnd) */ static PHP_MSHUTDOWN_FUNCTION(mysqlnd) { - mysqlnd_library_end(); + mysqlnd_library_end(TSRMLS_C); UNREGISTER_INI_ENTRIES(); return SUCCESS; |