Skip to content

Commit 1fc4c89

Browse files
committed
Fixed bug #80761
When row data split across multiple packets, allocate a temporary buffer that can be reallocated, and only copy into the row buffer pool arena once we know the final size. This avoids quadratic memory usage for very large results.
1 parent 8be711b commit 1fc4c89

File tree

2 files changed

+30
-35
lines changed

2 files changed

+30
-35
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ PHP NEWS
2727
. Fixed bug #80329 (Add option to specify LOAD DATA LOCAL white list folder
2828
(including libmysql)). (Darek Ślusarczyk)
2929

30+
- MySQLnd:
31+
. Fixed bug #80761 (PDO uses too much memory). (Nikita)
32+
3033
- Opcache:
3134
. Added inheritance cache. (Dmitry)
3235

ext/mysqlnd/mysqlnd_wireprotocol.c

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,47 +1381,39 @@ php_mysqlnd_read_row_ex(MYSQLND_PFC * pfc,
13811381
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
13821382
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
13831383
} else {
1384+
/* If the packet is split in multiple chunks, allocate a temporary buffer that we can
1385+
* reallocate, and only afterwards copy it to the pool when we know the final size. */
1386+
zend_uchar *buf = NULL;
1387+
while (header.size >= MYSQLND_MAX_PACKET_SIZE) {
1388+
buf = erealloc(buf, *data_size + header.size);
1389+
p = buf + *data_size;
1390+
*data_size += header.size;
1391+
1392+
if (UNEXPECTED(PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info)))) {
1393+
DBG_ERR("Empty row packet body");
1394+
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
1395+
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
1396+
efree(buf);
1397+
DBG_RETURN(FAIL);
1398+
}
1399+
if (FAIL == mysqlnd_read_header(pfc, vio, &header, stats, error_info)) {
1400+
efree(buf);
1401+
DBG_RETURN(FAIL);
1402+
}
1403+
}
1404+
1405+
buffer->ptr = pool->get_chunk(pool, *data_size + header.size + prealloc_more_bytes);
1406+
if (buf) {
1407+
memcpy(buffer->ptr, buf, *data_size);
1408+
efree(buf);
1409+
}
1410+
p = buffer->ptr + *data_size;
13841411
*data_size += header.size;
1385-
buffer->ptr = pool->get_chunk(pool, *data_size + prealloc_more_bytes);
1386-
p = buffer->ptr;
13871412

13881413
if (UNEXPECTED(PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info)))) {
13891414
DBG_ERR("Empty row packet body");
13901415
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
13911416
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
1392-
} else {
1393-
while (header.size >= MYSQLND_MAX_PACKET_SIZE) {
1394-
if (FAIL == mysqlnd_read_header(pfc, vio, &header, stats, error_info)) {
1395-
ret = FAIL;
1396-
break;
1397-
}
1398-
1399-
*data_size += header.size;
1400-
1401-
/* Empty packet after MYSQLND_MAX_PACKET_SIZE packet. That's ok, break */
1402-
if (!header.size) {
1403-
break;
1404-
}
1405-
1406-
/*
1407-
We have to realloc the buffer.
1408-
*/
1409-
buffer->ptr = pool->resize_chunk(pool, buffer->ptr, *data_size - header.size, *data_size + prealloc_more_bytes);
1410-
if (!buffer->ptr) {
1411-
SET_OOM_ERROR(error_info);
1412-
ret = FAIL;
1413-
break;
1414-
}
1415-
/* The position could have changed, recalculate */
1416-
p = (zend_uchar *) buffer->ptr + (*data_size - header.size);
1417-
1418-
if (PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info))) {
1419-
DBG_ERR("Empty row packet body");
1420-
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
1421-
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
1422-
break;
1423-
}
1424-
}
14251417
}
14261418
}
14271419
if (ret == FAIL && buffer->ptr) {

0 commit comments

Comments
 (0)