Skip to content

Commit f0bbd08

Browse files
committed
CONPY-302: fix segfault with threaded
Callback functions for session or fetch callbacks didn't work properly when passing connection->thread_state in threaded environment. Instead we acquire and release the GIL explicitly in these callback functions.
1 parent 40690c4 commit f0bbd08

File tree

4 files changed

+53
-78
lines changed

4 files changed

+53
-78
lines changed

include/mariadb_python.h

-31
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ typedef struct st_parser {
241241
/* PEP-249: Connection object */
242242
typedef struct {
243243
PyObject_HEAD
244-
PyThreadState *thread_state;
245244
MYSQL *mysql;
246245
int open;
247246
uint8_t is_buffered;
@@ -808,33 +807,3 @@ MrdbParser_parse(MrdbParser *p, uint8_t is_batch, char *errmsg, size_t errmsg_le
808807

809808
#endif /* __i386__ OR _WIN32 */
810809

811-
/* Due to callback functions we cannot use PY_BEGIN/END_ALLOW_THREADS */
812-
813-
#define MARIADB_BEGIN_ALLOW_THREADS(obj)\
814-
{\
815-
(obj)->thread_state= PyEval_SaveThread();\
816-
}
817-
818-
#define MARIADB_END_ALLOW_THREADS(obj)\
819-
if ((obj)->thread_state)\
820-
{\
821-
PyEval_RestoreThread((obj)->thread_state);\
822-
(obj)->thread_state= NULL;\
823-
}
824-
825-
#define MARIADB_UNBLOCK_THREADS(obj)\
826-
{\
827-
if ((obj)->thread_state)\
828-
{\
829-
_save= (obj)->thread_state;\
830-
PyEval_RestoreThread(_save);\
831-
(obj)->thread_state= NULL;\
832-
}\
833-
}
834-
835-
#define MARIADB_BLOCK_THREADS(obj)\
836-
if (_save)\
837-
{\
838-
(obj)->thread_state= PyEval_SaveThread();\
839-
_save= NULL;\
840-
}

mariadb/mariadb_codecs.c

+10-1
Original file line numberDiff line numberDiff line change
@@ -677,13 +677,18 @@ field_fetch_callback(void *data, unsigned int column, unsigned char **row)
677677
MrdbCursor *self= (MrdbCursor *)data;
678678
Mrdb_ExtFieldType *ext_field_type;
679679

680+
PyGILState_STATE gstate;
681+
682+
/* Acquire the GIL */
683+
gstate = PyGILState_Ensure();
684+
680685
ext_field_type= mariadb_extended_field_type(&self->fields[column]);
681686

682687
if (!row)
683688
{
684689
Py_INCREF(Py_None);
685690
self->values[column]= Py_None;
686-
return;
691+
goto end;
687692
}
688693
switch(self->fields[column].type) {
689694
case MYSQL_TYPE_NULL:
@@ -904,6 +909,10 @@ field_fetch_callback(void *data, unsigned int column, unsigned char **row)
904909
if ((val= ma_convert_value(self, type, self->values[column])))
905910
self->values[column]= val;
906911
}
912+
913+
end:
914+
/* Release the GIL */
915+
PyGILState_Release(gstate);
907916
}
908917

909918
static uint8_t ma_is_vector(PyObject *obj)

mariadb/mariadb_connection.c

+29-28
Original file line numberDiff line numberDiff line change
@@ -212,33 +212,33 @@ int connection_datetime_init(void)
212212
void MrdbConnection_process_status_info(void *data, enum enum_mariadb_status_info type, ...)
213213
{
214214
va_list ap;
215-
PyThreadState *_save= NULL;
216215
MrdbConnection *self= (MrdbConnection *)data;
217216
PyObject *dict= NULL;
218217
PyObject *dict_key= NULL, *dict_val= NULL;
219-
va_start(ap, type);
220218

219+
PyGILState_STATE gstate;
220+
/* Acquire the GIL */
221+
gstate = PyGILState_Ensure();
222+
223+
va_start(ap, type);
221224
if (self->status_callback) {
222225
if (type == STATUS_TYPE)
223226
{
224227
unsigned int server_status= va_arg(ap, int);
225228

226-
MARIADB_UNBLOCK_THREADS(self);
227229
dict_key= PyUnicode_FromString("server_status");
228230
dict_val= PyLong_FromLong(server_status);
229231
dict= PyDict_New();
230232
PyDict_SetItem(dict, dict_key, dict_val);
231233
Py_DECREF(dict_key);
232234
Py_DECREF(dict_val);
233235
PyObject_CallFunction(self->status_callback, "OO", (PyObject *)data, dict);
234-
MARIADB_BLOCK_THREADS(self);
235236
}
236237
}
237238
if (type == SESSION_TRACK_TYPE)
238239
{
239240
enum enum_session_state_type track_type= va_arg(ap, enum enum_session_state_type);
240241

241-
MARIADB_UNBLOCK_THREADS(self);
242242

243243
if (self->status_callback) {
244244
switch (track_type) {
@@ -279,9 +279,9 @@ void MrdbConnection_process_status_info(void *data, enum enum_mariadb_status_inf
279279

280280
memcpy(charset, val->str, val->length);
281281
charset[val->length]= 0;
282-
va_end(ap);
283282
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 1,
284283
"Character set '%s' is not supported", charset);
284+
goto end;
285285
}
286286
if (self->status_callback)
287287
{
@@ -294,9 +294,11 @@ void MrdbConnection_process_status_info(void *data, enum enum_mariadb_status_inf
294294
PyObject_CallFunction(self->status_callback, "OO", (PyObject *)data, dict);
295295
}
296296
}
297-
MARIADB_BLOCK_THREADS(self);
298297
}
298+
end:
299299
va_end(ap);
300+
/* Release the GIL */
301+
PyGILState_Release(gstate);
300302
}
301303
#endif
302304

@@ -376,8 +378,6 @@ MrdbConnection_Initialize(MrdbConnection *self,
376378
}
377379
#endif
378380

379-
MARIADB_BEGIN_ALLOW_THREADS(self);
380-
381381
if (mysql_options(self->mysql, MYSQL_SET_CHARSET_NAME, mariadb_default_charset))
382382
goto end;
383383

@@ -474,8 +474,10 @@ MrdbConnection_Initialize(MrdbConnection *self,
474474
goto end;
475475
}
476476

477+
Py_BEGIN_ALLOW_THREADS;
477478
mysql_real_connect(self->mysql, host, user, password, schema, port,
478479
socket, client_flags);
480+
Py_END_ALLOW_THREADS;
479481

480482
if (mysql_errno(self->mysql))
481483
{
@@ -489,7 +491,6 @@ MrdbConnection_Initialize(MrdbConnection *self,
489491

490492
has_error= 0;
491493
end:
492-
MARIADB_END_ALLOW_THREADS(self);
493494

494495
if (has_error)
495496
{
@@ -567,9 +568,9 @@ void MrdbConnection_finalize(MrdbConnection *self)
567568
{
568569
if (self->mysql)
569570
{
570-
MARIADB_BEGIN_ALLOW_THREADS(self)
571+
Py_BEGIN_ALLOW_THREADS
571572
mysql_close(self->mysql);
572-
MARIADB_END_ALLOW_THREADS(self)
573+
Py_END_ALLOW_THREADS
573574
self->mysql= NULL;
574575
}
575576
}
@@ -586,9 +587,9 @@ MrdbConnection_executecommand(MrdbConnection *self,
586587

587588
cmd= PyUnicode_AsUTF8AndSize(command, NULL);
588589

589-
MARIADB_BEGIN_ALLOW_THREADS(self);
590+
Py_BEGIN_ALLOW_THREADS;
590591
rc= mysql_send_query(self->mysql, cmd, (long)strlen(cmd));
591-
MARIADB_END_ALLOW_THREADS(self);
592+
Py_END_ALLOW_THREADS;
592593

593594
if (rc)
594595
{
@@ -604,9 +605,9 @@ PyObject *MrdbConnection_close(MrdbConnection *self)
604605
/* Todo: check if all the cursor stuff is deleted (when using prepared
605606
statements this should be handled in mysql_close) */
606607

607-
MARIADB_BEGIN_ALLOW_THREADS(self)
608+
Py_BEGIN_ALLOW_THREADS
608609
mysql_close(self->mysql);
609-
MARIADB_END_ALLOW_THREADS(self)
610+
Py_END_ALLOW_THREADS
610611
self->mysql= NULL;
611612
self->closed= 1;
612613
Py_RETURN_NONE;
@@ -628,9 +629,9 @@ PyObject *MrdbConnection_ping(MrdbConnection *self)
628629

629630
MARIADB_CHECK_CONNECTION(self, NULL);
630631

631-
MARIADB_BEGIN_ALLOW_THREADS(self);
632+
Py_BEGIN_ALLOW_THREADS;
632633
rc= mysql_ping(self->mysql);
633-
MARIADB_END_ALLOW_THREADS(self);
634+
Py_END_ALLOW_THREADS;
634635

635636
if (rc) {
636637
mariadb_throw_exception(self->mysql, Mariadb_InterfaceError, 0, NULL);
@@ -654,9 +655,9 @@ PyObject *MrdbConnection_change_user(MrdbConnection *self,
654655
if (!PyArg_ParseTuple(args, "szz", &user, &password, &database))
655656
return NULL;
656657

657-
MARIADB_BEGIN_ALLOW_THREADS(self);
658+
Py_BEGIN_ALLOW_THREADS;
658659
rc= mysql_change_user(self->mysql, user, password, database);
659-
MARIADB_END_ALLOW_THREADS(self);
660+
Py_END_ALLOW_THREADS;
660661

661662
if (rc)
662663
{
@@ -863,9 +864,9 @@ PyObject *MrdbConnection_reconnect(MrdbConnection *self)
863864
if (!save_reconnect)
864865
mysql_optionsv(self->mysql, MYSQL_OPT_RECONNECT, &reconnect);
865866

866-
MARIADB_BEGIN_ALLOW_THREADS(self);
867+
Py_BEGIN_ALLOW_THREADS;
867868
rc= mariadb_reconnect(self->mysql);
868-
MARIADB_END_ALLOW_THREADS(self);
869+
Py_END_ALLOW_THREADS;
869870

870871
if (!save_reconnect)
871872
mysql_optionsv(self->mysql, MYSQL_OPT_RECONNECT, &save_reconnect);
@@ -886,9 +887,9 @@ PyObject *MrdbConnection_reset(MrdbConnection *self)
886887
int rc;
887888
MARIADB_CHECK_CONNECTION(self, NULL);
888889

889-
MARIADB_BEGIN_ALLOW_THREADS(self);
890+
Py_BEGIN_ALLOW_THREADS;
890891
rc= mysql_reset_connection(self->mysql);
891-
MARIADB_END_ALLOW_THREADS(self);
892+
Py_END_ALLOW_THREADS;
892893

893894
if (rc)
894895
{
@@ -952,9 +953,9 @@ MrdbConnection_dump_debug_info(MrdbConnection *self)
952953
int rc;
953954
MARIADB_CHECK_CONNECTION(self, NULL);
954955

955-
MARIADB_BEGIN_ALLOW_THREADS(self);
956+
Py_BEGIN_ALLOW_THREADS;
956957
rc= mysql_dump_debug_info(self->mysql);
957-
MARIADB_END_ALLOW_THREADS(self);
958+
Py_END_ALLOW_THREADS;
958959

959960
if (rc)
960961
{
@@ -968,9 +969,9 @@ static PyObject *MrdbConnection_readresponse(MrdbConnection *self)
968969
{
969970
int rc;
970971

971-
MARIADB_BEGIN_ALLOW_THREADS(self);
972+
Py_BEGIN_ALLOW_THREADS;
972973
rc= self->mysql->methods->db_read_query_result(self->mysql);
973-
MARIADB_END_ALLOW_THREADS(self);
974+
Py_END_ALLOW_THREADS;
974975

975976
if (rc)
976977
{

mariadb/mariadb_cursor.c

+14-18
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,6 @@ static PyMethodDef MrdbCursor_Methods[] =
169169
{"_execute_bulk", (PyCFunction)MrdbCursor_execute_bulk,
170170
METH_NOARGS,
171171
NULL},
172-
{"_initresult", (PyCFunction)MrdbCursor_InitResultSet,
173-
METH_NOARGS,
174-
NULL},
175172
{"_readresponse", (PyCFunction)MrdbCursor_readresponse,
176173
METH_NOARGS,
177174
NULL},
@@ -414,7 +411,6 @@ PyObject *MrdbCursor_clear_result(MrdbCursor *self)
414411
} while (!mysql_next_result(self->connection->mysql));
415412
}
416413
}
417-
MARIADB_END_ALLOW_THREADS(self->connection);
418414
/* CONPY-52: Avoid possible double free */
419415
self->result= NULL;
420416
Py_RETURN_NONE;
@@ -506,9 +502,9 @@ void ma_cursor_close(MrdbCursor *self)
506502
{
507503
/* Todo: check if all the cursor stuff is deleted (when using prepared
508504
statements this should be handled in mysql_stmt_close) */
509-
MARIADB_BEGIN_ALLOW_THREADS(self->connection);
505+
Py_BEGIN_ALLOW_THREADS;
510506
mysql_stmt_close(self->stmt);
511-
MARIADB_END_ALLOW_THREADS(self->connection);
507+
Py_END_ALLOW_THREADS;
512508
self->stmt= NULL;
513509
}
514510
MrdbCursor_clear(self, 0);
@@ -630,7 +626,7 @@ static int Mrdb_execute_direct(MrdbCursor *self,
630626
{
631627
int rc;
632628

633-
MARIADB_BEGIN_ALLOW_THREADS(self->connection);
629+
Py_BEGIN_ALLOW_THREADS;
634630
long ext_caps;
635631

636632
mariadb_get_infov(self->connection->mysql,
@@ -660,7 +656,7 @@ static int Mrdb_execute_direct(MrdbCursor *self,
660656
rc= mariadb_stmt_execute_direct(self->stmt, statement, statement_len);
661657
}
662658
end:
663-
MARIADB_END_ALLOW_THREADS(self->connection);
659+
Py_END_ALLOW_THREADS;
664660
return rc;
665661
}
666662

@@ -890,12 +886,12 @@ static PyObject *MrdbCursor_seek(MrdbCursor *self, PyObject *pos)
890886

891887
new_position= (uint64_t)PyLong_AsUnsignedLongLong(pos);
892888

893-
MARIADB_BEGIN_ALLOW_THREADS(self->connection);
889+
Py_BEGIN_ALLOW_THREADS;
894890
if (self->parseinfo.is_text)
895891
mysql_data_seek(self->result, new_position);
896892
else
897893
mysql_stmt_data_seek(self->stmt, new_position);
898-
MARIADB_END_ALLOW_THREADS(self->connection);
894+
Py_END_ALLOW_THREADS;
899895

900896
Py_RETURN_NONE;
901897
}
@@ -929,9 +925,9 @@ MrdbCursor_nextset(MrdbCursor *self)
929925
{
930926
if (!self->stmt)
931927
Py_RETURN_NONE;
932-
MARIADB_BEGIN_ALLOW_THREADS(self->connection);
928+
Py_BEGIN_ALLOW_THREADS;
933929
rc= mysql_stmt_next_result(self->stmt);
934-
MARIADB_END_ALLOW_THREADS(self->connection);
930+
Py_END_ALLOW_THREADS;
935931
}
936932
else
937933
{
@@ -940,9 +936,9 @@ MrdbCursor_nextset(MrdbCursor *self)
940936
mysql_free_result(self->result);
941937
self->result= NULL;
942938
}
943-
MARIADB_BEGIN_ALLOW_THREADS(self->connection);
939+
Py_BEGIN_ALLOW_THREADS;
944940
rc= mysql_next_result(self->connection->mysql);
945-
MARIADB_END_ALLOW_THREADS(self->connection);
941+
Py_END_ALLOW_THREADS;
946942
}
947943

948944
if (rc)
@@ -1143,9 +1139,9 @@ MrdbCursor_execute_text(MrdbCursor *self, PyObject *stmt)
11431139
}
11441140
db= self->connection->mysql;
11451141

1146-
MARIADB_BEGIN_ALLOW_THREADS(self->connection);
1142+
Py_BEGIN_ALLOW_THREADS;
11471143
rc= mysql_send_query(db, statement, (long)statement_len);
1148-
MARIADB_END_ALLOW_THREADS(self->connection);
1144+
Py_END_ALLOW_THREADS;
11491145

11501146
if (rc)
11511147
{
@@ -1167,9 +1163,9 @@ MrdbCursor_readresponse(MrdbCursor *self)
11671163

11681164
if (self->parseinfo.is_text)
11691165
{
1170-
MARIADB_BEGIN_ALLOW_THREADS(self->connection);
1166+
Py_BEGIN_ALLOW_THREADS;
11711167
rc= db->methods->db_read_query_result(db);
1172-
MARIADB_END_ALLOW_THREADS(self->connection);
1168+
Py_END_ALLOW_THREADS;
11731169

11741170
if (rc)
11751171
{

0 commit comments

Comments
 (0)