Fix for CONPY-108 (memory leak):

- initialize datetime API only once per object file
- don't reparse same statement
This commit is contained in:
Georg Richter
2020-08-29 12:20:52 +02:00
parent 3c264b2d2a
commit bd560c2bb9
4 changed files with 109 additions and 105 deletions

View File

@ -17,6 +17,7 @@
51 Franklin St., Fifth Floor, Boston, MA 02110, USA
******************************************************************************/
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#include "bytesobject.h"
#include "structmember.h"
@ -29,7 +30,6 @@
#include <time.h>
#include <docs/common.h>
#if defined(_WIN32)
#include <windows.h>
typedef CRITICAL_SECTION pthread_mutex_t;
@ -62,6 +62,8 @@ int clock_gettime(int dummy, struct timespec *ct);
#define CLOCK_MONOTONIC_RAW 1
#endif
int codecs_datetime_init();
#define REQUIRED_CC_VERSION 30103

View File

@ -106,6 +106,17 @@ mariadb_module= {
Mariadb_Methods
};
static int mariadb_datetime_init()
{
PyDateTime_IMPORT;
if (!PyDateTimeAPI) {
PyErr_SetString(PyExc_ImportError, "DateTimeAPI initialization failed");
return 1;
}
return 0;
}
static void mariadb_add_exception(PyObject *module,
PyObject **exception,
const char *exception_name,
@ -132,6 +143,13 @@ PyMODINIT_FUNC PyInit__mariadb(void)
pre_release= PY_MARIADB_PRE_RELEASE_SEGMENT;
#endif
/* Initialite DateTimeAPI */
if (mariadb_datetime_init() ||
codecs_datetime_init())
{
goto error;
}
Py_TYPE(&MrdbConnection_Type) = &PyType_Type;
if (PyType_Ready(&MrdbConnection_Type) == -1)
{
@ -304,11 +322,6 @@ Mariadb_date_from_ticks(PyObject *module, PyObject *args)
struct tm *ts;
time_t epoch;
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (!PyArg_ParseTuple(args, "O", &o))
{
return NULL;
@ -328,11 +341,6 @@ Mariadb_time_from_ticks(PyObject *module, PyObject *args)
time_t epoch;
PyObject *o, *Time= NULL;
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (!PyArg_ParseTuple(args, "O", &o))
{
return NULL;
@ -352,11 +360,6 @@ Mariadb_timestamp_from_ticks(PyObject *module, PyObject *args)
struct tm *ts;
time_t epoch;
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (!PyArg_ParseTuple(args, "O", &o))
{
return NULL;
@ -377,11 +380,6 @@ Mariadb_date(PyObject *self, PyObject *args)
PyObject *date= NULL;
int32_t year=0, month=0, day= 0;
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (!PyArg_ParseTuple(args, "iii", &year, &month, &day))
{
return NULL;
@ -398,11 +396,6 @@ Mariadb_timestamp(PyObject *self, PyObject *args)
int32_t year=0, month=0, day= 0;
int32_t hour=0, min=0, sec= 0;
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (!PyArg_ParseTuple(args, "iiiiii", &year, &month, &day,
&hour, &min, &sec))
{
@ -419,10 +412,6 @@ Mariadb_time(PyObject *self, PyObject *args)
{
PyObject *time= NULL;
int32_t hour=0, min=0, sec= 0;
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (!PyArg_ParseTuple(args, "iii", &hour, &min, &sec))
{

View File

@ -28,6 +28,18 @@
#define IS_DECIMAL_TYPE(type) \
((type) == MYSQL_TYPE_NEWDECIMAL || (type) == MYSQL_TYPE_DOUBLE || (type) == MYSQL_TYPE_FLOAT)
int codecs_datetime_init()
{
PyDateTime_IMPORT;
if (!PyDateTimeAPI) {
PyErr_SetString(PyExc_ImportError, "DateTimeAPI initialization failed");
return 1;
}
return 0;
}
/*
converts a Python date/time/datetime object to MYSQL_TIME
*/
@ -36,10 +48,6 @@ mariadb_pydate_to_tm(enum enum_field_types type,
PyObject *obj,
MYSQL_TIME *tm)
{
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
memset(tm, 0, sizeof(MYSQL_TIME));
if (type == MYSQL_TYPE_TIME ||
type == MYSQL_TYPE_DATETIME)
@ -387,11 +395,6 @@ field_fetch_fromtext(MrdbCursor *self, char *data, unsigned int column)
MYSQL_TIME tm;
unsigned long *length;
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (!data)
{
Py_INCREF(Py_None);
@ -540,10 +543,6 @@ field_fetch_callback(void *data, unsigned int column, unsigned char **row)
MrdbCursor *self= (MrdbCursor *)data;
MARIADB_UNBLOCK_THREADS(self);
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (!row)
{
@ -771,11 +770,6 @@ end:
static uint8_t
mariadb_get_column_info(PyObject *obj, MrdbParamInfo *paraminfo)
{
if (!PyDateTimeAPI)
{
PyDateTime_IMPORT;
}
if (obj == NULL)
{
paraminfo->type= MYSQL_TYPE_NULL;
@ -880,14 +874,14 @@ mariadb_get_parameter(MrdbCursor *self,
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
"Can't access data at row %d, column %d",
row_nr + 1, column_nr + 1);
return 1;
goto end;
}
if (!(row= ListOrTuple_GetItem(self->data, row_nr)))
{
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
"Can't access row number %d", row_nr + 1);
return 1;
goto end;
}
}
else
@ -1191,7 +1185,7 @@ mariadb_check_execute_parameters(MrdbCursor *self,
{
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
"Invalid number of parameters");
return 1;
goto error;
}
if (!self->params &&

View File

@ -398,6 +398,39 @@ static PyObject *Mariadb_no_operation(MrdbCursor *self,
}
/* }}} */
/* {{{ MrdbCursor_clear_result(MrdbCursor *self)
clear pending result sets
*/
static void MrdbCursor_clear_result(MrdbCursor *self)
{
if (!self->is_text &&
self->stmt)
{
/* free current result */
mysql_stmt_free_result(self->stmt);
/* check if there are more pending result sets */
while (mysql_stmt_next_result(self->stmt) == 0)
{
mysql_stmt_free_result(self->stmt);
}
} else if (self->is_text)
{
/* free current result */
if (self->result)
{
mysql_free_result(self->result);
}
/* clear pending result sets */
if (self->connection->mysql)
{
while (!mysql_next_result(self->connection->mysql));
}
}
/* CONPY-52: Avoid possible double free */
self->result= NULL;
}
/* {{{ MrdbCursor_clear
Resets statement attributes and frees
associated memory
@ -405,16 +438,10 @@ static PyObject *Mariadb_no_operation(MrdbCursor *self,
static
void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
{
/* clear pending result sets */
MrdbCursor_clear_result(self);
if (!self->is_text && self->stmt) {
mysql_stmt_free_result(self->stmt);
/* CONPY-52: avoid possible double free */
self->result= NULL;
while (!mysql_stmt_next_result(self->stmt))
mysql_stmt_free_result(self->stmt);
if (new_stmt)
{
mysql_stmt_close(self->stmt);
@ -423,29 +450,15 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
else {
uint32_t val= 0;
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, 0);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_PARAM, 0);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_RESULT, 0);
mysql_stmt_reset(self->stmt);
/* we need to unset array size only */
mysql_stmt_attr_set(self->stmt, STMT_ATTR_ARRAY_SIZE, &val);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &val);
}
}
self->fetched= 0;
if (self->is_text)
{
if (self->result)
{
mysql_free_result(self->result);
self->result= 0;
self->is_text= 0;
}
/* clear also pending result sets */
if (self->connection->mysql)
while (!mysql_next_result(self->connection->mysql));
}
MARIADB_FREE_MEM(self->sequence_fields);
self->fields= NULL;
self->row_count= 0;
@ -456,7 +469,6 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
MARIADB_FREE_MEM(self->statement);
MARIADB_FREE_MEM(self->value);
MARIADB_FREE_MEM(self->params);
}
/* }}} */
@ -614,6 +626,30 @@ static Py_ssize_t data_count(PyObject *data)
return 0;
}
static int Mrdb_execute_direct(MrdbCursor *self)
{
int rc;
MARIADB_BEGIN_ALLOW_THREADS(self);
/* execute_direct was implemented together with bulk operations, so we need
to check if MARIADB_CLIENT_STMT_BULK_OPERATIONS is set in extended server
capabilities */
if (!(self->connection->extended_server_capabilities &
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
{
if (!(rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str,
(unsigned long)self->parser->statement.length)))
{
rc= mysql_stmt_execute(self->stmt);
}
} else {
rc= mariadb_stmt_execute_direct(self->stmt, self->parser->statement.str,
self->parser->statement.length);
}
MARIADB_END_ALLOW_THREADS(self);
return rc;
}
/* {{{ MrdbCursor_execute
PEP-249 execute() method
*/
@ -671,18 +707,20 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
{
uint8_t do_prepare= 1;
if (self->is_prepared && self->statement)
if ((self->is_prepared && self->statement))
do_prepare= 0;
/* if cursor type is not prepared, we need to clear the cursor first */
if (!self->is_prepared && self->statement)
{
uint8_t new_stmt= 1;
if (!strcmp(self->statement, statement))
new_stmt= 0;
MrdbCursor_clear(self, new_stmt);
MrdbParser_end(self->parser);
self->parser= NULL;
{
do_prepare= 0;
} else {
MrdbCursor_clear(self, 1);
MrdbParser_end(self->parser);
self->parser= NULL;
}
}
self->is_text= 0;
@ -729,23 +767,8 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
mysql_stmt_bind_param(self->stmt, self->params);
MARIADB_BEGIN_ALLOW_THREADS(self);
/* execute_direct was implemented together with bulk operations, so we need
to check if MARIADB_CLIENT_STMT_BULK_OPERATIONS is set in extended server
capabilities */
if ((self->connection->extended_server_capabilities &
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
{
rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str,
(unsigned long)self->parser->statement.length);
if (!rc)
rc= mysql_stmt_execute(self->stmt);
}
else
rc= mariadb_stmt_execute_direct(self->stmt, self->parser->statement.str,
self->parser->statement.length);
MARIADB_END_ALLOW_THREADS(self);
rc= Mrdb_execute_direct(self);
if (rc)
{
/* in case statement is not supported via binary protocol, we try
@ -1355,11 +1378,7 @@ MrdbCursor_executemany(MrdbCursor *self,
mysql_stmt_bind_param(self->stmt, self->params);
MARIADB_BEGIN_ALLOW_THREADS(self);
rc= mariadb_stmt_execute_direct(self->stmt, self->parser->statement.str,
(unsigned long)self->parser->statement.length);
MARIADB_END_ALLOW_THREADS(self);
if (rc)
if (Mrdb_execute_direct(self))
{
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
goto error;