Skip to content

Commit 635303a

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. (cherry picked from commit 1fc4c89)
1 parent f9fd359 commit 635303a

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
@@ -13,6 +13,9 @@ PHP NEWS
1313
reference). (Dmitry)
1414
. Fixed bug #80968 (JIT segfault with return from required file). (Dmitry)
1515

16+
- MySQLnd:
17+
. Fixed bug #80761 (PDO uses too much memory). (Nikita)
18+
1619
- Standard:
1720
. Fixed bug #81048 (phpinfo(INFO_VARIABLES) "Array to string conversion").
1821
(cmb)

ext/mysqlnd/mysqlnd_wireprotocol.c

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,47 +1391,39 @@ php_mysqlnd_read_row_ex(MYSQLND_PFC * pfc,
13911391
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
13921392
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
13931393
} else {
1394+
/* If the packet is split in multiple chunks, allocate a temporary buffer that we can
1395+
* reallocate, and only afterwards copy it to the pool when we know the final size. */
1396+
zend_uchar *buf = NULL;
1397+
while (header.size >= MYSQLND_MAX_PACKET_SIZE) {
1398+
buf = erealloc(buf, *data_size + header.size);
1399+
p = buf + *data_size;
1400+
*data_size += header.size;
1401+
1402+
if (UNEXPECTED(PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info)))) {
1403+
DBG_ERR("Empty row packet body");
1404+
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
1405+
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
1406+
efree(buf);
1407+
DBG_RETURN(FAIL);
1408+
}
1409+
if (FAIL == mysqlnd_read_header(pfc, vio, &header, stats, error_info)) {
1410+
efree(buf);
1411+
DBG_RETURN(FAIL);
1412+
}
1413+
}
1414+
1415+
buffer->ptr = pool->get_chunk(pool, *data_size + header.size + prealloc_more_bytes);
1416+
if (buf) {
1417+
memcpy(buffer->ptr, buf, *data_size);
1418+
efree(buf);
1419+
}
1420+
p = (zend_uchar *) buffer->ptr + *data_size;
13941421
*data_size += header.size;
1395-
buffer->ptr = pool->get_chunk(pool, *data_size + prealloc_more_bytes);
1396-
p = buffer->ptr;
13971422

13981423
if (UNEXPECTED(PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info)))) {
13991424
DBG_ERR("Empty row packet body");
14001425
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
14011426
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
1402-
} else {
1403-
while (header.size >= MYSQLND_MAX_PACKET_SIZE) {
1404-
if (FAIL == mysqlnd_read_header(pfc, vio, &header, stats, error_info)) {
1405-
ret = FAIL;
1406-
break;
1407-
}
1408-
1409-
*data_size += header.size;
1410-
1411-
/* Empty packet after MYSQLND_MAX_PACKET_SIZE packet. That's ok, break */
1412-
if (!header.size) {
1413-
break;
1414-
}
1415-
1416-
/*
1417-
We have to realloc the buffer.
1418-
*/
1419-
buffer->ptr = pool->resize_chunk(pool, buffer->ptr, *data_size - header.size, *data_size + prealloc_more_bytes);
1420-
if (!buffer->ptr) {
1421-
SET_OOM_ERROR(error_info);
1422-
ret = FAIL;
1423-
break;
1424-
}
1425-
/* The position could have changed, recalculate */
1426-
p = (zend_uchar *) buffer->ptr + (*data_size - header.size);
1427-
1428-
if (PASS != (ret = pfc->data->m.receive(pfc, vio, p, header.size, stats, error_info))) {
1429-
DBG_ERR("Empty row packet body");
1430-
SET_CONNECTION_STATE(connection_state, CONN_QUIT_SENT);
1431-
set_packet_error(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
1432-
break;
1433-
}
1434-
}
14351427
}
14361428
}
14371429
if (ret == FAIL && buffer->ptr) {

0 commit comments

Comments
 (0)